Tuuu's Blog

安全盒子创始人,专注于Web安全研究。

分类 代码 下的文章

作者:@王松_Tuuu   时间:May 5, 2017

Wscanner - 又一个调sqlmap测注入的轮子

A another sqli scanner,maybe is a full scanner.

下载地址 https://github.com/Strikersb/Wscanner

About this project

写这个扫描器的时候还很年轻,觉得写出来一定特别牛逼,但后来由于工作和个人的各种原因,没有再写这个项目。

后来看了很多的扫描器案例以及自己的视野越来越宽,有了更好的想法,另外再返回来看的时候,对自己写的这些代码特别不满意。

故该项目为废弃的半成品,仅供后生研究使用,当然也欢迎各位小伙伴继续开发,无版权限制 :)

About me

Author: 王松_Striker

Team: 安全盒子团队

Blog: www.hackersb.cn

作者:@王松_Tuuu   时间:March 6, 2017

《C Primer Plus》随书习题 - C语言概述

我要开始学习C语言啦,总是断断续续的。

@周梦禹 师傅推荐我在博客写上一个C语言系列文章,然而这对于一个新手来讲,太难了。

因为知识储备太少,想系统性的写C系列的文章太难了。

所以我就来把《C Primer Plus》的课后习题做出来,然后把答案发出来吧(前面的习题简单,后面的习题不知道会不会难到我吐血 2333)。

我看的这本是《C Primer Plus》第六版中文版。(不要问我为啥不看英文版!!!)

题目

书中第37页,是第二章”C语言概述“的编程练习题。

1.编写一个程序,调用一次printf()函数,把你的姓名打印在一行。再调用一次printf()函数,把你的姓名分别打印在两行。然后再次调用两次printf()函数,把你的姓名打印在一行。输出应该如下所示(当然要把实例的内容换成你的姓名):

Gustav Mahler        <- 第一次打印的内容
Gustav               <- 第二次打印的内容
Mahler               <- 仍是第二次打印的内容
Gustav Mahler        <- 第三次和第四次打印的内容

答案:

#include <stdio.h>

int main(void)
{
    printf("Song Wang\n");
    printf("Song\nWang\n");
    printf("Song Wang\n");
    return 0;
}

输出:

Song Wang
Song
Wang
Song Wang

2.编写一个程序,打印你的姓名和地址。

答案:

#include <stdio.h>

int main(void)
{
    printf("WangSong,Chaoyang District of Beijing City\n");
    return 0;
}

输出:

WangSong,Chaoyang District of Beijing City

3.编写一个程序把你的年龄转换成天数,并显示着两个值,这里不用考虑闰年的问题。

答案:

#include <stdio.h>

int main(void)
{
    int age;
    int days;
    age = 19;
    
    days = age * 365;
    printf("I am %d years old, a total of %d days\n", age, days);
    
    return 0;
}

输出:

I am 19 years old, a total of 6935 days

4.编写一个程序,生成以下输出:

For he's a jolly good fellow!
For he's a jolly good fellow!
For he's a jolly good fellow!
Which nobody can deny!

除了main()函数以外,该程序还要调用两个自定义函数:一个名为jolly(),用于打印前3条消息,调用一次打印一条;另一个函数名为deny(),打印最后一条消息。

答案:

#include <stdio.h>

int jolly(void);
int deny(void);

int main(void)
{
    jolly();
    jolly();
    jolly();
    deny();
    return 0;
}

int jolly()
{
    printf("For he's a jolly good fellow!\n");
    return 0;
}

int deny()
{
    printf("Which nobody can deny!\n");
    return 0;
}

输出:

For he's a jolly good fellow!
For he's a jolly good fellow!
For he's a jolly good fellow!
Which nobody can deny!

5.编写一个程序,生成以下输出:

Brazil, Russia, India, China
India, China,
Brazil, Russia

除了main()函数以外,该程序还要调用两个自定义函数,一个名为br(),调用一次打印一次“Brazil,Russia”;另一个名为ic(),调用一次打印一次“India, China”。其他内容在main()函数中完成。

答案:

#include <stdio.h>

int br(void);
int ic(void);

int main(void)
{
    br();
    printf(", ");
    ic();
    printf("\n");
    ic();
    printf(",\n");
    br();
    printf("\n");
    return 0;
}

int br()
{
    printf("Brazil, Russia");
    return 0;
}

int ic()
{
    printf("India, China");
    return 0;
}

输出:

Brazil, Russia, India, China
India, China,
Brazil, Russia

6.编写一个程序,创建一个整形变量toes,并将toes设置为10.程序中还要计算toes的两倍和toes的平方。该程序应打印3个值,并分别标书以示区分。

