Skip to content

Path traversal in kws HTTP parser via URI segment overflow

High
andywolk published GHSA-684h-wjm9-2p6j Jun 3, 2026

Package

libks (C)

Affected versions

<= 2.0.10

Patched versions

>= 2.0.11

Description

Summary

clean_uri() in libks's HTTP request parser fails to reject URIs whose path has more segments than its internal canonicalization buffer can hold. The canonicalization step silently passes such URIs through with embedded ".." sequences intact, enabling path traversal in any consumer that later joins the URI with a filesystem path.

Details

clean_uri() in src/kws.c splits the URI on "/" into a 64-slot pointer array, then walks the array dropping "." segments and popping ".." segments to produce a canonical path. A size check is meant to refuse inputs deeper than the array:

if (argc == sizeof(argv)) {  /* too deep */
  return KS_STATUS_FAIL;
}

The comparison is incorrect: sizeof(argv) is the byte size of the pointer array (512 on 64-bit, 256 on 32-bit), not the element count (64). Because ks_separate_string() clamps its return value to the array's element count, the comparison is never true and the reject branch is dead code.

When the input exceeds 64 segments, ks_separate_string() stops splitting at the boundary and leaves the entire unsplit remainder - including its embedded "/" characters - in argv[63]. The canonicalization loop matches each slot against the literal two-character string "..", so a slot whose content is, for example, "a/b/../../etc/passwd" is not recognized as parent-directory tokens and is copied through verbatim. The reconstructed URI keeps every embedded ".." sequence intact. When a downstream consumer later concatenates this URI with a filesystem root and passes it to open(2), the kernel resolves the embedded ".." segments literally and escapes the intended directory.

Impact

A remote attacker who can submit URIs through a consumer of kws_parse_header() can read files outside any intended document root, provided:

  1. The URI carries at least 63 leading path segments (to exhaust the splitter's slot budget),
  2. The traversal payload follows in the unsplit tail,
  3. The consumer joins the returned URI with a filesystem path without further sanitization.

The canonical attack shape is GET /a/a/a/.../a/../../../../etc/passwd HTTP/1.1 with at least 63 padding segments - comfortably within typical HTTP-URI size limits.

Reachability in FreeSWITCH

The primary in-tree consumer is mod_verto's HTTP-static handler. The default FreeSWITCH configuration is not affected: stock verto.conf.xml ships with no <vhosts> block, and without that mod_verto's HTTP-static path is disabled and kws_parse_header() is never invoked for plain HTTP requests. Deployments that opt into HTTP-static serving by adding a <vhost> become reachable:

  • If the <vhost> has no auth-realm, the traversal is unauthenticated.
  • If the <vhost> has an auth-realm, the file-read code path is gated on Basic Auth, though clean_uri() itself still executes pre-auth.

Any other downstream that passes the URI from kws_parse_header() to a filesystem call inherits the same exposure.

Patches

The fix hoists the array's element count into a named constant and compares argc against it. A test in tests/testhttp.c now exercises kws_parse_header() end-to-end and asserts that >64-segment inputs are rejected.

Workarounds

Until the fix is deployed, operators can mitigate by:

  • Leaving <vhosts> disabled in verto.conf.xml (the default).
  • If a vhost is required, configuring auth-realm so that file-serve and Lua-exec paths require authentication.
  • Fronting the verto listener with a reverse proxy that rejects URIs containing ".." or with an abnormally large number of path segments.

Credit

Adam Bedard, Security Researcher.

Severity

High

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
None
User interaction
None
Scope
Unchanged
Confidentiality
High
Integrity
None
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N

CVE ID

CVE-2026-49846

Weaknesses

Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')

The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory. Learn more on MITRE.

Incorrect Comparison

The product compares two entities in a security-relevant context, but the comparison is incorrect, which may lead to resultant weaknesses. Learn more on MITRE.

Credits