我是如何打造一款自动化SQL注入工具的


发布人:admin分类:网络安全浏览量:27发布时间:2017-12-12

0×01 前言

各位看官看到标题吐槽帝就开始了:已经有了各种各样的注入工具,为什么还要手工打造一个?

事实上,做为一名苦逼乙方测试工程师以及漏洞盒子屌丝白帽子 ,在疲于应对各种死缠滥打的甲方以及成堆的web测试需求时,我经常遇到以下场景:

(1)有大批量的网站需要检测的场景

乙方工程师工作辛苦劳累从来都不抱怨,有项目一定都是最能抗的,向无数奋斗在一线的乙方工程师致敬!

(2)系统内部业务复杂可能会存在众多测注入点

很多系内部业务复杂,查询功能较多,此时可能会较多的注入点,手工测试时间紧,容易纰漏,此时需要一个提取burpsuit的history记录的工具,来自动帮你分析问题所在。

(3)漏洞盒子测试时间需要争分夺秒

在漏洞盒子进行项目安全测试时,时间就是金钱——谁能以更快的速度挖到漏洞谁就能拿到更多的奖励.

系统框架组成:

[核心检测引擎]
Sqlmap
[核心信息收集引擎]
Python代理
[数据库]
mysql
[Web展示]
Bootstrap主题+JQuery(Ajax)

0×02 系统设计过程

先介绍一下Sqlmapi及其用法:

Sqlmapapi在sqlmap中是自带的功能,可能许多人都忽略了.当我们下载到sqlmap源码的适合会发现在根目录下还有一个sqlmapapi.py的文件,此时,使用命令python sqlmapapi.py -s -H 127.0.0.1 -p 8889就可以启动了

启动后会生成一个Admin ID,这个AdminID就是我们用于管理Sqlmapapi使用管理id

但是注意,在新建sqlmap任务的时候,这个AdminID没有什么作用,只是在查看任务和删除任务的时候才有用.这个AdminID也是后面PHP程序对sqlmapapi进行管理的时候使用的AdminID,但是为了方便,我将这一部分代码进行了重写,使得生成的AdminID是唯一的/或者写入一个特定的文件让PHP去读取。

使用的时候需要使用HTTP协议与该API进行交互。新建一个空任务,然后再向该任务POST sql注入的相关参数来启动该任务,/task/new为新建任务,/scan/taskid/start为启动任务接口。

需要使用POST方法向该接口提交json格式的数据,详情可参考后文的req2sqlmap.py

有了sqlmapapi的背景知识后,我们的打造自己的自动sql注入工具之路就开始了:

这款工具后台由Python代理实现且支持Https,启动sqlmapapi进程后,Python代理会截取http请求并将该请求发送给Sqlmapapi,Sqlmap就开始进行注入尝试,Web界面部分负责生成最后的结果便于测试人员直接分析,Web部分由PHP负责监控sqlmapapi并获取注入结果保存入mysql数据库,此处我写了一个单独的类库sqlmapapi.class.php处理,只要实例化一个对象并传入固定的adminid(sqlmap的管理id)就可以对sqlmapapi进程进行管理。

sqlmapapi.class.php代码如下:

<?php

class sqlmapapi {
    private $adminid='';
    private $sqlmapapi=SQLMAPAPI;
    private $tasknumber=0;
    function __construct($adminid=null) {
        if($adminid!=null){
            $this->adminid=$adminid;
        }

        $this->AutoTask();
        return 0;
    }
    //自动处理所有任务
    function AutoTask(){
        $tasklistarr=  $this->getTasklist();
        foreach ($tasklistarr as $taskid) {
            //查询结果并入库
            $this->Task2db($taskid);
        }
        return TRUE;
    }
    
    function getTasklist($adminid=null){
        if($adminid==null){
            $adminid=$this->adminid;
        }
        $jsonres=$this->doGet("/admin/".$this->adminid."/list");
        $jsonobj= json_decode($jsonres);
        $tasklist=$jsonobj->tasks;
        $tasknumber=$jsonobj->tasks_num;
        $this->tasknumber=$tasknumber;
        print_r($tasklist);
        return $tasklist;
    }
    function flushTask($adminid=null){
        if($adminid==null){
            $adminid=$this->adminid;
        }
        $jsonres=$this->doGet("/admin/".$this->adminid."/list");
        $res=  json_decode($jsonres);
        if($res['success']==true){
            return TRUE;
        }else{
            return FALSE;
        }
    }

