[GoldSrc] RCE via 'spk' Console Command
Team Summary
Official summary from Valve
Details: #Description RCE can be achieved on clients via the 'spk' console command due to missing length checks before copying into a stack based buffer. #POC 1. Place the attached cfg file in the root directory of the game: {F676967} 2. Launch the game and bring up the console with `~` 3. Type in `exec rce.cfg` and press enter 4. View calc pop #Vulnerable Code The problem arises because `VOX_LoadSound` calls `VOX_GetDirectory` which copies into the stack buffer `szpath` without any length validation which leads to a buffer overflow. ```cpp char *VOX_GetDirectory(char *szpath, char *psz) { char c; int cb = 0; char *pszscan = psz + strlen(psz) - 1; // scan backwards until first '/' or start of string c = *pszscan; while (pszscan > psz && c != '/') { c = *(--pszscan); cb++; } if (c != '/') { // didn't find '/', return default directory strcpy(szpath, "vox/"); return psz; } cb = strlen(psz) - cb; memcpy(szpath, psz, cb); // missing length validation szpath[cb] = 0; return pszscan + 1; } aud_sfxcache_t *VOX_LoadSound(aud_channel_t *pchan, char *pszin) { char buffer[512]; int i, j, k, cword; char pathbuffer[64]; char szpath[32]; aud_sfxcache_t *sc; voxword_t rgvoxword[CVOXWORDMAX]; char *psz; if (!pszin) return NULL; memset(rgvoxword, 0, sizeof (voxword_t) * CVOXWORDMAX); memset(buffer, 0, sizeof(buffer)); // lookup actual string in (*gAudEngine.rgpszrawsentence), // set pointer to string data psz = VOX_LookupString(pszin, NULL); if (!psz) { gEngfuncs.Con_DPrintf ("VOX_LoadSound: no sentence named %s\n",pszin); return NULL; } // get directory from string, advance psz psz = VOX_GetDirectory(szpath, psz); if (strlen(psz) > sizeof(buffer) - 1) { gEngfuncs.Con_DPrintf ("VOX_LoadSound: sentence is too long %s\n",psz); return NULL; } // copy into buffer strncpy(buffer, psz, sizeof(buffer) - 1); buffer[sizeof(buffer) - 1] = 0; psz = buffer; // parse sentence (also inserts null terminators between words) VOX_ParseString(psz); // for each word in the sentence, construct the filename, // lookup the sfx and save each pointer in a temp array i = 0; cword = 0; while (rgpparseword[i]) { // Get any pitch, volume, start, end params into voxword if (VOX_ParseWordParams(rgpparseword[i], &rgvoxword[cword], i == 0)) { // this is a valid word (as opposed to a parameter block) _snprintf(pathbuffer, sizeof(pathbuffer), "%s%s.wav", szpath, rgpparseword[i]); pathbuffer[sizeof(pathbuffer) - 1] = 0; if (strlen(pathbuffer) >= sizeof(pathbuffer)) continue; // find name, if already in cache, mark voxword // so we don't discard when word is done playing rgvoxword[cword].sfx = S_FindName(pathbuffer, &(rgvoxword[cword].fKeepCached)); cword++; } i++; } k = VOX_IFindEmptySentence(); if (k < 0) return NULL; j = 0; while (rgvoxword[j].sfx != NULL) rgrgvoxword[k][j] = rgvoxword[j++]; pchan->isentence = k; pchan->iword = 0; pchan->sfx = rgvoxword[0].sfx; sc = S_LoadSound(pchan->sfx, pchan); if (!sc) { S_FreeChannel(pchan); return NULL; } return sc; } ``` ## Impact RCE allows for an attacker to execute any arbitrary code on a chosen victim.
Report Details
Additional information and metadata
State
Closed
Substate
Resolved
Bounty
$350.00
Submitted
Weakness
Classic Buffer Overflow