Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

admin 2025年1月27日00:48:26评论16 views字数 13792阅读45分58秒阅读模式

免责声明 由于传播、利用本公众号所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号安全洞察知识图谱及作者不为承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!

1
漏洞描述

两个漏洞内容是一样的,只是 CVE-2024-50379 没有修复完全,CVE-2024-56337 做了二次修复

漏洞描述:

Time-of-check Time-of-use (TOCTOU) Race Condition vulnerability during 
JSP compilation in Apache Tomcat permits an RCE on case insensitive file 
systems when the default servlet is enabled for write (non-default 
configuration).

几个关键点:

  • insensitive file systems (大小写不敏感系统:windows 系统)
  • the default servlet (用于处理静态文件的 DefaultServlet 类)
  • enabled for write (允许写:参考 CVE-2017 的那个 PUT RCE 需要特殊配置)
  • Race Condition (条件竞争)

0x02 调试环境

参考曾经 PUT RCE 需要打开 conf/web.xml 的 readonly 为 false

<servlet>    <servlet-name>default</servlet-name>    <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>    <init-param>        <param-name>debug</param-name>        <param-value>0</param-value>    </init-param>    <init-param>        <param-name>listings</param-name>        <param-value>false</param-value>    </init-param>    <init-param>        <param-name>readonly</param-name>        <param-value>false</param-value>    </init-param>    <load-on-startup>1</load-on-startup></servlet>

启动 Tomcat 时使用 bin/catalina.bat 的 jpda 启动

catalina.bat jpda start

