2021年冬天,漏洞Log4j2的关键那个漏洞让全球安全圈过了个不眠夜。触发它只需要一行字符串——${ jndi:ldap://...。漏洞就这一行,关键服务器乖乖去外网拉了个恶意类回来执行。漏洞事后复盘,关键所有人都在问同一个问题:为什么一个日志组件会去解析用户输入里的漏洞表达式?答案藏在代码深处的一个特性里,那个特性叫Lookup,关键而Lookup的漏洞入口关键字,就是关键${ }。
关键字这个东西,漏洞在漏洞挖掘里比很多人想的关键要重要。它不是漏洞简单的字符串匹配,而是关键一把钥匙——找到正确的关键字,等于找到了从外部输入通往危险函数的漏洞路径。我见过太多人拿着扫描器跑了一遍又一遍,报告里全是"疑似存在注入点",但问他具体哪个参数、哪个关键字触发的,答不上来。这种活儿干得再多也长不了本事。
拿SQL注入来说,早期的WAF规则基本围着几个词转:UNION、SELECT、OR 1=1。攻击者就开始变形,大小写混写、内联注释、URL编码,一层套一层。这时候关键字就变了,变成了/*!50000UNION*/这种带版本号的MySQL内联注释。你光盯着UNION看没用,得看/*!这个前缀。我自己在挖某CMS的时候,就是靠一个/*!*/绕过了它那套基于正则的过滤——正则是/union\s+select/i,而实际payload里union和select之间塞了一堆注释,正则直接歇菜。
命令注入的关键字又是另一回事。分号、管道符、反引号、$(),每个都有不同的适用场景。Windows下&和|的行为差异,Linux下%0a换行符能不能用,这些细节决定了payload能不能打通。有次帮朋友看一个IoT设备的固件,过滤逻辑把;和|都禁了,但$(没管。就这一个漏网的关键字,直接拿到了root shell。
反序列化漏洞更典型。Java的readObject、PHP的unserialize、Python的pickle.loads,这些函数名本身就是关键字。但真正有意思的是gadget chain里的那些魔术方法——__wakeup、__destruct、__toString。你审计代码的时候,看到类里定义了__destruct,就得条件反射式地去看它里面调了什么。很多0day就是这么一个个魔术方法串出来的。ysoserial那堆payload,本质上就是不同库里的关键字组合。
模板注入(SSTI)的关键字识别最直观。{ { 7*7}}回显49,说明Jinja2或Twig在渲染。但真正要利用,得知道__class__、__mro__、__subclasses__这些Python内置属性怎么串。Flask的config、request对象,Django的settings,每个框架暴露的关键字不一样。我习惯先扔一个{ { config}}进去,能回显就说明离RCE不远了。
文件包含的关键字最朴素——include、require、php://input、data://。但朴素不代表简单。PHP的allow_url_include默认关闭之后,php://filter就成了主力。那个convert.base64-encode的过滤器链,配合resource=,能读出源码。CTF里考烂了的东西,真实环境里照样一抓一个准。
我说这些不是要列清单。清单网上一搜一大把,但关键字背后的逻辑得自己琢磨。同一个功能,不同语言、不同框架、不同版本,关键字可能完全不一样。Log4j那个${ }在别的日志框架里可能就是个普通占位符。你得理解它为什么会被解析、解析之后走了哪条路径、最终到了哪个危险方法。这个过程没有捷径,就是读代码、调payload、看回显、改关键字、再来一遍。
工具能帮你跑关键字,但工具不知道哪个关键字在目标环境里有效。那个判断,得靠人。