    function Task2db($taskid){

        $jsonres=  $this->doGet("/scan/".$taskid."/status");
        print_r($jsonres);
        $jsonobj=  json_decode($jsonres);
        $taskstatus=$jsonobj->status;
        if($taskstatus=='terminated'){
            $jsonres=  $this->doGet("/scan/".$taskid."/data");
            $jsonobj=  json_decode($jsonres);
            $data=$jsonobj->data;
            if($data==null || empty($data)||count($data)==0){
                $this->delTask($taskid);
                return TRUE;
            }
            $error=$jsonobj->error;
            $taskoptionlist=  $this->getOptionList($taskid);
            $url=$taskoptionlist->url;
            $urlarr=parse_url($url);
            $schema=$urlarr['scheme'];
            $host=$urlarr['host'];
            $port=0;
            if(!isset($urlarr['port'])){
                if($urlarr['scheme']=='http'){
                    $port=80;
                }elseif($urlarr['scheme']=='https'){
                    $port=443;
                }
            }else{
                $port=$urlarr['port'];
            }
            $cookie=$taskoptionlist->cookie;
            $headers=$taskoptionlist->headers;
            $postdata=$taskoptionlist->data;
            $uasplit=split("User-Agent:", $headers);
            $ua=$uasplit[1];
            $taskscandata=  serialize($data);
            $taskscanlog=  $this->getTaskScanLog($taskid);
            $taskerror=  serialize($error);
            $save2dbres=$this->save2Db($host, $port, $schema, $url, $cookie, $postdata,$ua, serialize($taskoptionlist), $taskscandata, serialize($taskscanlog), $taskerror);
            if($save2dbres){
                $this->delTask($taskid);
                return TRUE;
            }else{
                return FALSE;
            }
            
        }elseif($taskstatus=='not running'){
            $this->delTask($taskid);
            return TRUE;
        }elseif ($taskstatus=="running") {
            return FALSE;
            
        }
        
    }
    function save2Db($host,$port,$schema,$url,$cookie,$postdata,$ua,$taskoptiondata,$taskscandata,$taskscanlog,$taskerror){
        global $mysqli;
        var_dump(mysqli_error($mysqli));
        if($num>0){
            return TRUE;
        }else{
            return FALSE;
        }
        
    }
    function getOptionList($taskid){
        $jsonres=  $this->doGet("/option/".$taskid."/list");
        $jsonobj=  json_decode($jsonres);//生成数组
        return $jsonobj->options;
    }
    function getTaskScanLog($taskid){
        $jsonres=  $this->doGet("/scan/".$taskid."/log");
        $jsonobj=  json_decode($jsonres);
        return $jsonobj->log;
    }
    function getUrl($taskid){
        
    }
    function delTask($taskid){
        $jsonres=$this->doGet('/task/'.$taskid."/delete");
        $jsonobj=  json_decode($jsonres);
        if($jsonobj->success=='true'){
            return TRUE;
        }else{
            return FALSE;
        }
    }
    
    
    function __get($name) {
        return $this->$name;
    }
    function __set($name, $value) {
        $this->$name=$value;
    }
    function doGet($api){
        $options = array(
                CURLOPT_URL =>  $this->sqlmapapi.$api ,
                CURLOPT_POST=>false,
                CURLOPT_RETURNTRANSFER=>true,
                CURLOPT_HEADER=>false,
                CURLOPT_USERAGENT=>'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17',

        );  
       $myres=$this->mycurl($options);
       return  $myres;
    }
    function doPost($api,$body){
        $header = array(
            'Content-Type: application/json',
        );
        $options = array(
                CURLOPT_URL =>$this->sqlmapapi.$api ,
                CURLOPT_POST=>true,
                CURLOPT_RETURNTRANSFER=>true,
                CURLOPT_POSTFIELDS=>$body,
                CURLOPT_HEADER=>$header,
                CURLOPT_USERAGENT=>'Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.17 (KHTML, like Gecko) Chrome/24.0.1312.57 Safari/537.17',
        );
        return $this->mycurl($options);
    }
    function mycurl($options){
        $c=curl_init();
        curl_setopt_array($c,$options);
        $result=curl_exec($c);
        curl_close($c);
        return $result;
    }

}

我拜读了sqlmapapi的源代码和相关代码后根据情况进行了部分修改,使得其能够更好的适用于这款自动sql注入工具。

使用方法简单:测试人员只需要将浏览器代理设置为该工具所在主机的IP和端口,然后在需要测试的位置点击鼠标即可,稍后可以登录到web前端部分去查看结果。

为了结果的直观性,我们对sql注入的结果数据和中间数据进行了整理,并将几个常用信息直接入库,这样会使得sqlmap注入完成后,测试人员可以直接还原sqlmap注入的整个语句。

0×03 系统界面效果


是不是很简洁?因为他功能少!不要打我~其实我是来骗稿费的。

0×04 系统sql注入界面截图

上图设计几个实用功能:

点击主机名可以查看sqlmap注入结果的详细数据,包括payload,可用于手工测试.
点击V按钮,可以查看请求的简要信息,如URL,POSTDATA,UserAgent,Cookie等信息
点击R可以生产适用于Burpsuit的请求原文
点击Sql可以生成用于sqlmap测试的bash语句(如下图,方便我这样的懒人去复现~)

0×05 附code

核心代理引擎项目地址:点我!

这个代理虽然可以用,但是实现上存在一些bug,我做了少量的修改,以使得更加适用于自动sql注入工具
以下是我基于上面的代理服务器写的插件,该插件可以将请求包发给sqlmapapi

req2sqlmap.py
 
#encoding=utf-8
import urllib
import urllib2
import re
import  json
from urlparse import urlparse
 