把 lib/*.jar 文件使用 IDEA 右键 Add As Library 然后我们就可以动态调试了

注意 Tomcat 默认使用的是 8000 调试端口

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

0x03 初步分析

参考 0x01 中的几个关键点,猜测基本的漏洞原理:

  • 线程 A 写 JSP(大写,注意 JspServlet 不处理这种文件只处理小写)
  • 线程 B 读 jsp(小写)
  • 由于 windows 大小写问题导致目标解析了 JSP 文件

接下来我们从 DefaultServlet 的入口 doGet 开始

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {    this.serveResource(request, response, true, this.fileEncoding);}

跟入 serveResource 可以主要逻辑如下:

  • getResource
     得到文件如果不存在返回 404
  • getResource
     得到文件如果存在返回具体内容
protected voidserveResource(HttpServletRequestrequest,HttpServletResponseresponse,booleancontent,StringinputEncoding)throwsIOException,ServletException{    booleanserveContent=content;    Stringpath=this.getRelativePath(request,true);    // ...    if(path.length()==0){        this.doDirectoryRedirect(request,response);    }else{        WebResourceresource=this.resources.getResource(path);        booleanisError=DispatcherType.ERROR==request.getDispatcherType();        StringrequestUri;        if(!resource.exists()){            // 返回 404        }else{            // 返回文件内容        }    }}

跟入 this.resources.getResource(path) 到达 StandardRoot#getRource

protected WebResourcegetResource(Stringpath,booleanvalidate,booleanuseClassLoaderResources){    if(validate){        path=this.validate(path);    }    returnthis.isCachingAllowed()?this.cache.getResource(path,useClassLoaderResources):this.getResourceInternal(path,useClassLoaderResources);}

以上 DefaultServlet 负责静态资源处理,而 JspServlet 负责 jsp 处理和编译

其中 JspServlet 只处理 jsp/jspx 参考下图 org.apache.catalina.mapper.Mapper#internalMapWrapper

访问 a.JSP 和 a.Jsp 等变形内容,都是由 DefaultServlet 处理

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

跟入 JspServlet#serviceJspFile 方法,进入 getResource 方法

private voidserviceJspFile(HttpServletRequestrequest,HttpServletResponseresponse,StringjspUri,booleanprecompile)throwsServletException,IOException{    JspServletWrapperwrapper=this.rctxt.getWrapper(jspUri);    if(wrapper==null){        synchronized(this){            wrapper=this.rctxt.getWrapper(jspUri);            if(wrapper==null){                // 跟入                if(null==this.context.getResource(jspUri)) {

调试后发现同样到达了 StandardRoot#getRource 方法,二者在寻找本地文件的代码相同

回到 StandardRoot 类,由于该类是两个 Servlet 共同寻找文件的方法,我们从该类开始深入分析,即可找到竞争的原因

return this.isCachingAllowed() ? this.cache.getResource(path, useClassLoaderResources) : this.getResourceInternal(path, useClassLoaderResources);

在调用 this.cache.getResource 前判断是否开启了 cache

注意该功能文档位于:https://tomcat.apache.org/tomcat-9.0-doc/config/resources.html

主要内容是:

  • 如果此标志的值为true,则将使用静态资源的缓存。

  • 如果未指定,则标志的默认值为true。

跟入 cache 类(这里无需过多关心,竞争的原理和这里的缓存无关)

protected WebResourcegetResource(Stringpath,booleanuseClassLoaderResources){    // 这里匹配 path 不处理 .class 和 .jar 文件    if(this.noCache(path)){        returnthis.root.getResourceInternal(path,useClassLoaderResources);    }else{        // 默认 strategy 是空        WebResourceRoot.CacheStrategystrategy=this.getCacheStrategy();        if(strategy!=null&&strategy.noCache(path)){            returnthis.root.getResourceInternal(path,useClassLoaderResources);        }else{            this.lookupCount.increment();            // 关键方法            CachedResourcecacheEntry=(CachedResource)this.resourceCache.get(path);            if(cacheEntry!=null&&!cacheEntry.validateResource(useClassLoaderResources)){                this.removeCacheEntry(path);                cacheEntry=null;            }

注意第一次访问 favicon.ico 得到的是空

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

默认内容是以下

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

由于结果是空,进入下一行关键方法 cacheEntry.validateResource

this.root.getResourceInternal(this.webAppPath, useClassLoaderResources);

进入 StandardRoot 的 getResourceInternal

result = webResourceSet.getResource(path);

进入 DirResourceSet 的 getResource

public WebResourcegetResource(Stringpath){    this.checkPath(path);    StringwebAppMount=this.getWebAppMount();    WebResourceRootroot=this.getRoot();    if(path.startsWith(webAppMount)){        // webAppMount 默认是空        Filef=this.file(path.substring(webAppMount.length()),false);        // ...        // 返回文件    }else{        returnnewEmptyResource(root,path);    }}

0x04 AbstractFileResourceSet file

进入核心方法 this.file 中 (AbstractFileResourceSet)

protected finalFilefile(Stringname,booleanmustExist){    // 一些前置处理    StringcanPath=null;    try{        // JRE 方法        canPath=file.getCanonicalPath();    }catch(IOExceptionvar6){    }    // 一些后置处理 返回文件对象}

进入 JRE 方法 WinNTFileSystem#getCanonicalPath

该方法内容简单总结:将给定的文件路径标准化和规范化

  • 处理 Windows 驱动器
  • 方法检查是否启用缓存。如果缓存未启用,则调用 canonicalize0 方法标准化
  • 如果缓存可用,则首先检查缓存中是否已经存在该路径的标准化结果,如果存在直接返回
  • 另有一个前缀缓存机制,优化父目录相关的问题

可以看到在 JRE 中非常注重提升性能,避免重复代码和计算问题

关键部分我已经注释在代码中

@OverridepublicStringcanonicalize(Stringpath)throwsIOException{    // windows 盘符问题处理代码忽略    if(!useCanonCaches){        returncanonicalize0(path);    }else{        // 从缓存中获取        Stringres=cache.get(path);        if(res==null){            Stringdir=null;            StringresDir=null;            // 拿不到缓存 就从前缀缓存中获取下            if(useCanonPrefixCache){                dir=parentOrNull(path);                if(dir!=null){                    resDir=prefixCache.get(dir);                    if(resDir!=null){                        Stringfilename=path.substring(1+dir.length());                        res=canonicalizeWithPrefix(resDir,filename);                        cache.put(dir+File.separatorChar+filename,res);                    }                }            }            if(res==null){                // 缓存和前缀缓存都没拿到就调用 canonicalize0 方法                res=canonicalize0(path);                // 设置缓存                cache.put(path,res);                if(useCanonPrefixCache&&dir!=null){                    resDir=parentOrNull(res);                    if(resDir!=null){                        Filef=newFile(res);                        if(f.exists()&&!f.isDirectory()){                            // 设置前缀缓存                            prefixCache.put(dir,resDir);                        }                    }                }            }        }        returnres;    }}

观察到 cache 和 prefixCache 内容分别如下:一个是具体文件,一个是目录,都是 JRE 为性能而做的优化

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

注意到一个 native 方法 canonicalize0 底层做了什么

我找到了 WinNTFileSystem#canonicalize0 的 C 代码

JNIEXPORT jstringJNICALLJava_java_io_WinNTFileSystem_canonicalize0(JNIEnv*env,jobjectthis,                                           jstringpathname){    // ...    WCHAR*cp=(WCHAR*)malloc(len*sizeof(WCHAR));    if(cp!=NULL){        // 核心方法        if(wcanonicalize(path,cp,len)>=0){            rv=(*env)->NewString(env,cp,(jsize)wcslen(cp));        }        free(cp);    }else{        JNU_ThrowOutOfMemoryError(env,"native memory allocation failed");    }    // ...    returnrv;}

核心方法是:wcanonicalize

找到 jdk 的 jdk/src/windows/native/java/io/canonicalize_md.c 方法

intwcanonicalize(WCHAR*orig_path,WCHAR*result,intsize){    // ...    dst=result;     // ...    h=FindFirstFileW(path,&fd);    // ...    if(h!=INVALID_HANDLE_VALUE){        // 如果找到文件 拼接存在的文件名        /* Lookup succeeded; append true name to result and continue */        FindClose(h);        if(!(dst=wcp(dst,dend,L'\',fd.cFileName,                        fd.cFileName+wcslen(fd.cFileName)))){            gotoerr;        }        src=p;        continue;    }else{        if(!lastErrorReportable()){            // 找不到文件 拼接原始文件名           if(!(dst=wcp(dst,dend,L'�',src,src+wcslen(src)))){               gotoerr;           }            break;        }else{            gotoerr;        }    }}

其中 FindFirstFileW 函数说明如下

https://learn.microsoft.com/zh-cn/windows/win32/api/fileapi/nf-fileapi-findfirstfilew

忽略了大部分代码,只保留关键代码:

  • 假设输入 orig_path 是 xxx/a.jsp
  • FindFirstFileW 实际找到的文件是 PUT 成功上传的 a.JSP 文件(已存在文件)
  • 最终返回的结果是找到的本地的 a.JSP
  • 如果找不到文件,原样返回

总结:

  • canonicalize0 方法的输入是 a.jsp 时,如果本地有 a.JSP 文件,会返回 a.JSP 而不是 a.jsp
  • canonicalize0 方法输入是 a.jsp 时,如果本地没有 a.JSP 文件,原样返回 a.jsp

为了验证结果,我本地 PUT 写了个 a.JSP 文件,手动访问 a.jsp 成功断点并截图,证明了我的猜测

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

下一步 a.jsp 小写 jsp 被写进去了缓存中 (a.jsp -> a.JSP)

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

注意高版本 Java 的该属性默认是 false 不存在缓存机制,你无法进入这个 else 分支

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

回到 AbstractFileResourceSet 的 file 方法后置处理部分

try {    // JRE 方法    canPath=file.getCanonicalPath();}catch(IOExceptionvar6){}if(canPath!=null&&canPath.startsWith(this.canonicalBase)){    StringabsPath=this.normalize(file.getAbsolutePath());    // ... 一些处理    if(!canPath.equals(absPath)){        if(!canPath.equalsIgnoreCase(absPath)){            this.logIgnoredSymlink(this.getRoot().getContext().getName(),absPath,canPath);        }        returnnull;    }else{        // 只有 canPath 和 absPath 相同时才会返回文件        returnfile;    }    // ...    returnnull;}

0x05 abs path 与 can path

getCanonicalPath 的结果是 can path

后续和 abs path 做比较,决定了返回是 null 还是具体的 File 对象

其中 abs path 是用户输入的路径拼接处理后的本地绝对路径(不一定必须存在)

其中 can path 是 JRE 类 WinNTFileSystem JNI/cache 处理后得到的路径

大体上分两种情况分析:

(1)PUT a.JSP 情况

abs path 显然是 /a.JSP

情况1.1 can path 无缓存,本地不存在 /a.JSP 文件,原样返回,得到 /a.JSP

情况1.2 can path 无缓存,本地存在 /a.JSP 文件,原样返回,得到 /a.JSP

情况1.3 can path 有缓存 a.JSP -> a.JSP 直接读缓存,原样返回,得到 /a.JSP

情况1.4 can path 有缓存 a.JSP -> a.JSP 直接读缓存,原样返回,得到 /a.JSP

以上任意一种情况,都一定会导致缓存中写入一条新的 a.JSP -> a.JSP

当我们访问 GET a.JSP 时,可以找到上传的 a.JSP 文件(此时由 DefaultServlet 处理静态资源)

(2)GET a.jsp 情况

abs path 显然是 /a.jsp

此时应该有三种缓存情况:

2.1. 缓存 a.jsp -> a.jsp
2.2. 缓存 a.jsp -> a.JSP
2.3. 没有缓存

情况2.1的原因:GET a.jsp 的时候没有上传 a.JSP 文件,原样返回了

情况2.2的原因:GET a.jsp 的时候已经存在 PUT 上传的 a.JSP 文件,返回以存在文件优先(参考上文 JNI 部分)

情况2.3的原因:高版本 JDK 默认不开缓存 / 手动关了缓存功能 / 第一次访问

情况2.1分析:can path 直接读缓存 /a.jsp,进入 JspServlet 后续处理阶段,能不能 RCE 看 a.JSP 文件在不在

情况2.2分析:can path 直接读缓存 /a.JSP,无法通过 equals 验证,返回空,最终返回 404

情况2.3分析:can path 返回看本地有没有 a.JSP(又分为以下两种情况)

情况2.3.1分析:本地有 a.JSP 返回 a.JSP 导致 equals 校验失败,给缓存添加了 a.jsp -> a.JSP

情况2.3.2分析:本地无 a.JSP 返回 a.jsp 可以过校验,给缓存添加了 a.jsp -> a.jsp

如果是 情况 2.3.2 第一次访问,缓存添加了 a.jsp -> a.jsp 内容

然后 PUT a.JSP 成功上传一个问题

再走 情况2.1 进入 JspServlet 后续处理阶段,因为 a.JSP 文件存在,导致了 RCE

0x06 二次校验

在我后续调试中,发现即使 a.jsp 的 equals 校验通过,后续还有一层校验

当顺利通过这一层之后,后续存在 Tomcat 内置缓存的处理

org.apache.catalina.webresources#getResource

对得到的 resource 再次校验

cacheEntry.validateResource(useClassLoaderResources);

跟入到达:this.root.getResourceInternal(this.webAppPath, useClassLoaderResources);

层层深入再次到达:AbstractFileResourceSet file 方法

我们要保证二次校验这里还是 a.jsp -> a.jsp

0x07 n次校验

还没有结束,在 JspServlet 后续处理的过程中,会再次抵达 AbstractFileResourceSet file 方法

JspCompilationContext#getLastModified

URL jspUrl = this.getResource(resource);

JspCompilationContext#getResource

还没有结束

JspCompilationContext#getLastModified 中调用了

uc.getLastModified()

到达 org.apache.catalina.webresources.CachedResource#getResource

再次抵达 AbstractFileResourceSet file 方法

最后达到真正的 JspServlet 读取文件的地方

JspUtil#getInputStream

再次抵达 AbstractFileResourceSet file 方法

0x08 竞争的是什么

竞争的难点是什么:我们需要保证,以上的 n 次 file 方法,取到的 cache 都必须是 a.jsp -> a.jsp

只要有一次是:a.jsp -> a.JSP 就会导致无法通过 equals 校验,返回 null 无法后续走完全程

回顾一下,什么情况下会出现:a.jsp -> a.JSP

PUT a.JSP 只要完成了,本地存在了 a.JSP 文件

就会导致下一次的 AbstractFileResourceSet file 方法中 can path 得到 a.JSP 和 abs path 不一致

以上,可以看到,保存了一次 a.jsp -> a.jsp

我们必须要 PUT a.JSP 文件落地之前

完美地通过以上 n 次的 AbstractFileResourceSet file 方法(注意这个 n 不止4次,实际需要更多次)

以上 n 次的 equals 校验,必须都拿到 a.jsp - > a.jsp 才会成功执行 jsp 文件导致 RCE

通过以上 n 次之后,还需要再 InputStream 读取的时候,文件正好落地,成功读到文件内容

0x09 过期策略

如果你仔细调试,你会发现为什么有时候 cache 里的 a.jsp -> a.jsp 不见了

因为 cache 有过期策略

synchronized Stringget(Stringkey){    // 超过 300 个缓存就清空    if(++queryCount>=queryOverflow){        cleanup();    }    // 根据 key 取    Entryentry=entryFor(key);    if(entry!=null){        returnentry.val();    }    returnnull;}privateEntryentryFor(Stringkey){    Entryentry=map.get(key);    if(entry!=null){        longdelta=System.currentTimeMillis()-entry.timestamp();        // 默认超时 30 秒        if(delta<0||delta>=millisUntilExpiration){            // a.jsp -> a.jsp 只会保持 30 秒            map.remove(key);            entry=null;        }    }    returnentry;}

所以我们的调试要在 30 秒内速速操作,超过就找不到了

0x08 修复

CVE-2024-50379 的修复如下

https://github.com/apache/tomcat/commit/43b507ebac9d268b1ea3d908e296cc6e46795c00

https://github.com/apache/tomcat/commit/631500b0c9b2a2a2abb707e3de2e10a5936e5d41

读操作和写操作加了锁

官方修复时的想法应该是:(不考虑缓存情况)

当 PUT 操作没有完成写入的时候,GET a.jsp getResource 操作阻塞

官方的锁考虑了大小写,PUT a.JSP 的 write 操作和 GET a.jsp 的 read 操作是同一个锁,读和写冲突

private String getLockKey(String path) {    // Normalize path to ensure that the same key is used for the same path.    String normalisedPath = RequestUtil.normalize(path);    if (caseSensitive) {        return normalisedPath;    }    // a.JSP 和 a.jsp 都是 a.jsp 的 lock key    return normalisedPath.toLowerCase(Locale.ENGLISH);}

CVE-2024-56337 二次修复

官方给出建议:必须设置该属性为 false

sun.io.useCanonCaches

回顾上文 canonicalize 方法当该属性是 false 时直接 JNI 获取路径,不进行缓存

if (!useCanonCaches) {    return canonicalize0(path);} else {    // ...

缓存这里存在的安全问题

  • 第一次 GET a.jsp 时缓存中添加了 a.jsp -> a.jsp
  • 第 n 次 GET a.jsp 可能有两种缓存:a.jsp -> a.JSP / a.jsp->a.jsp

如果某一个时刻

  • PUT a.JSP 文件已经写入了
  • GET a.jsp 的时候缓存中内容是 a.jsp -> a.jsp 成功绕过 equals 验证
  • JspServlet 读到 a.JSP 文件有内容成功执行

如果不使用缓存,将会出现以下问题:

  • GET a.jsp 时如果 a.JSP 文件存在,can path 一定是 JSP 导致无法通过 equals 校验
  • GET a.jsp 时如果 a.JSP 文件不存在,can path 一定是 jsp 可以进入 JspServlet 后续

没有缓存,于是当 PUT a.JSP 文件落地后,后续的 GET a.jsp 将全部得到 a.JSP 无法通过 equals 校验

正是因为缓存,导致了一种可能:PUT a.JSP 后可能 GET a.jsp 得到的 can path 还是 a.jsp

以上内容,考虑情况比较多且比较复杂,难免存在一些错误之处,欢迎师傅们讨论和指出问题

0x05 检测

作为扫描器,不可以使用线程对冲的方式,可能会导致巨量脏数据

经过一些测试和综合考虑,我找到一种几乎无误报漏报,无损无脏数据的扫描方式

我已经给 xray 商业版(洞鉴)添加了该扫描方案,具体逻辑如下:

(1)判断 PUT 操作有效

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

如果 PUT 任意一个随机文件得到 204 返回,证明开启了 PUT 操作

补充:为了证实 PUT 操作确实有效和脏数据考虑,下一步可以 GET 访问确认成功,最后 DELETE 删除该临时文件

(2)判断操作系统

感谢漏洞百出群友的帮助,找到了一种判断目标是否是 WINDOWS 系统的方法

以 Linux 系统为例

PUT 上传一个 :.txt 文件成功,响应 201

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

访问 :.txt 成功,内容正确

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

但是在 Windows 系统会遇到 409 的报错

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

根据该差异,可以确定目标系统的类型

(3)版本信息

通过以上两点,可以证明开启 PUT 且是 WINDOWS

只需要再通过一个 404 路径,使用正则取到 Tomcat 报错版本信息

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

根据官方公告,精确匹配 tomcat 版本

// Affected versions://- Apache Tomcat 11.0.0-M1 through 11.0.1//- Apache Tomcat 10.1.0-M1 through 10.1.33//- Apache Tomcat 9.0.0.M1 through 9.0.97if(ver>=9_000_000&&ver<=9_000_097)||    (ver>=10_001_000&&ver<=10_001_033)||    (ver>=11_000_000&&ver<=11_000_001)||    // 目前来看 9/10/11 的 M 版本都有漏洞    (strings.Contains(verStr,"M")&&        (strings.HasPrefix(verStr,"9.")||            strings.HasPrefix(verStr,"10.")||         strings.HasPrefix(verStr,"11."))){        // 报告漏洞    }}

通过这三步,即可完成一个几乎无误报漏报无损无脏数据的漏洞检测方案

(注意:几乎完美,但不能覆盖所有情况,如果用户手动关闭缓存,远程无法检测到)

Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

0x06 致谢

感谢 漏洞百出 群友一起交流以及给出 windows 系统检测思路

 

以上文章来源于先知社区,作者4ra1n,原文链接:https://xz.aliyun.com/news/16337如有侵权请联系删除

原文始发于微信公众号(安全洞察知识图谱):Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年1月27日00:48:26
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   Tomcat CVE-2024-50379 / CVE-2024-56337 条件竞争漏洞分析http://cn-sec.com/archives/3669626.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息