Coordinated Disclosure Timeline

Summary

Partial path traversal allows to break out of expected folder and access another user’s mailbox.

Product

Apache James Server

Tested Version

v3.6.1

Linux

Details

Issue 1: Path traversal in MaildirStore.java (GHSL-2022-002)

validateWithinFolder validates if this.rootFolder path starts with the expected maildirRoot. getCanonicalPath transforms the path into a canonical form preventing such attack types as .. in path segments. It also gets rid of the terminating slash symbol in case of directory path.

public MaildirFolder validateWithinFolder(File maildirRoot) throws MailboxNotFoundException {
    try {
        if (!rootFolder.getCanonicalPath().startsWith(maildirRoot.getCanonicalPath())) {
            throw new MailboxNotFoundException(rootFolder.getCanonicalPath() + " jail breaks out of " + maildirRoot.getCanonicalPath());
        }
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
    return this;
}

If the result of maildirRoot.getCanonicalPath() is not slash terminated it allows for partial path traversal.
Consider "/var/store/maildir/domain/admin".startsWith("/var/store/maildir/domain/adm"). The check is bypassed although it is not the adm directory. The terminating slash may be removed in various places. On Linux println(new File("/var/")) returns /var, but println(new File("/var", "/")) - /var/, however println(new File("/var", "/").getCanonicalPath()) - /var.

The validateWithinFolder function is used for example in loadMailbox where it is checked if the mailboxPath is under expected user mail directory, which is typically in the following form ../var/store/maildir/%domain/%user.

MaildirFolder folder = new MaildirFolder(getFolderName(mailboxPath), mailboxPath, locker)
    .validateWithinFolder(getMaildirRoot())
    .validateWithinFolder(new File(userRoot(session.getUser())));

Impact

The partial path traversal allows the user named a to load mailboxes of all users whose name starts from a.

Issue 2: Path traversal in SieveFileRepository.java (GHSL-2022-003)

private void enforceRoot(File file) throws StorageException {
    try {
        if (!file.getCanonicalPath().startsWith(root.getCanonicalPath())) {
            throw new StorageException(new IllegalStateException("Path traversal attempted"));
        }
    } catch (IOException e) {
        throw new StorageException(e);
    }
}

enforceRoot similarly to validateWithinFolder doesn’t take into account that root may not be terminated with the slash even though it is hardcoded with it:

private static final String SIEVE_ROOT = FileSystem.FILE_PROTOCOL + "sieve/"

Impact

The partial path traversal allows allow one user to read scripts of the other.

CVE

Credit

These issues were discovered and reported by GHSL team member @JarLob (Jaroslav Lobačevski).

Contact

You can contact the GHSL team at securitylab@github.com, please include a reference to GHSL-2022-002 or GHSL-2022-003 in any communication regarding these issues.