#每一个分布式客户端需要又一个唯一的clientid,否则会引起冲突
#如果重启代理,相当于添加一个新的client,因此也需要更换clientid
ClintId="f3ca2b6f1b2fc73f148b6cbd0db70f42"
 
#sqlmapapiurl 注意后面不能有 /
sqlmapapiurl="http://127.0.0.1:8775"
 
def getSeqKey(reqresdata):
    reqresdata=str(reqresdata)
    index=reqresdata.index('#')
    seq=reqresdata[0:index]
    return
def getSeqNum(reqresdata):
    reqresdata=str(reqresdata)
    index=reqresdata.index('#')
    reqresdata=reqresdata[index+2:]
    index=reqresdata.index('#')
    seq=reqresdata[0:index]
    return seq
#生成用户唯一的标识
def generateSeq(reqresseq):
    return str(reqresseq)+str(ClintId)
 
def doGet(url,cookies='',ua=''):
    req=urllib2.Request(url)
    if cookies!='':
        req.add_header('Cookie',cookies)
    if(''==ua):
        req.add_header('User-Agent','Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36')
    else:
        req.add_header('User-Agent',ua)
    f=urllib2.urlopen(req)
    return f.read()
def doPost(url,data='',cookies='',ua=''):
    if data=='':
        data={}
    req=urllib2.Request(url,data,{'Content-Type': 'application/json'})
    if cookies!='':
        req.add_header('Cookie',cookies)
    if(''==ua):
        req.add_header('User-Agent','Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36')
    else:
        req.add_header('User-Agent',ua)
    f=urllib2.urlopen(req)
    return f.read()
def send2Sqlmap(url,ua,cookie='',body='',otherheaders=''):
  
    sqlurl=sqlmapapiurl+'/task/new'
    resjson=doGet(sqlurl)
    jsonobj=json.loads(resjson)
    taskid=jsonobj['taskid']
    data={}
    data['url']=url
    if(cookie!=[] and cookie!=''):
        data['cookie']=cookie[0]
    data['headers']="User-Agent:"+ua[0]
    if(''!=body):
        data['data']=body
    myjsondata=json.dumps(data)
    sqlurl=sqlmapapiurl+'/scan/'+taskid+'/start'
    doPost(sqlurl,myjsondata,cookie,ua)
    if(otherheaders!=''):
        print otherheaders
def sendReq2Api():
 
    return
def sendRes2Api():
    return
def proxy_mangle_request(req):
    ReqSeqNum=getSeqNum(req)
    print "ReqSeqNum: "+str(ReqSeqNum)
    cookie=req.getHeader("Cookie")
    ua=req.getHeader("User-Agent")
    body=req.body
    url=req.url
    if(req.method=="CONNECT"):
        url="https://"+url
    if(isHavaParam(url,body)):
        send2Sqlmap(url,ua,cookie,body)
        print "send["+url+"]to sqlmapapi"
    return req
def proxy_mangle_response(res):
    #print res
    print "ResSeq: "+str(getSeqNum(res))
    return res
def fileNameCheck(urlpath):
    i = len(urlpath) - 1
    while i > 0:
        if urlpath[i] == '/':
            break
        i = i - 1
    filename=urlpath[i+1:len(urlpath)]
    print "Filename: ",filename
    res=filename.split('.')
    if(len(res)>1):
        extname=res[-1]
        ext=["css","js","jpg","jpeg","gif","png","bmp","html","htm","swf","ico","ttf","woff","svg","cur","woff2"]
        for blacklist in ext:
            if(extname==blacklist):
                return False
    return True
def isHavaParam(urlori,body=''):
    url = urlparse(urlori)
    #放过管理地址URL
    if(url.hostname=='termite.xseclab.com'):
        return False
    if not fileNameCheck(url.path):
        return False
    if(''!=body or url.params!='' or url.query!='' or url.username!=None or url.password!=None):
        return True
    #you can add your own filter here!
    return False

*本文来自FreeBuf特约作者扛把子首席小弟投稿,属FreeBuf黑客与极客(Freebuf.COM)独家发布,未经允许


被黑站点统计 - 文章版权1、本主题所有言论和图片纯属会员个人意见,与本文章立场无关
2、本站所有主题由该文章作者发表,该文章作者与被黑站点统计享有文章相关版权
3、其他单位或个人使用、转载或引用本文时必须同时征得该文章作者和被黑站点统计的同意
4、文章作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意
7、被黑站点统计管理员有权不事先通知发贴者而删除本文

免责声明

本站主要通过网络搜集国内被黑网站信息,统计分析数据,为部署安全型网络提供强有力的依据.本站所有工作人员均不参与黑站,挂马或赢利性行为,所有数据均为网民提供,提交者不一定是黑站人,所有提交采取不记名,先提交先审核的方式,如有任何疑问请及时与我们联系.

admin  的文章


微信公众号

微信公众号


Copyright © 2012-2022被黑网站统计系统All Rights Reserved
页面总访问量:21445833(PV) 页面执行时间:60.479(MS)
  • xml
  • 网站地图