Implement Express.js app with known vulnerabilities#46
Conversation
Added an Express.js application with multiple vulnerabilities including path traversal, insecure deserialization, and SSRF.
| app.post('/import', (req, res) => { | ||
| const obj = serialize.unserialize(req.body); // RCE via crafted payload | ||
| res.json({ imported: Object.keys(obj) }); | ||
| }); |
There was a problem hiding this comment.
Remote Code Execution via Insecure Deserialization of Untrusted Input
The application exposes an endpoint /import in testerror/trfsd.html that parses the request body using node-serialize's unserialize method. node-serialize is vulnerable to arbitrary code execution when deserializing untrusted data containing serialized JavaScript functions using the _$$ND_FUNC$$_ prefix. An attacker can send a crafted payload containing an immediately invoked function expression (IIFE) in the request body to execute arbitrary shell commands on the server.
Steps to Reproduce
- Start the application using Node.js:
node testerror/trfsd.html(assuming dependencies likeexpressandnode-serializeare installed). - Send a POST request to
http://localhost:4000/importcontaining a serialized JavaScript function payload with the_$$ND_FUNC$$_prefix. - Observe that the immediately invoked function expression executes arbitrary commands on the host system.
curl -X POST -H "Content-Type: text/plain" -d '{"rce":"_$$ND_FUNC$$_function (){require(\"child_process\").execSync(\"id\");}()"}' http://localhost:4000/importFix with AI
A security vulnerability was found by Hacktron.
File: trfsd.html
Lines: 22-25
Severity: critical
Vulnerability: Remote Code Execution via Insecure Deserialization of Untrusted Input
Description:
The application exposes an endpoint `/import` in `testerror/trfsd.html` that parses the request body using `node-serialize`'s `unserialize` method. `node-serialize` is vulnerable to arbitrary code execution when deserializing untrusted data containing serialized JavaScript functions using the `_$$ND_FUNC$$_` prefix. An attacker can send a crafted payload containing an immediately invoked function expression (IIFE) in the request body to execute arbitrary shell commands on the server.
Proof of Concept:
**Steps to Reproduce**
1. Start the application using Node.js: `node testerror/trfsd.html` (assuming dependencies like `express` and `node-serialize` are installed).
2. Send a POST request to `http://localhost:4000/import` containing a serialized JavaScript function payload with the `_$$ND_FUNC$$_` prefix.
3. Observe that the immediately invoked function expression executes arbitrary commands on the host system.
```bash
curl -X POST -H "Content-Type: text/plain" -d '{"rce":"_$$ND_FUNC$$_function (){require(\"child_process\").execSync(\"id\");}()"}' http://localhost:4000/import
```
Affected Code:
app.post('/import', (req, res) => {
const obj = serialize.unserialize(req.body); // RCE via crafted payload
res.json({ imported: Object.keys(obj) });
});
Acceptance criteria:
- Acceptance is defined by the **actual reported behavior**, not by tests passing.
- Reproduce the issue, or narrow the exact code path that produces it, *before* changing code. State what you confirmed.
- Fix the underlying cause. Mitigations that paper over the reported behavior do not count as a fix.
- Add a regression test that fails on the unpatched code and passes on the fix. If a regression test is genuinely impractical (e.g. race condition, infra-level issue), say so and explain why.
- Existing tests passing is **not** the bar. Do not declare done on tests-pass theatre.
Only change what is necessary to fix this vulnerability. Do not refactor adjacent code or modify unrelated files.
Triage: Reply !fp <reason> (false positive), !valid (confirmed), !accepted_risk <reason>, or !fixed (resolved). Any other reply is saved as a triage note.
Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing.
| // VULN 3: SSRF — server fetches an attacker-supplied URL with no allowlist | ||
| app.get('/fetch', (req, res) => { | ||
| const target = req.query.url; // ?url=http://169.254.169.254/latest/meta-data/ | ||
| http.get(target, (upstream) => { |
There was a problem hiding this comment.
Server-Side Request Forgery (SSRF) in /fetch Endpoint
The Node.js Express application in trfsd.html exposes a /fetch endpoint that accepts an attacker-controlled URL via the url query parameter. This URL is passed directly to http.get without any validation, sanitization, or allowlisting. An attacker can exploit this to perform Server-Side Request Forgery (SSRF), allowing them to scan internal ports, access private network services, or query cloud metadata services (e.g., AWS IMDS) and retrieve sensitive information.
Steps to Reproduce
- Install the required Node.js dependencies:
npm install express node-serialize. - Run the server using Node:
node testerror/trfsd.html. - Send a GET request to the
/fetchendpoint with a target URL in theurlquery parameter:
curl "http://localhost:4000/fetch?url=http://example.com" - Observe that the server fetches the content of
http://example.comand returns it in the response.
# Install dependencies and start the vulnerable server
npm install express node-serialize
node testerror/trfsd.html &
sleep 2
# Trigger SSRF to fetch an external or internal resource
curl -i "http://localhost:4000/fetch?url=http://example.com"Fix with AI
A security vulnerability was found by Hacktron.
File: trfsd.html
Lines: 30
Severity: high
Vulnerability: Server-Side Request Forgery (SSRF) in /fetch Endpoint
Description:
The Node.js Express application in `trfsd.html` exposes a `/fetch` endpoint that accepts an attacker-controlled URL via the `url` query parameter. This URL is passed directly to `http.get` without any validation, sanitization, or allowlisting. An attacker can exploit this to perform Server-Side Request Forgery (SSRF), allowing them to scan internal ports, access private network services, or query cloud metadata services (e.g., AWS IMDS) and retrieve sensitive information.
Proof of Concept:
**Steps to Reproduce**
1. Install the required Node.js dependencies: `npm install express node-serialize`.
2. Run the server using Node: `node testerror/trfsd.html`.
3. Send a GET request to the `/fetch` endpoint with a target URL in the `url` query parameter:
`curl "http://localhost:4000/fetch?url=http://example.com"`
4. Observe that the server fetches the content of `http://example.com` and returns it in the response.
```bash
# Install dependencies and start the vulnerable server
npm install express node-serialize
node testerror/trfsd.html &
sleep 2
# Trigger SSRF to fetch an external or internal resource
curl -i "http://localhost:4000/fetch?url=http://example.com"
```
Affected Code:
app.get('/fetch', (req, res) => {
const target = req.query.url; // ?url=http://169.254.169.254/latest/meta-data/
http.get(target, (upstream) => {
let body = '';
upstream.on('data', (c) => (body += c));
upstream.on('end', () => res.send(body));
});
});
Acceptance criteria:
- Acceptance is defined by the **actual reported behavior**, not by tests passing.
- Reproduce the issue, or narrow the exact code path that produces it, *before* changing code. State what you confirmed.
- Fix the underlying cause. Mitigations that paper over the reported behavior do not count as a fix.
- Add a regression test that fails on the unpatched code and passes on the fix. If a regression test is genuinely impractical (e.g. race condition, infra-level issue), say so and explain why.
- Existing tests passing is **not** the bar. Do not declare done on tests-pass theatre.
Only change what is necessary to fix this vulnerability. Do not refactor adjacent code or modify unrelated files.
Triage: Reply !fp <reason> (false positive), !valid (confirmed), !accepted_risk <reason>, or !fixed (resolved). Any other reply is saved as a triage note.
Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing.
| app.post('/import', (req, res) => { | ||
| const obj = serialize.unserialize(req.body); // RCE via crafted payload | ||
| res.json({ imported: Object.keys(obj) }); | ||
| }); |
There was a problem hiding this comment.
Remote Code Execution via Insecure Deserialization of Untrusted Input
The PR introduces a new endpoint /import that accepts a POST request body and passes it directly to serialize.unserialize() from the node-serialize package. The node-serialize package is vulnerable to arbitrary code execution when deserializing untrusted objects, particularly when they contain Immediately Invoked Function Expressions (IIFEs).
An attacker can exploit this by sending a crafted JSON or text payload containing a serialized object with a function that executes immediately upon deserialization. This allows the attacker to execute arbitrary system commands on the host running the Node.js backend service.
Attack Path
- The attacker sends an HTTP POST request to
/importwith a payload like:
{"rce":"_$$ND_FUNC$$_function (){ require('child_process').exec('id', function(error, stdout, stderr) { console.log(stdout) }); }() "} - The server receives the request and passes
req.bodytoserialize.unserialize(). - The function is executed, leading to Remote Code Execution (RCE).
Steps to Reproduce
- Start the Node.js backend service by running
node testerror/trfsd.html. - Send an HTTP POST request to
/importwith theContent-Type: text/plainheader and a serialized payload containing an Immediately Invoked Function Expression (IIFE) prefixed with_$$ND_FUNC$$_. - The
express.text()middleware parses the body as a string, which is then passed directly toserialize.unserialize(). - The IIFE is executed immediately upon deserialization, resulting in arbitrary code execution.
curl -X POST http://localhost:4000/import -H "Content-Type: text/plain" -d '{"rce":"_$$ND_FUNC$$_function (){ require(\"child_process\").exec(\"id\", function(error, stdout, stderr) { console.log(stdout) }); }() "}'curl -X POST http://localhost:4000/import -H "Content-Type: text/plain" -d '{"rce":"_$$ND_FUNC$$_function (){ require(\"child_process\").exec(\"id\", function(error, stdout, stderr) { console.log(stdout) }); }() "}'Fix with AI
A security vulnerability was found by Hacktron.
File: trfsd.html
Lines: 22-25
Severity: critical
Vulnerability: Remote Code Execution via Insecure Deserialization of Untrusted Input
Description:
The PR introduces a new endpoint `/import` that accepts a POST request body and passes it directly to `serialize.unserialize()` from the `node-serialize` package. The `node-serialize` package is vulnerable to arbitrary code execution when deserializing untrusted objects, particularly when they contain Immediately Invoked Function Expressions (IIFEs).
An attacker can exploit this by sending a crafted JSON or text payload containing a serialized object with a function that executes immediately upon deserialization. This allows the attacker to execute arbitrary system commands on the host running the Node.js backend service.
### Attack Path
1. The attacker sends an HTTP POST request to `/import` with a payload like:
`{"rce":"_$$ND_FUNC$$_function (){ require('child_process').exec('id', function(error, stdout, stderr) { console.log(stdout) }); }() "}`
2. The server receives the request and passes `req.body` to `serialize.unserialize()`.
3. The function is executed, leading to Remote Code Execution (RCE).
Proof of Concept:
**Steps to Reproduce**
1. Start the Node.js backend service by running `node testerror/trfsd.html`.
2. Send an HTTP POST request to `/import` with the `Content-Type: text/plain` header and a serialized payload containing an Immediately Invoked Function Expression (IIFE) prefixed with `_$$ND_FUNC$$_`.
3. The `express.text()` middleware parses the body as a string, which is then passed directly to `serialize.unserialize()`.
4. The IIFE is executed immediately upon deserialization, resulting in arbitrary code execution.
```bash
curl -X POST http://localhost:4000/import -H "Content-Type: text/plain" -d '{"rce":"_$$ND_FUNC$$_function (){ require(\"child_process\").exec(\"id\", function(error, stdout, stderr) { console.log(stdout) }); }() "}'
```
```bash
curl -X POST http://localhost:4000/import -H "Content-Type: text/plain" -d '{"rce":"_$$ND_FUNC$$_function (){ require(\"child_process\").exec(\"id\", function(error, stdout, stderr) { console.log(stdout) }); }() "}'
```
Affected Code:
app.post('/import', (req, res) => {
const obj = serialize.unserialize(req.body); // RCE via crafted payload
res.json({ imported: Object.keys(obj) });
});
Acceptance criteria:
- Acceptance is defined by the **actual reported behavior**, not by tests passing.
- Reproduce the issue, or narrow the exact code path that produces it, *before* changing code. State what you confirmed.
- Fix the underlying cause. Mitigations that paper over the reported behavior do not count as a fix.
- Add a regression test that fails on the unpatched code and passes on the fix. If a regression test is genuinely impractical (e.g. race condition, infra-level issue), say so and explain why.
- Existing tests passing is **not** the bar. Do not declare done on tests-pass theatre.
Only change what is necessary to fix this vulnerability. Do not refactor adjacent code or modify unrelated files.
Triage: Reply !fp <reason> (false positive), !valid (confirmed), !accepted_risk <reason>, or !fixed (resolved). Any other reply is saved as a triage note.
Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing.
| app.get('/fetch', (req, res) => { | ||
| const target = req.query.url; // ?url=http://169.254.169.254/latest/meta-data/ | ||
| http.get(target, (upstream) => { | ||
| let body = ''; | ||
| upstream.on('data', (c) => (body += c)); | ||
| upstream.on('end', () => res.send(body)); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Server-Side Request Forgery (SSRF) via Arbitrary URL Fetching
The application exposes an endpoint /fetch that accepts a user-controlled url query parameter. This URL is passed directly to http.get() without any validation, allowlisting, or restriction. An attacker can exploit this Server-Side Request Forgery (SSRF) vulnerability to force the server to make outbound HTTP requests to internal network resources, loopback services, or cloud metadata endpoints (e.g., http://169.254.169.254/), and retrieve the responses.
Steps to Reproduce
- Start the Node.js application by running
node testerror/trfsd.html. - Send an HTTP GET request to the
/fetchendpoint with theurlparameter set to the target internal resource (e.g.,http://localhost:4000/fetch?url=http://169.254.169.254/latest/meta-data/). - Observe that the server performs the request and returns the response body directly to the client.
# Start the server
node testerror/trfsd.html
# Trigger SSRF to fetch internal/external resources
curl "http://localhost:4000/fetch?url=http://169.254.169.254/latest/meta-data/"Fix with AI
A security vulnerability was found by Hacktron.
File: trfsd.html
Lines: 28-35
Severity: high
Vulnerability: Server-Side Request Forgery (SSRF) via Arbitrary URL Fetching
Description:
The application exposes an endpoint `/fetch` that accepts a user-controlled `url` query parameter. This URL is passed directly to `http.get()` without any validation, allowlisting, or restriction. An attacker can exploit this Server-Side Request Forgery (SSRF) vulnerability to force the server to make outbound HTTP requests to internal network resources, loopback services, or cloud metadata endpoints (e.g., `http://169.254.169.254/`), and retrieve the responses.
Proof of Concept:
**Steps to Reproduce**
1. Start the Node.js application by running `node testerror/trfsd.html`.
2. Send an HTTP GET request to the `/fetch` endpoint with the `url` parameter set to the target internal resource (e.g., `http://localhost:4000/fetch?url=http://169.254.169.254/latest/meta-data/`).
3. Observe that the server performs the request and returns the response body directly to the client.
```bash
# Start the server
node testerror/trfsd.html
# Trigger SSRF to fetch internal/external resources
curl "http://localhost:4000/fetch?url=http://169.254.169.254/latest/meta-data/"
```
Affected Code:
app.get('/fetch', (req, res) => {
const target = req.query.url; // ?url=http://169.254.169.254/latest/meta-data/
http.get(target, (upstream) => {
let body = '';
upstream.on('data', (c) => (body += c));
upstream.on('end', () => res.send(body));
});
});
Acceptance criteria:
- Acceptance is defined by the **actual reported behavior**, not by tests passing.
- Reproduce the issue, or narrow the exact code path that produces it, *before* changing code. State what you confirmed.
- Fix the underlying cause. Mitigations that paper over the reported behavior do not count as a fix.
- Add a regression test that fails on the unpatched code and passes on the fix. If a regression test is genuinely impractical (e.g. race condition, infra-level issue), say so and explain why.
- Existing tests passing is **not** the bar. Do not declare done on tests-pass theatre.
Only change what is necessary to fix this vulnerability. Do not refactor adjacent code or modify unrelated files.
Triage: Reply !fp <reason> (false positive), !valid (confirmed), !accepted_risk <reason>, or !fixed (resolved). Any other reply is saved as a triage note.
Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing.
| app.get('/download', (req, res) => { | ||
| const file = req.query.file; | ||
| const full = path.join('/var/www/uploads/', file); // ?file=../../etc/passwd | ||
| fs.readFile(full, (err, data) => { | ||
| if (err) return res.status(404).send('not found'); | ||
| res.send(data); | ||
| }); | ||
| }); |
There was a problem hiding this comment.
Arbitrary File Read via Path Traversal in File Download Endpoint
The PR introduces a new endpoint /download that allows users to download files from /var/www/uploads/. However, the user-supplied file query parameter is joined directly with the base directory using path.join() without any sanitization or validation.
Because path.join() resolves relative directory segments like ../, an attacker can supply directory traversal sequences (e.g., ../../etc/passwd) to escape the intended directory and read arbitrary files on the server's filesystem that the Node.js process has permissions to read.
Attack Path
- The attacker sends an HTTP GET request to
/download?file=../../etc/passwd. - The server joins
/var/www/uploads/and../../etc/passwd, resolving the path to/etc/passwd. - The server reads
/etc/passwdviafs.readFile()and returns its contents to the attacker.
Steps to Reproduce
- Start the Node.js application (e.g., via
node testerror/trfsd.html). - Send an HTTP GET request to the
/downloadendpoint with a path traversal payload in thefilequery parameter. - Observe that the server returns the contents of the requested file (e.g.,
/etc/passwdor another system file reachable by the process).
curl "http://localhost:4000/download?file=../../etc/passwd"Fix with AI
A security vulnerability was found by Hacktron.
File: trfsd.html
Lines: 12-19
Severity: high
Vulnerability: Arbitrary File Read via Path Traversal in File Download Endpoint
Description:
The PR introduces a new endpoint `/download` that allows users to download files from `/var/www/uploads/`. However, the user-supplied `file` query parameter is joined directly with the base directory using `path.join()` without any sanitization or validation.
Because `path.join()` resolves relative directory segments like `../`, an attacker can supply directory traversal sequences (e.g., `../../etc/passwd`) to escape the intended directory and read arbitrary files on the server's filesystem that the Node.js process has permissions to read.
### Attack Path
1. The attacker sends an HTTP GET request to `/download?file=../../etc/passwd`.
2. The server joins `/var/www/uploads/` and `../../etc/passwd`, resolving the path to `/etc/passwd`.
3. The server reads `/etc/passwd` via `fs.readFile()` and returns its contents to the attacker.
Proof of Concept:
**Steps to Reproduce**
1. Start the Node.js application (e.g., via `node testerror/trfsd.html`).
2. Send an HTTP GET request to the `/download` endpoint with a path traversal payload in the `file` query parameter.
3. Observe that the server returns the contents of the requested file (e.g., `/etc/passwd` or another system file reachable by the process).
```bash
curl "http://localhost:4000/download?file=../../etc/passwd"
```
Affected Code:
app.get('/download', (req, res) => {
const file = req.query.file;
const full = path.join('/var/www/uploads/', file); // ?file=../../etc/passwd
fs.readFile(full, (err, data) => {
if (err) return res.status(404).send('not found');
res.send(data);
});
});
Acceptance criteria:
- Acceptance is defined by the **actual reported behavior**, not by tests passing.
- Reproduce the issue, or narrow the exact code path that produces it, *before* changing code. State what you confirmed.
- Fix the underlying cause. Mitigations that paper over the reported behavior do not count as a fix.
- Add a regression test that fails on the unpatched code and passes on the fix. If a regression test is genuinely impractical (e.g. race condition, infra-level issue), say so and explain why.
- Existing tests passing is **not** the bar. Do not declare done on tests-pass theatre.
Only change what is necessary to fix this vulnerability. Do not refactor adjacent code or modify unrelated files.
Triage: Reply !fp <reason> (false positive), !valid (confirmed), !accepted_risk <reason>, or !fixed (resolved). Any other reply is saved as a triage note.
Reason is optional but improves future scans — e.g. !fp internal endpoint, not user-facing.
Added an Express.js application with multiple vulnerabilities including path traversal, insecure deserialization, and SSRF.