Coordinated Disclosure Timeline

Summary

Treasure Data’s digdag workload automation system is susceptible to a path traversal vulnerability if it’s configured to store log files locally.

Project

digdag

Tested Version

v0.10.5

Details

Absolute Path Traversal (GHSL-2023-221)

Treasure Data’s digdag workload automation system was susceptible to a path traversal vulnerability if it’s configured to store log files locally. The problem stems from the fact that the filename param from the API method getFile:

@GET
@Produces("application/gzip")
@Path("/api/logs/{attempt_id}/files/{file_name}")
@ApiOperation("Download a log file")
public byte[] getFile(
        @ApiParam(value="attempt id", required=true)
        @PathParam("attempt_id") long attemptId,
        @ApiParam(value="log file name", required=true)
        @PathParam("file_name") String fileName)

flows into the getFile method of the configurable logserver instance (after performing access and sanity checks):

return tm.<byte[], ResourceNotFoundException, IOException, StorageFileNotFoundException, AccessControlException>begin(() -> {
    final LogFilePrefix prefix = getPrefix(attemptId, // NotFound, AccessControl
            (p, a) -> ac.checkGetLogFiles(
                    WorkflowTarget.of(getSiteId(), a.getSession().getWorkflowName(), p.getName()),
                    getAuthenticatedUser()));
    return logServer.getFile(prefix, fileName);
}, ResourceNotFoundException.class, IOException.class, StorageFileNotFoundException.class, AccessControlException.class);

If digdag is configured to store log files locally (e.g. whens started with the --task-log command line argument). A LocalFileLogServerFactory instance is used. The LocalFileLogServerFactory class defines getFile as follows:

@Override
protected byte[] getFile(String dateDir, String attemptDir, String fileName)
    throws StorageFileNotFoundException
{
    Path path = getPrefixDir(dateDir, attemptDir).resolve(fileName);
    try (InputStream in = Files.newInputStream(path)) {
        return ByteStreams.toByteArray(in);

Since the user-controlled fileName parameter flows largely unchecked into the resolve method of java.nio.file.Path an absolute path can be provided to the web application in an URL-encoded form such as %2fetc%2fpasswd. This method circumvents typical servlet filters that look for .. and similar in URLs.

The resolve(Path other) and resolve(String other) methods of java.nio.file.Path ignore the original path if other is an absolute Path.

This vulnerability was discovered with the help of CodeQL’s Uncontrolled data used in path expression query.

Impact

This issue may lead to Information Disclosure.

CVE

Credit

This issue was discovered and reported by GHSL team member @p- (Peter Stöckli).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2023-221 in any communication regarding this issue.