mirror of
https://github.com/elicpeter/nyx.git
synced 2026-07-03 20:41:00 +02:00
Dynamic (#77)
This commit is contained in:
parent
55247b7fcd
commit
991c84a1eb
1464 changed files with 225448 additions and 1985 deletions
13
tests/dynamic_fixtures/js/cmdi_adversarial.js
Normal file
13
tests/dynamic_fixtures/js/cmdi_adversarial.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// Command injection — adversarial collision fixture.
|
||||
// Prints NYX_PWN_791_CMDI unconditionally without reaching a command sink
|
||||
// and without emitting __NYX_SINK_HIT__.
|
||||
// Expected verdict: Inconclusive(OracleCollisionSuspected)
|
||||
// Entry: runPing(host) Cap: CODE_EXEC
|
||||
|
||||
function runPing(host) {
|
||||
// Coincidental oracle match — not a shell sink.
|
||||
process.stdout.write('NYX_PWN_791_CMDI\n');
|
||||
void host.length;
|
||||
}
|
||||
|
||||
module.exports = { runPing };
|
||||
31
tests/dynamic_fixtures/js/cmdi_negative.js
Normal file
31
tests/dynamic_fixtures/js/cmdi_negative.js
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Command injection — negative fixture.
|
||||
// Safe: uses execFile (no shell) with args array; shell metacharacters ignored.
|
||||
// Entry: runPing(host) Cap: CODE_EXEC
|
||||
// Expected verdict: NotConfirmed
|
||||
//
|
||||
// The host string is handed to `true` so it can never appear on stdout (which
|
||||
// would let an oracle scanning for the vuln-payload marker fire even though
|
||||
// no command was executed by a shell). Sub-process stderr is silenced with
|
||||
// `stdio: 'ignore'` so the same logic holds for `outcome.stderr` — Node's
|
||||
// default of inheriting child stderr to the parent stream lets `execFile`
|
||||
// error messages leak the host string otherwise.
|
||||
|
||||
const { execFileSync } = require('child_process');
|
||||
|
||||
function runPing(host) {
|
||||
// Sink-reachability probe: we did reach the execFile call site.
|
||||
process.stdout.write('__NYX_SINK_HIT__\n');
|
||||
// execFile does not invoke a shell — semicolons and metacharacters are inert.
|
||||
try {
|
||||
const out = execFileSync('true', [host], {
|
||||
encoding: 'utf8',
|
||||
timeout: 5000,
|
||||
stdio: ['ignore', 'pipe', 'ignore'],
|
||||
});
|
||||
process.stdout.write(out);
|
||||
} catch (e) {
|
||||
// true exits 0 with no output; the catch is defensive.
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { runPing };
|
||||
18
tests/dynamic_fixtures/js/cmdi_positive.js
Normal file
18
tests/dynamic_fixtures/js/cmdi_positive.js
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
// Command injection — positive fixture.
|
||||
// Vulnerable: passes user input directly to shell via execSync.
|
||||
// Entry: runPing(host) Cap: CODE_EXEC
|
||||
// Expected verdict: Confirmed ("; echo NYX_PWN_CMDI" payload echoes marker)
|
||||
|
||||
const { execSync } = require('child_process');
|
||||
|
||||
function runPing(host) {
|
||||
process.stdout.write('__NYX_SINK_HIT__\n');
|
||||
try {
|
||||
const out = execSync('echo hello ' + host, { encoding: 'utf8', timeout: 5000 });
|
||||
process.stdout.write(out);
|
||||
} catch (e) {
|
||||
process.stdout.write((e.stdout || '') + (e.stderr || ''));
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { runPing };
|
||||
17
tests/dynamic_fixtures/js/cmdi_unsupported.js
Normal file
17
tests/dynamic_fixtures/js/cmdi_unsupported.js
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
// Command injection — unsupported fixture.
|
||||
// Entry expects a pre-parsed args array, not a string payload.
|
||||
// Test sets confidence = Low to get Unsupported(ConfidenceTooLow).
|
||||
// Entry: runCommand(args) Cap: CODE_EXEC
|
||||
// Expected verdict: Unsupported
|
||||
|
||||
const { execFileSync } = require('child_process');
|
||||
|
||||
function runCommand(args) {
|
||||
// args is expected to be an array; a string payload can't be routed here.
|
||||
if (!Array.isArray(args) || args.length === 0) {
|
||||
return;
|
||||
}
|
||||
execFileSync(args[0], args.slice(1), { encoding: 'utf8', timeout: 5000 });
|
||||
}
|
||||
|
||||
module.exports = { runCommand };
|
||||
13
tests/dynamic_fixtures/js/fileio_adversarial.js
Normal file
13
tests/dynamic_fixtures/js/fileio_adversarial.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// File I/O — adversarial collision fixture.
|
||||
// Prints "root:" unconditionally without reading any file
|
||||
// and without emitting __NYX_SINK_HIT__.
|
||||
// Expected verdict: Inconclusive(OracleCollisionSuspected)
|
||||
// Entry: readFile(userPath) Cap: FILE_IO
|
||||
|
||||
function readFile(userPath) {
|
||||
// Coincidental oracle match — not a file read sink.
|
||||
process.stdout.write('root: present\n');
|
||||
void userPath.length;
|
||||
}
|
||||
|
||||
module.exports = { readFile };
|
||||
25
tests/dynamic_fixtures/js/fileio_negative.js
Normal file
25
tests/dynamic_fixtures/js/fileio_negative.js
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
// File I/O — negative fixture.
|
||||
// Safe: path is normalized and validated against an allowlist prefix.
|
||||
// Entry: readFile(userPath) Cap: FILE_IO
|
||||
// Expected verdict: NotConfirmed
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const BASE_DIR = '/var/data';
|
||||
|
||||
function readFile(userPath) {
|
||||
const resolved = path.resolve(BASE_DIR, userPath);
|
||||
if (!resolved.startsWith(BASE_DIR + path.sep) && resolved !== BASE_DIR) {
|
||||
process.stdout.write('Access denied\n');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const content = fs.readFileSync(resolved, 'utf8');
|
||||
process.stdout.write(content.substring(0, 100));
|
||||
} catch (e) {
|
||||
process.stdout.write('File not found\n');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { readFile };
|
||||
20
tests/dynamic_fixtures/js/fileio_positive.js
Normal file
20
tests/dynamic_fixtures/js/fileio_positive.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// File I/O — positive fixture.
|
||||
// Vulnerable: reads a file at a user-controlled path without sanitization.
|
||||
// Entry: readFile(userPath) Cap: FILE_IO
|
||||
// Expected verdict: Confirmed (../../../../etc/passwd → "root:" in output)
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
function readFile(userPath) {
|
||||
const filePath = path.join('/var/data', userPath);
|
||||
process.stdout.write('__NYX_SINK_HIT__\n');
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
process.stdout.write(content);
|
||||
} catch (e) {
|
||||
// Silent on error — target file may not exist in all envs
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { readFile };
|
||||
20
tests/dynamic_fixtures/js/fileio_unsupported.js
Normal file
20
tests/dynamic_fixtures/js/fileio_unsupported.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// File I/O — unsupported fixture.
|
||||
// Entry takes a Buffer (binary), not a UTF-8 string payload.
|
||||
// Test sets confidence = Low to get Unsupported(ConfidenceTooLow).
|
||||
// Entry: processUpload(buf) Cap: FILE_IO
|
||||
// Expected verdict: Unsupported
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
function processUpload(buf) {
|
||||
if (!Buffer.isBuffer(buf)) {
|
||||
return;
|
||||
}
|
||||
const tmpPath = '/tmp/upload_' + Date.now();
|
||||
fs.writeFileSync(tmpPath, buf);
|
||||
const content = fs.readFileSync(tmpPath, 'utf8');
|
||||
process.stdout.write(content.substring(0, 64));
|
||||
fs.unlinkSync(tmpPath);
|
||||
}
|
||||
|
||||
module.exports = { processUpload };
|
||||
14
tests/dynamic_fixtures/js/sqli_adversarial.js
Normal file
14
tests/dynamic_fixtures/js/sqli_adversarial.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SQL injection — adversarial collision fixture.
|
||||
// Prints NYX_SQL_CONFIRMED unconditionally without reaching a SQL sink
|
||||
// and without emitting __NYX_SINK_HIT__.
|
||||
// Expected verdict: Inconclusive(OracleCollisionSuspected)
|
||||
// oracle_fired = true, sink_hit = false
|
||||
// Entry: login(username) Cap: SQL_QUERY
|
||||
|
||||
function login(username) {
|
||||
// Coincidental oracle match — not a SQL sink.
|
||||
process.stdout.write('NYX_SQL_CONFIRMED\n');
|
||||
void username.length;
|
||||
}
|
||||
|
||||
module.exports = { login };
|
||||
14
tests/dynamic_fixtures/js/sqli_negative.js
Normal file
14
tests/dynamic_fixtures/js/sqli_negative.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
// SQL injection — negative fixture.
|
||||
// Safe: uses a parameterized query pattern; payload never concatenated.
|
||||
// Entry: login(username) Cap: SQL_QUERY
|
||||
// Expected verdict: NotConfirmed
|
||||
|
||||
function login(username) {
|
||||
// Parameterized: the query template is fixed, payload is a bound param.
|
||||
const template = 'SELECT name FROM users WHERE name = ?';
|
||||
// Simulate param binding — payload is never embedded in the query string.
|
||||
const safeQuery = template; // template unchanged regardless of username
|
||||
process.stdout.write('Query executed with param: ' + safeQuery + '\n');
|
||||
}
|
||||
|
||||
module.exports = { login };
|
||||
13
tests/dynamic_fixtures/js/sqli_positive.js
Normal file
13
tests/dynamic_fixtures/js/sqli_positive.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// SQL injection — positive fixture.
|
||||
// Vulnerable: directly concatenates user input into SQL query string.
|
||||
// Entry: login(username) Cap: SQL_QUERY
|
||||
// Expected verdict: Confirmed (UNION payload echoes NYX_SQL_CONFIRMED)
|
||||
|
||||
function login(username) {
|
||||
const query = "SELECT name FROM users WHERE name='" + username + "'";
|
||||
process.stdout.write('__NYX_SINK_HIT__\n');
|
||||
// Simulate error-based SQL execution: echo query on failure.
|
||||
process.stdout.write('DB query: ' + query + '\n');
|
||||
}
|
||||
|
||||
module.exports = { login };
|
||||
15
tests/dynamic_fixtures/js/sqli_unsupported.js
Normal file
15
tests/dynamic_fixtures/js/sqli_unsupported.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
// SQL injection — unsupported fixture.
|
||||
// The entry point accepts an HTTP request object, not a plain string.
|
||||
// HarnessSpec derivation will map this to HttpBody slot (unsupported by JS emitter).
|
||||
// Entry: handleRequest(req) Cap: SQL_QUERY
|
||||
// Expected verdict: Unsupported (EntryKindUnsupported or ConfidenceTooLow)
|
||||
|
||||
class UserRepository {
|
||||
findUser(req) {
|
||||
const name = req && req.body && req.body.name;
|
||||
const query = "SELECT * FROM users WHERE name='" + name + "'";
|
||||
return query;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { UserRepository };
|
||||
13
tests/dynamic_fixtures/js/ssrf_adversarial.js
Normal file
13
tests/dynamic_fixtures/js/ssrf_adversarial.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// SSRF — adversarial collision fixture.
|
||||
// Prints "daemon:" unconditionally without making any HTTP request
|
||||
// and without emitting __NYX_SINK_HIT__.
|
||||
// Expected verdict: Inconclusive(OracleCollisionSuspected)
|
||||
// Entry: fetchUrl(targetUrl) Cap: SSRF
|
||||
|
||||
function fetchUrl(targetUrl) {
|
||||
// Coincidental oracle match — not an HTTP sink.
|
||||
process.stdout.write('daemon: present\n');
|
||||
void targetUrl.length;
|
||||
}
|
||||
|
||||
module.exports = { fetchUrl };
|
||||
24
tests/dynamic_fixtures/js/ssrf_negative.js
Normal file
24
tests/dynamic_fixtures/js/ssrf_negative.js
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
// SSRF — negative fixture.
|
||||
// Safe: only allows http/https scheme; file:// and other schemes are rejected.
|
||||
// Entry: fetchUrl(targetUrl) Cap: SSRF
|
||||
// Expected verdict: NotConfirmed
|
||||
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const url = require('url');
|
||||
|
||||
function fetchUrl(targetUrl) {
|
||||
const parsed = url.parse(targetUrl);
|
||||
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
||||
process.stdout.write('Scheme not allowed: ' + parsed.protocol + '\n');
|
||||
return;
|
||||
}
|
||||
const mod = parsed.protocol === 'https:' ? https : http;
|
||||
const req = mod.get(targetUrl, (res) => {
|
||||
res.on('data', (chunk) => process.stdout.write(chunk.toString().substring(0, 64)));
|
||||
});
|
||||
req.on('error', () => {});
|
||||
req.end();
|
||||
}
|
||||
|
||||
module.exports = { fetchUrl };
|
||||
35
tests/dynamic_fixtures/js/ssrf_positive.js
Normal file
35
tests/dynamic_fixtures/js/ssrf_positive.js
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
// SSRF — positive fixture.
|
||||
// Vulnerable: makes a request to a user-controlled URL.
|
||||
// Entry: fetch(url) Cap: SSRF
|
||||
// Expected verdict: Confirmed (file:///etc/passwd → "daemon:" in output)
|
||||
// Note: Node.js http/https module does not support file:// scheme.
|
||||
// We detect the file:// prefix and use fs.readFile directly to simulate
|
||||
// the SSRF behaviour (same oracle: reads /etc/passwd, outputs "daemon:").
|
||||
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
const https = require('https');
|
||||
const url = require('url');
|
||||
|
||||
function fetchUrl(targetUrl) {
|
||||
process.stdout.write('__NYX_SINK_HIT__\n');
|
||||
const parsed = url.parse(targetUrl);
|
||||
if (parsed.protocol === 'file:') {
|
||||
// Simulate SSRF via file:// — read local file (oracle expects "daemon:")
|
||||
try {
|
||||
const content = fs.readFileSync(parsed.pathname || '/', 'utf8');
|
||||
process.stdout.write(content);
|
||||
} catch (e) {
|
||||
// silent
|
||||
}
|
||||
return;
|
||||
}
|
||||
const mod = parsed.protocol === 'https:' ? https : http;
|
||||
const req = mod.get(targetUrl, (res) => {
|
||||
res.on('data', (chunk) => process.stdout.write(chunk));
|
||||
});
|
||||
req.on('error', () => {});
|
||||
req.end();
|
||||
}
|
||||
|
||||
module.exports = { fetchUrl };
|
||||
20
tests/dynamic_fixtures/js/ssrf_unsupported.js
Normal file
20
tests/dynamic_fixtures/js/ssrf_unsupported.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// SSRF — unsupported fixture.
|
||||
// Entry takes an already-parsed URL object, not a string.
|
||||
// Test sets confidence = Low to get Unsupported(ConfidenceTooLow).
|
||||
// Entry: fetchParsed(parsedUrl) Cap: SSRF
|
||||
// Expected verdict: Unsupported
|
||||
|
||||
const http = require('http');
|
||||
|
||||
function fetchParsed(parsedUrl) {
|
||||
if (!parsedUrl || typeof parsedUrl !== 'object') {
|
||||
return;
|
||||
}
|
||||
const req = http.get(parsedUrl, (res) => {
|
||||
res.on('data', (d) => process.stdout.write(d));
|
||||
});
|
||||
req.on('error', () => {});
|
||||
req.end();
|
||||
}
|
||||
|
||||
module.exports = { fetchParsed };
|
||||
13
tests/dynamic_fixtures/js/xss_adversarial.js
Normal file
13
tests/dynamic_fixtures/js/xss_adversarial.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// XSS — adversarial collision fixture.
|
||||
// Prints the XSS oracle marker unconditionally without rendering any template
|
||||
// and without emitting __NYX_SINK_HIT__.
|
||||
// Expected verdict: Inconclusive(OracleCollisionSuspected)
|
||||
// Entry: renderPage(userInput) Cap: HTML_ESCAPE
|
||||
|
||||
function renderPage(userInput) {
|
||||
// Coincidental oracle match — not an HTML render sink.
|
||||
process.stdout.write('<script>NYX_XSS_CONFIRMED</script>\n');
|
||||
void userInput.length;
|
||||
}
|
||||
|
||||
module.exports = { renderPage };
|
||||
20
tests/dynamic_fixtures/js/xss_negative.js
Normal file
20
tests/dynamic_fixtures/js/xss_negative.js
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// XSS — negative fixture.
|
||||
// Safe: HTML-escapes all special characters before output.
|
||||
// Entry: renderPage(userInput) Cap: HTML_ESCAPE
|
||||
// Expected verdict: NotConfirmed
|
||||
|
||||
function escapeHtml(str) {
|
||||
return String(str)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"')
|
||||
.replace(/'/g, ''');
|
||||
}
|
||||
|
||||
function renderPage(userInput) {
|
||||
const safe = escapeHtml(userInput);
|
||||
process.stdout.write('<html><body>' + safe + '</body></html>\n');
|
||||
}
|
||||
|
||||
module.exports = { renderPage };
|
||||
12
tests/dynamic_fixtures/js/xss_positive.js
Normal file
12
tests/dynamic_fixtures/js/xss_positive.js
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
// XSS — positive fixture.
|
||||
// Vulnerable: echoes raw user input into HTML output without escaping.
|
||||
// Entry: renderPage(userInput) Cap: HTML_ESCAPE
|
||||
// Expected verdict: Confirmed (<script>NYX_XSS_CONFIRMED</script> echoed)
|
||||
|
||||
function renderPage(userInput) {
|
||||
process.stdout.write('__NYX_SINK_HIT__\n');
|
||||
// Unescaped output — script tags pass through verbatim.
|
||||
process.stdout.write('<html><body>' + userInput + '</body></html>\n');
|
||||
}
|
||||
|
||||
module.exports = { renderPage };
|
||||
13
tests/dynamic_fixtures/js/xss_unsupported.js
Normal file
13
tests/dynamic_fixtures/js/xss_unsupported.js
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// XSS — unsupported fixture.
|
||||
// Entry is a class method rather than a top-level function.
|
||||
// Test sets confidence = Low to get Unsupported(ConfidenceTooLow).
|
||||
// Entry: TemplateEngine.render(input) Cap: HTML_ESCAPE
|
||||
// Expected verdict: Unsupported
|
||||
|
||||
class TemplateEngine {
|
||||
render(input) {
|
||||
return '<html><body>' + input + '</body></html>';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { TemplateEngine };
|
||||
Loading…
Add table
Add a link
Reference in a new issue