答案:

#include <stdio.h>

int main(void)
{
    int toes;
    toes = 10;
    printf("totes is %d\n", toes);
    printf("%d times as much as two is %d\n", toes, toes * 2);
    printf("The square of toes is %d\n", toes * toes);
    return 0;
}

输出:

totes is 10
10 times as much as two is 20
The square of toes is 100

7.许多研究表明,微笑益处多多。编写一个程序,生成一下格式的输出:

Smile!Smile!Smile!
Smile!Smile!
Smile!

该程序要定义一个函数,该函数被调用一次打印一次“Smile!”,根据程序的需要使用该函数。

答案:

#include <stdio.h>

int smile(void);

int main(void)
{
    smile();
    smile();
    smile();
    printf("\n");
    smile();
    smile();
    printf("\n");
    smile();
    printf("\n");
    return 0;
}

int smile()
{
    printf("Smile!");
    return 0;
}

输出:

Smile!Smile!Smile!
Smile!Smile!
Smile!

8.在C语言中,函数可以调用另一个函数。编写一个程序,调用一个名为one_three()的函数,该函数在一行打印单词“one”,再调用第2个函数two(),然后在另一行打印单词“three”,two()函数在一行显示单词“two”。main()函数在调用one_three()函数前要打印短语"starting now:",并在调用完毕后显示短语“done!”。因此,该程序的输出应如下所示:

starting now:
one
two
three
done!

答案:

#include <stdio.h>

int one_three(void);
int two(void);

int main(void)
{
    printf("starting now:\n");
    one_three();
    printf("done!\n");
    return 0;
}

int one_three()
{
    printf("one\n");
    two();
    printf("three\n");
    
    return 0;
}

int two()
{
    printf("two\n");
    
    return 0;
}

输出:

starting now:
one
two
three
done!

英语太蹩脚了,下次里面还是直接用中文/拼音来写吧。

作者:@王松_Tuuu   时间:June 16, 2016

截断在文件包含和上传中的利用

本文转自http://www.joychou.org/index.php/web/truncated.html

截断大概可以在以下情况适用

  • include(require)
  • file_get_contents
  • file_exists
  • 所有url中参数可以用%00控制

0x01. 本地文件包含

1.1 截断类型:php %00截断

截断条件:

php版本小于5.3.4 详情关注CVE-2006-7243
php的magic_quotes_gpc为OFF状态
漏洞文件lfi.php

<?php 
$temp = $_REQUEST['action'].".php"; 
include $temp; // include造成了LFI和php的%00截断
?>

要include的password文件

Password

<?php

phpinfo();

?>

利用代码:lfi.php?action=password%00
注意:url正宗%00是被会url解码成0x00,所以可能导致截断。

password文件被成功包含并且执行phpinfo()函数。

如果没有截断条件,lfi.php就只能包含php扩展名的文件。
相反,如果有截断条件,lfi.php可以包含任意文件的扩展名。

当把magic_quotes_gpc打开,php版本依然是5.2.9时,再测试,结果%00被转义成了0两个单体字符,不再具有截断功能。

3385494049.jpg

原因是:当打开magic_quotes_gpc时,所有的 '(单引号),"(双引号),(反斜线)和 NULL字符(%00)都会被自动加上一个反斜线进行转义。还有很多函数有类似的作用 如:addslashes()mysql_escape_string()mysql_real_escape_string()

当把magic_quotes_gpc关闭,php版本依然是5.3.10时,依然不能截断。所以证明,php版本和gpc两个条件都必须满足,才能截断。

除了上面的include、require、include_once、require_once还有file_get_contents也能配合php %00利用。

FileGetContents.php

<?php
$file = $_GET['file'].'PNG';
$contents =  file_get_contents($file);
file_put_contents('put.txt', $contents);
?>

利用方式:
http://www.victim.com/FileGetContents.php?file=password%00

此时可以看到当前目录put.txt是上面password中的内容。

Password

<?php

phpinfo();

?>

1.2 文件路径长度截断

除了1.1说的%00可以截断,还可以用字符.或者/.,或者./(注意顺序)来截断,不能纯用/,至于为什么,不能用其他字符,想必应该和php实现有关。

系统文件路径长度限制:

windows 259个bytes
linux 4096个bytes
具体可以看这篇文章:http://joychou.org/index.php/Misc/filename-length-limit-on-windows-linux.html

截断条件:

php版本为5.3.4以下(具体哪个版本不是很清楚,乌云上kukki写的5.2.8以下,这明显是不对的,因为我测试用的5.2.9)
GPC是否开启没关系
漏洞代码lfi.php,和1.1中的lfi.php一样

