漏洞介绍
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。

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

漏洞原理
假设已经知道了:
当访问一个模板文件时,smarty会根据模板文件生成对应的.php编译文件,在下次有相同请求时直接调用,否则重新编译并写新文件。
根据poc,先打个请求看看,/?juju={function+name=test}{/function},发现在tmp/tempaltes_c/生成一个编译文件:
1 | <?php |
打个poc:?juju={function+name=’rce(){};system(“whoami”);function ‘}{/function}
1 | <?php |
大概可以看出第28行中,rce(){};system(“whoami”);function 插入函数名中,通过{};结束前面一个函数定义,通过function 使后面生成的编译文件名部分字符串提前成为了函数名,中间便导致了插入。
debug跟入看看:(确保删除已经生成的编译文件,调试比较长,只列出一些关键函数)
从display进入->_execute
lib/smarty/sysplugins/smarty_internal_templatebase.php#L119-L123
_execute调用createTemplate生成模板,
lib/smarty/sysplugins/smarty_internal_templatebase.php#L156-L175
接着开始render: 
lib/smarty/sysplugins/smarty_internal_templatebase.php#L216
进入smarty_internal_template.php的render,跟进到compiled->render:
lib/smarty/sysplugins/smarty_internal_template.php#L206
进入smarty_internal_compiled.php的render,跟进到process:
lib/smarty/sysplugins/smarty_internal_compiled.php#L163
进入smarty_internal_compiled.php的process中,断点的两个地方:
lib/smarty/sysplugins/smarty_internal_compiled.php#L100, L103
compileTemplateSource编译模板源,文件的最后写入就是发生在这里,loadCompiledTemplate再进行加载,
跟进compileTemplateSource:
lib/smarty/sysplugins/smarty_internal_compiled.php#L204
compileTemplateSource通过$this->write写文件,跟进write:
lib/smarty/sysplugins/smarty_internal_compiled.php#L226-L239
filepath就是文件路径,code则为最终编译文件的代码。不过这是最后一步了,先得往回看如何compileTemplate
在smarty_internal_templatecompilerbase.php中注释也说了,get code frame of compiled template,
lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php#L334
跟进如何compileTemplateSource的:
在smarty_internal_templatecompilerbase.php的compileTemplateSource中,调用了doCompile
lib/smarty/sysplugins/smarty_internal_templatecompilerbase.php#L404
在这里还是我们传入的payload,
对于payload主要的解析过程,就在doParse过程中:
lib/smarty/sysplugins/smarty_internal_smartytemplatecompiler.php#L114, L118
偷了个懒,因为这部分太长了,加之我也没仔细去跟,就直接跳到最后callTagCompiler
callTagCompiler就相当于调用某类的compile方法:
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:
lib/smarty/sysplugins/smarty_internal_compile_function.php#L106
可以看见,payload被原原本本给了$_name,然后就是简单了,$_name直接拼接,带入了最后的内容中
lib/smarty/sysplugins/smarty_internal_compile_function.php#L130-L133
然后就是写个新的模板文件什么的,不多说了。
现在跳回一开始compileTemplateSource和loadCompiledTemplate的地方,loadCompiledTemplate中include了编译文件。
lib/smarty/sysplugins/smarty_template_compiled.php#L127-L139

位于编译文件中
漏洞复现
poc使用
使用https://srcincite.io/pocs/cve-2021-26120.py.txt中分享的POC,可以使用SQL注入漏洞重置管理员密码,并执行任意命令:
1 | python poc.py 127.0.0.1 / id |

id命令执行成功