kavin

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

kavin 安全防护 2022-12-21 464浏览 0

0×01 前言

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

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

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

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

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

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

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

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

系统框架组成:

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

[核心检测引擎]
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就可以启动了

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

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

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

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

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

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

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

有了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 系统界面效果

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

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

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

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

上图设计几个实用功能:

点击主机名可以查看sqlmap注入结果的详细数据,包括payload,可用于手工测试.

点击V按钮,可以查看请求的简要信息,如URL,POSTDATA,UserAgent,Cookie等信息

点击R可以生产适用于Burpsuit的请求原文

点击Sql可以生成用于sqlmap测试的bash语句(如下图,方便我这样的懒人去复现~)

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

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

继续浏览有关 安全 的文章
发表评论