Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

140: Crash Exploit - Sendmap #158

Open
theonlypwner opened this issue Aug 9, 2015 · 1 comment
Open

140: Crash Exploit - Sendmap #158

theonlypwner opened this issue Aug 9, 2015 · 1 comment

Comments

@theonlypwner
Copy link
Member

Crash Exploit - Sendmap by theonlypwner
Issue 140 posted to Google Code on 2013 Aug 17 at 15:12:33

Current status: Verified
Current labels: Type-Defect, Priority-Critical, Component-Logic, Security, Usability

theonlypwner on 2013 Aug 17 at 15:12:33:

What steps will reproduce the problem?
Attack: cfgsizegz is a very low negative number
Conditions: the attacker is connected to the server
Impact: this is a potential shellcode exploit. The negative number is treated as an unsigned integer, but the memcpy size is the same as the allocation size, so it is unlikely.

See server.cpp for an outline of the exploit.

What is the expected output? What do you see instead?
See crash.txt for the crash output. Note that it crashes in another place.

Access to this is restricted, in order to protect the public servers until the next release.

theonlypwner on 2013 Aug 17 at 15:12:59:

I forgot to attach these.

server.cpp

bool sendmapserv(int n, string mapname, int mapsize, int cfgsize, int cfgsizegz, uchar *data){
    FILE *fp;

    if(!mapname[0] || mapsize <= 0 || mapsize + cfgsizegz > MAXMAPSENDSIZE || cfgsize > MAXCFGFILESIZE) return false; // malformed: probably modded client
    if(m_edit(gamemode) && !strcmp(mapname, behindpath(smapname)))
    { // update mapbuffer only in coopedit mode (and on same map)
        copystring(copyname, mapname);
        copymapsize = mapsize;
        copycfgsize = cfgsize;
        copycfgsizegz = cfgsizegz;
        copysize = mapsize + cfgsizegz;
        copyrw = true;
        DELETEA(copydata);
        copydata = new uchar[copysize]; /* <-- EXPLOIT: BOOM! allocates too much memory */
        memcpy(copydata, data, copysize); /* <-- EXPLOIT: if not up there, BOOM! */
    }
/* from network */
case N_MAPC2S: // client sendmap
{
    getstring(text, p);
    filtertext(text, text);
    int mapsize = getint(p);
    int cfgsize = getint(p);
    int cfgsizegz = getint(p); /* EXPLOIT: this is low, like -200000000 */
    if(p.remaining() < mapsize + cfgsizegz || MAXMAPSENDSIZE < mapsize + cfgsizegz)
    {
        p.forceoverread();
        break;
    }
    const char *sentmap = behindpath(text), *reject = NULL;
    const int mp = findmappath(sentmap);
    if(readonlymap(mp))
    {
        reject = "map is read-only";
        defformatstring(msg)("\f3map upload rejected: map %s is readonly", sentmap);
        sendservmsg(msg, sender);
    }
    else if(!m_edit(gamemode) && !strcmp(sentmap, behindpath(smapname)))
    {
        reject = "currently loaded";
        sendservmsg("\f3you cannot upload the currently loaded map (except coopedit)", sender);
    }
    // else if // too much uploaded?
    else if(!m_edit(gamemode) && mp == MAP_NOTFOUND && strchr(scl.mapperm, 'C') && cl->priv < PRIV_ADMIN) // default: everyone can upload the initial map
    {
        reject = "no permission for initial upload";
        sendservmsg("\f3initial map upload rejected: you need admin (except coopedit)", sender);
    }
    else if(!m_edit(gamemode) && mp == MAP_TEMP /*&& revision >= mapbuffer.revision*/ && !strchr(scl.mapperm, 'u') && cl->priv < PRIV_ADMIN) // default: only admins can update maps
    {
        reject = "no permission to update";
        sendservmsg("\f3map update rejected: you need admin (except coopedit)", sender);
    }
    else
    {
        if(sendmapserv(sender, text, mapsize, cfgsize, cfgsizegz, &p.buf[p.len]))
        {
            //incoming_size += mapsize + cfgsizegz;
            logline(ACLOG_INFO,"[%s] %s sent map %s, %d + %d(%d) bytes written",
                cl->hostname, cl->name, sentmap, mapsize, cfgsize, cfgsizegz);
            sendf(-1, 1, "ri2s", N_MAPC2S, sender, sentmap);
        }
        else
        {
            reject = "write failed (no 'incoming'?)";
            sendservmsg("\f3map upload failed -- no incoming", sender);
        }
    }
    mapsending = false;
    if (reject) logline(ACLOG_INFO,"[%s] %s sent map '%s', %d + %d(%d) bytes rejected: %s", cl->hostname, cl->name, sentmap, mapsize, cfgsize, cfgsizegz, reject);
    p.len += mapsize + cfgsizegz;
    break;
}

crash.txt

ERROR: ACR fatal error: Win32 Exception: 0xc0000005 [0x890e4134]
ERROR:
ERROR: getint - protocol.cpp [25]
ERROR: process - server.cpp [3558]
ERROR: enet_protocol_dispatch_incoming_commands - protocol.c [71]
ERROR: enet_host_service - protocol.c [1799]
ERROR: serverslice - server.cpp [5030]
ERROR: initserver - ser

theonlypwner on 2013 Aug 17 at 15:14:51:

Status: Started

theonlypwner on 2013 Aug 17 at 15:22:42:

New conditions: the attacker must be connected and able to send maps

server.cpp

bool sendmapserv(int n, string mapname, int mapsize, int cfgsize, int cfgsizegz, uchar *data){
    FILE *fp;

    if(!mapname[0] || mapsize <= 0 || mapsize + cfgsizegz > MAXMAPSENDSIZE || cfgsize > MAXCFGFILESIZE) return false; // malformed: probably modded client
    if(m_edit(gamemode) && !strcmp(mapname, behindpath(smapname)))
    { // update mapbuffer only in coopedit mode (and on same map)
        copystring(copyname, mapname);
        copymapsize = mapsize;
        copycfgsize = cfgsize;
        copycfgsizegz = cfgsizegz;
        copysize = mapsize + cfgsizegz;
        copyrw = true;
        DELETEA(copydata);
        copydata = new uchar[copysize]; /* <-- EXPLOIT: BOOM! allocates too much memory */
        memcpy(copydata, data, copysize); /* <-- EXPLOIT: if not up there, BOOM! */
    }

    defformatstring(name)(SERVERMAP_PATH_INCOMING "%s.cgz", mapname);
    path(name);
    fp = fopen(name, "wb");
    if(fp)
    {
        fwrite(data, 1, mapsize, fp);
        fclose(fp);
        if(!cfgsize || !cfgsizegz) return true;
        formatstring(name)(SERVERMAP_PATH_INCOMING "%s.cfg", mapname);
        path(name);
        fp = fopen(name, "wb");
        if(fp)
        {
            uchar *rawcfg = new uchar[cfgsize]; /* <-- EXPLOIT: BOOM! another huge allocation */
            uLongf rawsize = cfgsize;
            if(uncompress(rawcfg, &rawsize, data + mapsize, cfgsizegz) == Z_OK && rawsize == cfgsize) // rawsize - cfgsize == 0 (why?!)
                fwrite(rawcfg, 1, cfgsize, fp);
            fclose(fp);
            DELETEA(rawcfg);
            return true;
        }
    }
    return false;
}
/* from network */
case N_MAPC2S: // client sendmap
{
    getstring(text, p);
    filtertext(text, text);
    int mapsize = getint(p);
    int cfgsize = getint(p); /* EXPLOIT: this is low, like -200000000 */
    int cfgsizegz = getint(p); /* EXPLOIT: this is low, like -200000000 */
    if(p.remaining() < mapsize + cfgsizegz || MAXMAPSENDSIZE < mapsize + cfgsizegz)
    {
        p.forceoverread();
        break;
    }
    const char *sentmap = behindpath(text), *reject = NULL;
    const int mp = findmappath(sentmap);
    if(readonlymap(mp))
    {
        reject = "map is read-only";
        defformatstring(msg)("\f3map upload rejected: map %s is readonly", sentmap);
        sendservmsg(msg, sender);
    }
    else if(!m_edit(gamemode) && !strcmp(sentmap, behindpath(smapname)))
    {
        reject = "currently loaded";
        sendservmsg("\f3you cannot upload the currently loaded map (except coopedit)", sender);
    }
    // else if // too much uploaded?
    else if(!m_edit(gamemode) && mp == MAP_NOTFOUND && strchr(scl.mapperm, 'C') && cl->priv < PRIV_ADMIN) // default: everyone can upload the initial map
    {
        reject = "no permission for initial upload";
        sendservmsg("\f3initial map upload rejected: you need admin (except coopedit)", sender);
    }
    else if(!m_edit(gamemode) && mp == MAP_TEMP /*&& revision >= mapbuffer.revision*/ && !strchr(scl.mapperm, 'u') && cl->priv < PRIV_ADMIN) // default: only admins can update maps
    {
        reject = "no permission to update";
        sendservmsg("\f3map update rejected: you need admin (except coopedit)", sender);
    }
    else
    {
        /* EXPLOIT: must be able to send maps */
        if(sendmapserv(sender, text, mapsize, cfgsize, cfgsizegz, &p.buf[p.len]))
        {
            //incoming_size += mapsize + cfgsizegz;
            logline(ACLOG_INFO,"[%s] %s sent map %s, %d + %d(%d) bytes written",
                cl->hostname, cl->name, sentmap, mapsize, cfgsize, cfgsizegz);
            sendf(-1, 1, "ri2s", N_MAPC2S, sender, sentmap);
        }
        else
        {
            reject = "write failed (no 'incoming'?)";
            sendservmsg("\f3map upload failed -- no incoming", sender);
        }
    }
    mapsending = false;
    if (reject) logline(ACLOG_INFO,"[%s] %s sent map '%s', %d + %d(%d) bytes rejected: %s", cl->hostname, cl->name, sentmap, mapsize, cfgsize, cfgsizegz, reject);
    p.len += mapsize + cfgsizegz;
    break;
}

theonlypwner on 2013 Aug 24 at 02:22:06:

This is fixed in TFS 30379, which also fixes Issue 139 and the client version of this exploit.

Labels: -Restrict-View-Commit
Status: Fixed

theonlypwner on 2013 Aug 24 at 02:22:56:

My server now disconnects my client for Tag Type when the exploit is used.

Status: Verified

@theonlypwner
Copy link
Member Author

Is this exploit possible again?

bool sendmap(const char *nmapname, int nmapsize, int ncfgsize, int ncfgsizegz, uchar *ndata)
{
FILE *fp;
bool written = false;
if(!nmapname[0] || nmapsize <= 0 || ncfgsizegz < 0 || nmapsize + ncfgsizegz > MAXMAPSENDSIZE || ncfgsize > MAXCFGFILESIZE) return false; // malformed: probably modded client
int cfgsize = ncfgsize;
if(m_edit(smode) && !strcmp(nmapname, behindpath(smapname)))
{ // update mapbuffer only in coopedit mode (and on same map)
copystring(mapname, nmapname);
datasize = nmapsize + ncfgsizegz;
revision = 0;
DELETEA(data);
data = new uchar[datasize];
memcpy(data, ndata, datasize);
}
defformatstring(name)(SERVERMAP_PATH_INCOMING "%s.cgz", nmapname);
path(name);
fp = fopen(name, "wb");
if(fp)
{
fwrite(ndata, 1, nmapsize, fp);
fclose(fp);
formatstring(name)(SERVERMAP_PATH_INCOMING "%s.cfg", nmapname);
path(name);
fp = fopen(name, "wb");
if(fp)
{
uLongf rawsize = ncfgsize;
if(uncompress(gzbuf, &rawsize, ndata + nmapsize, ncfgsizegz) == Z_OK && rawsize - ncfgsize == 0)
fwrite(gzbuf, 1, cfgsize, fp);
fclose(fp);
written = true;
}
}
return written;
}

@theonlypwner theonlypwner reopened this Jun 24, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant