mirror of
https://github.com/norandom/OpenUltraSAST.git
synced 2026-10-16 11:49:28 +00:00
spec(pre-push-safety-net): reserve the independent population v1 (15.1), unscanned
Eleven cases chosen by the maintainer from fifteen researched by advisory and fix diff only -- nothing cloned, nothing scanned, none among the 262 repositories the benchmarks reference -- plus the earlier Ghost reservation. Four are PHP repositories that ship JavaScript (two WordPress plugins), satisfying the multi-language requirement; families are limited to the v1 scope (injection, untrusted destination, permissive config). The development subjects shaped every rule of the last week and can no longer measure whether those rules generalise; this population can, until something scans it. A guard test fails the moment a reserved repository is referenced by a recipe, catalog, manifest or measurement outside benchmarks/independent/. Freezing (pins and sinks verified by reading source, a benign change per case) is the next step. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_01EmM1j46qgKMh3zqD1QMwEo
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
d5277c60d0
commit
451d4e4be7
@@ -0,0 +1,174 @@
|
||||
# Independent evaluation population v1 (pre-push-safety-net task 15.1, milestone M4).
|
||||
#
|
||||
# RESERVED, NOT SCANNED. Every case below was chosen from public advisories and fix diffs by web research alone:
|
||||
# no repository was cloned, no scanner or project tooling was run on any of them, and none appears among the
|
||||
# 262 repositories referenced anywhere in this project's benchmarks. They exist to answer the question the
|
||||
# development subjects can no longer answer honestly -- PMPro, WP Statistics, MW WP Form, NodeGoat and VAmPI all
|
||||
# shaped the rules they would be measured by.
|
||||
#
|
||||
# Rules for this file:
|
||||
# * Do not scan, trace, probe or census any of these repositories until the population is FROZEN (status
|
||||
# below) and the freeze commit is recorded. A scan before that makes the case a development subject.
|
||||
# * Freezing requires, per case: the vulnerable and fixed pins verified in the checkout, the sink confirmed at
|
||||
# the vulnerable pin by reading source, and a BENIGN change chosen from the same history without scanning.
|
||||
# * No rule, fact or query change may cite anything learned from these cases before their evaluation is
|
||||
# recorded; after it, a retune needs a new untouched population (release-milestones M4).
|
||||
#
|
||||
# Selected by the maintainer on 2026-09-24 from 15 researched candidates (menu selection); rejected alternates
|
||||
# and the research notes are in the research log referenced below.
|
||||
|
||||
schema_version = 1
|
||||
status = "reserved-unscanned"
|
||||
reserved_on = "2026-09-24"
|
||||
selected_by = "maintainer"
|
||||
research = "web research of GitHub advisories and fix diffs via the GitHub API; no clone, no scan"
|
||||
multi_language_members = ["wp-ultimate-member-sqli", "wp-learnpress-sqli", "phpmyfaq-captcha-ua-sqli", "yeswiki-bazar-form-sqli"]
|
||||
also_reserved = ["../push/ghost-reserved.toml"]
|
||||
|
||||
[[case]]
|
||||
id = "wp-ultimate-member-sqli"
|
||||
repo = "https://github.com/ultimatemember/ultimatemember"
|
||||
language = "php"
|
||||
also = ["javascript"]
|
||||
family = "injection"
|
||||
advisory = ["CVE-2024-1071", "GHSA-mh7h-gpmx-fggj"]
|
||||
vulnerable = "a49b3061ded1630e4f6860b4ebece5ccf0a493d2"
|
||||
fixed = "476de92f4c9aaa56c3e2f5527b242ce0476f4655"
|
||||
sink = "includes/core/class-member-directory-meta.php :: Member_Directory_Meta::ajax_get_members (ORDER BY built from esc_sql'd sort values)"
|
||||
privilege = "unauthenticated (only with the custom usermeta table option on)"
|
||||
license = "GPL"
|
||||
notes = "Release pair 2.8.2 (e1885a73) -> 2.8.3 (a6a7d13a) spans 94 commits; the commit pair is the clean one. c61008bd is a larger follow-up refactor of the same file."
|
||||
|
||||
[[case]]
|
||||
id = "wp-learnpress-sqli"
|
||||
repo = "https://github.com/LearnPress/learnpress"
|
||||
language = "php"
|
||||
also = ["javascript"]
|
||||
family = "injection"
|
||||
advisory = ["CVE-2024-8522", "GHSA-3w3r-r6g6-w8x5", "CVE-2024-8529", "GHSA-98wp-w76v-f75p"]
|
||||
vulnerable = "73b3cbceef8669056e9da5e2f2c78944eb139af5"
|
||||
fixed = "aaf42fe0c6bf05dcf2ff125750b7bd836e76fa3c"
|
||||
sink = "inc/Databases/class-lp-db.php :: LP_Database::execute (implode of only_fields into SELECT); source REST get_courses c_only_fields/c_fields"
|
||||
privilege = "unauthenticated (REST /wp-json/learnpress/v1/courses)"
|
||||
license = "GPL"
|
||||
notes = "The fix also touches assets/src/js/loadAJAX.js: the change itself is multi-language."
|
||||
|
||||
[[case]]
|
||||
id = "phpmyfaq-captcha-ua-sqli"
|
||||
repo = "https://github.com/thorsten/phpMyFAQ"
|
||||
language = "php"
|
||||
also = ["typescript", "javascript"]
|
||||
family = "injection"
|
||||
advisory = ["CVE-2026-46364", "GHSA-289f-fq7w-6q2w"]
|
||||
vulnerable = "11583abcf4787543d00c25d6c7926af6f509faf0"
|
||||
fixed = "545bdffb11244a4741cd29ac909a849c9a6a2e53"
|
||||
sink = "phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php :: garbageCollector, saveCaptcha (User-Agent and client IP in sprintf SQL)"
|
||||
privilege = "unauthenticated (GET /api/captcha)"
|
||||
license = "MPL-2.0"
|
||||
notes = "The advisory's commit b9f25109 is the vulnerable master commit the reporter tested, not the fix. Release pair 4.1.1 (f791858b) -> 4.1.2 (66792f25)."
|
||||
|
||||
[[case]]
|
||||
id = "yeswiki-bazar-form-sqli"
|
||||
repo = "https://github.com/YesWiki/yeswiki"
|
||||
language = "php"
|
||||
also = ["javascript"]
|
||||
family = "injection"
|
||||
advisory = ["CVE-2026-46670", "GHSA-jwvv-qr7q-cv8j"]
|
||||
vulnerable = "6f222915df36fca628fc83325cb0feed8dc2aca0"
|
||||
fixed = "fe7244bffcbeffd42777496ebc2a6f6118cc2388"
|
||||
sink = "tools/bazar/services/FormManager.php :: FormManager::create (bn_id_nature unquoted in INSERT); source FormController::displayAll imported-form JSON"
|
||||
privilege = "unauthenticated"
|
||||
license = "AGPL-3.0"
|
||||
notes = "Four-line fix: intval plus a Guard::isAllowed check. Default branch doryphore-dev."
|
||||
|
||||
[[case]]
|
||||
id = "signalk-remote-conn-ssrf"
|
||||
repo = "https://github.com/SignalK/signalk-server"
|
||||
language = "typescript"
|
||||
family = "untrusted_destination"
|
||||
advisory = ["CVE-2026-55591", "GHSA-q59x-jc9f-gfqf"]
|
||||
vulnerable = "1c479cc0d2d26d14ed74416cff36fa7336c7bf9e"
|
||||
fixed = "2816b0dda1bdde893d85c969a9c5950e7825eee7"
|
||||
sink = "src/serverroutes.ts :: makeRemoteRequest (req.body.host/port to http(s).request)"
|
||||
privilege = "unauthenticated by default (security not configured); admin otherwise"
|
||||
license = "Apache-2.0"
|
||||
notes = "Fix adds a schema, an ssrfGuard module and requestId validation (~450 added lines, mostly guard code and tests)."
|
||||
|
||||
[[case]]
|
||||
id = "saltcorn-sync-sqli"
|
||||
repo = "https://github.com/saltcorn/saltcorn"
|
||||
language = "javascript"
|
||||
also = ["typescript"]
|
||||
family = "injection"
|
||||
advisory = ["CVE-2026-41478", "GHSA-jp74-mfrx-3qvh"]
|
||||
vulnerable = "e7e09254a3c69dc6582eda9e5b5769935db108af"
|
||||
fixed = "2b6c8a2eab88955ea6b12b1b4e5c2e613faefef5"
|
||||
sink = "packages/server/routes/sync.js :: getSyncRows, getDelRows (maxLoadedId and timestamps in SQL template literals)"
|
||||
privilege = "authenticated low-privilege user"
|
||||
license = "MIT"
|
||||
notes = "Large multi-package repository; the fix is on the main line (v1.4.6 is a separate backport), so use the commit pair."
|
||||
|
||||
[[case]]
|
||||
id = "mongo-express-bson-rce"
|
||||
repo = "https://github.com/mongo-express/mongo-express"
|
||||
language = "javascript"
|
||||
family = "injection"
|
||||
advisory = ["CVE-2019-10758", "GHSA-h47j-hc6x-h3qq"]
|
||||
vulnerable = "ea02b364d43f179f191fc91fb9962efdb0843a8d"
|
||||
fixed = "d8c9bda46a204ecba1d35558452685cd0674e6f2"
|
||||
sink = "lib/bson.js :: toBSON (vm.runInNewContext over request text); callers lib/routes/collection.js, lib/routes/document.js"
|
||||
privilege = "authenticated (HTTP basic auth, default admin:pass)"
|
||||
license = "MIT"
|
||||
notes = "Code injection, not SQL. The advisory's first reference 7d365141 is only the v0.54.0 release commit."
|
||||
|
||||
[[case]]
|
||||
id = "mockoon-admin-api-cors"
|
||||
repo = "https://github.com/mockoon/mockoon"
|
||||
language = "typescript"
|
||||
family = "config_secrets"
|
||||
advisory = ["CVE-2026-59148", "GHSA-rqx4-3f6q-3x2v"]
|
||||
vulnerable = "b42bdfb7f82e83f0e81bea8e6fe41adf5ec82585"
|
||||
fixed = "c420b5a56918475b8663977b51e5f986e45b3299"
|
||||
sink = "packages/commons-server/src/libs/server/admin-api.ts :: admin-API middleware (Access-Control-Allow-Origin: *)"
|
||||
privilege = "unauthenticated"
|
||||
license = "MIT"
|
||||
notes = "Weaker fit: the fix commit touches 34 files; only one hunk in admin-api.ts is the CORS fix."
|
||||
|
||||
[[case]]
|
||||
id = "alerta-query-sqli"
|
||||
repo = "https://github.com/alerta/alerta"
|
||||
language = "python"
|
||||
family = "injection"
|
||||
advisory = ["CVE-2026-34400", "GHSA-8prr-286p-4w7j"]
|
||||
vulnerable = "71c098d8bb8091e76edaefeef4662eb77c753ce7"
|
||||
fixed = "fdd52cd1abad8d02d1dfb8ecdcdbb43b6af3b883"
|
||||
sink = "alerta/database/backends/postgres/queryparser.py :: SearchTerm.__repr__ (f-string WHERE) used by utils.py QueryBuilder.from_params"
|
||||
privilege = "API user; possibly unauthenticated if AUTH_REQUIRED defaults to False (unverified)"
|
||||
license = "Apache-2.0"
|
||||
notes = "The advisory's aeba85a3 introduced the parser; the fix is fdd52cd1."
|
||||
|
||||
[[case]]
|
||||
id = "wger-next-open-redirect"
|
||||
repo = "https://github.com/wger-project/wger"
|
||||
language = "python"
|
||||
family = "untrusted_destination"
|
||||
advisory = ["GHSA-vqv8-j3mj-wjxj"]
|
||||
vulnerable = "d30e85b23fb1307ac6579e9e3ae393c50470cea7"
|
||||
fixed = "0ba371bbb50cd5668820f240995405e1b2483384"
|
||||
sink = "wger/core/views/user.py :: trainer_login; wger/core/views/languages.py :: use_browser_language (request.GET next redirected unchecked)"
|
||||
privilege = "use_browser_language: unauthenticated; trainer_login: authenticated trainer"
|
||||
license = "AGPL-3.0"
|
||||
notes = "No CVE. A later commit cfa27a7f fixes the same pattern in core/middleware.py; not part of this fix."
|
||||
|
||||
[[case]]
|
||||
id = "opencve-notification-ssrf"
|
||||
repo = "https://github.com/opencve/opencve"
|
||||
language = "python"
|
||||
family = "untrusted_destination"
|
||||
advisory = ["CVE-2026-62282", "GHSA-ch3g-4xvr-674q"]
|
||||
vulnerable = "928bbadbb1672051bcfc2a6289a9e6a8f029c106"
|
||||
fixed = "0af7f894debc95c67fa53e7ba5a6cdecb10c34f0"
|
||||
sink = "web/projects/notifications.py :: _http_post_json (webhook and Slack test POST to a user-supplied URL)"
|
||||
privilege = "authenticated (can configure project notifications)"
|
||||
license = "unverified (GitHub reports NOASSERTION; possibly non-OSS BSL) -- verify before publishing any result"
|
||||
notes = "Selected by the maintainer despite the licence question; results must not be redistributed until it is checked."
|
||||
@@ -0,0 +1,151 @@
|
||||
# Independent evaluation population: candidate real-world cases
|
||||
|
||||
Research date: 2026-09-24. Selected by the maintainer: 11 of these 15 (see population-v1.toml); not selected: The Events Calendar, Tutor LMS, pyLoad, unfurl. Method: web research only. I read GitHub advisories (GHSA/CVE), commit pages, compare views and diffs through the GitHub REST API (`gh api`) and the web. I did not clone any repository or run any scanner or project tooling.
|
||||
|
||||
Exclusion check: I compared every repo URL below against `used-repos.txt` (case-insensitive exact match on `https://github.com/<owner>/<repo>`). None of them is on the list. None is NodeGoat, VAmPI, PMPro, WP Statistics, MW WP Form, libpng, a deliberately vulnerable training app or a benchmark suite. I did not use Ghost.
|
||||
|
||||
"Diff seen" means I read the patch hunks of the named fix commit through the GitHub API. "Vuln" is the parent of the fix commit unless a tag is given. Tags come with the commit SHA they resolve to.
|
||||
|
||||
## Summary table
|
||||
|
||||
| # | id | repo | lang | family | advisory | vulnerable (commit / tag) | fixed (commit / tag) | sink file :: function | privilege | diff seen |
|
||||
|---|----|------|------|--------|----------|---------------------------|----------------------|-----------------------|-----------|-----------|
|
||||
| 1 | wp-ultimate-member-sqli | https://github.com/ultimatemember/ultimatemember | PHP+JS (WP plugin) | SQLi | CVE-2024-1071 / GHSA-mh7h-gpmx-fggj | tag 2.8.2 = `e1885a73a8ad542cfd2ed60aef38a6bce4ab8553`; commit-level parent `a49b3061ded1630e4f6860b4ebece5ccf0a493d2` | `476de92f4c9aaa56c3e2f5527b242ce0476f4655` (+ `c61008bdbe1ce8de4597b41c7aad8dfda88f527d`); tag 2.8.3 = `a6a7d13a433b24e468ee10e778e3da5668b6ef59` | includes/core/class-member-directory-meta.php :: `Member_Directory_Meta::ajax_get_members()` (`$this->sql_order` ORDER BY) | unauthenticated | yes |
|
||||
| 2 | wp-learnpress-sqli | https://github.com/LearnPress/learnpress | PHP+JS (WP plugin) | SQLi | CVE-2024-8522 / GHSA-3w3r-r6g6-w8x5 (+ CVE-2024-8529 / GHSA-98wp-w76v-f75p) | `73b3cbceef8669056e9da5e2f2c78944eb139af5` (4.2.7) | `aaf42fe0c6bf05dcf2ff125750b7bd836e76fa3c` (4.2.7.1) | inc/Databases/class-lp-db.php :: `LP_Database::execute()` (`implode(',', $filter->only_fields)` into SELECT); source inc/jwt/rest-api/version1/class-lp-rest-courses-v1-controller.php :: `get_courses()` | unauthenticated (REST `/wp-json/learnpress/v1/courses`) | yes |
|
||||
| 3 | wp-events-calendar-sqli | https://github.com/the-events-calendar/the-events-calendar | PHP+JS (WP plugin) | SQLi | CVE-2024-8275 / GHSA-v6x4-m48j-jm68 | tag 6.6.4 = `4e4baa3fee7ef49abdd4cb277d963d555ffab4c7` | tag 6.6.4.1 = `419965a87d99195ad3fca22738e19dd287051f1c` (fix hunk in `e128ec5e2a25fe4fdc4542a9023918bf915b4084`) | src/Events/Custom_Tables/V1/WP_Query/Custom_Tables_Query.php :: `redirect_posts_orderby()` / `parse_orderby()` | unauthenticated per Wordfence (reached through `tribe_has_next_event` `order`) | yes |
|
||||
| 4 | wp-tutor-lms-coupon-sqli | https://github.com/themeum/tutor | PHP+JS/TS (WP plugin) | SQLi | CVE-2025-13673 / GHSA-f7q9-gg7h-5rff | tag v3.9.6 = `71187a525dbdbdb045137af702307f05dda6a7a6` (= parent of fix) | `e4a25f69f0fb63a46d57eff6ba5b58378a4c088d` (3.9.7, no GitHub tag) | helpers/QueryHelper.php :: `prepare_where_clause()` / `make_clause()`; source models/CouponModel.php :: `get_coupon_details_for_checkout()`, `get_coupon_by_code()` | unauthenticated (checkout `coupon_code`) | yes (mapping partly inferred) |
|
||||
| 5 | phpmyfaq-captcha-ua-sqli | https://github.com/thorsten/phpMyFAQ | PHP+TS/JS | SQLi | CVE-2026-46364 / GHSA-289f-fq7w-6q2w | `11583abcf4787543d00c25d6c7926af6f509faf0`; tag 4.1.1 = `f791858b2aa4b811f5b19d10c20ba5c4a30786de` | `545bdffb11244a4741cd29ac909a849c9a6a2e53`; tag 4.1.2 = `66792f2575b07d65896b504d47deb5f4f903f9ff` | phpmyfaq/src/phpMyFAQ/Captcha/BuiltinCaptcha.php :: `garbageCollector()`, `saveCaptcha()` | unauthenticated (`GET /api/captcha`, User-Agent header) | yes |
|
||||
| 6 | yeswiki-bazar-form-sqli | https://github.com/YesWiki/yeswiki | PHP+JS | SQLi | CVE-2026-46670 / GHSA-jwvv-qr7q-cv8j | `6f222915df36fca628fc83325cb0feed8dc2aca0`; tag v4.6.3 = `5e8fc114d01d558dc4799b95219549ccf02ebd11` | `fe7244bffcbeffd42777496ebc2a6f6118cc2388`; tag v4.6.4 = `c1bae9fc260eb3e4346f3b0bc65ab609d1cf783a` | tools/bazar/services/FormManager.php :: `FormManager::create()`; source tools/bazar/controllers/FormController.php :: `displayAll()` | unauthenticated | yes |
|
||||
| 7 | signalk-remote-conn-ssrf | https://github.com/SignalK/signalk-server | TypeScript (Node/Express) | SSRF | CVE-2026-55591 / GHSA-q59x-jc9f-gfqf | `1c479cc0d2d26d14ed74416cff36fa7336c7bf9e`; tag v2.27.0 = `4592992aaf7c44548842be9390b651cfb816eaf4` | `2816b0dda1bdde893d85c969a9c5950e7825eee7`; tag v2.28.0 = `3645160b235364e1fd3afe1f2f6b2270b8194b7d` | src/serverroutes.ts :: `makeRemoteRequest()` (from `testSignalKConnection`, `requestAccess`, `checkAccessRequest` routes) | unauthenticated by default (security not configured); admin otherwise | yes |
|
||||
| 8 | saltcorn-sync-sqli | https://github.com/saltcorn/saltcorn | JavaScript/TS (Node/Express) | SQLi | CVE-2026-41478 / GHSA-jp74-mfrx-3qvh | `e7e09254a3c69dc6582eda9e5b5769935db108af`; tag v1.4.5 = `92d24ca5ce8480d3b0cf1067e33113f2a790030c` | `2b6c8a2eab88955ea6b12b1b4e5c2e613faefef5`; tag v1.4.6 = `d49e6a947de777053a203a27ab4d9e1a1754ccd0` (backport) | packages/server/routes/sync.js :: `getSyncRows()`, `getDelRows()` (+ packages/saltcorn-data/models/table.ts sync_info queries) | authenticated low-privilege user | yes |
|
||||
| 9 | mongo-express-bson-rce | https://github.com/mongo-express/mongo-express | JavaScript (Node/Express) | code injection (vm eval) | CVE-2019-10758 / GHSA-h47j-hc6x-h3qq | `ea02b364d43f179f191fc91fb9962efdb0843a8d`; tag v0.53.0 = `9eb49994f3842260f7c6db75592ea6ba9376fad6` | `d8c9bda46a204ecba1d35558452685cd0674e6f2`; tag v0.54.0 = `7d365141deadbd38fa961cd835ce68eab5731494` | lib/bson.js :: `toBSON()` (`vm.runInNewContext` over request text); callers lib/routes/collection.js, lib/routes/document.js | authenticated (HTTP basic auth, default admin:pass) | yes |
|
||||
| 10 | mockoon-admin-api-cors | https://github.com/mockoon/mockoon | TypeScript (Node/Express) | permissive CORS | CVE-2026-59148 / GHSA-rqx4-3f6q-3x2v | `b42bdfb7f82e83f0e81bea8e6fe41adf5ec82585`; tag v9.6.1 = `1c874584c058b891f6affbb44d5a5223f3f8f812` | `c420b5a56918475b8663977b51e5f986e45b3299`; tag v9.7.0 = `df02ba3b0d9e8b88651230f3b38d8e6fa3679b9b` | packages/commons-server/src/libs/server/admin-api.ts :: admin-API middleware (`app.use(`${adminApiPrefix}*`)`, `Access-Control-Allow-Origin: *`) | unauthenticated | yes (large diff, one CORS hunk) |
|
||||
| 11 | pyload-add-package-sqli | https://github.com/pyload/pyload | Python (Flask) | SQLi | CVE-2025-55156 / GHSA-pwh4-6r3m-j2rf | `a123accec0618798db77f9f5bc66838b741d9f58` | `134edcdf6e2a10c393743c254da3d9d90b74258f` (0.5.0b3.dev91) | src/pyload/core/database/file_database.py :: `FileDatabaseMethods.update_link_info()`; source src/pyload/webui/app/blueprints/json_blueprint.py :: `add_package` | authenticated (ADD permission) | yes |
|
||||
| 12 | alerta-query-sqli | https://github.com/alerta/alerta | Python (Flask) | SQLi | CVE-2026-34400 / GHSA-8prr-286p-4w7j | `71c098d8bb8091e76edaefeef4662eb77c753ce7`; tag v9.0.4 = `b7111d23ae9aefb5064ef107976f62e3836217b6` | `fdd52cd1abad8d02d1dfb8ecdcdbb43b6af3b883`; tag v9.1.0 = `f4312727783dad64198b62ed778bbca98a5144a3` | alerta/database/backends/postgres/queryparser.py :: `SearchTerm.__repr__` (f-string WHERE); used by alerta/database/backends/postgres/utils.py :: `QueryBuilder.from_params()` | API user (API key when `AUTH_REQUIRED`; see notes) | yes |
|
||||
| 13 | wger-next-open-redirect | https://github.com/wger-project/wger | Python (Django) | open redirect | GHSA-vqv8-j3mj-wjxj (no CVE) | `d30e85b23fb1307ac6579e9e3ae393c50470cea7`; tag 2.5 = `c1fca6bdf77d8ed80048d1b73a8da80cf36ee751` | `0ba371bbb50cd5668820f240995405e1b2483384`; tag 2.6 = `e1d70bcc38cd56ae4a254dca1713c404d069f319` | wger/core/views/user.py :: `trainer_login()`; wger/core/views/languages.py :: `use_browser_language()` | `use_browser_language`: unauthenticated; `trainer_login`: authenticated trainer | yes |
|
||||
| 14 | unfurl-flask-debug | https://github.com/obsidianforensics/unfurl | Python (Flask) | permissive config (debug on) | GHSA-vg9h-jx4v-cwx2 (no CVE) | `6b8e97853090c909936a18d41228168b7a35fe94` | `4c0a07ab1e9af3a1ddf0e7f47153ec9ba77946dd` | unfurl/app.py :: `web_app()` → `UnfurlApp.__init__` `app.run(debug=...)` | unauthenticated (Werkzeug debugger) | yes |
|
||||
| 15 | opencve-notification-ssrf | https://github.com/opencve/opencve | Python (Django) | SSRF | CVE-2026-62282 / GHSA-ch3g-4xvr-674q | `928bbadbb1672051bcfc2a6289a9e6a8f029c106` | `0af7f894debc95c67fa53e7ba5a6cdecb10c34f0`; tag v3.0.0 = `928943aa87ceac789061114159d74c2067cc6ae6` | web/projects/notifications.py :: `_http_post_json()` (webhook and Slack test); forms in web/projects/forms.py | authenticated (can configure project notifications) | yes |
|
||||
|
||||
Mix: 6 PHP (all six repos ship JS, four are WordPress plugins), 4 JS/TS, 5 Python. By family: 10 injection (9 SQLi: #1–6, #8, #11, #12; 1 code injection: #9), 3 untrusted destination (SSRF #7 and #15, open redirect #13), 2 permissive configuration (CORS #10, debug #14). By privilege: 10 unauthenticated or unauthenticated by default (#1–7, #10, #14, plus #13's `use_browser_language` sink) and 5 authenticated (#8, #9, #11, #15, and #12 depending on `AUTH_REQUIRED`).
|
||||
|
||||
## Per-candidate notes
|
||||
|
||||
### 1. wp-ultimate-member-sqli (PHP+JS, WordPress)
|
||||
- Repo: https://github.com/ultimatemember/ultimatemember (GPL, PHP 2.7 MB + JS 0.6 MB).
|
||||
- Advisory: CVE-2024-1071, GHSA-mh7h-gpmx-fggj (unreviewed GHSA mirror of the Wordfence record). Affects 2.1.3–2.8.2, CVSS 9.8.
|
||||
- Flaw: `ajax_get_members()` builds `$this->sql_order = " ORDER BY CAST(umm_sort.um_value AS {$custom_sort_type}) {$custom_sort_order} "` and similar ORDER BY strings. Values from the member-directory `sorting` request pass only through `esc_sql()`. That does not protect a context without quotes. Only exploitable when the "custom usermeta table" option is on.
|
||||
- Fix: `476de92f` checks `$order` / `$custom_sort_order` against `ASC`/`DESC` and `$custom_sort_type` against `$this->sort_data_types`. `c61008bd` ("fixed security issue with mysql query escape") is a larger WPCS refactor of the same file. Use `476de92f` as the minimal fix and the 2.8.2 → 2.8.3 tags for a release-level pair.
|
||||
- Confidence: high. I read both diffs. The 2.8.2...2.8.3 compare has 94 commits, so a tag pair gives a noisy diff. The commit pair is cleaner.
|
||||
|
||||
### 2. wp-learnpress-sqli (PHP+JS, WordPress)
|
||||
- Repo: https://github.com/LearnPress/learnpress (PHP 5.9 MB, JS 1.4 MB).
|
||||
- Advisories: CVE-2024-8522 (`c_only_fields`, CVSS 10.0) and CVE-2024-8529 (`c_fields`). Both are fixed by the same commit.
|
||||
- Flaw: the REST controller `get_courses()` put `explode(',', $params['c_only_fields'])` straight into `$filter->only_fields`. `LP_Database::execute()` then runs `implode(',', array_unique($filter->only_fields))` into the SELECT column list (class-lp-db.php around line 607).
|
||||
- Fix `aaf42fe0`: `Courses::handle_params_for_query_courses()` now runs every field through `$wpdb->prepare('%i', $field)` (identifier quoting), and the controller's direct assignment is removed. It also touches `assets/src/js/loadAJAX.js`, so the commit is itself multi-language.
|
||||
- Confidence: high. I read the diff and checked the sink line at the vulnerable commit.
|
||||
|
||||
### 3. wp-events-calendar-sqli (PHP+JS, WordPress)
|
||||
- Repo: https://github.com/the-events-calendar/the-events-calendar (large plugin: PHP 10 MB, JS 0.7 MB; license field NOASSERTION, the plugin is GPL). **It has a git submodule `common` (tribe-common), so a checkout needs `--recursive`.**
|
||||
- Advisory: CVE-2024-8275, GHSA-v6x4-m48j-jm68. The `order` parameter of `tribe_has_next_event`, ≤ 6.6.4.
|
||||
- Flaw: `redirect_posts_orderby()` split ORDER BY fragments with a regex and kept any trailing `order` token as is. `parse_orderby()` inserted `$order` into `"ID $order, ..."`.
|
||||
- Fix (6.6.4.1, commit `e128ec5e`): adds `sanitize_order()` (only ASC/DESC) and applies it at both sites. It also bumps the submodule.
|
||||
- Privilege: Wordfence rates it unauthenticated. Reachability in practice depends on how the template tag's arguments reach WP_Query. The ground-truth sink is still unambiguous.
|
||||
- Confidence: high for the diff. Medium for how exploitable it is from a clean install.
|
||||
|
||||
### 4. wp-tutor-lms-coupon-sqli (PHP+JS/TS, WordPress)
|
||||
- Repo: https://github.com/themeum/tutor (GPL-3.0; PHP 3.8 MB, TS 3.3 MB, JS 0.4 MB).
|
||||
- Advisory: CVE-2025-13673, GHSA-f7q9-gg7h-5rff. ≤ 3.9.6, unauthenticated `coupon_code`. Partial fixes shipped in 3.9.4 and 3.9.6.
|
||||
- Flaw: `QueryHelper::prepare_where_clause()` built values as `"'" . $val . "'"`. The only escaping was an `esc_sql()` in `get_coupon_details_for_checkout()`. Other coupon paths (`get_coupon_by_code()`, `get_coupon_usage_count()`, `get_coupon_applications()`) pass `$coupon_code` through without escaping.
|
||||
- Fix `e4a25f69` (parent = tag v3.9.6): `make_clause()` now binds every value through `$wpdb->prepare('%s'|'%d', ...)`, and the manual quoting and `esc_sql` are removed.
|
||||
- Confidence: medium. I read the diff and it is the only SQL change right after v3.9.6. The trac changeset cited by the CVE (3469242) returned 403, so the exact exploited call chain from the checkout request to QueryHelper is my inference. There is no v3.9.7 tag on GitHub. Use the commit pair.
|
||||
|
||||
### 5. phpmyfaq-captcha-ua-sqli (PHP+TS/JS)
|
||||
- Repo: https://github.com/thorsten/phpMyFAQ (MPL-2.0; PHP 14.7 MB, TS 1.7 MB).
|
||||
- Advisory: CVE-2026-46364, GHSA-289f-fq7w-6q2w. ≤ 4.1.1. The advisory's commit reference `b9f25109` is the **vulnerable** master commit the reporter tested, not the fix.
|
||||
- Flaw: the constructor stores `$request->headers->get('user-agent')` and `getClientIp()`. `garbageCollector()` (DELETE) and `saveCaptcha()` (SELECT/INSERT) put them into `sprintf` SQL without escaping. Both run on every `GET /api/captcha`.
|
||||
- Fix `545bdffb` ("fix: added missing escaping"): adds `escapeQueryValue()` for userAgent, ip, language and code.
|
||||
- Confidence: high. I read the diff in the 4.1.1...4.1.2 compare and the commit.
|
||||
|
||||
### 6. yeswiki-bazar-form-sqli (PHP+JS)
|
||||
- Repo: https://github.com/YesWiki/yeswiki (AGPL-3.0; PHP 4 MB, JS 0.8 MB). Default branch `doryphore-dev`.
|
||||
- Advisory: CVE-2026-46670, GHSA-jwvv-qr7q-cv8j. < 4.6.4.
|
||||
- Flaw: `FormManager::create()` concatenates `$data['bn_id_nature']` unquoted into `INSERT ... VALUES (`. The data comes from `$_POST['imported-form']` JSON in `FormController::displayAll()`, and that path had no permission check.
|
||||
- Fix `fe7244bf`: `intval($data['bn_id_nature'])` plus a `Guard::isAllowed('saisie_formulaire')` check. The diff is 4 lines.
|
||||
- Confidence: high. I read the diff. Related YesWiki SQLi advisories (GHSA-qg78-vmvc-fhjw, GHSA-f58v-p6j9-24c2, GHSA-4pf7-cc4r-g63h) could add more cases from the same repo.
|
||||
|
||||
### 7. signalk-remote-conn-ssrf (TypeScript, Node/Express)
|
||||
- Repo: https://github.com/SignalK/signalk-server (Apache-2.0, about 24 MB).
|
||||
- Advisory: CVE-2026-55591, GHSA-q59x-jc9f-gfqf. ≤ 2.27.0.
|
||||
- Flaw: `makeRemoteRequest(host, port, useTLS, selfsignedcert, path)` passes `req.body.host`/`port` to `http(s).request` without validation. It is reached from three admin routes.
|
||||
- Fix `2816b0dd`: TypeBox schema validation (new src/remoteConnectionSchemas.ts), a new src/ssrfGuard.ts (BlockList for loopback, link-local and metadata, plus a DNS lookup guard) wired into `makeRemoteRequest`, and UUID validation of `requestId`.
|
||||
- Confidence: high. I read the diff. It is medium-sized, about 450 added lines, mostly new guard code and tests.
|
||||
|
||||
### 8. saltcorn-sync-sqli (JavaScript/TS, Node/Express)
|
||||
- Repo: https://github.com/saltcorn/saltcorn (MIT). It is a monorepo of packages and on the large side (about 340 MB of git data), but the relevant code is in `packages/server` and `packages/saltcorn-data`.
|
||||
- Advisory: CVE-2026-41478, GHSA-jp74-mfrx-3qvh. < 1.4.6 / < 1.5.6 / < 1.6.0-beta.5.
|
||||
- Flaw: `getSyncRows()` interpolates `${syncInfo.maxLoadedId}` (from `req.body.syncInfos[table].maxLoadedId`) and `${user.id}` into SQL template literals. `getDelRows()` does the same with timestamps.
|
||||
- Fix `2b6c8a2e`: integer and date validation plus `$1`-style parameterised queries in sync.js and table.ts.
|
||||
- Confidence: high. I read the diff. The fix commit is on the main line, and v1.4.6 is a separate backport. For a clean pair, use the commit and its parent `e7e09254`.
|
||||
|
||||
### 9. mongo-express-bson-rce (JavaScript, Node/Express)
|
||||
- Repo: https://github.com/mongo-express/mongo-express (MIT, about 16 MB).
|
||||
- Advisory: CVE-2019-10758, GHSA-h47j-hc6x-h3qq (CWE-94/78). < 0.54.0.
|
||||
- Flaw: `lib/bson.js` `toBSON()` evaluates user-supplied document/query text with `vm.runInNewContext` in a BSON sandbox. The sandbox can be escaped, which gives RCE through the `/checkValid` and document/collection routes.
|
||||
- Fix `d8c9bda4`: replaces the vm-based parser with `mongodb-query-parser`. It removes 101 lines from bson.js, deletes lib/json.js and changes collection.js.
|
||||
- Confidence: high. I read the diff. It is an older case (2019), useful as a known-answer eval sink. Note that the advisory's first reference, `7d365141`, is only the version-bump or release commit (= tag v0.54.0).
|
||||
|
||||
### 10. mockoon-admin-api-cors (TypeScript, Node/Express)
|
||||
- Repo: https://github.com/mockoon/mockoon (MIT; monorepo of app, cli, commons-server and serverless; about 42 MB).
|
||||
- Advisory: CVE-2026-59148, GHSA-rqx4-3f6q-3x2v (CWE-942/306/352/732). < 9.7.0.
|
||||
- Flaw: the admin API middleware sets `Access-Control-Allow-Origin: *` with all methods on every `/mockoon-admin/*` endpoint. The admin API is on by default and has no authentication.
|
||||
- Fix `c420b5a5`: adds a configurable `adminApiCorsOrigins` allowlist (no header by default, 403 on preflight), optional admin API auth, and an env-var prefix check.
|
||||
- Confidence: high that the CORS hunk is the fix. The commit is **large (34 files)**, but the permissive-CORS sink sits in one clearly identifiable hunk of admin-api.ts. It is a weaker fit for "small diff" than the others.
|
||||
|
||||
### 11. pyload-add-package-sqli (Python, Flask)
|
||||
- Repo: https://github.com/pyload/pyload (AGPL-3.0, GitHub reports NOASSERTION; about 50 MB).
|
||||
- Advisory: CVE-2025-55156, GHSA-pwh4-6r3m-j2rf. < 0.5.0b3.dev91.
|
||||
- Flaw: `update_link_info()` builds `"','".join(x[3] for x in data)` into `f"SELECT id FROM links WHERE url IN ('{statuses}')"`. The URLs come from `add_links` on `/json/add_package`. Chain: json_blueprint `add_package` → `Api.add_package` → `FileManager.add_links` → `InfoThread` → `update_file_info` → `update_link_info`.
|
||||
- Fix `134edcdf`: `?` placeholders. The diff is 5 lines. The source-to-sink path crosses a thread, which makes it a good cross-thread taint test.
|
||||
- Confidence: high. I read the diff. The same repo also has CVE-2023-0297 (GHSA-pf38-5p22-x6h6, unauthenticated js2py code injection, fix `7d73ba7919e594d783b3411d7ddb87885aea782d`, parent `6aa71cd99a6f510de7137d17ec99f930916704c0`). I kept it as an alternate only, because that fix just calls `js2py.disable_pyimport()`. The `eval_js` sink stays in the fixed tree, so a sink-based analyzer would still correctly flag the fixed version.
|
||||
|
||||
### 12. alerta-query-sqli (Python, Flask)
|
||||
- Repo: https://github.com/alerta/alerta (Apache-2.0, about 11 MB).
|
||||
- Advisory: CVE-2026-34400, GHSA-8prr-286p-4w7j. < 9.1.0. The advisory's `aeba85a3` reference (2018) is the commit that **introduced** the parser. The fix is `fdd52cd1`.
|
||||
- Flaw: the Postgres query-string parser (`?q=`) turned parsed search terms into SQL with f-strings in `SearchTerm.__repr__`. `QueryBuilder.from_params()` then used that as a WHERE clause with an empty `qvars`.
|
||||
- Fix: new `to_sql(params)` methods produce `%(name)s` placeholders, and `from_params()` passes the parsed vars.
|
||||
- Privilege: alerta requires an API key or login when `AUTH_REQUIRED=True`. I believe the default is `False`, which would make this unauthenticated on a default install, but I did not check settings.py. Record it as "API user, possibly unauthenticated".
|
||||
- Confidence: high for the diff. The fix is about 100 lines in one file plus tests.
|
||||
|
||||
### 13. wger-next-open-redirect (Python, Django)
|
||||
- Repo: https://github.com/wger-project/wger (AGPL-3.0, about 91 MB with a JS frontend).
|
||||
- Advisory: GHSA-vqv8-j3mj-wjxj (no CVE assigned). ≤ 2.5, fixed in 2.6.
|
||||
- Flaw: `trainer_login()` returns `HttpResponseRedirect(request.GET['next'])`. `use_browser_language()` redirects to `remove_language_code(request.GET.get('next','/'))`. Neither checks host or scheme.
|
||||
- Fix `0ba371bb`: `url_has_allowed_host_and_scheme()` at both sites. The diff is 20 lines of code plus tests.
|
||||
- Two sinks, two privilege levels: `use_browser_language` is unauthenticated, `trainer_login` needs a trainer. A later follow-up, `cfa27a7fc78c682ad4244020505dce4825fe1e26`, fixes the same pattern in `wger/core/middleware.py` (AuthProxyMiddleware). I found no separate advisory for it. It is optional as a third sink, but it is not in the 2.6 fix.
|
||||
- Confidence: high. I read the diff.
|
||||
|
||||
### 14. unfurl-flask-debug (Python, Flask)
|
||||
- Repo: https://github.com/obsidianforensics/unfurl (Apache-2.0, about 24 MB).
|
||||
- Advisory: GHSA-vg9h-jx4v-cwx2 (critical, CWE-489, no CVE). The GHSA lists "patched: none" as a package version, but it references the fix commit.
|
||||
- Flaw: `web_app()` reads `debug` with `config[...].get('debug')`, which returns a string. Any non-empty string, including `"False"`, is truthy, so `app.run(debug=...)` always enables the Werkzeug debugger.
|
||||
- Fix `4c0a07ab`: `getboolean('debug')` and `debug = false` in unfurl.ini. The diff is 2 lines.
|
||||
- Caveat: the function default `debug='True'` is unchanged in the fixed version, so a run without an `[UNFURL_APP]` section still starts in debug mode. The fixed revision may therefore still, correctly, be flagged for debug-on by a strict analyzer.
|
||||
- Confidence: high. I read the diff.
|
||||
|
||||
### 15. opencve-notification-ssrf (Python, Django)
|
||||
- Repo: https://github.com/opencve/opencve (GitHub license: NOASSERTION. I believe OpenCVE v2+ is under a non-OSI license (BSL), but I did not verify this. Check before use).
|
||||
- Advisory: CVE-2026-62282, GHSA-ch3g-4xvr-674q. The advisory's version ranges are confusing ("≤ 1.5.0, patched 3.0.0"). Use the commit pair.
|
||||
- Flaw: webhook and Slack notification "test" requests POST to a user-supplied URL (`_http_post_json` with urllib) and do not restrict the destination.
|
||||
- Fix `0af7f894`: new web/opencve/utils/ssrf.py (`validate_public_http_url`, `safe_request` with DNS pinning), `clean_url`/`clean_webhook_url` validators in forms.py, and notifications.py switched to `safe_request`.
|
||||
- Confidence: high for the diff. The license is the concern. It is the weakest Python pick; drop it if an OSS license is mandatory.
|
||||
|
||||
## Considered and rejected (for traceability)
|
||||
- vantage6 CVE-2024-23823 (permissive CORS, fix `70bb4e1d889230a841eb364d6c03accd7dd01a41`): the fix only adds a `cors_allowed_origins` option whose default is still `"*"`, so the fixed version is still permissive by default.
|
||||
- FlaskBB CVE-2026-46556 (SSRF): the advisory has no patched version. The only related commit (`e87e585f`) just sets `allow_redirects=False`.
|
||||
- SillyTavern CVE-2026-46372 (SSRF): the fix is a global request filter that is off by default.
|
||||
- NodeBB CVE-2025-50979 (SQLi): no patched version or fix commit is listed, and I could not confirm the claim.
|
||||
- Etherpad CVE-2026-55087 (open redirect): the referenced commits introduced the code. The fix is inside a large "assorted hardening" PR (#7784).
|
||||
- EverShop CVE-2023-46943 (hardcoded HMAC secret): the fix is a large dev→main merge commit.
|
||||
- claude-code-router CVE-2025-57755 (CORS): no tags, and I found no identifiable fix commit.
|
||||
- calibre-web CVE-2022-0339 (SSRF): the fix commit is mixed with unrelated Kobo-sync changes. calibre-web SQLi CVE-2022-30765 (GHSA-8ppf-x4gr-2x7g) could be a Python alternate, but I did not check its diff.
|
||||
- Tutor LMS PRs #3011/#3014/#3018 (SQLi hardening, Sept 2026): no CVE linked yet.
|
||||
Reference in New Issue
Block a user