Vendor: playSMS
Vendor URL: https://playsms.org/
Versions affected: Before 1.4.3
Systems Affected: All
Author: Lucas Rosevear
Advisory URL / CVE Identifier: CVE-2020-8644
Risk: Critical
Summary:
PlaySMS is an open source SMS gateway, which has a web management portal written in PHP. PlaySMS supports a custom PHP templating system, called tpl
(https://github.com/antonraharja/tpl).
PlaySMS double processes a server-side template, resulting in unauthenticated user control of input to the PlaySMS template engine. The template engine’s implementation then permits arbitrary code execution.
Location:
src/Playsms/Tpl.php:_compile()
Impact:
An attacker with network connectivity to an instance of PlaySMS can execute arbitrary code.
Details:
The vulnerability can be broken into two parts:
- A server-side template is processed twice. After the first pass, user
controlled input is present in the template when it is processed a second time. - The
tpl
template language is vulnerable to PHP code injection.
These are both detailed below.
Server-side Template Processed Twice
PlaySMS caches the last POST handled by each plugin/route in web/init.php
with the following logic:
// save last $_POST in $_SESSION
if ($_POST['X-CSRF-Token']) {
$_SESSION['tmp']['last_post'][md5(trim(_APP_ . _INC_ . _ROUTE_ . _INC_))] = $_POST;
}
The login flow in PlaySMS starts by dynamically loading a specified plugin in web/inc/app/main.php
. For authentication, the web/plugin/core/auth/login.php
file is included. When a user authenticates, a POST request is sent, populating the cache. The authentication plugin then populates a template, found in web/plugin/core/auth/templates/auth_login.html
.
This template includes the following:
This template gets processed within the authentication plugin, executing the _lastpost
method, which returns a value from the POST cache, populating the username field with the last submitted username.
At this point, if a user submitted a custom template string, such as {{1+1}}
, this would not be processed, but would now be present in this HTML blob.
Subsequently, back in web/inc/app/main.php
, the following logic is executed:
$content = ob_get_clean();
_p(themes_apply($content));
The $content
variable contains the HTML with user-controlled input present from the authentication plugin. The themes_apply
function ultimately ends up executing the following template:
$tpl = array(
'name' => $themes_layout,
'vars' => array(
'CONTENT' => $content,
)
);
$content = tpl_apply($tpl, array(
'core_config',
'user_config'
));
This logic maps the user-controlled $content
to the CONTENT
variable in the theme template, as seen in web/plugin/themes/common/templates/themes_layout.html
{{ CONTENT }}
Template remote code execution
The vulnerable logic in the tpl
templating system begins with variable substitution:
// check static replaces
if ($this->vars) {
foreach ($this->vars as $key => $val) {
$this->_setString($key, $val);
}
empty($this->vars);
}
After this logic executes, the attacker controlled input is directly present in the template that is being processed. Subsequently, the following snippet is executed:
$pattern = "{{(.*?)}}";
preg_match_all("/" . $pattern . "/", $this->_result, $matches, PREG_SET_ORDER);
This looks for a pattern of form {{input}}
. The input ultimately gets concatenated to a PHP string:
foreach ($matches as $block) {
$chunk = $block[0];
$codes = '<?php ' . $this->config['echo'] . '(' . trim($block[1]) . ')' . '; ?>';
$this->_result = str_replace($chunk, $codes, $this->_result);
}
The resulting string is <?php echo(input); ?>
The code then executes this string in one of two ways:
tpl
attempts to create a cache file with the malicious string as
content.$cache_file = md5($this->_filename) . '.compiled'; $cache = $this->config['dir_cache'] . '/' . $cache_file; $fd = @fopen($cache, 'w+'); @fwrite($fd, $this->_result); @fclose($fd);
If this succeeds, the code is executed by including the cache file:if (file_exists($cache)) { ob_start(); include $cache;
- If caching fails,
tpl
directly evals the malicious stringelse { ob_start(); eval('?>' . $this->_result . '<?php ');
Reproduction:
Attempt to authenticate with the following username, and no password:
{{`ls -lah`}}
Notice the username field is replaced with the result of the command.
Recommendation:
Update to PlaySMS version 1.4.3.
Vendor Communication:
01/25/20: Initial contact with PlaySMS maintainer
01/30/20: Vulnerability details sent to PlaySMS
02/04/20: Vulnerability patched
02/04/20: Patch tested by NCC Group
02/05/20: PlaySMS 1.4.3 released
Thanks to:
Anton Raharja for their responsiveness and quickly patching the vulnerability.
About NCC Group:
NCC Group is a global expert in cyber security and risk mitigation, working with businesses to protect their brand, value and reputation against the ever-evolving threat landscape.
With our knowledge, experience and global footprint, we are best placed to help businesses identify, assess, mitigate and respond to the risks they face.
We are passionate about making the Internet safer and revolutionizing the way in which organizations think about cybersecurity.