危险函数检索为什么在真实项目里失效
代码审计的第一课,通常教你搜这几个词:eval、system、exec、unserialize。靶场里一搜一个准。真实项目里,你可能一次都不命中。
不是因为没有危险函数,是因为危险的东西不长那个样子。
源码里根本没有这个 token
看这段代码:
$fn = $_GET['f'];
$fn($_GET['a']);
它把参数当函数名调用。你搜 eval 搜不到它,搜 system 也搜不到,因为它在源码里的形态是"变量加括号"。
PHP 编译之后,这类调用落到 ZEND_INIT_FCALL,函数名是运行时才确定的。Java 走反射,方法名藏在字符串里,Python 的 getattr 也一样。正则检索在这里一次都不会命中,因为你要找的那个词根本不存在于源码。
危险点被封装了
真实项目长这样:
// 业务层:看起来只是一次普通查询
$this->db->query("select * from t where id = " . $id);
// 底层 Db 类:拼接发生在这里
class Db {
public function query($sql) {
return $this->conn->query($sql); // 收敛点,业务代码看不见
}
}
你在业务层搜 query(,只看到一层薄薄的封装,看不到拼接。收敛点在框架里,在基类里,在组件里。你搜业务代码,搜的是结果;要搜的是汇聚的那一行。
危险的不是函数,是上下文
同样是查数据库:
// 参数化:值不参与语法构建,安全
DB::table('t')->where('id', $id)->first();
// 拼接:值参与语法,有风险
DB::select("select * from t where id = " . $id);
两个都叫"查数据库"。前者把值和指令分开,后者混在一起。你按函数名搜,两个都命中;按上下文搜,只有一个有问题。
真正要找的不是
query这个词,是"值参与了语法构建"这个形态。
那该怎么找
顺序反过来:先找入口,再追数据流,最后找收敛点。
一、入口。 哪些参数是外部可控的。路由表、控制器方法、参数绑定。先圈定范围,不急着找漏洞。
二、数据流。 这个参数往下走,经过谁的手。中间有没有类型强转、有没有白名单、有没有长度限制。每过一道,可控性就降低一分。
三、收敛点。 最终在哪一行汇入"值参与语法"的形态。可能是 orderByRaw,可能是 include,可能是命令拼接。
三个都对上,才是能写进报告的。少一环就是假阳性:少入口,代码没被调用;少数据流,中途被截断;少收敛点,那个函数本身是安全的。
三个假阳性来源
判据是同一行为至少在两个不同入口、两次独立会话复现,才算稳定信号。单次成功先怀疑请求构造。
常见三种。第一种叫不可达,代码存在但入口没注册、类没装配、开关关着、方法被注释了,写报告前必须确认调用链上有真实请求进来。第二种叫被截断,前置中间件、参数绑定、类型强转、签名校验把可控性抹掉了,此时汇聚点再有缺陷也打不到。第三种叫汇聚点无害,看着危险,实际只写日志、只拼展示串、只在错误分支执行、只操作隔离目录。
一句话
危险函数检索是枚举,数据流追踪是推理。
枚举列得完,推理列不完。换个框架、换个封装方式,枚举就失效了;推理不会,因为你找的是"值怎么流",不是"叫什么名字"。
仅限授权测试,严禁非法滥用。
读到这里,说明你在认真看。加入社群,与我们一起深挖底层。
或点此直接进入「星巡底层研习」 →
扫码加入交流社群