<?php 
$temp = $_REQUEST['action'].".php"; 
include $temp;
echo $temp;
?>

在windows下需要.字符最少的利用POC:

lfi.php?action=password..............................................................................................................................................................................................................................................

成功包含,执行password里面的phpinfo函数

1.jpg

加上根目录路径一共为258个字节。所以需要的最少的.数为
258 - (lfi.php文件的路径长度即C:/wamp/www/+strlen('password'))

2.jpg

或者用./截断,最短的POC为,并且最短路径长度为258

lfi.php?action=password./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././

将password文件名改为password123后,最短的POC为,最短路径长度依然为258

lfi.php?password123/./././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././././

注意这两者一个是/开始,一个是.开始,这和路径长度的奇偶有关系,真正遇到这样的情况,就丢很长的/.,最后再跳整下第一个/或者.即可。

linux就自行测试吧。

0x02. 文件上传

截断类型:php的%00截断。所以截断的条件依然是php %00截断的条件

  • php版本5.3.4以下
  • gpc关闭

测试环境:

  • php版本5.2.9
  • gpc关闭
  • 漏洞代码 upload.php
<html>
<body>

<h2> File Upload Vulnerability </h2>
<form action="" method="post" enctype="multipart/form-data">
<label>文件:</label>
<input type="file" name = "file" >   <!-- 选择文件,第二个name是file类型的名字,php中$_FILES第一个参数-->
<input type="submit" value="submit" name = "upload">
</form>

</body>
</html>
<?php
error_reporting(0);

if(isset($_POST['upload']))
{
    $ext_arr = array('flv','swf','mp3','mp4','3gp','zip','rar','gif','jpg','png','bmp');
    $file_ext = substr($_FILES['file']['name'],strrpos($_FILES['file']['name'],".")+1);
    if(in_array($file_ext,$ext_arr))
    {
        $tempFile = $_FILES['file']['tmp_name'];
        // 这句话的$_REQUEST['jieduan']造成可以利用截断上传
        $targetPath = $_SERVER['DOCUMENT_ROOT'].$_REQUEST['jieduan'].rand(10, 99).date("YmdHis").".".$file_ext;
        if(move_uploaded_file($tempFile,$targetPath))
        {
            echo '上传成功'.'<br>';
            echo '路径:'.$targetPath;
        }
        else
        {
            echo("上传失败");
        }

    }
else
{
    echo("上传失败");
}

}

?>

这个漏洞代码是我YY的,可能实际情况不一定能够用上。只是证明截断可以达到上传的功能。

先将一个php木马重命名为上面扩展名为白名单的后缀,比如.jpg

访问:http://www.victim.com/upload.php?jieduan=xxoo.php%00

点击submit按钮,就在server上生成了一个xxoo.php的马。

1.png

0x03. file_exists判断文件是否存在

file_exists在判断文件存在的时候也有被截断的现象。

截断条件:

  • php版本小于5.3.4
  • GPC关闭状态
  • 漏洞代码如下,和CVE-2014-8959 phpmyadmin的这个漏洞一样。
<?php 

$file = $_GET['file'];

$filename = $file . '.php';
echo $filename . '<br>';
if(! file_exists($filename)){
    echo 'not exist';
}

else{
    include_once($filename);
    echo 'exist';
}
?>

当前目录存在一个shell.jpg文件,此时访问?file=shell.jpg%00,返回结果是文件存在。

有一个小技巧:

当上面文件第五行变成$filename = 'xxoo' . $file . '.php';,如果仍然要用shell.jpg,那么只需这样构造:?file=/../shell.jpg%00,利用/../回到当前目录。

在php中一些目录切换

  • ../表示上一层目录
  • ./表示当前目录
  • /单独使用不能表示当前目录,只用xx/这样才能表示xx这个目录

参考文章

作者:@王松_Tuuu   时间:March 28, 2016

Web目录扫描工具webdirscan.py编写

写在前面

一直没有找到一款自己喜欢的跨平台的web目录扫描工具,因为我自己电脑装的是ubuntu,毕竟买不起mac

然后ubuntu下也没有找到几个我自己满意的工具,所以最终决定:自己撸!!!

想法:预期功能

  • 可指定初始扫描目录(可选,默认根目录)
  • 可设置文件后缀(可选,默认php)
  • 可设置线程数(可选,默认20)
  • 字典分为多种分类

想法:一些不错的思路

  • 遇到/a/目录,然后扫描a.rara.zip等文件
  • 遇到/1.php,然后扫描1.php.bak1.php.swp等、
  • 遇到有参数的网址以及所有表单,扔给sqlmap API进行sql注入检测
  • 扫描robots.txt并且访问里面的目录,探测是否可以列目录

