BUG_Author: Zy0t1
Affected Version: flow-ci / flow-core-x all versions (since 2019 to master branch, never patched)
Vendor: FlowCI/flow-core-x
Software: flow-ci
Vulnerability Files: - common/src/main/java/com/flowci/common/helper/FileHelper.java (L98-L112, unzip method)
CWE: CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ("Path Traversal") - The software uses external input to construct a pathname that is intended to identify a file or directory located beneath a restricted parent directory, but it does not properly neutralize special elements such as ../ sequences that can resolve to a location that is outside of that directory.
CVSS 3.1: 7.6 (High) - AV:N/AC:H/PR:L/UI:R/S:C/C:N/I:H/A:H
flow-ci's FileHelper.unzip() extracts ZIP archives using ZipInputStream and constructs output paths with Paths.get(destDir.toString(), entry.getName()). The ZIP entry name is attacker-controlled metadata and is never sanitized. There is no normalize() call, no rejection of .., and no check that the resolved path remains inside destDir. A ZIP entry such as ../../../../cve03_browser_escape.txt is therefore written outside the extraction directory.
The production trigger chain is:
POST /api/flow/{name}/job/{number}/report. The report metadata sets zipped: true.GET /jobs/{flow}/{buildNumber}/reports/{reportId}, ReportServiceImpl.fetch() passes the archive stream to FileHelper.unzip(stream, destFile).ZipEntry name is joined to the destination path without validation, allowing the archive entry to escape the report extraction directory.The vulnerable method is:
// common/src/main/java/com/flowci/common/helper/FileHelper.java L98-L112
public static void unzip(InputStream src, Path destDir) throws IOException {
try (ZipInputStream zipIn = new ZipInputStream(src)) {
ZipEntry entry = zipIn.getNextEntry();
while (entry != null) {
Path filePath = Paths.get(destDir.toString(), entry.getName());
if (!entry.isDirectory()) {
writeToFile(zipIn, filePath);
} else {
filePath.toFile().mkdirs();
}
zipIn.closeEntry();
entry = zipIn.getNextEntry();
}
}
}
http://localhost:2015..flowci.yml in the browser.