一、前言
Struts2 是较早出现实现 MVC 思想的 java 框架。struts2 在 jsp 文件中使用 ognl 表达式来取出值栈中的数据。 struts 标签与 ognl 表达式的关系类似于 jstl 标签与 el 表达式的关系。[故在 jsp 文件中需引入 struts 标签库]
# 二、概述
S2-001 漏洞由于在其解析 jsp 文件的标签数据时,官方举例是 form 标签的 textfield 数据中,在验证表单出错时,页面再次回到验证出错页面,这时如果开启了 altSyntax (默认开启)且为字符串类型时, struts2 会对标签中的数据在值栈中自栈顶向栈底找与表单 name 名同名的属性值进行 ognl 表达式解析并显示。故在表单中输入形如 %{…} (代码逻辑中根据此形式截取字符串来获取表达式)将会对其中的表达式进行解析。
官方链接: https://cwiki.apache.org/confluence/display/WW/S2-001
影响版本: WebWork 2.1 (with altSyntax enabled), WebWork 2.2.0 - WebWork 2.2.5, Struts 2.0.0 - Struts 2.0.8
默认开启 altSyntax
# 三、复现
环境:apache-tomcat-9.0.37 、 jdk1.8.0_261 、 struts 2.0.1
坑点:
- ○ 进行表单验证时 500 报错: java.lang.NoClassDefFoundError: org/apache/struts2/spi/RequestContext ,加入 struts2-api 的 jar 包即可解决。
- ○ 找不到 struts-default.xml ,在 Project Structure 的 Facet 中 File Set 编辑并引进来。
1、首先创建一个 struts2 的项目 … (其实网上有,就不贴图了)
2、结构如下图,在 WEB-INF 下创建 lib ,将六个 jar 包复制过来,并将其 Add as Library ,在 Project Structure 的 Artifacts 将其 put into output root 即可。
3、在 index.jsp 中添加一个 struts 的 form 标签。
4、修改 struts.xml 文件。如下图,验证出错重新回到 index.jsp ,成功则进入 welcome.jsp
5、LoginAction.java :默认会执行 execute 方法,有 username 、 password 两个属性(和 jsp 文件中表单的 name 保持一致)同时配置其各自的 set 、get 方法( struts 都是通过反射去获得或设置属性值)
6、welcome.jsp
7、运行
# 四、分析
首先在进入到 LoginAction 之前,会由 FilterDispatcher 创建一个 ActionProxy 根据 struts.xml 中的配置找到处理该请求的 Action ,接着默认会执行 18 个拦截器,在拦截器中初始化值栈,将 Action 相应参数值压入栈。进入 Action 后根据逻辑返回对应视图。
这里在表单验证出错后返回 index.jsp 页面,而此时的 username 、 password 已压入栈中,在将其取出时若开启了 altSyntax 则进行递归解析。
在解析的过程中,会调用 ComponentTagSupport 的 doStartTag 及 doEndTag 方法对标签内容进行解析,在解析到表单中 name 为 username 的数据时,首先获取到 username 在值栈中的值。
返回给 com.opensymphony.xwork2.util.TextParseUtil#translateVariables 中的 o ,而此时的 o 将再次作为表达式传入 translateVariables 方法中进行解析(他的循环条件为 true )。
再次进入 findValue 方法,进行解析
最终在 ognl.Ognl#getValue 中得到结果值。
# 五、修复
官方建议:升级到 Struts 2.0.9 / XWork 2.0.4
关键代码:设置最大循环解析次数为 1 次,通过最开始的 %{…} 找到表单的属性值后,不再往下循环解析。不再往下循环解析是通过 pos 变量来控制下次从哪个索引开始检索表达式,当前属性值已经解析一次,pos 值则设置为当前属性值的长度,故解析了一次的表达式不再解析第二次。
# 疑问???
• 除了 form 标签可以解析 ognl 表达式之外,其他标签是不是也可以解析 ognl 表达式。在业务功能上体现的话就是可由用户输入,且将其用户输入再次呈现的地方。
• 为什么漏洞修复不能直接在 拦截器 的步骤中将用户输入的 形如 %{} 的表达式直接剔除
-
○ 这里直接转发到 index.jsp 页面不由 struts2 接收请求,故不能在拦截器层面将其拦截。
-
○ 提出这个问题是因为觉得这种修复应该在没进入正式业务前做,比如这里的拦截器层面。但是后来细想还是有很多不妥。上面回答的点也有些不妥,因为最开始进入到 LoginAction 是可以进行拦截的。而后面对 index.jsp 中的标签进行解析时出现问题。ojz