CVE-2024-42499
Description
Path traversal in FitNesse before 20241026 allows attackers to check file existence and, under specific conditions, retrieve partial file contents.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Path traversal in FitNesse before 20241026 allows attackers to check file existence and, under specific conditions, retrieve partial file contents.
Vulnerability
Overview FitNesse releases prior to 20241026 contain a path traversal vulnerability in the HistoryComparerResponder component. The application fails to properly restrict user-supplied file paths, enabling traversal outside the intended TestHistory directory [1]. This issue is classified as CWE-22 (Path Traversal).
Exploitation
Details An attacker can exploit this flaw by sending HTTP requests with specially crafted file paths containing '../' sequences to the FitNesse wiki server. No prior authentication is required, making the attack remotely exploitable. The vulnerability allows an attacker to confirm the existence of arbitrary files on the server and, under specific conditions, read portions of those files' contents [1][4].
Impact
Successful exploitation could lead to information disclosure. Attackers may enumerate system files, obtain configuration details, or leak sensitive data stored on the server. The exact impact depends on the security context of the FitNesse instance and the files accessible [1].
Mitigation
The vulnerability is fixed in FitNesse version 20241026 and later. Users should upgrade to the latest release available on the official download page or GitHub repository [3]. No workarounds have been documented [4].
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 |
|---|---|---|
org.fitnesse:fitnesseMaven | < 20241026 | 20241026 |
Affected products
2Patches
2c753e66ba162850c599e5177Fix file traversal issue
2 files changed · +51 −18
src/fitnesse/responders/testHistory/HistoryComparerResponder.java+19 −4 modified@@ -9,14 +9,17 @@ import fitnesse.http.SimpleResponse; import fitnesse.reporting.history.PageHistory; import fitnesse.responders.ErrorResponder; +import fitnesse.util.StringUtils; import fitnesse.wiki.PageCrawler; import fitnesse.wiki.PageData; import fitnesse.wiki.PathParser; import fitnesse.wiki.WikiPage; import fitnesse.wiki.WikiPagePath; import java.io.File; +import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.nio.file.Path; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; @@ -72,8 +75,10 @@ private Response makeResponseFromComparison(FitNesseContext context, } private boolean filesExist() { - return ((new File(firstFilePath)).exists()) - || ((new File(secondFilePath)).exists()); + if (StringUtils.isBlank(firstFileName) || StringUtils.isBlank(secondFileName)) { + return false; + } + return (new File(firstFilePath)).exists() || (new File(secondFilePath)).exists(); } private void initializeReponseComponents() { @@ -82,8 +87,18 @@ private void initializeReponseComponents() { } private String composeFileName(Request request, String fileName) { - return context.getTestHistoryDirectory().getPath() + File.separator - + request.getResource() + File.separator + fileName; + try { + String basePath = context.getTestHistoryDirectory().getPath(); + String absoluteBase = new File(basePath).getCanonicalPath() + File.separator; + Path filePath = Path.of(basePath, request.getResource(), fileName); + String absoluteFile = filePath.toFile().getCanonicalPath(); + if (absoluteFile.startsWith(absoluteBase)) { + return filePath.toString(); + } + } catch (IOException e) { + // ignore we will just return empty string + } + return ""; } private boolean getFileNameFromRequest(Request request) {
test/fitnesse/responders/testHistory/HistoryComparerResponderTest.java+32 −14 modified@@ -1,24 +1,25 @@ package fitnesse.responders.testHistory; -import static org.junit.Assert.assertEquals; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static util.RegexTestCase.assertHasRegexp; +import fitnesse.FitNesseContext; +import fitnesse.http.MockRequest; +import fitnesse.http.SimpleResponse; +import fitnesse.testutil.FitNesseUtil; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import util.FileUtil; import java.io.File; import java.io.IOException; import java.util.ArrayList; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; - -import util.FileUtil; -import fitnesse.FitNesseContext; -import fitnesse.http.MockRequest; -import fitnesse.http.SimpleResponse; -import fitnesse.testutil.FitNesseUtil; +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static util.RegexTestCase.assertHasRegexp; public class HistoryComparerResponderTest { public HistoryComparerResponder responder; @@ -96,11 +97,26 @@ public void shouldReturnErrorPageIfFilesAreInvalid() throws Exception { request.addInput("TestResult_firstFile", ""); request.addInput("TestResult_secondFile", ""); request.setResource("TestFolder"); + SimpleResponse response = (SimpleResponse) responder.makeResponse(context, + request); + assertEquals(400, response.getStatus()); + assertHasRegexp("Compare Failed because the files were not found.", + response.getContent()); + verify(mockedComparer, never()).compare(anyString(), anyString()); + } + + @Test + public void shouldReturnErrorPageIfFilesAreNotInTestHistory() throws Exception { + request = new MockRequest(); + request.addInput("TestResult_../../../../../../../../../etc/passwd", ""); + request.addInput("TestResult_../../../../../../../../../../etc/passwd", ""); + request.setResource("TestFolder"); SimpleResponse response = (SimpleResponse) responder.makeResponse(context, request); assertEquals(400, response.getStatus()); assertHasRegexp("Compare Failed because the files were not found.", response.getContent()); + verify(mockedComparer, never()).compare(anyString(), anyString()); } @Test @@ -115,6 +131,7 @@ public void shouldReturnErrorPageIfThereAreTooFewInputFiles() assertHasRegexp( "Compare Failed because the wrong number of Input Files were given. Select two please.", response.getContent()); + verify(mockedComparer, never()).compare(anyString(), anyString()); } @Test @@ -127,6 +144,7 @@ public void shouldReturnErrorPageIfThereAreTooManyInputFiles() assertHasRegexp( "Compare Failed because the wrong number of Input Files were given. Select two please.", response.getContent()); + verify(mockedComparer, never()).compare(anyString(), anyString()); } @Test
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
7- github.com/advisories/GHSA-q297-5ff8-hc92ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-42499ghsaADVISORY
- fitnesse.org/FitNesseDownloadnvdWEB
- github.com/unclebob/fitnesse/commit/850c599e5177d6f877af063374086f3e36b4b956ghsaWEB
- github.com/unclebob/fitnesse/releases/tag/20241026nvdWEB
- jvn.jp/en/jp/JVN36791327ghsaWEB
- jvn.jp/en/jp/JVN36791327/nvd
News mentions
0No linked articles in our index yet.