开发:最基础的目录扫描

首先我们把最基础的遍历字典,扫描目录写出来,代码如下:

#!/usr/bin/env python
#-*- coding: utf-8 -*-

import argparse
import requests

# 命令行传值
parser = argparse.ArgumentParser()
parser.add_argument('website',help="Website for scan, eg: http://www.secbox.cn | www.secbox.cn",type=str)
args = parser.parse_args()

# 字典设置
webdic = 'dict/dict.txt'

# 对输入的网址进行处理
website = args.website

# 请求头设置
headers = {
    'Accept': '*/*',
    'Referer': website,
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; ',
    'Connection': 'Keep-Alive',
    'Cache-Control': 'no-cache',
}

# 字典存入数组
webdict = []

with open(webdic) as infile:
    while True:
        dirdic = infile.readline().strip()
        if(len(dirdic) == 0): break
        webdict.append(website+dirdic)


for url in webdict:
    try:
        respon = requests.get(url, headers=headers)
    except Exception,e:
        print url
        print e

    if(respon.status_code == 200):
        print '['+str(respon.status_code)+']' + ":" + url

最简单的功能已经搞定,但是还有很多坑要补,比如有的网站所有页面都返回200,这个时候就要做一些措施,下一步打算把提高扫描页面的准确性,判断页面是否存在多加几个标准:

  • 首先访问songgeshuozhegeyemianbucunzai/strikersb666.phponggeshuozhegeyemianbucunzai/strikersb555.php判断两个文件内容是否一样
  • 如果一样表示这个内容是404的内容,然后在后来的扫描中,返回长度等于这个长度的判定为404

开发:对不存在的页面也返回200进行兼容

有的网站访问不存在的页面为了SEO同样会返回200或者302,此时就需要做兼容,我这里的解决办法是:请求时禁止302跳转,然后抓取一个404页面,判断文件存在的条件是返回状态码200且与404页面内容不一样,具体代码如下:

#!/usr/bin/env python
#-*- coding: utf-8 -*-

import re
import argparse
import requests
from termcolor import colored

# 版权区域

mycopyright = '''
*****************************************************

            Web目录扫描工具 - webdirscan.py
            作者:王松_Striker
            邮箱:song@secbox.cn
            团队:安全盒子团队[SecBox.CN]

*****************************************************
'''
print colored(mycopyright,'cyan')

# 命令行传值
parser = argparse.ArgumentParser()
parser.add_argument('website',help="Website for scan, eg: http://www.secbox.cn | www.secbox.cn",type=str)
args = parser.parse_args()

# 字典设置
webdic = 'dict/dict.txt'

# 对输入的网址进行处理
website = args.website
pattern = re.compile(r'^[http\:\/\/|https\:\/\/]')
res = pattern.match(website)

if not(res):
    website = 'http://' + website

# 请求头设置
headers = {
    'Accept': '*/*',
    'Referer': website,
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; ',
    'Cache-Control': 'no-cache',
}

# 字典存入数组
webdict = []

with open(webdic) as infile:
    while True:
        dirdic = infile.readline().strip()
        if(len(dirdic) == 0): break
        webdict.append(website+dirdic)

# 404页面分析,避免有的网站所有页面都返回200的情况
notfoundpage = requests.get(website+'/songgeshigedashuaibi/hello.html',allow_redirects=False)

# 遍历扫描过程
for url in webdict:
    try:
        respon = requests.get(url, headers=headers,timeout=30,allow_redirects=False)
    except Exception,e:
        print e

    if(respon.status_code == 200 and respon.text != notfoundpage.text):
        print colored('['+str(respon.status_code)+']','green') + " " + url

简单的扫描基本已经搞定了,下一步打算做一下视觉上的优化,比如显示进程:有多少待检测、有多少正在检测等等

不把过程往博客写了,主要是太麻烦了,前往Github看吧~

作者:@王松_Tuuu   时间:March 28, 2016

Python Argparse模块学习

最近是打算写个好用的webdirscan,毕竟现成的没找到几个好用的

所以发挥麒麟臂,自己写一套适合自己以及自己喜欢的web目录扫描工具,

也就当是巩固Python基础的一个过程。

前言

首先考虑到要做webdirscan就必须传入主机、字典、脚本语言等,所以就涉及到了命令行传入参数的问题,于是看了看其他的脚本都是如何接收参数,调用参数,我这里看的是lijiejie的子域名爆破工具,里面import了argparse模块,所以就去官方找了文档,这里对文档进行简单的翻译和理解。

