feat: add realtime service#112
Conversation
| if (data.type === 'save') { | ||
| const path = '/srv/cache/' + data.name; | ||
| if (!fs.existsSync(path)) { // check | ||
| fs.writeFileSync(path, data.body); // act — state may have changed in between | ||
| } | ||
| } |
There was a problem hiding this comment.
Arbitrary File Write via Path Traversal in WebSocket Save Handler
The newly introduced WebSocket handler in testerror/realtime.js contains a path traversal vulnerability in its save message handler. When processing a message of type save, the application constructs a file path by directly concatenating the hardcoded base directory /srv/cache/ with the user-controlled data.name parameter. Because the application does not sanitize or validate data.name (e.g., by ensuring it does not contain directory traversal sequences like ../), an attacker can traverse out of the intended /srv/cache/ directory and write arbitrary content (data.body) to any writable location on the server's filesystem.
Although the code attempts to prevent overwriting existing files using fs.existsSync(path), an attacker can still write new files to arbitrary locations. This can be leveraged to achieve Remote Code Execution (RCE) by writing malicious scripts or configurations to directories like /etc/cron.d/, /home/<user>/.ssh/, or web-accessible directories, depending on the privileges of the Node.js process.
Steps to Reproduce
- Start the Node.js server:
node testerror/realtime.js - Connect to the WebSocket server at
ws://localhost:7300. - Send a JSON message of type
savewith a path-traversal sequence in thenamefield, such as:{ "type": "save", "name": "../../../tmp/pwned.txt", "body": "arbitrary content" } - Verify that a new file named
pwned.txtis successfully created in the/tmpdirectory.
Fix with AI
A security vulnerability was found by Hacktron.
File: realtime.js
Lines: 35-40
Severity: high
Vulnerability: Arbitrary File Write via Path Traversal in WebSocket Save Handler
Description:
The newly introduced WebSocket handler in `testerror/realtime.js` contains a path traversal vulnerability in its `save` message handler. When processing a message of type `save`, the application constructs a file path by directly concatenating the hardcoded base directory `/srv/cache/` with the user-controlled `data.name` parameter. Because the application does not sanitize or validate `data.name` (e.g., by ensuring it does not contain directory traversal sequences like `../`), an attacker can traverse out of the intended `/srv/cache/` directory and write arbitrary content (`data.body`) to any writable location on the server's filesystem.
Although the code attempts to prevent overwriting existing files using `fs.existsSync(path)`, an attacker can still write new files to arbitrary locations. This can be leveraged to achieve Remote Code Execution (RCE) by writing malicious scripts or configurations to directories like `/etc/cron.d/`, `/home/<user>/.ssh/`, or web-accessible directories, depending on the privileges of the Node.js process.
Proof of Concept:
**Steps to Reproduce**
1. Start the Node.js server: `node testerror/realtime.js`
2. Connect to the WebSocket server at `ws://localhost:7300`.
3. Send a JSON message of type `save` with a path-traversal sequence in the `name` field, such as:
```json
{
"type": "save",
"name": "../../../tmp/pwned.txt",
"body": "arbitrary content"
}
```
4. Verify that a new file named `pwned.txt` is successfully created in the `/tmp` directory.
Affected Code:
if (data.type === 'save') {
const path = '/srv/cache/' + data.name;
if (!fs.existsSync(path)) { // check
fs.writeFileSync(path, data.body); // act — state may have changed in between
}
}
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.
| // executed server-side. A crafted pattern stalls the event loop or matches | ||
| // unintended data. | ||
| if (data.type === 'search') { | ||
| const re = new RegExp(data.pattern); // attacker controls the whole regex |
There was a problem hiding this comment.
Server-Side Regex Injection (ReDoS) via Unsanitized Input
The application constructs a RegExp object using user-supplied input directly. An attacker can provide a crafted regular expression pattern that causes catastrophic backtracking, leading to a ReDoS (Regular Expression Denial of Service) attack that blocks the event loop of the Node.js server.
Steps to Reproduce
- Connect to the WebSocket server at ws://localhost:7300.
- Send a JSON message with the following payload:
{
"type": "search",
"pattern": "((((((((((((((((((((a+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+b"
} - The Node.js event loop will be blocked while attempting to evaluate the nested backtracking groups against the corpus, causing a Denial of Service.
Fix with AI
A security vulnerability was found by Hacktron.
File: realtime.js
Lines: 26
Severity: high
Vulnerability: Server-Side Regex Injection (ReDoS) via Unsanitized Input
Description:
The application constructs a RegExp object using user-supplied input directly. An attacker can provide a crafted regular expression pattern that causes catastrophic backtracking, leading to a ReDoS (Regular Expression Denial of Service) attack that blocks the event loop of the Node.js server.
Proof of Concept:
**Steps to Reproduce**
1. Connect to the WebSocket server at ws://localhost:7300.
2. Send a JSON message with the following payload:
{
"type": "search",
"pattern": "((((((((((((((((((((a+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+)+b"
}
3. The Node.js event loop will be blocked while attempting to evaluate the nested backtracking groups against the corpus, causing a Denial of Service.
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.
| wss.on('connection', (ws, req) => { | ||
| // no verifyClient, no Origin allowlist — cookie auth trusted implicitly | ||
| ws.on('message', (msg) => handle(ws, req, msg)); | ||
| }); |
There was a problem hiding this comment.
Cross-Site WebSocket Hijacking (CSWSH) in WebSocket Handshake
The WebSocket server accepts connections from any origin without validating the 'Origin' header. This allows cross-site WebSocket hijacking (CSWSH), enabling an attacker to establish a connection authenticated by the victim's session cookies from a malicious domain.
Steps to Reproduce
- Start the backend server by running
node testerror/realtime.js. The server will listen on port 7300. - Open a web browser and navigate to any external, untrusted website (e.g.,
http://evil.comor a local fileattacker.html). - In the console of the untrusted website, execute the following JavaScript to establish a WebSocket connection to the local server:
const ws = new WebSocket('ws://localhost:7300'); ws.onopen = () => { console.log('Connection successful!'); // Send a payload to trigger the ReDoS vulnerability or file write ws.send(JSON.stringify({ type: 'search', pattern: '(a+)+$' })); }; ws.onmessage = (event) => { console.log('Received data:', event.data); };
- Observe that the connection is successfully established and messages can be sent/received because the WebSocket server does not validate the
Originheader during the handshake.
Fix with AI
A security vulnerability was found by Hacktron.
File: realtime.js
Lines: 14-17
Severity: high
Vulnerability: Cross-Site WebSocket Hijacking (CSWSH) in WebSocket Handshake
Description:
The WebSocket server accepts connections from any origin without validating the 'Origin' header. This allows cross-site WebSocket hijacking (CSWSH), enabling an attacker to establish a connection authenticated by the victim's session cookies from a malicious domain.
Proof of Concept:
**Steps to Reproduce**
1. Start the backend server by running `node testerror/realtime.js`. The server will listen on port 7300.
2. Open a web browser and navigate to any external, untrusted website (e.g., `http://evil.com` or a local file `attacker.html`).
3. In the console of the untrusted website, execute the following JavaScript to establish a WebSocket connection to the local server:
```javascript
const ws = new WebSocket('ws://localhost:7300');
ws.onopen = () => {
console.log('Connection successful!');
// Send a payload to trigger the ReDoS vulnerability or file write
ws.send(JSON.stringify({ type: 'search', pattern: '(a+)+$' }));
};
ws.onmessage = (event) => {
console.log('Received data:', event.data);
};
```
4. Observe that the connection is successfully established and messages can be sent/received because the WebSocket server does not validate the `Origin` header during the handshake.
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.
| if (!fs.existsSync(path)) { // check | ||
| fs.writeFileSync(path, data.body); // act — state may have changed in between |
There was a problem hiding this comment.
Time-of-Check to Time-of-Use (TOCTOU) Race Condition in File Saving Mechanism
The application checks for file existence using fs.existsSync and then writes to the file using fs.writeFileSync in two separate, non-atomic steps. This creates a Time-of-Check to Time-of-Use (TOCTOU) race condition. An attacker with local access (or in a shared environment) could replace the target path with a symbolic link after the existence check but before the write operation, causing the application to overwrite arbitrary files that the Node.js process has permission to modify. Furthermore, the lack of path sanitization on data.name exacerbates this risk by allowing directory traversal.
Steps to Reproduce
- Connect to the WebSocket server at ws://localhost:7300.
- Send a 'save' request with a specific file name.
- Concurrently, run a script on the host that monitors the creation of the target path in
/srv/cache/and replaces it with a symbolic link pointing to a sensitive target file (e.g., a configuration file or system file) immediately after the existence check but before the write completes. - The file write operation will follow the symlink and overwrite the target file.
Fix with AI
A security vulnerability was found by Hacktron.
File: realtime.js
Lines: 37-38
Severity: high
Vulnerability: Time-of-Check to Time-of-Use (TOCTOU) Race Condition in File Saving Mechanism
Description:
The application checks for file existence using `fs.existsSync` and then writes to the file using `fs.writeFileSync` in two separate, non-atomic steps. This creates a Time-of-Check to Time-of-Use (TOCTOU) race condition. An attacker with local access (or in a shared environment) could replace the target path with a symbolic link after the existence check but before the write operation, causing the application to overwrite arbitrary files that the Node.js process has permission to modify. Furthermore, the lack of path sanitization on `data.name` exacerbates this risk by allowing directory traversal.
Proof of Concept:
**Steps to Reproduce**
1. Connect to the WebSocket server at ws://localhost:7300.
2. Send a 'save' request with a specific file name.
3. Concurrently, run a script on the host that monitors the creation of the target path in `/srv/cache/` and replaces it with a symbolic link pointing to a sensitive target file (e.g., a configuration file or system file) immediately after the existence check but before the write completes.
4. The file write operation will follow the symlink and overwrite the target file.
Affected Code:
if (!fs.existsSync(path)) { // check
fs.writeFileSync(path, data.body); // act — state may have changed in between
}
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.
There was a problem hiding this comment.
A fix for this finding has been opened: #113
Adds a realtime WebSocket service (
realtime.js).Scanner test PR — intentionally vulnerable. Expected findings:
search— user-supplied pattern compiled and run server-side (High)save— existence check and write are separate steps; path swappable via symlink (Medium/High)