Undertow: directory traversal vulnerability
Description
A path traversal vulnerability was found in Undertow. This issue may allow a remote attacker to append a specially-crafted sequence to an HTTP request for an application deployed to JBoss EAP, which may permit access to privileged or restricted files and directories.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
A path traversal vulnerability in Undertow allows remote attackers to access restricted files on JBoss EAP by sending a crafted HTTP request.
Vulnerability
Overview
A path traversal vulnerability was identified in Undertow, the web server component used in Red Hat JBoss Enterprise Application Platform (EAP). The flaw allows a remote attacker to append a specially-crafted sequence to an HTTP request targeting an application deployed on JBoss EAP, which may bypass intended access controls and expose privileged or restricted files and directories [1].
Attack
Vector
The vulnerability is exploitable remotely without authentication. An attacker only needs to send a maliciously crafted HTTP request to an application deployed on JBoss EAP. The attack leverages a sequence that traverses the file system path, enabling the attacker to read files that should be protected. No special network position is required [1].
Impact
If successfully exploited, the attacker could gain unauthorized read access to sensitive files and directories on the server. This could include configuration files, application source code, or other data containing credentials or business secrets. The impact is primarily confidentiality breach, with potential for further escalation if exposed files contain privileged information [1].
Mitigation
Red Hat has released security updates to address this vulnerability. The fix is included in RHSA-2024:1674 (for JBoss EAP 7 on RHEL 7), RHSA-2024:1675 (for RHEL 8), and RHSA-2024:1676 (for RHEL 9) [2][3][4]. Users are strongly advised to apply these updates to their JBoss EAP installations.
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
io.undertow:undertow-coreMaven | < 2.2.31.Final | 2.2.31.Final |
io.undertow:undertow-coreMaven | >= 2.3.0.Alpha1, < 2.3.12.Final | 2.3.12.Final |
Affected products
2Patches
254f3e4325425Merge pull request #1556 from fl4via/UNDERTOW-2339
3 files changed · +63 −0
core/src/main/java/io/undertow/server/protocol/http/HttpRequestParser.java+23 −0 modified@@ -388,6 +388,10 @@ private void sanitazeListTypeHeaders(final HttpServerExchange builder) { private static final int IN_PATH = 4; private static final int HOST_DONE = 5; + private static final int PATH_SEGMENT_START = 0; + private static final int PATH_DOT_SEGMENT = 1; + private static final int PATH_NON_DOT_SEGMENT = 2; + /** * Parses a path value * @@ -403,6 +407,8 @@ final void handlePath(ByteBuffer buffer, ParseState state, HttpServerExchange ex int canonicalPathStart = state.pos; boolean urlDecodeRequired = state.urlDecodeRequired; + int pathSubState = 0; + while (buffer.hasRemaining()) { char next = (char) (buffer.get() & 0xFF); if(!allowUnescapedCharactersInUrl && !ALLOWED_TARGET_CHARACTER[next]) { @@ -426,6 +432,11 @@ final void handlePath(ByteBuffer buffer, ParseState state, HttpServerExchange ex state.urlDecodeRequired = urlDecodeRequired; // store at canonical path the partial path parsed up until here state.canonicalPath.append(stringBuilder.substring(canonicalPathStart)); + if (parseState == IN_PATH && pathSubState == PATH_DOT_SEGMENT) { + // Inside a dot-segment (".", ".."), we don't want to allow removal of the ';' character from + // the path. This is to avoid path traversal issues - "/..;" should not be treated as "/..". + state.canonicalPath.append(";"); + } state.stringBuilder.append(";"); // set position to end of path (possibly start of parameter name) state.pos = state.stringBuilder.length(); @@ -459,6 +470,18 @@ final void handlePath(ByteBuffer buffer, ParseState state, HttpServerExchange ex } else if (next == '/' && parseState != HOST_DONE) { parseState = IN_PATH; } + + // This is helper state that tracks if the parser is currently in a path dot-segment (".", "..") or not. + if (parseState == IN_PATH) { + if (next == '/') { + pathSubState = PATH_SEGMENT_START; + } else if (next == '.' && (pathSubState == PATH_SEGMENT_START || pathSubState == PATH_DOT_SEGMENT)) { + pathSubState = PATH_DOT_SEGMENT; + } else { + pathSubState = PATH_NON_DOT_SEGMENT; + } + } + stringBuilder.append(next); }
core/src/main/java/io/undertow/server/protocol/http/ParseState.java+1 −0 modified@@ -142,6 +142,7 @@ public void reset() { this.leftOver = 0; this.urlDecodeRequired = false; this.stringBuilder.setLength(0); + this.canonicalPath.setLength(0); this.nextHeader = null; this.nextQueryParam = null; this.mapCount = 0;
core/src/test/java/io/undertow/server/protocol/http/SimpleParserTestCase.java+39 −0 modified@@ -676,6 +676,45 @@ public void testNonEncodedAsciiCharactersExplicitlyAllowed() throws UnsupportedE Assert.assertEquals("/bÃ¥r", result.getRequestURI()); //not decoded } + @Test + public void testDirectoryTraversal() throws Exception { + byte[] in = "GET /path/..;/ HTTP/1.1\r\n\r\n".getBytes(); + ParseState context = new ParseState(10); + HttpServerExchange result = new HttpServerExchange(null); + HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); + Assert.assertEquals("/path/..;/", result.getRequestURI()); + Assert.assertEquals("/path/..;/", result.getRequestPath()); + Assert.assertEquals("/path/..;/", result.getRelativePath()); + Assert.assertEquals("", result.getQueryString()); + + in = "GET /path/../ HTTP/1.1\r\n\r\n".getBytes(); + context = new ParseState(10); + result = new HttpServerExchange(null); + HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); + Assert.assertEquals("/path/../", result.getRequestURI()); + Assert.assertEquals("/path/../", result.getRequestPath()); + Assert.assertEquals("/path/../", result.getRelativePath()); + Assert.assertEquals("", result.getQueryString()); + + in = "GET /path/..?/ HTTP/1.1\r\n\r\n".getBytes(); + context = new ParseState(10); + result = new HttpServerExchange(null); + HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); + Assert.assertEquals("/path/..", result.getRequestURI()); + Assert.assertEquals("/path/..", result.getRequestPath()); + Assert.assertEquals("/path/..", result.getRelativePath()); + Assert.assertEquals("/", result.getQueryString()); + + in = "GET /path/..~/ HTTP/1.1\r\n\r\n".getBytes(); + context = new ParseState(10); + result = new HttpServerExchange(null); + HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); + Assert.assertEquals("/path/..~/", result.getRequestURI()); + Assert.assertEquals("/path/..~/", result.getRequestPath()); + Assert.assertEquals("/path/..~/", result.getRelativePath()); + Assert.assertEquals("", result.getQueryString()); + } + private void runTest(final byte[] in) throws BadRequestException { runTest(in, "some value");
40bb3314f013[UNDERTOW-2339] CVE-2024-1459 Path segment "/..;" should not be treated as "/.."
3 files changed · +63 −0
core/src/main/java/io/undertow/server/protocol/http/HttpRequestParser.java+23 −0 modified@@ -372,6 +372,10 @@ private void handleStateful(ByteBuffer buffer, ParseState currentState, HttpServ private static final int IN_PATH = 4; private static final int HOST_DONE = 5; + private static final int PATH_SEGMENT_START = 0; + private static final int PATH_DOT_SEGMENT = 1; + private static final int PATH_NON_DOT_SEGMENT = 2; + /** * Parses a path value * @@ -387,6 +391,8 @@ final void handlePath(ByteBuffer buffer, ParseState state, HttpServerExchange ex int canonicalPathStart = state.pos; boolean urlDecodeRequired = state.urlDecodeRequired; + int pathSubState = 0; + while (buffer.hasRemaining()) { char next = (char) (buffer.get() & 0xFF); if(!allowUnescapedCharactersInUrl && !ALLOWED_TARGET_CHARACTER[next]) { @@ -410,6 +416,11 @@ final void handlePath(ByteBuffer buffer, ParseState state, HttpServerExchange ex state.urlDecodeRequired = urlDecodeRequired; // store at canonical path the partial path parsed up until here state.canonicalPath.append(stringBuilder.substring(canonicalPathStart)); + if (parseState == IN_PATH && pathSubState == PATH_DOT_SEGMENT) { + // Inside a dot-segment (".", ".."), we don't want to allow removal of the ';' character from + // the path. This is to avoid path traversal issues - "/..;" should not be treated as "/..". + state.canonicalPath.append(";"); + } state.stringBuilder.append(";"); // set position to end of path (possibly start of parameter name) state.pos = state.stringBuilder.length(); @@ -443,6 +454,18 @@ final void handlePath(ByteBuffer buffer, ParseState state, HttpServerExchange ex } else if (next == '/' && parseState != HOST_DONE) { parseState = IN_PATH; } + + // This is helper state that tracks if the parser is currently in a path dot-segment (".", "..") or not. + if (parseState == IN_PATH) { + if (next == '/') { + pathSubState = PATH_SEGMENT_START; + } else if (next == '.' && (pathSubState == PATH_SEGMENT_START || pathSubState == PATH_DOT_SEGMENT)) { + pathSubState = PATH_DOT_SEGMENT; + } else { + pathSubState = PATH_NON_DOT_SEGMENT; + } + } + stringBuilder.append(next); }
core/src/main/java/io/undertow/server/protocol/http/ParseState.java+1 −0 modified@@ -142,6 +142,7 @@ public void reset() { this.leftOver = 0; this.urlDecodeRequired = false; this.stringBuilder.setLength(0); + this.canonicalPath.setLength(0); this.nextHeader = null; this.nextQueryParam = null; this.mapCount = 0;
core/src/test/java/io/undertow/server/protocol/http/SimpleParserTestCase.java+39 −0 modified@@ -676,6 +676,45 @@ public void testNonEncodedAsciiCharactersExplicitlyAllowed() throws UnsupportedE Assert.assertEquals("/bÃ¥r", result.getRequestURI()); //not decoded } + @Test + public void testDirectoryTraversal() throws Exception { + byte[] in = "GET /path/..;/ HTTP/1.1\r\n\r\n".getBytes(); + ParseState context = new ParseState(10); + HttpServerExchange result = new HttpServerExchange(null); + HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); + Assert.assertEquals("/path/..;/", result.getRequestURI()); + Assert.assertEquals("/path/..;/", result.getRequestPath()); + Assert.assertEquals("/path/..;/", result.getRelativePath()); + Assert.assertEquals("", result.getQueryString()); + + in = "GET /path/../ HTTP/1.1\r\n\r\n".getBytes(); + context = new ParseState(10); + result = new HttpServerExchange(null); + HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); + Assert.assertEquals("/path/../", result.getRequestURI()); + Assert.assertEquals("/path/../", result.getRequestPath()); + Assert.assertEquals("/path/../", result.getRelativePath()); + Assert.assertEquals("", result.getQueryString()); + + in = "GET /path/..?/ HTTP/1.1\r\n\r\n".getBytes(); + context = new ParseState(10); + result = new HttpServerExchange(null); + HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); + Assert.assertEquals("/path/..", result.getRequestURI()); + Assert.assertEquals("/path/..", result.getRequestPath()); + Assert.assertEquals("/path/..", result.getRelativePath()); + Assert.assertEquals("/", result.getQueryString()); + + in = "GET /path/..~/ HTTP/1.1\r\n\r\n".getBytes(); + context = new ParseState(10); + result = new HttpServerExchange(null); + HttpRequestParser.instance(OptionMap.EMPTY).handle(ByteBuffer.wrap(in), context, result); + Assert.assertEquals("/path/..~/", result.getRequestURI()); + Assert.assertEquals("/path/..~/", result.getRequestPath()); + Assert.assertEquals("/path/..~/", result.getRelativePath()); + Assert.assertEquals("", result.getQueryString()); + } + private void runTest(final byte[] in) throws BadRequestException { runTest(in, "some value");
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
15- access.redhat.com/errata/RHSA-2024:1674ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2024:1675ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2024:1676ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2024:2763ghsavendor-advisoryx_refsource_REDHATWEB
- access.redhat.com/errata/RHSA-2024:2764ghsavendor-advisoryx_refsource_REDHATWEB
- github.com/advisories/GHSA-v76w-3ph8-vm66ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-1459ghsaADVISORY
- access.redhat.com/errata/RHSA-2024:1677ghsaWEB
- access.redhat.com/security/cve/CVE-2024-1459ghsavdb-entryx_refsource_REDHATWEB
- bugzilla.redhat.com/show_bug.cgighsaissue-trackingx_refsource_REDHATWEB
- github.com/undertow-io/undertow/commit/40bb3314f013247af8e222870bd5045ca8650c5cghsaWEB
- github.com/undertow-io/undertow/commit/54f3e4325425c472f5af5fc973e02df83d7a711aghsaWEB
- github.com/undertow-io/undertow/pull/1556ghsaWEB
- issues.redhat.com/browse/UNDERTOW-2339ghsaWEB
- security.netapp.com/advisory/ntap-20241122-0008ghsaWEB
News mentions
0No linked articles in our index yet.