没有接收参数(--help除外)

#!/usr/bin/env python
# coding:utf-8

import argparse

parse = argparse.ArgumentParser()
args = parse.parse_args()

接收必须参数

简单接收一个参数

举个栗子,接收一个host参数并输出:

#!/usr/bin/env python
# coding:utf-8

import argparse

parse = argparse.ArgumentParser()
parse.add_argument('host')
args = parse.parse_args()

print args.host

结果如下:

striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py www.hackersb.cn
www.hackersb.cn

如果没有带参数,则返回:

striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py 
usage: argparseStudy.py [-h] host
argparseStudy.py: error: too few arguments

给接收的参数添加帮助文字

举个例子,给上文的host添加帮助文字:

#!/usr/bin/env python
# coding:utf-8

import argparse

parse = argparse.ArgumentParser()
parse.add_argument('host',help="This is host for scan.")
args = parse.parse_args()

print args.host

这个时候执行argparseStudy.py -h会返回:

striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py -h
usage: argparseStudy.py [-h] host

positional arguments:
  host        This is host for scan.

optional arguments:
  -h, --help  show this help message and exit

设置接收参数的类型

这里以int型为例,当然也有string类型,这里还支持什么类型,我不知道.. 反正常用的就int和string吧...

#!/usr/bin/env python
# coding:utf-8

import argparse

parse = argparse.ArgumentParser()
parse.add_argument('host',help="This is host for scan.",type=int)
args = parse.parse_args()

print args.host

以下是我输入string类型和int类型的返回结果:

striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py aaa
usage: argparseStudy.py [-h] host
argparseStudy.py: error: argument host: invalid int value: 'aaa'
striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py 123
123

输入aaa的时候因为不是int型,所以报错~

接收可选参数

接收需要传值的可选参数

比如做webdirscan需要输入文件后缀,如:php、jsp、aspx、asp,但这个参数是可选的:

#!/usr/bin/env python
# coding:utf-8

import argparse

parse = argparse.ArgumentParser()
parse.add_argument('--suffix',help="This is suffix on scan file")
args = parse.parse_args()

if args.suffix:
    print args.suffix

以下是我可选参数的传入方式、并且不传该参数也不会报错~ 毕竟是可选参数:

striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py --suffix php
php
striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py

接收无需传值的可选参数

比如我们做webdirscan的时候,如果传入--thread就启用多线程,不传入则单线程:

#!/usr/bin/env python
# coding:utf-8

import argparse

parse = argparse.ArgumentParser()
parse.add_argument('--thread',help="open the more thread",action="store_true")
args = parse.parse_args()


if args.thread:
    print 'more thread!'

返回结果如下,如果我带上--thread则输出多线程:

striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py
striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py --thread
more thread!

短配置

缩短我们的参数,很多程序中都用单个字母代替,比如nmap的-port简写为-p,我们也可以这样:

#!/usr/bin/env python
# coding:utf-8

import argparse

parse = argparse.ArgumentParser()
parse.add_argument('-t','--thread',help="open the more thread",action="store_true")
args = parse.parse_args()


if args.thread:
    print 'more thread!'

运行结果如下:

striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py
striker@Striker-Ubuntu:~/Desktop/PythonStudy$ ./argparseStudy.py -t
more thread!

大概就学习到这里,基本上常用的已经介绍了,其他少用的可以去看官方文档

参考

作者:@王松_Tuuu   时间:March 8, 2016

spl_autoload_register和__autoload的用法

__autoload

首先要了解一下__autoload的用法,__autoload__call__get等函数一样,是一个自动加载函数,当我们实例化一个未定义的类时,就会触发__autoload,比如:

test.php:

<?php
function __autoload($class){
    $file = $class.'.class.php';
    if(is_file($file)){
        require_once($file);
    }
}

$person = new Person();
$person->writehello();

Person.class.php:

<?php
class Person{
    public function writehello(){
        echo 'hello world!';
    }
}

结果如图:

QQ截图20160308144214.png

spl_autoload_register

spl_autoload_register__autoload很像,如果遇到实例化未定义的类,那么PHP会执行spl_autoload_register指定的函数,如下:

test.php:

<?php
function loadclass($class){
    $file = $class.'.class.php';
    if(is_file($file)){
        require_once($file);
    }
}

spl_autoload_register('loadclass');

$person = new Person();
$person->writehello();

Person.class.php:

<?php
class Person{
    public function writehello(){
        echo 'hello world!';
    }
}

整个过程Person.class.php都没改变内容,只是实例化未定义类的时候调用的方法不一样而已。

学习笔记,欢迎各位PHP大神指教!