diff --git a/docs/VULNERABILITY_CATALOG.md b/docs/VULNERABILITY_CATALOG.md index 01acd4c..eddc8fd 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, objective-c, python, ruby, text +- **CWE categories:** 93 — CWE-20, CWE-22, CWE-78, CWE-79, CWE-89, CWE-90, CWE-94, CWE-95, CWE-113, CWE-117, CWE-121, CWE-129, CWE-134, 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-416, 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 | +## Objective C + +| Test case | File | CWE | Severity | Expected | Markers | +|---|---|---|---|---|---| +| Hard-coded administrative password | [`hardcoded-password.m`](../vulns/objective-c/hardcoded-password.m) | CWE-798 | high | yes | 1 vuln / 1 safe | +| Integer overflow in allocation size calculation | [`integer-overflow-allocation.m`](../vulns/objective-c/integer-overflow-allocation.m) | CWE-190 | high | yes | 1 vuln / 1 safe | +| Externally controlled logging format string | [`log-format-injection.m`](../vulns/objective-c/log-format-injection.m) | CWE-134 | high | yes | 1 vuln / 1 safe | +| Open redirect through untrusted destination | [`open-redirect.m`](../vulns/objective-c/open-redirect.m) | CWE-601 | medium | yes | 1 vuln / 1 safe | +| Authentication using a truncated token comparison | [`partial-token-comparison.m`](../vulns/objective-c/partial-token-comparison.m) | CWE-187 | high | yes | 1 vuln / 1 safe | +| Path traversal in file download | [`path-traversal.m`](../vulns/objective-c/path-traversal.m) | CWE-22 | high | yes | 1 vuln / 1 safe | +| Predictable shared temporary file | [`predictable-temp-file.m`](../vulns/objective-c/predictable-temp-file.m) | CWE-377 | medium | yes | 1 vuln / 1 safe | +| SQL injection through formatted query text | [`sql-injection.m`](../vulns/objective-c/sql-injection.m) | CWE-89 | critical | yes | 1 vuln / 1 safe | +| Server-side request forgery through user URL | [`ssrf-user-url.m`](../vulns/objective-c/ssrf-user-url.m) | CWE-918 | high | yes | 1 vuln / 1 safe | +| Stack buffer overflow through strcpy | [`stack-buffer-overflow.m`](../vulns/objective-c/stack-buffer-overflow.m) | CWE-121 | critical | yes | 1 vuln / 1 safe | +| Internal diagnostic trace returned to a client | [`stack-trace-exposure.m`](../vulns/objective-c/stack-trace-exposure.m) | CWE-209 | medium | yes | 1 vuln / 1 safe | +| Shell command injection through system | [`system-command-injection.m`](../vulns/objective-c/system-command-injection.m) | CWE-78 | critical | yes | 1 vuln / 1 safe | +| TLS peer verification disabled | [`tls-verification-disabled.m`](../vulns/objective-c/tls-verification-disabled.m) | CWE-295 | high | yes | 1 vuln / 1 safe | +| Heap value consumed after free | [`use-after-free.m`](../vulns/objective-c/use-after-free.m) | CWE-416 | critical | yes | 1 vuln / 1 safe | +| Predictable session token from rand | [`weak-random-token.m`](../vulns/objective-c/weak-random-token.m) | CWE-338 | 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..e1d3354 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", + "objective-c", "python", "ruby", "text" @@ -20,6 +21,7 @@ "go", "java", "javascript", + "objective-c", "python", "ruby", "secrets", @@ -36,7 +38,10 @@ "CWE-95", "CWE-113", "CWE-117", + "CWE-121", "CWE-129", + "CWE-134", + "CWE-187", "CWE-190", "CWE-201", "CWE-203", @@ -68,6 +73,7 @@ "CWE-384", "CWE-400", "CWE-409", + "CWE-416", "CWE-434", "CWE-441", "CWE-460", @@ -1594,6 +1600,351 @@ 34 ] }, + { + "id": "objc-hardcoded-password", + "file": "vulns/objective-c/hardcoded-password.m", + "title": "Hard-coded administrative password", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-798", + "cwes": [ + "CWE-798" + ], + "severity": "high", + "expected_detection": true, + "description": "A synthetic administrative password is embedded in source.", + "detection_target": "Password argument sourced from a string literal.", + "safe_guard": "NEVER_RUN is zero and the credential protects no account.", + "attribution": "line", + "vulnerable_lines": [ + 19 + ], + "safe_lines": [ + 22 + ] + }, + { + "id": "objc-integer-overflow-allocation", + "file": "vulns/objective-c/integer-overflow-allocation.m", + "title": "Integer overflow in allocation size calculation", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-190", + "cwes": [ + "CWE-190" + ], + "severity": "high", + "expected_detection": true, + "description": "An untrusted element count is multiplied without an overflow check.", + "detection_target": "Unchecked multiplication used as malloc size.", + "safe_guard": "NEVER_RUN is zero, so no allocation occurs.", + "attribution": "line", + "vulnerable_lines": [ + 20 + ], + "safe_lines": [ + 26 + ] + }, + { + "id": "objc-log-format-injection", + "file": "vulns/objective-c/log-format-injection.m", + "title": "Externally controlled logging format string", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-134", + "cwes": [ + "CWE-134" + ], + "severity": "high", + "expected_detection": true, + "description": "User text is passed as the format argument to a variadic logger.", + "detection_target": "Tainted value used as a format string.", + "safe_guard": "NEVER_RUN is zero, so no log call occurs.", + "attribution": "line", + "vulnerable_lines": [ + 17 + ], + "safe_lines": [ + 19 + ] + }, + { + "id": "objc-open-redirect", + "file": "vulns/objective-c/open-redirect.m", + "title": "Open redirect through untrusted destination", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-601", + "cwes": [ + "CWE-601" + ], + "severity": "medium", + "expected_detection": true, + "description": "A request destination is passed directly to a redirect sink.", + "detection_target": "Tainted URL passed to response_redirect.", + "safe_guard": "NEVER_RUN is zero, so no response is sent.", + "attribution": "line", + "vulnerable_lines": [ + 19 + ], + "safe_lines": [ + 22 + ] + }, + { + "id": "objc-partial-token-comparison", + "file": "vulns/objective-c/partial-token-comparison.m", + "title": "Authentication using a truncated token comparison", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-187", + "cwes": [ + "CWE-187" + ], + "severity": "high", + "expected_detection": true, + "description": "Only the first eight bytes of a bearer token are compared.", + "detection_target": "strncmp with a fixed short credential length.", + "safe_guard": "NEVER_RUN is zero, so no access decision occurs.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 27 + ] + }, + { + "id": "objc-path-traversal", + "file": "vulns/objective-c/path-traversal.m", + "title": "Path traversal in file download", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-22", + "cwes": [ + "CWE-22" + ], + "severity": "high", + "expected_detection": true, + "description": "A request filename is appended to a trusted directory unchecked.", + "detection_target": "Tainted path passed to fopen.", + "safe_guard": "NEVER_RUN is zero, so no file is opened.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 25 + ] + }, + { + "id": "objc-predictable-temp-file", + "file": "vulns/objective-c/predictable-temp-file.m", + "title": "Predictable shared temporary file", + "category": "objective-c", + "language": "objective-c", + "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 passed to fopen.", + "safe_guard": "NEVER_RUN is zero, so no file is created.", + "attribution": "line", + "vulnerable_lines": [ + 19 + ], + "safe_lines": [ + 23 + ] + }, + { + "id": "objc-sql-injection", + "file": "vulns/objective-c/sql-injection.m", + "title": "SQL injection through formatted query text", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-89", + "cwes": [ + "CWE-89" + ], + "severity": "critical", + "expected_detection": true, + "description": "User input is formatted directly into a SQL statement.", + "detection_target": "Tainted data embedded in SQL passed to db_query.", + "safe_guard": "NEVER_RUN is zero, so no database query executes.", + "attribution": "line", + "vulnerable_lines": [ + 22 + ], + "safe_lines": [ + 24 + ] + }, + { + "id": "objc-ssrf-user-url", + "file": "vulns/objective-c/ssrf-user-url.m", + "title": "Server-side request forgery through user URL", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-918", + "cwes": [ + "CWE-918" + ], + "severity": "high", + "expected_detection": true, + "description": "A user-provided URL is fetched without host validation.", + "detection_target": "Tainted URL passed to http_get.", + "safe_guard": "NEVER_RUN is zero, so no network request occurs.", + "attribution": "line", + "vulnerable_lines": [ + 19 + ], + "safe_lines": [ + 23 + ] + }, + { + "id": "objc-stack-buffer-overflow", + "file": "vulns/objective-c/stack-buffer-overflow.m", + "title": "Stack buffer overflow through strcpy", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-121", + "cwes": [ + "CWE-121" + ], + "severity": "critical", + "expected_detection": true, + "description": "Unbounded request text is copied into a fixed-size stack buffer.", + "detection_target": "Tainted input passed to strcpy with a fixed destination.", + "safe_guard": "NEVER_RUN is zero, so no memory copy occurs.", + "attribution": "line", + "vulnerable_lines": [ + 20 + ], + "safe_lines": [ + 24 + ] + }, + { + "id": "objc-stack-trace-exposure", + "file": "vulns/objective-c/stack-trace-exposure.m", + "title": "Internal diagnostic trace returned to a client", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-209", + "cwes": [ + "CWE-209" + ], + "severity": "medium", + "expected_detection": true, + "description": "A detailed diagnostic string is sent in an HTTP response.", + "detection_target": "Internal trace data passed to a client response sink.", + "safe_guard": "NEVER_RUN is zero, so no response is emitted.", + "attribution": "line", + "vulnerable_lines": [ + 18 + ], + "safe_lines": [ + 21 + ] + }, + { + "id": "objc-system-command-injection", + "file": "vulns/objective-c/system-command-injection.m", + "title": "Shell command injection through system", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-78", + "cwes": [ + "CWE-78" + ], + "severity": "critical", + "expected_detection": true, + "description": "User-controlled text is passed directly to a shell command sink.", + "detection_target": "Tainted data passed to system.", + "safe_guard": "NEVER_RUN is zero, so no subprocess starts.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 24 + ] + }, + { + "id": "objc-tls-verification-disabled", + "file": "vulns/objective-c/tls-verification-disabled.m", + "title": "TLS peer verification disabled", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-295", + "cwes": [ + "CWE-295" + ], + "severity": "high", + "expected_detection": true, + "description": "A client configuration disables certificate verification.", + "detection_target": "Verification flag set to false before an HTTPS request.", + "safe_guard": "NEVER_RUN is zero, so no client configuration is changed.", + "attribution": "line", + "vulnerable_lines": [ + 19 + ], + "safe_lines": [ + 22 + ] + }, + { + "id": "objc-use-after-free", + "file": "vulns/objective-c/use-after-free.m", + "title": "Heap value consumed after free", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-416", + "cwes": [ + "CWE-416" + ], + "severity": "critical", + "expected_detection": true, + "description": "A freed heap pointer is passed to a consumer.", + "detection_target": "Pointer use after free.", + "safe_guard": "NEVER_RUN is zero, so no allocation or free occurs.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 25 + ] + }, + { + "id": "objc-weak-random-token", + "file": "vulns/objective-c/weak-random-token.m", + "title": "Predictable session token from rand", + "category": "objective-c", + "language": "objective-c", + "cwe": "CWE-338", + "cwes": [ + "CWE-338" + ], + "severity": "high", + "expected_detection": true, + "description": "A session token is created with a non-cryptographic PRNG.", + "detection_target": "rand used for authentication material.", + "safe_guard": "NEVER_RUN is zero, so no token is issued.", + "attribution": "line", + "vulnerable_lines": [ + 21 + ], + "safe_lines": [ + 23 + ] + }, { "id": "py-cleartext-sensitive-storage", "file": "vulns/python/cleartext-sensitive-storage.py", diff --git a/vulns/objective-c/hardcoded-password.m b/vulns/objective-c/hardcoded-password.m new file mode 100644 index 0000000..c11a0ea --- /dev/null +++ b/vulns/objective-c/hardcoded-password.m @@ -0,0 +1,24 @@ +// @id objc-hardcoded-password +// @test-case Hard-coded administrative password +// @cwe CWE-798 +// @severity high +// @language objective-c +// @expected-detection true +// @description A synthetic administrative password is embedded in source. +// @safe-guard NEVER_RUN is zero and the credential protects no account. +// @detection-target Password argument sourced from a string literal. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include + +void authenticate(const char *user, const char *password); +static const int NEVER_RUN = 0; + +void objc_password_fixture(void) { + if (NEVER_RUN) { + const char *admin_password = "TEST_ONLY_admin_password_123"; // VULNERABLE: CWE-798 + authenticate("admin", admin_password); + + authenticate("admin", getenv("ADMIN_PASSWORD")); // SAFE: external secret source + } +} diff --git a/vulns/objective-c/integer-overflow-allocation.m b/vulns/objective-c/integer-overflow-allocation.m new file mode 100644 index 0000000..05082bd --- /dev/null +++ b/vulns/objective-c/integer-overflow-allocation.m @@ -0,0 +1,31 @@ +// @id objc-integer-overflow-allocation +// @test-case Integer overflow in allocation size calculation +// @cwe CWE-190 +// @severity high +// @language objective-c +// @expected-detection true +// @description An untrusted element count is multiplied without an overflow check. +// @safe-guard NEVER_RUN is zero, so no allocation occurs. +// @detection-target Unchecked multiplication used as malloc size. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include +#include + +void consume_bytes(const void *data); +static const int NEVER_RUN = 0; + +void objc_integer_fixture(size_t count) { + if (NEVER_RUN) { + size_t bytes = count * sizeof(uint32_t); // VULNERABLE: CWE-190 + void *unsafe = malloc(bytes); + consume_bytes(unsafe); + free(unsafe); + + if (count <= SIZE_MAX / sizeof(uint32_t)) { + void *safe = calloc(count, sizeof(uint32_t)); // SAFE: multiplication checked + consume_bytes(safe); + free(safe); + } + } +} diff --git a/vulns/objective-c/log-format-injection.m b/vulns/objective-c/log-format-injection.m new file mode 100644 index 0000000..4b213e7 --- /dev/null +++ b/vulns/objective-c/log-format-injection.m @@ -0,0 +1,21 @@ +// @id objc-log-format-injection +// @test-case Externally controlled logging format string +// @cwe CWE-134 +// @severity high +// @language objective-c +// @expected-detection true +// @description User text is passed as the format argument to a variadic logger. +// @safe-guard NEVER_RUN is zero, so no log call occurs. +// @detection-target Tainted value used as a format string. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +void audit_log(const char *format, ...); +static const int NEVER_RUN = 0; + +void objc_log_fixture(const char *message) { + if (NEVER_RUN) { + audit_log(message); // VULNERABLE: CWE-134 + + audit_log("%s", message); // SAFE: fixed format string + } +} diff --git a/vulns/objective-c/open-redirect.m b/vulns/objective-c/open-redirect.m new file mode 100644 index 0000000..cbb8027 --- /dev/null +++ b/vulns/objective-c/open-redirect.m @@ -0,0 +1,24 @@ +// @id objc-open-redirect +// @test-case Open redirect through untrusted destination +// @cwe CWE-601 +// @severity medium +// @language objective-c +// @expected-detection true +// @description A request destination is passed directly to a redirect sink. +// @safe-guard NEVER_RUN is zero, so no response is sent. +// @detection-target Tainted URL passed to response_redirect. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include + +void response_redirect(const char *location); +static const int NEVER_RUN = 0; + +void objc_redirect_fixture(const char *next) { + if (NEVER_RUN) { + response_redirect(next); // VULNERABLE: CWE-601 + + const char *safe = strcmp(next, "account") == 0 ? "/account" : "/"; + response_redirect(safe); // SAFE: internal allow-list + } +} diff --git a/vulns/objective-c/partial-token-comparison.m b/vulns/objective-c/partial-token-comparison.m new file mode 100644 index 0000000..2b59f31 --- /dev/null +++ b/vulns/objective-c/partial-token-comparison.m @@ -0,0 +1,31 @@ +// @id objc-partial-token-comparison +// @test-case Authentication using a truncated token comparison +// @cwe CWE-187 +// @severity high +// @language objective-c +// @expected-detection true +// @description Only the first eight bytes of a bearer token are compared. +// @safe-guard NEVER_RUN is zero, so no access decision occurs. +// @detection-target strncmp with a fixed short credential length. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include +#include + +void grant_access(void); +int constant_time_equal(const char *left, const char *right, size_t length); +static const int NEVER_RUN = 0; + +void objc_token_fixture(const char *provided, const char *expected) { + if (NEVER_RUN) { + if (strncmp(provided, expected, 8) == 0) { // VULNERABLE: CWE-187 + grant_access(); + } + + size_t expected_length = strlen(expected); + if (strlen(provided) == expected_length && + constant_time_equal(provided, expected, expected_length)) { // SAFE: full comparison + grant_access(); + } + } +} diff --git a/vulns/objective-c/path-traversal.m b/vulns/objective-c/path-traversal.m new file mode 100644 index 0000000..0c513bf --- /dev/null +++ b/vulns/objective-c/path-traversal.m @@ -0,0 +1,29 @@ +// @id objc-path-traversal +// @test-case Path traversal in file download +// @cwe CWE-22 +// @severity high +// @language objective-c +// @expected-detection true +// @description A request filename is appended to a trusted directory unchecked. +// @safe-guard NEVER_RUN is zero, so no file is opened. +// @detection-target Tainted path passed to fopen. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include + +int validate_basename(const char *name); +static const int NEVER_RUN = 0; + +void objc_path_fixture(const char *name) { + if (NEVER_RUN) { + char path[512]; + snprintf(path, sizeof(path), "/srv/public/%s", name); + FILE *unsafe = fopen(path, "r"); // VULNERABLE: CWE-22 + if (unsafe != NULL) fclose(unsafe); + + if (validate_basename(name)) { + FILE *safe = fopen(path, "r"); // SAFE: basename allow-list checked + if (safe != NULL) fclose(safe); + } + } +} diff --git a/vulns/objective-c/predictable-temp-file.m b/vulns/objective-c/predictable-temp-file.m new file mode 100644 index 0000000..84847d5 --- /dev/null +++ b/vulns/objective-c/predictable-temp-file.m @@ -0,0 +1,29 @@ +// @id objc-predictable-temp-file +// @test-case Predictable shared temporary file +// @cwe CWE-377 +// @severity medium +// @language objective-c +// @expected-detection true +// @description Sensitive output uses a fixed path in a shared temporary directory. +// @safe-guard NEVER_RUN is zero, so no file is created. +// @detection-target Fixed /tmp path passed to fopen. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include +#include + +static const int NEVER_RUN = 0; + +void objc_temp_fixture(void) { + if (NEVER_RUN) { + FILE *unsafe = fopen("/tmp/account-report.txt", "w"); // VULNERABLE: CWE-377 + if (unsafe != NULL) fclose(unsafe); + + char template[] = "/tmp/account-report.XXXXXX"; + int descriptor = mkstemp(template); // SAFE: exclusive unique file + if (descriptor >= 0) { + FILE *safe = fdopen(descriptor, "w"); + if (safe != NULL) fclose(safe); + } + } +} diff --git a/vulns/objective-c/sql-injection.m b/vulns/objective-c/sql-injection.m new file mode 100644 index 0000000..c02069d --- /dev/null +++ b/vulns/objective-c/sql-injection.m @@ -0,0 +1,26 @@ +// @id objc-sql-injection +// @test-case SQL injection through formatted query text +// @cwe CWE-89 +// @severity critical +// @language objective-c +// @expected-detection true +// @description User input is formatted directly into a SQL statement. +// @safe-guard NEVER_RUN is zero, so no database query executes. +// @detection-target Tainted data embedded in SQL passed to db_query. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include + +void db_query(const char *sql); +void db_query_parameter(const char *sql, const char *value); +static const int NEVER_RUN = 0; + +void objc_sql_fixture(const char *email) { + if (NEVER_RUN) { + char query[512]; + snprintf(query, sizeof(query), "SELECT * FROM users WHERE email = '%s'", email); + db_query(query); // VULNERABLE: CWE-89 + + db_query_parameter("SELECT * FROM users WHERE email = ?", email); // SAFE: bound value + } +} diff --git a/vulns/objective-c/ssrf-user-url.m b/vulns/objective-c/ssrf-user-url.m new file mode 100644 index 0000000..14a491d --- /dev/null +++ b/vulns/objective-c/ssrf-user-url.m @@ -0,0 +1,26 @@ +// @id objc-ssrf-user-url +// @test-case Server-side request forgery through user URL +// @cwe CWE-918 +// @severity high +// @language objective-c +// @expected-detection true +// @description A user-provided URL is fetched without host validation. +// @safe-guard NEVER_RUN is zero, so no network request occurs. +// @detection-target Tainted URL passed to http_get. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +void http_get(const char *url); +int parse_report_id(const char *input, unsigned long *report_id); +void get_fixed_report(unsigned long report_id); +static const int NEVER_RUN = 0; + +void objc_ssrf_fixture(const char *target) { + if (NEVER_RUN) { + http_get(target); // VULNERABLE: CWE-918 + + unsigned long report_id = 0; + if (parse_report_id(target, &report_id)) { + get_fixed_report(report_id); // SAFE: fixed service, numeric identifier + } + } +} diff --git a/vulns/objective-c/stack-buffer-overflow.m b/vulns/objective-c/stack-buffer-overflow.m new file mode 100644 index 0000000..df56f6a --- /dev/null +++ b/vulns/objective-c/stack-buffer-overflow.m @@ -0,0 +1,27 @@ +// @id objc-stack-buffer-overflow +// @test-case Stack buffer overflow through strcpy +// @cwe CWE-121 +// @severity critical +// @language objective-c +// @expected-detection true +// @description Unbounded request text is copied into a fixed-size stack buffer. +// @safe-guard NEVER_RUN is zero, so no memory copy occurs. +// @detection-target Tainted input passed to strcpy with a fixed destination. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include +#include + +static const int NEVER_RUN = 0; + +void objc_buffer_fixture(const char *username) { + if (NEVER_RUN) { + char unsafe[16]; + strcpy(unsafe, username); // VULNERABLE: CWE-121 + puts(unsafe); + + char safe[16]; + snprintf(safe, sizeof(safe), "%s", username); // SAFE: destination is bounded + puts(safe); + } +} diff --git a/vulns/objective-c/stack-trace-exposure.m b/vulns/objective-c/stack-trace-exposure.m new file mode 100644 index 0000000..2291795 --- /dev/null +++ b/vulns/objective-c/stack-trace-exposure.m @@ -0,0 +1,23 @@ +// @id objc-stack-trace-exposure +// @test-case Internal diagnostic trace returned to a client +// @cwe CWE-209 +// @severity medium +// @language objective-c +// @expected-detection true +// @description A detailed diagnostic string is sent in an HTTP response. +// @safe-guard NEVER_RUN is zero, so no response is emitted. +// @detection-target Internal trace data passed to a client response sink. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +void response_send(int status, const char *body); +void server_log(const char *message); +static const int NEVER_RUN = 0; + +void objc_error_fixture(const char *diagnostic_trace) { + if (NEVER_RUN) { + response_send(500, diagnostic_trace); // VULNERABLE: CWE-209 + + server_log(diagnostic_trace); + response_send(500, "internal error"); // SAFE: generic client message + } +} diff --git a/vulns/objective-c/system-command-injection.m b/vulns/objective-c/system-command-injection.m new file mode 100644 index 0000000..e668a1f --- /dev/null +++ b/vulns/objective-c/system-command-injection.m @@ -0,0 +1,26 @@ +// @id objc-system-command-injection +// @test-case Shell command injection through system +// @cwe CWE-78 +// @severity critical +// @language objective-c +// @expected-detection true +// @description User-controlled text is passed directly to a shell command sink. +// @safe-guard NEVER_RUN is zero, so no subprocess starts. +// @detection-target Tainted data passed to system. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include +#include + +static const int NEVER_RUN = 0; + +void objc_command_fixture(const char *filename) { + if (NEVER_RUN) { + char command[512] = "cat "; + strncat(command, filename, sizeof(command) - strlen(command) - 1); + system(command); // VULNERABLE: CWE-78 + + const char *const arguments[] = {"cat", "--", filename, NULL}; + (void)arguments; // SAFE: intended direct exec argument vector, no shell + } +} diff --git a/vulns/objective-c/tls-verification-disabled.m b/vulns/objective-c/tls-verification-disabled.m new file mode 100644 index 0000000..1d28c26 --- /dev/null +++ b/vulns/objective-c/tls-verification-disabled.m @@ -0,0 +1,25 @@ +// @id objc-tls-verification-disabled +// @test-case TLS peer verification disabled +// @cwe CWE-295 +// @severity high +// @language objective-c +// @expected-detection true +// @description A client configuration disables certificate verification. +// @safe-guard NEVER_RUN is zero, so no client configuration is changed. +// @detection-target Verification flag set to false before an HTTPS request. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +typedef struct TLSConfig TLSConfig; +void tls_config_set_verify(TLSConfig *config, int enabled); +void tls_get(TLSConfig *config, const char *url); +static const int NEVER_RUN = 0; + +void objc_tls_fixture(TLSConfig *config) { + if (NEVER_RUN) { + tls_config_set_verify(config, 0); // VULNERABLE: CWE-295 + tls_get(config, "https://service.invalid/status"); + + tls_config_set_verify(config, 1); // SAFE: verification enabled + tls_get(config, "https://service.invalid/status"); + } +} diff --git a/vulns/objective-c/use-after-free.m b/vulns/objective-c/use-after-free.m new file mode 100644 index 0000000..b218915 --- /dev/null +++ b/vulns/objective-c/use-after-free.m @@ -0,0 +1,27 @@ +// @id objc-use-after-free +// @test-case Heap value consumed after free +// @cwe CWE-416 +// @severity critical +// @language objective-c +// @expected-detection true +// @description A freed heap pointer is passed to a consumer. +// @safe-guard NEVER_RUN is zero, so no allocation or free occurs. +// @detection-target Pointer use after free. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include + +void consume_bytes(const void *data); +static const int NEVER_RUN = 0; + +void objc_uaf_fixture(void) { + if (NEVER_RUN) { + void *data = malloc(64); + free(data); + consume_bytes(data); // VULNERABLE: CWE-416 + + void *safe = malloc(64); + consume_bytes(safe); + free(safe); // SAFE: final use occurs before free + } +} diff --git a/vulns/objective-c/weak-random-token.m b/vulns/objective-c/weak-random-token.m new file mode 100644 index 0000000..a26e2cb --- /dev/null +++ b/vulns/objective-c/weak-random-token.m @@ -0,0 +1,25 @@ +// @id objc-weak-random-token +// @test-case Predictable session token from rand +// @cwe CWE-338 +// @severity high +// @language objective-c +// @expected-detection true +// @description A session token is created with a non-cryptographic PRNG. +// @safe-guard NEVER_RUN is zero, so no token is issued. +// @detection-target rand used for authentication material. +// NEVER RUN IN PRODUCTION -- intentional scanner fixture. + +#include +#include + +void issue_session(uint32_t token); +uint32_t secure_random_uint32(void); +static const int NEVER_RUN = 0; + +void objc_random_fixture(void) { + if (NEVER_RUN) { + issue_session((uint32_t)rand()); // VULNERABLE: CWE-338 + + issue_session(secure_random_uint32()); // SAFE: cryptographic random source + } +}