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 classsqlmapapi{ private$adminid=''; private$sqlmapapi=SQLMAPAPI; private$tasknumber=0; function__construct($adminid=null){ if($adminid!=null){ $this ->adminid=$adminid; } $this ->AutoTask(); return0; } //自动处理所有任务 functionAutoTask(){ $tasklistarr =$this->getTasklist(); foreach($tasklistarras$taskid){ //查询结果并入库 $this ->Task2db($taskid); } returnTRUE; } functiongetTasklist($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; } functionflushTask($adminid=null){ if($adminid==null){ $adminid =$this->adminid; } $jsonres =$this->doGet("/admin/".$this->adminid."/list"); $res =json_decode($jsonres); if($res['success']==true){ returnTRUE; }else{ returnFALSE; } } functionTask2db($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); returnTRUE; } $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); returnTRUE; }else{ returnFALSE; } }elseif($taskstatus=='notrunning'){ $this ->delTask($taskid); returnTRUE; }elseif($taskstatus=="running"){ returnFALSE; } } functionsave2Db($host,$port,$schema,$url,$cookie,$postdata,$ua,$taskoptiondata,$taskscandata,$taskscanlog,$taskerror){ global$mysqli; var_dump (mysqli_error($mysqli)); if($num>0){ returnTRUE; }else{ returnFALSE; } } functiongetOptionList($taskid){ $jsonres =$this->doGet("/option/".$taskid."/list"); $jsonobj =json_decode($jsonres);//生成数组 return$jsonobj->options; } functiongetTaskScanLog($taskid){ $jsonres =$this->doGet("/scan/".$taskid."/log"); $jsonobj =json_decode($jsonres); return$jsonobj->log; } functiongetUrl($taskid){ } functiondelTask($taskid){ $jsonres =$this->doGet('/task/'.$taskid."/delete"); $jsonobj =json_decode($jsonres); if($jsonobj->success=='true'){ returnTRUE; }else{ returnFALSE; } } function__get($name){ return$this->$name; } function__set($name,$value){ $this ->$name=$value; } functiondoGet($api){ $options =array( CURLOPT_URL =>$this->sqlmapapi.$api, CURLOPT_POST =>false, CURLOPT_RETURNTRANSFER =>true, CURLOPT_HEADER =>false, CURLOPT_USERAGENT =>'Mozilla/5.0(WindowsNT5.1)AppleWebKit/537.17(KHTML,likeGecko)Chrome/24.0.1312.57Safari/537.17', ); $myres=$this->mycurl($options); return$myres; } functiondoPost($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(WindowsNT5.1)AppleWebKit/537.17(KHTML,likeGecko)Chrome/24.0.1312.57Safari/537.17', ); return$this->mycurl($options); } functionmycurl($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 fromurlparseimport urlparse #每一个分布式客户端需要又一个唯一的clientid,否则会引起冲突 #如果重启代理,相当于添加一个新的client,因此也需要更换clientid ClintId="f3ca2b6f1b2fc73f148b6cbd0db70f42" #sqlmapapiurl注意后面不能有/ sqlmapapiurl ="http://127.0.0.1:8775" defgetSeqKey(reqresdata): reqresdata =str(reqresdata) index =reqresdata.index('#') seq =reqresdata[0:index] return defgetSeqNum(reqresdata): reqresdata =str(reqresdata) index =reqresdata.index('#') reqresdata =reqresdata[index+2:] index =reqresdata.index('#') seq =reqresdata[0:index] return seq #生成用户唯一的标识 defgenerateSeq(reqresseq): returnstr(reqresseq)+str(ClintId) defdoGet(url,cookies='',ua=''): req =urllib2.Request(url) ifcookies!='': req .add_header('Cookie',cookies) if(''==ua): req .add_header('User-Agent','Mozilla/5.0(WindowsNT6.3;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/41.0.2272.118Safari/537.36') else: req .add_header('User-Agent',ua) f =urllib2.urlopen(req) returnf.read() defdoPost(url,data='',cookies='',ua=''): ifdata=='': data ={} req =urllib2.Request(url,data,{'Content-Type':'application/json'}) ifcookies!='': req .add_header('Cookie',cookies) if(''==ua): req .add_header('User-Agent','Mozilla/5.0(WindowsNT6.3;WOW64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/41.0.2272.118Safari/537.36') else: req .add_header('User-Agent',ua) f =urllib2.urlopen(req) returnf.read() defsend2Sqlmap(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!=[]andcookie!=''): 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 defsendReq2Api(): return defsendRes2Api(): return defproxy_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+"]tosqlmapapi" return req defproxy_mangle_response(res): #printres print"ResSeq:"+str(getSeqNum(res)) return res deffileNameCheck(urlpath): i =len(urlpath)-1 whilei>0: ifurlpath[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"] forblacklistinext: if(extname==blacklist): returnFalse returnTrue defisHavaParam(urlori,body=''): url =urlparse(urlori) #放过管理地址URL if(url.hostname=='termite.xseclab.com'): returnFalse ifnotfileNameCheck(url.path): returnFalse if(''!=bodyorurl.params!=''orurl.query!=''orurl.username!=Noneorurl.password!=None): returnTrue #youcanaddyourownfilterhere! returnFalse
转载请注明:IT运维空间 » 安全防护 » 我是如何打造一款自动化SQL注入工具的
发表评论