Java安全学习之S2-001


前言

看来不学java安全不行啊,流眼泪,已经看完了panda师傅的关于java安全基础入门,比如配置啊,基础sql,XSS,SSRF了,现在就要开始对知名的框架和中间件进行进攻了

Strut2-001

环境搭建

  1. 需要导入的包
    1.png
  2. Project Structure
    2.png
    这里有一点需要注意,我们要导入tomcat的包,不然在后面会出现无法步入的情况,不过后缀是provided
  3. 配置tomcat,这个就不用多说
  4. 将struts_default.xml的加入,不然会出现报错
    3.png
  • index.jsp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <%@ page contentType="text/html; charset=UTF-8" %>
    <%@ taglib prefix="s" uri="/struts-tags" %>
    <html>
    <head>
    <title>Sign On</title>
    </head>

    <body>
    <s:form action="Login">
    <s:textfield label="username" name="username"/>
    <s:textfield label="password" name="password" />
    <s:submit/>
    </s:form>
    </body>
    </html>
  • welcome.jsp

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <%@ page contentType="text/html; charset=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>
  • com.demo.action

    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
    import com.opensymphony.xwork2.ActionSupport;

    public class Login 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";
    }
    }
  • structs.xml

    1
    2
    3
    4
    5
    6
    7
    8
    <struts>
    <package name="s2" extends="struts-default">
    <action name="Login" class="com.demo.action.Login">
    <result name="success">welcome.jsp</result>
    <result name="error">index.jsp</result>
    </action>
    </package>
    </struts>

Start

首先本环节中struts.xml

1
2
3
4
5
6
7
8
<struts>
<package name="s2" extends="struts-default">
<action name="login" class="com.test.LoginAction">
<result name="success">/success.jsp</result>
<result name="error">/index.jsp</result>
</action>
</package>
</struts>

所以我们会使用默认的拦截器(interceptors)
structs-defaults.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<interceptors>

<interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/>
<interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>
<interceptor name="i18n" class="com.opensymphony.xwork2.interceptor.I18nInterceptor"/>
<interceptor name="logger" class="com.opensymphony.xwork2.interceptor.LoggingInterceptor"/>
<interceptor name="model-driven" class="com.opensymphony.xwork2.interceptor.ModelDrivenInterceptor"/>
<interceptor name="scoped-model-driven" class="com.opensymphony.xwork2.interceptor.ScopedModelDrivenInterceptor"/>
<interceptor name="params" class="com.opensymphony.xwork2.interceptor.ParametersInterceptor"/>
<interceptor name="prepare" class="com.opensymphony.xwork2.interceptor.PrepareInterceptor"/>
<interceptor name="static-params" class="com.opensymphony.xwork2.interceptor.StaticParametersInterceptor"/>
<interceptor name="scope" class="org.apache.struts2.interceptor.ScopeInterceptor"/>
....

大量拦截器中就有params这个拦截器,就是对我们请求进去的参数进行拦截处理,拦截器位于xwork中,因此我们将断点直接下在拦截器处理的地方

4.png
在断点上方的getValueStack函数,我们获取到了我们传入的username和passwd值

我们继续步进invocation.invoke(),直至
5.png
由于我们触发了错误,所以我们将触发tomcat的内部跳转机制,先获取最后访问的页面,然后将reponse重定向到那个页面

首先对于漏洞描述中的一点需要注意,为什么需要在触发验证错误的时候才会触发漏洞呢?原因是S2在触发错误后不会发生跳转,会原样返回页面,而此时原来的payload也就被解析了,这块的执行就完美解释啦

6.png

既然要返回前面的网页同时又是S2作为中间件,所以肯定要渲染原来的网页啊,用index.jsp中的一行来做个例子
<s:textfield label="username" name="username"/>

  • 这里的s:xxxx就是自定义标签,struts有自己的theme来对此标签进行解释,同时这里会根据OGNL表达式来进行赋值,即,如果容器中存在username中这个变量,则会对标签填上一个默认值
  • 这里的匹配方法很简单,就是匹配开始标签和结束标签,匹配到了算一整个块,然后进行统一的处理
    7.png

在匹配到结束标签即\>后就会进入end函数,我们再继续步入this.evaluateParams();
在后面可以看到这样一段
8.png
这里的altSyntax在2.1.7前都是默认为true的,所以值就变为了%{value}

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
public 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);
}

最后在这里分割执行表达式

总结

  1. 触发报错后返回原页面
  2. 由于altSyntax的默认开启,所以导致了出现原值被套上了一层%{},进行OGNL解析
  3. 在原页面回显

解决方案:
ognl只能执行一次,就会停止,所以就不会出现套娃的情况了