1. 工具 2. IDEA配置tomcat服务器 3. 创建struts2项目 进行下一步分析前,你可能需要尽可能多的了解以下知识: 从apache通告来看 换揣着这两个疑惑,我们开始看代码: 跟进 跟进 开始对 可以看到此时传入 思考第二个问题:为何 查看官方文档,可以看到验证器的有一个特别的作用: 如果在 继续思考,程序中我们也没有设置相关的 如果你仔细阅读官方文档,则会看到如下一段: 会调用默认的 所以我们的命令执行后会被回显到 可以看到,在再次处理1. 环境搭建
IntelliJ IDEA
、tomcat 8
、struts2.0.1
https://blog.csdn.net/huo920/article/details/78307797
Java Enterprise
Web Application
Struts 2
,注意librariies选择 稍后设置,点击next
WEB-INF
目录下新建lib
目录
struts-2.0.1/apps/struts2-blank-2.0.1/WEB-INF/lib
目录下的jar包全部放入刚才新建的lib
目录,然后在IDEA中将lib添加为libraryindex.jsp
的内容,主要是form
表单,因为漏洞就在password
的输入点<%@ 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>
WEB-INF
下添加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>
<p>Hello <s:property value="username"></s:property></p>
</body>
</html>
web.xml
,中添加welcome-file-list
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<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>
struts.xml
,添加action
<?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>
src
目录下,创建用于处理登录的LoginAction.java
,继承了ActionSupport
,重写其execute()
方法,以便struts
找到对应的jsp
显示页面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.password;
}
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("youncyb")) && (this.password.equals("1")))
{
return "success";
}
return "error";
}
}
2. 前置知识
1. struts2是怎么运作的
2. Java的反射机制和Java的类加载机制和Java的动态代理
3. Ognl表达式(划重点,就是这玩意导致的漏洞)
4. IDEA调试方法
3. 漏洞分析
altSyntax
属性开启后,允许OGNL表达式插入到文本字符串中,并且会被递归执行,并且强调了是因为递归执行字符串,而且由于表单的验证错误,username
和password
处理后的结果会被回显给用户,那么这里就有两个问题:
rce
?validation error
,怎么理解?
首先看看Ognl表达式是如何执行命令的:
再来看看,这里漏洞点的位置:
可以看到payload
的写法就是Ognl表达式的语法,继续看代码,先给LoginAction.java
下断点:
用debug的方式启动tomcat,在输入过用户名密码后,对代码进行step over
直到com/opensymphony/xwork2/DefaultActionInvocation.class#171
,step into
跟进this.executeResult()
:
然后step over
直到com/opensymphony/xwork2/DefaultActionInvocation.class#248
,step into
跟进this.result.execute(this)
:
跟到org/apache/struts2/dispatcher/StrutsResultSupport.class#58
,step into
跟进this.doExecute(this.lastFinalLocation, invocation)
继续step over
到/Users/youncyb/IdeaProjects/share/s2-001/web/WEB-INF/lib/struts2-core-2.0.1.jar!/org/apache/struts2/dispatcher/ServletDispatcherResult.class#48
,可以看到通过dispatcher.forward(request, response)
对request的请求内容进行处理,我们继续step into
跟进这个函数,这里一直step into
,要花费很久的时间,函数栈如下:
到达org/apache/struts2/views/jsp/ComponentTagSupport.class#23
,step into
跟进this.component.end(this.pageContext.getOut(), this.getBody())
,然后跟入web/WEB-INF/lib/struts2-core-2.0.1.jar!/org/apache/struts2/components/UIBean.class#72
一路step over
来到/Users/youncyb/IdeaProjects/share/s2-001/web/WEB-INF/lib/struts2-core-2.0.1.jar!/org/apache/struts2/components/UIBean.class#272
:
可以看到开启了altSyntax
后,会对传入的数据加上%{}
,而我们传入的password
,就会变成%{password}
,而这时的expr还是%{password}
,我们继续跟进this.addParameter("nameValue", this.findValue(expr, valueClazz))
:
TextParseUtil.translateVariables('%', expr, this.stack)
:
translateVariables(open, expression, stack, String.class, (TextParseUtil.ParsedValueEvaluator)null).toString()
expr
进行处理,取出%{}
中的数据,即password
,然后传入stack.findValue(var, asType)
进行处理,我们继续跟进:
在这里,就可以看到OgnlUtil.getValue(expr, this.context, this.root, asType)
,一个标准的OGNL
取值表达式,而此时的expr='password'
,即取出password
对应的数据%{1+1}
,然后继续返回translateVariables
这个函数中的循环:while(true) {
int start = expression.indexOf(open + "{");
int length = expression.length();
int x = start + 2;
int count = 1;
while(start != -1 && x < length && count != 0) {
char c = expression.charAt(x++);
if (c == '{') {
++count;
} else if (c == '}') {
--count;
}
}
int end = x - 1;
if (start == -1 || end == -1 || count != 0) {
return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
}
String var = expression.substring(start + 2, end);
Object o = stack.findValue(var, asType);
if (evaluator != null) {
o = evaluator.evaluate(o);
}
String left = expression.substring(0, start);
String right = expression.substring(end + 1);
if (o != null) {
if (TextUtils.stringSet(left)) {
result = left + o;
} else {
result = o;
}
if (TextUtils.stringSet(right)) {
result = result + right;
}
expression = left + o + right;
} else {
result = left + right;
expression = left + right;
}
}
}
stack.findValue(var, asType)
时,var
变成了1+1
,而执行后结果变成了2
,完成了命令执行,再次思考提出的问题1,不难看出递归执行就在于com/opensymphony/xwork2/util/TextParseUtil.class
的while循环中,其会循环解析%{}
符号,直到字符中不存在此字符串。validation error
也是漏洞形成的原因之一?(此处分析,和chybeta师傅的分析相反)
default interceptor stack
中,如果出现验证错误,则会将输入返回到页面,并且将将用户带回到表单页面。validators.xml
,这个validation error
和我们有什么关系?
default interceptor stack
会自动开启validation
,并且从代码的调用栈我们也可以看到:
ValidationInterceptor
。index.jsp
。4. 与struts2.0.9的补丁对比
expression
,会对循环进行判断,loopCount > maxLoopCount
, 我们输入的%{1+1}
,就会被原原本本显示为%{1+1}
。5. 参考
Comments NOTHING