From 2a25399474da16492612584674080a55b74123e6 Mon Sep 17 00:00:00 2001 From: walker Date: Mon, 24 Aug 2026 23:53:36 +0800 Subject: [PATCH] test: add 15 Lua security scanner fixtures --- docs/VULNERABILITY_CATALOG.md | 32 +- vulns/VULNERABILITY_CATALOG.json | 356 ++++++++++++++++++++++- vulns/lua/dynamic-code-injection.lua | 22 ++ vulns/lua/hardcoded-password.lua | 20 ++ vulns/lua/log-injection.lua | 20 ++ vulns/lua/open-redirect.lua | 20 ++ vulns/lua/os-command-injection.lua | 23 ++ vulns/lua/partial-token-comparison.lua | 23 ++ vulns/lua/path-traversal.lua | 24 ++ vulns/lua/popen-command-injection.lua | 23 ++ vulns/lua/predictable-temp-file.lua | 23 ++ vulns/lua/sql-injection.lua | 19 ++ vulns/lua/ssrf-user-url.lua | 21 ++ vulns/lua/stack-trace-exposure.lua | 20 ++ vulns/lua/unsafe-deserialization.lua | 22 ++ vulns/lua/weak-random-token.lua | 21 ++ vulns/lua/world-writable-permissions.lua | 18 ++ 17 files changed, 697 insertions(+), 10 deletions(-) create mode 100644 vulns/lua/dynamic-code-injection.lua create mode 100644 vulns/lua/hardcoded-password.lua create mode 100644 vulns/lua/log-injection.lua create mode 100644 vulns/lua/open-redirect.lua create mode 100644 vulns/lua/os-command-injection.lua create mode 100644 vulns/lua/partial-token-comparison.lua create mode 100644 vulns/lua/path-traversal.lua create mode 100644 vulns/lua/popen-command-injection.lua create mode 100644 vulns/lua/predictable-temp-file.lua create mode 100644 vulns/lua/sql-injection.lua create mode 100644 vulns/lua/ssrf-user-url.lua create mode 100644 vulns/lua/stack-trace-exposure.lua create mode 100644 vulns/lua/unsafe-deserialization.lua create mode 100644 vulns/lua/weak-random-token.lua create mode 100644 vulns/lua/world-writable-permissions.lua diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index 01acd4c..981c61e 100644 --- a/docs/VULNERABILITY_CATALOG.md +++ b/docs/VULNERABILITY_CATALOG.md @@ -7,12 +7,12 @@ from each file's header comment, so this page cannot drift from the source. ## Totals -- **Test cases:** 127 -- **Expected detections:** 127 -- **`VULNERABLE:` markers:** 236 (individual lines a scanner should flag) -- **`SAFE:` markers:** 144 (lines a scanner must not flag — the false-positive control group) -- **Languages:** 8 — dotenv, go, java, javascript, json, python, ruby, text -- **CWE categories:** 89 — CWE-20, CWE-22, CWE-78, CWE-79, CWE-89, CWE-90, CWE-94, CWE-95, CWE-113, CWE-117, CWE-129, CWE-190, CWE-201, CWE-203, CWE-208, CWE-209, CWE-256, CWE-285, CWE-287, CWE-288, CWE-291, CWE-295, CWE-306, CWE-307, CWE-311, CWE-312, CWE-319, CWE-321, CWE-326, CWE-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, CWE-409, CWE-434, CWE-441, CWE-460, CWE-472, CWE-475, CWE-480, CWE-488, CWE-489, CWE-502, CWE-506, CWE-509, CWE-512, CWE-521, CWE-522, CWE-525, CWE-532, CWE-598, CWE-601, CWE-602, CWE-611, CWE-613, CWE-614, CWE-620, CWE-639, CWE-640, CWE-643, CWE-681, CWE-693, CWE-732, CWE-759, CWE-776, CWE-798, CWE-835, CWE-862, CWE-863, CWE-915, CWE-916, CWE-918, CWE-922, CWE-942, CWE-943, CWE-1021, CWE-1236, CWE-1321, CWE-1333, CWE-1336, CWE-1357 +- **Test cases:** 142 +- **Expected detections:** 142 +- **`VULNERABLE:` markers:** 251 (individual lines a scanner should flag) +- **`SAFE:` markers:** 159 (lines a scanner must not flag — the false-positive control group) +- **Languages:** 9 — dotenv, go, java, javascript, json, lua, python, ruby, text +- **CWE categories:** 90 — CWE-20, CWE-22, CWE-78, CWE-79, CWE-89, CWE-90, CWE-94, CWE-95, CWE-113, CWE-117, CWE-129, CWE-187, CWE-190, CWE-201, CWE-203, CWE-208, CWE-209, CWE-256, CWE-285, CWE-287, CWE-288, CWE-291, CWE-295, CWE-306, CWE-307, CWE-311, CWE-312, CWE-319, CWE-321, CWE-326, CWE-327, CWE-329, CWE-330, CWE-338, CWE-345, CWE-346, CWE-347, CWE-352, CWE-362, CWE-377, CWE-384, CWE-400, CWE-409, CWE-434, CWE-441, CWE-460, CWE-472, CWE-475, CWE-480, CWE-488, CWE-489, CWE-502, CWE-506, CWE-509, CWE-512, CWE-521, CWE-522, CWE-525, CWE-532, CWE-598, CWE-601, CWE-602, CWE-611, CWE-613, CWE-614, CWE-620, CWE-639, CWE-640, CWE-643, CWE-681, CWE-693, CWE-732, CWE-759, CWE-776, CWE-798, CWE-835, CWE-862, CWE-863, CWE-915, CWE-916, CWE-918, CWE-922, CWE-942, CWE-943, CWE-1021, CWE-1236, CWE-1321, CWE-1333, CWE-1336, CWE-1357 ## How coverage is scored @@ -100,6 +100,26 @@ counts as a detection. See `docs/SCANNER_INTEGRATION.md`. | Code injection via eval() of user input | [`xss-eval-user-input.js`](../vulns/javascript/xss-eval-user-input.js) | CWE-95 | critical | yes | 3 vuln / 1 safe | | XML parser with external entity resolution enabled | [`xxe-parser-config.js`](../vulns/javascript/xxe-parser-config.js) | CWE-611 | high | yes | 1 vuln / 1 safe | +## Lua + +| Test case | File | CWE | Severity | Expected | Markers | +|---|---|---|---|---|---| +| Dynamic code execution from user input | [`dynamic-code-injection.lua`](../vulns/lua/dynamic-code-injection.lua) | CWE-95 | critical | yes | 1 vuln / 1 safe | +| Hard-coded administrative password | [`hardcoded-password.lua`](../vulns/lua/hardcoded-password.lua) | CWE-798 | high | yes | 1 vuln / 1 safe | +| Log injection through unsanitized username | [`log-injection.lua`](../vulns/lua/log-injection.lua) | CWE-117 | medium | yes | 1 vuln / 1 safe | +| Open redirect through untrusted destination | [`open-redirect.lua`](../vulns/lua/open-redirect.lua) | CWE-601 | medium | yes | 1 vuln / 1 safe | +| Shell command injection through os.execute | [`os-command-injection.lua`](../vulns/lua/os-command-injection.lua) | CWE-78 | critical | yes | 1 vuln / 1 safe | +| Authentication using a partial token comparison | [`partial-token-comparison.lua`](../vulns/lua/partial-token-comparison.lua) | CWE-187 | high | yes | 1 vuln / 1 safe | +| Path traversal in file read | [`path-traversal.lua`](../vulns/lua/path-traversal.lua) | CWE-22 | high | yes | 1 vuln / 1 safe | +| Command injection through io.popen | [`popen-command-injection.lua`](../vulns/lua/popen-command-injection.lua) | CWE-78 | critical | yes | 1 vuln / 1 safe | +| Predictable shared temporary file | [`predictable-temp-file.lua`](../vulns/lua/predictable-temp-file.lua) | CWE-377 | medium | yes | 1 vuln / 1 safe | +| SQL injection through string concatenation | [`sql-injection.lua`](../vulns/lua/sql-injection.lua) | CWE-89 | critical | yes | 1 vuln / 1 safe | +| Server-side request forgery through user URL | [`ssrf-user-url.lua`](../vulns/lua/ssrf-user-url.lua) | CWE-918 | high | yes | 1 vuln / 1 safe | +| Internal stack trace exposed to a client | [`stack-trace-exposure.lua`](../vulns/lua/stack-trace-exposure.lua) | CWE-209 | medium | yes | 1 vuln / 1 safe | +| Code execution while deserializing untrusted text | [`unsafe-deserialization.lua`](../vulns/lua/unsafe-deserialization.lua) | CWE-502 | critical | yes | 1 vuln / 1 safe | +| Predictable session token from math.random | [`weak-random-token.lua`](../vulns/lua/weak-random-token.lua) | CWE-338 | high | yes | 1 vuln / 1 safe | +| World-writable permissions on exported data | [`world-writable-permissions.lua`](../vulns/lua/world-writable-permissions.lua) | CWE-732 | high | yes | 1 vuln / 1 safe | + ## Python | Test case | File | CWE | Severity | Expected | Markers | diff --git a/vulns/VULNERABILITY_CATALOG.json b/vulns/VULNERABILITY_CATALOG.json index 8fb4128..d8d3eb7 100644 --- a/vulns/VULNERABILITY_CATALOG.json +++ b/vulns/VULNERABILITY_CATALOG.json @@ -2,16 +2,17 @@ "schema": "threatcrush-testbed-catalog/1", "note": "Generated by scripts/generate-catalog.py \u2014 do not edit by hand.", "totals": { - "test_cases": 127, - "expected_detections": 127, - "vulnerable_markers": 236, - "safe_markers": 144, + "test_cases": 142, + "expected_detections": 142, + "vulnerable_markers": 251, + "safe_markers": 159, "languages": [ "dotenv", "go", "java", "javascript", "json", + "lua", "python", "ruby", "text" @@ -20,6 +21,7 @@ "go", "java", "javascript", + "lua", "python", "ruby", "secrets", @@ -37,6 +39,7 @@ "CWE-113", "CWE-117", "CWE-129", + "CWE-187", "CWE-190", "CWE-201", "CWE-203", @@ -1594,6 +1597,351 @@ 34 ] }, + { + "id": "lua-dynamic-code-injection", + "file": "vulns/lua/dynamic-code-injection.lua", + "title": "Dynamic code execution from user input", + "category": "lua", + "language": "lua", + "cwe": "CWE-95", + "cwes": [ + "CWE-95" + ], + "severity": "critical", + "expected_detection": true, + "description": "Attacker-controlled text is compiled and executed as Lua code.", + "detection_target": "Tainted input passed to load and then invoked.", + "safe_guard": "NEVER_RUN is false, so neither example executes.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 20 + ] + }, + { + "id": "lua-hardcoded-password", + "file": "vulns/lua/hardcoded-password.lua", + "title": "Hard-coded administrative password", + "category": "lua", + "language": "lua", + "cwe": "CWE-798", + "cwes": [ + "CWE-798" + ], + "severity": "high", + "expected_detection": true, + "description": "A synthetic password is embedded directly in application source.", + "detection_target": "Password variable assigned from a source-code literal.", + "safe_guard": "NEVER_RUN is false and the credential is synthetic.", + "attribution": "line", + "vulnerable_lines": [ + 15 + ], + "safe_lines": [ + 18 + ] + }, + { + "id": "lua-log-injection", + "file": "vulns/lua/log-injection.lua", + "title": "Log injection through unsanitized username", + "category": "lua", + "language": "lua", + "cwe": "CWE-117", + "cwes": [ + "CWE-117" + ], + "severity": "medium", + "expected_detection": true, + "description": "Attacker-controlled text is written directly into a structured log line.", + "detection_target": "Tainted text passed to a logging sink without neutralization.", + "safe_guard": "NEVER_RUN is false, so nothing is logged.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 19 + ] + }, + { + "id": "lua-open-redirect", + "file": "vulns/lua/open-redirect.lua", + "title": "Open redirect through untrusted destination", + "category": "lua", + "language": "lua", + "cwe": "CWE-601", + "cwes": [ + "CWE-601" + ], + "severity": "medium", + "expected_detection": true, + "description": "A user-provided destination is sent directly to the redirect API.", + "detection_target": "Tainted URL passed to a redirect sink.", + "safe_guard": "NEVER_RUN is false, so no response is emitted.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 19 + ] + }, + { + "id": "lua-os-command-injection", + "file": "vulns/lua/os-command-injection.lua", + "title": "Shell command injection through os.execute", + "category": "lua", + "language": "lua", + "cwe": "CWE-78", + "cwes": [ + "CWE-78" + ], + "severity": "critical", + "expected_detection": true, + "description": "A user-controlled filename is concatenated into a shell command.", + "detection_target": "Tainted data flowing into os.execute.", + "safe_guard": "NEVER_RUN is false, so no command executes.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 20 + ] + }, + { + "id": "lua-partial-token-comparison", + "file": "vulns/lua/partial-token-comparison.lua", + "title": "Authentication using a partial token comparison", + "category": "lua", + "language": "lua", + "cwe": "CWE-187", + "cwes": [ + "CWE-187" + ], + "severity": "high", + "expected_detection": true, + "description": "Only the first eight characters of a bearer token are compared.", + "detection_target": "Truncated credential comparison in an auth check.", + "safe_guard": "NEVER_RUN is false, so no authentication decision occurs.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 20 + ] + }, + { + "id": "lua-path-traversal", + "file": "vulns/lua/path-traversal.lua", + "title": "Path traversal in file read", + "category": "lua", + "language": "lua", + "cwe": "CWE-22", + "cwes": [ + "CWE-22" + ], + "severity": "high", + "expected_detection": true, + "description": "A request path is appended to a trusted directory without validation.", + "detection_target": "Tainted path passed to io.open.", + "safe_guard": "NEVER_RUN is false, so no file is opened.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 21 + ] + }, + { + "id": "lua-popen-command-injection", + "file": "vulns/lua/popen-command-injection.lua", + "title": "Command injection through io.popen", + "category": "lua", + "language": "lua", + "cwe": "CWE-78", + "cwes": [ + "CWE-78" + ], + "severity": "critical", + "expected_detection": true, + "description": "A user-controlled host is concatenated into a shell command.", + "detection_target": "Tainted data flowing into io.popen.", + "safe_guard": "NEVER_RUN is false, so no subprocess starts.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 21 + ] + }, + { + "id": "lua-predictable-temp-file", + "file": "vulns/lua/predictable-temp-file.lua", + "title": "Predictable shared temporary file", + "category": "lua", + "language": "lua", + "cwe": "CWE-377", + "cwes": [ + "CWE-377" + ], + "severity": "medium", + "expected_detection": true, + "description": "Sensitive output uses a fixed path in a shared temporary directory.", + "detection_target": "Fixed /tmp path used for application output.", + "safe_guard": "NEVER_RUN is false, so no file is created.", + "attribution": "line", + "vulnerable_lines": [ + 15 + ], + "safe_lines": [ + 19 + ] + }, + { + "id": "lua-sql-injection", + "file": "vulns/lua/sql-injection.lua", + "title": "SQL injection through string concatenation", + "category": "lua", + "language": "lua", + "cwe": "CWE-89", + "cwes": [ + "CWE-89" + ], + "severity": "critical", + "expected_detection": true, + "description": "User input is inserted directly into a SQL statement.", + "detection_target": "Tainted input concatenated into SQL query text.", + "safe_guard": "NEVER_RUN is false, so no database call occurs.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 18 + ] + }, + { + "id": "lua-ssrf-user-url", + "file": "vulns/lua/ssrf-user-url.lua", + "title": "Server-side request forgery through user URL", + "category": "lua", + "language": "lua", + "cwe": "CWE-918", + "cwes": [ + "CWE-918" + ], + "severity": "high", + "expected_detection": true, + "description": "A user-provided URL is fetched without scheme or host validation.", + "detection_target": "Tainted URL passed to an HTTP client.", + "safe_guard": "NEVER_RUN is false, so no network request occurs.", + "attribution": "line", + "vulnerable_lines": [ + 17 + ], + "safe_lines": [ + 20 + ] + }, + { + "id": "lua-stack-trace-exposure", + "file": "vulns/lua/stack-trace-exposure.lua", + "title": "Internal stack trace exposed to a client", + "category": "lua", + "language": "lua", + "cwe": "CWE-209", + "cwes": [ + "CWE-209" + ], + "severity": "medium", + "expected_detection": true, + "description": "A detailed runtime traceback is returned in an HTTP response.", + "detection_target": "debug.traceback output reaching a client response.", + "safe_guard": "NEVER_RUN is false, so no error response is sent.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 19 + ] + }, + { + "id": "lua-unsafe-deserialization", + "file": "vulns/lua/unsafe-deserialization.lua", + "title": "Code execution while deserializing untrusted text", + "category": "lua", + "language": "lua", + "cwe": "CWE-502", + "cwes": [ + "CWE-502" + ], + "severity": "critical", + "expected_detection": true, + "description": "Untrusted serialized text is compiled as a Lua expression.", + "detection_target": "Untrusted serialized data passed to load.", + "safe_guard": "NEVER_RUN is false, so no payload is compiled.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 20 + ] + }, + { + "id": "lua-weak-random-token", + "file": "vulns/lua/weak-random-token.lua", + "title": "Predictable session token from math.random", + "category": "lua", + "language": "lua", + "cwe": "CWE-338", + "cwes": [ + "CWE-338" + ], + "severity": "high", + "expected_detection": true, + "description": "A security token is generated with a non-cryptographic PRNG.", + "detection_target": "math.random used for authentication or session material.", + "safe_guard": "NEVER_RUN is false, so no token is issued.", + "attribution": "line", + "vulnerable_lines": [ + 16 + ], + "safe_lines": [ + 19 + ] + }, + { + "id": "lua-world-writable-permissions", + "file": "vulns/lua/world-writable-permissions.lua", + "title": "World-writable permissions on exported data", + "category": "lua", + "language": "lua", + "cwe": "CWE-732", + "cwes": [ + "CWE-732" + ], + "severity": "high", + "expected_detection": true, + "description": "An exported file is assigned mode 0777.", + "detection_target": "chmod invocation granting write access to everyone.", + "safe_guard": "NEVER_RUN is false, so no permission is changed.", + "attribution": "line", + "vulnerable_lines": [ + 15 + ], + "safe_lines": [ + 17 + ] + }, { "id": "py-cleartext-sensitive-storage", "file": "vulns/python/cleartext-sensitive-storage.py", diff --git a/vulns/lua/dynamic-code-injection.lua b/vulns/lua/dynamic-code-injection.lua new file mode 100644 index 0000000..efa3f40 --- /dev/null +++ b/vulns/lua/dynamic-code-injection.lua @@ -0,0 +1,22 @@ +-- @id lua-dynamic-code-injection +-- @test-case Dynamic code execution from user input +-- @cwe CWE-95 +-- @severity critical +-- @language lua +-- @expected-detection true +-- @description Attacker-controlled text is compiled and executed as Lua code. +-- @safe-guard NEVER_RUN is false, so neither example executes. +-- @detection-target Tainted input passed to load and then invoked. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local source = request.query.code -- SOURCE: attacker-controlled + local chunk = load(source) -- VULNERABLE: CWE-95 + chunk() + + local allowed = { status = function() return "ok" end } + local action = allowed[request.query.action] -- SAFE: allow-listed function + if action then action() end +end diff --git a/vulns/lua/hardcoded-password.lua b/vulns/lua/hardcoded-password.lua new file mode 100644 index 0000000..5af18e7 --- /dev/null +++ b/vulns/lua/hardcoded-password.lua @@ -0,0 +1,20 @@ +-- @id lua-hardcoded-password +-- @test-case Hard-coded administrative password +-- @cwe CWE-798 +-- @severity high +-- @language lua +-- @expected-detection true +-- @description A synthetic password is embedded directly in application source. +-- @safe-guard NEVER_RUN is false and the credential is synthetic. +-- @detection-target Password variable assigned from a source-code literal. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local admin_password = "TEST_ONLY_admin_password_123" -- VULNERABLE: CWE-798 + authenticate("admin", admin_password) + + local configured_password = os.getenv("ADMIN_PASSWORD") -- SAFE: external secret source + authenticate("admin", configured_password) +end diff --git a/vulns/lua/log-injection.lua b/vulns/lua/log-injection.lua new file mode 100644 index 0000000..3aa5195 --- /dev/null +++ b/vulns/lua/log-injection.lua @@ -0,0 +1,20 @@ +-- @id lua-log-injection +-- @test-case Log injection through unsanitized username +-- @cwe CWE-117 +-- @severity medium +-- @language lua +-- @expected-detection true +-- @description Attacker-controlled text is written directly into a structured log line. +-- @safe-guard NEVER_RUN is false, so nothing is logged. +-- @detection-target Tainted text passed to a logging sink without neutralization. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local username = request.form.username -- SOURCE + logger.info("login failed user=" .. username) -- VULNERABLE: CWE-117 + + local clean = username:gsub("[\r\n]", "_") + logger.info("login failed user=" .. clean) -- SAFE: line breaks neutralized +end diff --git a/vulns/lua/open-redirect.lua b/vulns/lua/open-redirect.lua new file mode 100644 index 0000000..9a45fdf --- /dev/null +++ b/vulns/lua/open-redirect.lua @@ -0,0 +1,20 @@ +-- @id lua-open-redirect +-- @test-case Open redirect through untrusted destination +-- @cwe CWE-601 +-- @severity medium +-- @language lua +-- @expected-detection true +-- @description A user-provided destination is sent directly to the redirect API. +-- @safe-guard NEVER_RUN is false, so no response is emitted. +-- @detection-target Tainted URL passed to a redirect sink. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local next_url = request.query.next -- SOURCE + response.redirect(next_url) -- VULNERABLE: CWE-601 + + local routes = { home = "/", account = "/account" } + response.redirect(routes[next_url] or "/") -- SAFE: internal allow-list +end diff --git a/vulns/lua/os-command-injection.lua b/vulns/lua/os-command-injection.lua new file mode 100644 index 0000000..aa155d2 --- /dev/null +++ b/vulns/lua/os-command-injection.lua @@ -0,0 +1,23 @@ +-- @id lua-os-command-injection +-- @test-case Shell command injection through os.execute +-- @cwe CWE-78 +-- @severity critical +-- @language lua +-- @expected-detection true +-- @description A user-controlled filename is concatenated into a shell command. +-- @safe-guard NEVER_RUN is false, so no command executes. +-- @detection-target Tainted data flowing into os.execute. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local filename = request.query.filename -- SOURCE + os.execute("cat " .. filename) -- VULNERABLE: CWE-78 + + local allowed = { ["summary.txt"] = true, ["status.txt"] = true } + if allowed[filename] then + local file = io.open("/srv/reports/" .. filename, "r") -- SAFE: allow-listed name + if file then file:close() end + end +end diff --git a/vulns/lua/partial-token-comparison.lua b/vulns/lua/partial-token-comparison.lua new file mode 100644 index 0000000..bfafd85 --- /dev/null +++ b/vulns/lua/partial-token-comparison.lua @@ -0,0 +1,23 @@ +-- @id lua-partial-token-comparison +-- @test-case Authentication using a partial token comparison +-- @cwe CWE-187 +-- @severity high +-- @language lua +-- @expected-detection true +-- @description Only the first eight characters of a bearer token are compared. +-- @safe-guard NEVER_RUN is false, so no authentication decision occurs. +-- @detection-target Truncated credential comparison in an auth check. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local provided = request.headers.authorization -- SOURCE + if provided:sub(1, 8) == expected_token:sub(1, 8) then -- VULNERABLE: CWE-187 + grant_access() + end + + if crypto.constant_time_equals(provided, expected_token) then -- SAFE: full comparison + grant_access() + end +end diff --git a/vulns/lua/path-traversal.lua b/vulns/lua/path-traversal.lua new file mode 100644 index 0000000..0f2607f --- /dev/null +++ b/vulns/lua/path-traversal.lua @@ -0,0 +1,24 @@ +-- @id lua-path-traversal +-- @test-case Path traversal in file read +-- @cwe CWE-22 +-- @severity high +-- @language lua +-- @expected-detection true +-- @description A request path is appended to a trusted directory without validation. +-- @safe-guard NEVER_RUN is false, so no file is opened. +-- @detection-target Tainted path passed to io.open. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local name = request.query.name -- SOURCE + local file = io.open("/srv/public/" .. name, "r") -- VULNERABLE: CWE-22 + if file then file:close() end + + local safe_name = name:match("^([%w_.-]+)$") + if safe_name then + local safe_file = io.open("/srv/public/" .. safe_name, "r") -- SAFE: basename allow-list + if safe_file then safe_file:close() end + end +end diff --git a/vulns/lua/popen-command-injection.lua b/vulns/lua/popen-command-injection.lua new file mode 100644 index 0000000..b561186 --- /dev/null +++ b/vulns/lua/popen-command-injection.lua @@ -0,0 +1,23 @@ +-- @id lua-popen-command-injection +-- @test-case Command injection through io.popen +-- @cwe CWE-78 +-- @severity critical +-- @language lua +-- @expected-detection true +-- @description A user-controlled host is concatenated into a shell command. +-- @safe-guard NEVER_RUN is false, so no subprocess starts. +-- @detection-target Tainted data flowing into io.popen. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local host = request.query.host -- SOURCE + local pipe = io.popen("nslookup " .. host) -- VULNERABLE: CWE-78 + local output = pipe:read("*a") + pipe:close() + + local safe_host = assert(host:match("^([%w.-]+)$")) + local answer = dns.resolve(safe_host) -- SAFE: no shell involved + consume(output, answer) +end diff --git a/vulns/lua/predictable-temp-file.lua b/vulns/lua/predictable-temp-file.lua new file mode 100644 index 0000000..c15d6de --- /dev/null +++ b/vulns/lua/predictable-temp-file.lua @@ -0,0 +1,23 @@ +-- @id lua-predictable-temp-file +-- @test-case Predictable shared temporary file +-- @cwe CWE-377 +-- @severity medium +-- @language lua +-- @expected-detection true +-- @description Sensitive output uses a fixed path in a shared temporary directory. +-- @safe-guard NEVER_RUN is false, so no file is created. +-- @detection-target Fixed /tmp path used for application output. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local file = io.open("/tmp/account-report.txt", "w") -- VULNERABLE: CWE-377 + file:write(report) + file:close() + + local safe_path = assert(os.tmpname()) -- SAFE: unique runtime-generated path + local safe_file = io.open(safe_path, "w") + safe_file:write(report) + safe_file:close() +end diff --git a/vulns/lua/sql-injection.lua b/vulns/lua/sql-injection.lua new file mode 100644 index 0000000..55b91b1 --- /dev/null +++ b/vulns/lua/sql-injection.lua @@ -0,0 +1,19 @@ +-- @id lua-sql-injection +-- @test-case SQL injection through string concatenation +-- @cwe CWE-89 +-- @severity critical +-- @language lua +-- @expected-detection true +-- @description User input is inserted directly into a SQL statement. +-- @safe-guard NEVER_RUN is false, so no database call occurs. +-- @detection-target Tainted input concatenated into SQL query text. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local email = request.form.email -- SOURCE + db:query("SELECT * FROM users WHERE email = '" .. email .. "'") -- VULNERABLE: CWE-89 + + db:query("SELECT * FROM users WHERE email = ?", { email }) -- SAFE: parameterized query +end diff --git a/vulns/lua/ssrf-user-url.lua b/vulns/lua/ssrf-user-url.lua new file mode 100644 index 0000000..d8bc805 --- /dev/null +++ b/vulns/lua/ssrf-user-url.lua @@ -0,0 +1,21 @@ +-- @id lua-ssrf-user-url +-- @test-case Server-side request forgery through user URL +-- @cwe CWE-918 +-- @severity high +-- @language lua +-- @expected-detection true +-- @description A user-provided URL is fetched without scheme or host validation. +-- @safe-guard NEVER_RUN is false, so no network request occurs. +-- @detection-target Tainted URL passed to an HTTP client. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local http = require("socket.http") + local target = request.query.url -- SOURCE + http.request(target) -- VULNERABLE: CWE-918 + + local report_id = assert(target:match("^report%-(%d+)$")) + http.request("https://reports.invalid/" .. report_id) -- SAFE: fixed .invalid host +end diff --git a/vulns/lua/stack-trace-exposure.lua b/vulns/lua/stack-trace-exposure.lua new file mode 100644 index 0000000..789fab5 --- /dev/null +++ b/vulns/lua/stack-trace-exposure.lua @@ -0,0 +1,20 @@ +-- @id lua-stack-trace-exposure +-- @test-case Internal stack trace exposed to a client +-- @cwe CWE-209 +-- @severity medium +-- @language lua +-- @expected-detection true +-- @description A detailed runtime traceback is returned in an HTTP response. +-- @safe-guard NEVER_RUN is false, so no error response is sent. +-- @detection-target debug.traceback output reaching a client response. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local err = request.context.error + response.send(500, debug.traceback(err)) -- VULNERABLE: CWE-209 + + logger.error(debug.traceback(err)) + response.send(500, "internal error") -- SAFE: generic client message +end diff --git a/vulns/lua/unsafe-deserialization.lua b/vulns/lua/unsafe-deserialization.lua new file mode 100644 index 0000000..73d116b --- /dev/null +++ b/vulns/lua/unsafe-deserialization.lua @@ -0,0 +1,22 @@ +-- @id lua-unsafe-deserialization +-- @test-case Code execution while deserializing untrusted text +-- @cwe CWE-502 +-- @severity critical +-- @language lua +-- @expected-detection true +-- @description Untrusted serialized text is compiled as a Lua expression. +-- @safe-guard NEVER_RUN is false, so no payload is compiled. +-- @detection-target Untrusted serialized data passed to load. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + local serialized = request.body -- SOURCE + local decode = load("return " .. serialized) -- VULNERABLE: CWE-502 + local value = decode() + consume(value) + + local safe_value = json.decode(serialized) -- SAFE: data-only JSON parser + consume(safe_value) +end diff --git a/vulns/lua/weak-random-token.lua b/vulns/lua/weak-random-token.lua new file mode 100644 index 0000000..216a68f --- /dev/null +++ b/vulns/lua/weak-random-token.lua @@ -0,0 +1,21 @@ +-- @id lua-weak-random-token +-- @test-case Predictable session token from math.random +-- @cwe CWE-338 +-- @severity high +-- @language lua +-- @expected-detection true +-- @description A security token is generated with a non-cryptographic PRNG. +-- @safe-guard NEVER_RUN is false, so no token is issued. +-- @detection-target math.random used for authentication or session material. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + math.randomseed(os.time()) + local session_token = tostring(math.random(100000, 999999)) -- VULNERABLE: CWE-338 + issue_session(session_token) + + local secure_token = crypto.random_bytes(32) -- SAFE: cryptographic random bytes + issue_session(secure_token) +end diff --git a/vulns/lua/world-writable-permissions.lua b/vulns/lua/world-writable-permissions.lua new file mode 100644 index 0000000..a16a4c0 --- /dev/null +++ b/vulns/lua/world-writable-permissions.lua @@ -0,0 +1,18 @@ +-- @id lua-world-writable-permissions +-- @test-case World-writable permissions on exported data +-- @cwe CWE-732 +-- @severity high +-- @language lua +-- @expected-detection true +-- @description An exported file is assigned mode 0777. +-- @safe-guard NEVER_RUN is false, so no permission is changed. +-- @detection-target chmod invocation granting write access to everyone. +-- NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +local NEVER_RUN = false + +if NEVER_RUN then + filesystem.chmod("/srv/export/report.json", tonumber("0777", 8)) -- VULNERABLE: CWE-732 + + filesystem.chmod("/srv/export/report.json", tonumber("0600", 8)) -- SAFE: owner-only access +end