You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by bt...@apache.org on 2021/09/23 02:49:39 UTC

[james-project] branch 3.6.x updated: JAMES-3646 [3.6.1] Prevent directory traversal on top of maildir mailbox (#659)

This is an automated email from the ASF dual-hosted git repository.

btellier pushed a commit to branch 3.6.x
in repository https://gitbox.apache.org/repos/asf/james-project.git


The following commit(s) were added to refs/heads/3.6.x by this push:
     new c019c34  JAMES-3646 [3.6.1] Prevent directory traversal on top of maildir mailbox  (#659)
c019c34 is described below

commit c019c349f9cb2dc6e767e71ea26cb1789ec482f9
Author: Benoit TELLIER <bt...@linagora.com>
AuthorDate: Thu Sep 23 09:49:35 2021 +0700

    JAMES-3646 [3.6.1] Prevent directory traversal on top of maildir mailbox  (#659)
---
 .../james/mailbox/maildir/MaildirFolder.java       | 12 ++++
 .../apache/james/mailbox/maildir/MaildirStore.java | 40 +++++++++++--
 .../mailbox/maildir/mail/MaildirMailboxMapper.java |  4 +-
 .../mailbox/maildir/DirectoryTraversalTest.java    | 65 ++++++++++++++++++++++
 4 files changed, 113 insertions(+), 8 deletions(-)

diff --git a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirFolder.java b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirFolder.java
index e4f56bb..455f053 100644
--- a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirFolder.java
+++ b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirFolder.java
@@ -49,6 +49,7 @@ import org.apache.james.mailbox.MailboxSession;
 import org.apache.james.mailbox.MessageUid;
 import org.apache.james.mailbox.ModSeq;
 import org.apache.james.mailbox.exception.MailboxException;
+import org.apache.james.mailbox.exception.MailboxNotFoundException;
 import org.apache.james.mailbox.model.MailboxACL;
 import org.apache.james.mailbox.model.MailboxACL.EntryKey;
 import org.apache.james.mailbox.model.MailboxACL.Rfc4314Rights;
@@ -104,6 +105,17 @@ public class MaildirFolder {
         this.lastUid = Optional.empty();
     }
 
+    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;
+    }
+
     private MaildirMessageName newMaildirMessageName(MaildirFolder folder, String fullName) {
         MaildirMessageName mdn = new MaildirMessageName(folder, fullName);
         mdn.setMessageNameStrictParse(isMessageNameStrictParse());
diff --git a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirStore.java b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirStore.java
index 18a81c7..1713b5d 100644
--- a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirStore.java
+++ b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/MaildirStore.java
@@ -83,8 +83,10 @@ public class MaildirStore implements UidProvider, ModSeqProvider {
      *
      * @return The MaildirFolder
      */
-    public MaildirFolder createMaildirFolder(Mailbox mailbox) {
-        MaildirFolder mf = new MaildirFolder(getFolderName(mailbox), mailbox.generateAssociatedPath(), locker);
+    public MaildirFolder createMaildirFolder(Mailbox mailbox) throws MailboxNotFoundException {
+        MaildirFolder mf = new MaildirFolder(getFolderName(mailbox), mailbox.generateAssociatedPath(), locker)
+            .validateWithinFolder(getMaildirRoot())
+            .validateWithinFolder(new File(userRoot(mailbox.getUser())));
         mf.setMessageNameStrictParse(isMessageNameStrictParse());
         return mf;
     }
@@ -110,9 +112,10 @@ public class MaildirStore implements UidProvider, ModSeqProvider {
      * @throws MailboxNotFoundException If the mailbox folder doesn't exist
      * @throws MailboxException If the mailbox folder can't be read
      */
-    public Mailbox loadMailbox(MailboxSession session, MailboxPath mailboxPath)
-    throws MailboxNotFoundException, MailboxException {
-        MaildirFolder folder = new MaildirFolder(getFolderName(mailboxPath), mailboxPath, locker);
+    public Mailbox loadMailbox(MailboxSession session, MailboxPath mailboxPath) throws MailboxNotFoundException, MailboxException {
+        MaildirFolder folder = new MaildirFolder(getFolderName(mailboxPath), mailboxPath, locker)
+            .validateWithinFolder(getMaildirRoot())
+            .validateWithinFolder(new File(userRoot(session.getUser())));
         folder.setMessageNameStrictParse(isMessageNameStrictParse());
         if (!folder.exists()) {
             throw new MailboxNotFoundException(mailboxPath);
@@ -120,6 +123,16 @@ public class MaildirStore implements UidProvider, ModSeqProvider {
         return loadMailbox(session, folder.getRootFile(), mailboxPath);
     }
 
+    public Mailbox loadMailboxNoUserCheck(MailboxSession session, MailboxPath mailboxPath) throws MailboxNotFoundException, MailboxException {
+        MaildirFolder folder = new MaildirFolder(getFolderName(mailboxPath), mailboxPath, locker)
+            .validateWithinFolder(getMaildirRoot());
+        folder.setMessageNameStrictParse(isMessageNameStrictParse());
+        if (!folder.exists()) {
+            throw new MailboxNotFoundException(mailboxPath);
+        }
+        return loadMailboxNoChecks(session, folder.getRootFile(), mailboxPath);
+    }
+
     /**
      * Creates a Mailbox object with data loaded from the file system
      * @param mailboxFile File object referencing the folder for the mailbox
@@ -128,7 +141,22 @@ public class MaildirStore implements UidProvider, ModSeqProvider {
      * @throws MailboxException If the mailbox folder doesn't exist or can't be read
      */
     private Mailbox loadMailbox(MailboxSession session, File mailboxFile, MailboxPath mailboxPath) throws MailboxException {
-        MaildirFolder folder = new MaildirFolder(mailboxFile.getAbsolutePath(), mailboxPath, locker);
+        MaildirFolder folder = new MaildirFolder(mailboxFile.getAbsolutePath(), mailboxPath, locker)
+            .validateWithinFolder(getMaildirRoot())
+            .validateWithinFolder(new File(userRoot(session.getUser())));
+        folder.setMessageNameStrictParse(isMessageNameStrictParse());
+        try {
+            Mailbox loadedMailbox = new Mailbox(mailboxPath, folder.getUidValidity(), folder.readMailboxId());
+            loadedMailbox.setACL(folder.getACL());
+            return loadedMailbox;
+        } catch (IOException e) {
+            throw new MailboxException("Unable to load Mailbox " + mailboxPath, e);
+        }
+    }
+
+    private Mailbox loadMailboxNoChecks(MailboxSession session, File mailboxFile, MailboxPath mailboxPath) throws MailboxException {
+        MaildirFolder folder = new MaildirFolder(mailboxFile.getAbsolutePath(), mailboxPath, locker)
+            .validateWithinFolder(getMaildirRoot());
         folder.setMessageNameStrictParse(isMessageNameStrictParse());
         try {
             Mailbox loadedMailbox = new Mailbox(mailboxPath, folder.getUidValidity(), folder.readMailboxId());
diff --git a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMailboxMapper.java b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMailboxMapper.java
index edadc5c..80668cd 100644
--- a/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMailboxMapper.java
+++ b/mailbox/maildir/src/main/java/org/apache/james/mailbox/maildir/mail/MaildirMailboxMapper.java
@@ -290,7 +290,7 @@ public class MaildirMailboxMapper extends NonTransactionalMapper implements Mail
             MailboxPath inboxMailboxPath = MailboxPath.forUser(Username.of(userName), MailboxConstants.INBOX);
 
             try {
-                mailboxList.add(maildirStore.loadMailbox(session, inboxMailboxPath));
+                mailboxList.add(maildirStore.loadMailboxNoUserCheck(session, inboxMailboxPath));
             } catch (MailboxException e) {
                 //do nothing, we should still be able to list the mailboxes even if INBOX does not exist
             }
@@ -301,7 +301,7 @@ public class MaildirMailboxMapper extends NonTransactionalMapper implements Mail
             for (File mailbox: mailboxes) {
                 MailboxPath mailboxPath = MailboxPath.forUser(Username.of(userName),
                     mailbox.getName().substring(1));
-                mailboxList.add(maildirStore.loadMailbox(session, mailboxPath));
+                mailboxList.add(maildirStore.loadMailboxNoUserCheck(session, mailboxPath));
             }
         }
 
diff --git a/mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/DirectoryTraversalTest.java b/mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/DirectoryTraversalTest.java
new file mode 100644
index 0000000..d8dfeb3
--- /dev/null
+++ b/mailbox/maildir/src/test/java/org/apache/james/mailbox/maildir/DirectoryTraversalTest.java
@@ -0,0 +1,65 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+package org.apache.james.mailbox.maildir;
+
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+import java.io.File;
+import java.util.UUID;
+
+import org.apache.james.core.Username;
+import org.apache.james.mailbox.MailboxSession;
+import org.apache.james.mailbox.model.MailboxPath;
+import org.apache.james.mailbox.store.StoreMailboxManager;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+class DirectoryTraversalTest {
+    StoreMailboxManager mailboxManager;
+
+    @BeforeEach
+    void setUp() {
+        mailboxManager = MaildirMailboxManagerProvider.createMailboxManager("/%fulluser",
+            new File(System.getProperty("java.io.tmpdir") + "/" + UUID.randomUUID()));
+    }
+
+    @Test
+    void directoryTraversalUsingUsernameFails() {
+        MailboxSession session = mailboxManager.createSystemSession(Username.of("../bob"));
+
+        assertThatThrownBy(() -> mailboxManager.createMailbox(MailboxPath.inbox(session), session))
+            .hasMessageContaining("jail breaks out of");
+    }
+
+    @Test
+    void directoryTraversalUsingMailboxName1Fails() {
+        MailboxSession session = mailboxManager.createSystemSession(Username.of("bob"));
+
+        assertThatThrownBy(() -> mailboxManager.createMailbox(MailboxPath.forUser(session.getUser(), "./alice/box2"), session))
+            .isNotNull();
+    }
+
+    @Test
+    void directoryTraversalUsingMailboxName2Fails() {
+        MailboxSession session = mailboxManager.createSystemSession(Username.of("bob"));
+
+        assertThatThrownBy(() -> mailboxManager.createMailbox(MailboxPath.forUser(session.getUser(), "alice/../box2"), session))
+            .isNotNull();
+    }
+}

---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org