各位看官看到标题吐槽帝就开始了:已经有了各种各样的注入工具,为什么还要手工打造一个?
事实上,做为一名苦逼乙方测试工程师以及漏洞盒子屌丝白帽子 ,在疲于应对各种死缠滥打的甲方以及成堆的web测试需求时,我经常遇到以下场景:
(1)有大批量的网站需要检测的场景
乙方工程师工作辛苦劳累从来都不抱怨,有项目一定都是最能抗的,向无数奋斗在一线的乙方工程师致敬!
(2)系统内部业务复杂可能会存在众多测注入点
很多系内部业务复杂,查询功能较多,此时可能会较多的注入点,手工测试时间紧,容易纰漏,此时需要一个提取burpsuit的history记录的工具,来自动帮你分析问题所在。
(3)漏洞盒子测试时间需要争分夺秒
在漏洞盒子进行项目安全测试时,时间就是金钱——谁能以更快的速度挖到漏洞谁就能拿到更多的奖励.
[核心检测引擎] Sqlmap [核心信息收集引擎] Python代理 [数据库] mysql [Web展示] Bootstrap主题+JQuery(Ajax)
先介绍一下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注入的整个语句。
是不是很简洁?因为他功能少!不要打我~其实我是来骗稿费的。
上图设计几个实用功能:
点击主机名可以查看sqlmap注入结果的详细数据,包括payload,可用于手工测试. 点击V按钮,可以查看请求的简要信息,如URL,POSTDATA,UserAgent,Cookie等信息 点击R可以生产适用于Burpsuit的请求原文 点击Sql可以生成用于sqlmap测试的bash语句(如下图,方便我这样的懒人去复现~)
核心代理引擎项目地址 : 点我!
这个代理虽然可以用,但是实现上存在一些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)独家发布,未经允许禁止转载