前言

经典 Java 软件漏洞 struts 系列,准备一个个看过去。


环境搭建

Action 的 execute 方法加上一句(其他成员变量也可以):

ActionContext.getContext().getSession().put("username", this.username);

jsp 的 form 表单里面加上:

<s:token />

struts.xml 的 action 标签里面加上(两个拦截器得顺序可以翻过来,但是不加 default 那个拦截器就无法触发 setter):

<interceptor-ref name="defaultStack" />
<interceptor-ref name="token" />
<result name="invalid.token">/error.jsp</result>

测试访问 Action,看看会不会正常触发 token 校验。

漏洞利用

CSRF 漏洞,可以先正常流程传一个 username 触发 execute 中的 session 赋值,然后用这个 payload 绕过 token 校验:

username=Twings&struts.token.name=username&struts.token=Twings

漏洞分析

调试 TokenInterceptor 跟一遍就明白了,主要问题在于 token 校验函数 validToken 中:

public static boolean validToken() {
    String tokenName = getTokenName();

    if (tokenName == null) {
        ...
        return false;
    }

    String token = getToken(tokenName);

    if (token == null) {
        ...
        return false;
    }

    Map session = ActionContext.getContext().getSession();
    String sessionToken = (String) session.get(tokenName);

    if (!token.equals(sessionToken)) {
        ...

        return false;
    }

    // remove the token so it won't be used again
    session.remove(tokenName);

    return true;
}

struts2 将 token 存放在 session 中,但是存放用的键可以由用户自行设定,所以用户就可以通过将键设置为一个已知值的键(比如 username),从而绕过 token 验证。

漏洞修复

2.3.4.1 版本的修复,validToken 方法里面从 session 取 token 的方式改变了:

Map session = ActionContext.getContext().getSession();
String tokenSessionName = buildTokenSessionAttributeName(tokenName);
String sessionToken = (String) session.get(tokenSessionName);

多了一个 buildTokenSessionAttributeName 方法:

public static String buildTokenSessionAttributeName( String tokenName ) {
    return TOKEN_NAMESPACE + "." + tokenName;
}

现在存放 token 的键都会以:

struts.tokens.

开头了,所以一般就无法用其他键来伪装 token 了。


Orz


Web Java Struts2

本博客所有文章除特别声明外,均采用 CC BY-SA 3.0协议 。转载请注明出处!

struts2系列漏洞 S2-012
struts2系列漏洞 S2-009