CVE-2020-8545: Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') (CWE-22)
value: Global.py in AIL framework 2.8 allows path traversal.
CWE description: The product uses external input to construct a pathname that is intended to identify a file or directory that is located underneath a restricted parent directory, but the product does not properly neutralize special elements within the pathname that can cause the pathname to resolve to a location that is outside of the restricted directory.
CVSS v3 base score: 7.5
Published: 2020-02-03T16:15Z
Repository: https://github.com/CIRCL/AIL-framework
Language: Python
Files changed: bin/Global.py
Commit date: 2020-02-03 10:32:20 +0100
Commit message: fix: [Global: filename provided by all feeders] avoid path tranversal

Diff: diff --git a/bin/Global.py b/bin/Global.py
index 00207c6..b0419be 100755
--- a/bin/Global.py
+++ b/bin/Global.py
@@ -43,12 +43,14 @@ if __name__ == '__main__':
 
     config_section = 'Global'
 
     p = Process(config_section)
 
+    # get and sanityze PASTE DIRECTORY
     PASTES_FOLDER = os.path.join(os.environ['AIL_HOME'], p.config.get("Directories", "pastes"))
     PASTES_FOLDERS = PASTES_FOLDER + '/'
+    PASTES_FOLDERS = os.path.join(os.path.realpath(PASTES_FOLDERS), '')
 
     # LOGGING #
     publisher.info("Feed Script started to receive & publish.")
 
     while True:
@@ -73,42 +75,48 @@ if __name__ == '__main__':
                 time_1 = time.time()
                 processed_paste = 0
             time.sleep(1)
             continue
 
+        # remove PASTES_FOLDER from item path (crawled item + submited)
+        if PASTES_FOLDERS in paste:
+            paste = paste.replace(PASTES_FOLDERS, '', 1)
+
         file_name_paste = paste.split('/')[-1]
         if len(file_name_paste)>255:
             new_file_name_paste = '{}{}.gz'.format(file_name_paste[:215], str(uuid.uuid4()))
             paste = rreplace(paste, file_name_paste, new_file_name_paste, 1)
 
         # Creating the full filepath
         filename = os.path.join(PASTES_FOLDER, paste)
+        filename = os.path.realpath(filename)
 
-        dirname = os.path.dirname(filename)
-        if not os.path.exists(dirname):
-            os.makedirs(dirname)
-
-        decoded = base64.standard_b64decode(gzip64encoded)
+        # incorrect filename
+        if not os.path.commonprefix([filename, PASTES_FOLDER]) == PASTES_FOLDER:
+            print('Path traversal detected {}'.format(filename))
+            publisher.warning('Global; Path traversal detected')
+        else:
+            dirname = os.path.dirname(filename)
+            if not os.path.exists(dirname):
+                os.makedirs(dirname)
 
-        with open(filename, 'wb') as f:
-            f.write(decoded)
-        '''try:
-            decoded2 = gunzip_bytes_obj(decoded)
-        except:
-            decoded2 =''
+            decoded = base64.standard_b64decode(gzip64encoded)
 
-        type = magic.from_buffer(decoded2, mime=True)
+            with open(filename, 'wb') as f:
+                f.write(decoded)
+            '''try:
+                decoded2 = gunzip_bytes_obj(decoded)
+            except:
+                decoded2 =''
 
-        if type!= 'text/x-c++' and type!= 'text/html' and type!= 'text/x-c' and type!= 'text/x-python' and type!= 'text/x-php' and type!= 'application/xml' and type!= 'text/x-shellscript' and type!= 'text/plain' and type!= 'text/x-diff' and type!= 'text/x-ruby':
+            type = magic.from_buffer(decoded2, mime=True)
 
-            print('-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------')
-            print(filename)
-            print(type)
-            print('-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------')
-        '''
+            if type!= 'text/x-c++' and type!= 'text/html' and type!= 'text/x-c' and type!= 'text/x-python' and type!= 'text/x-php' and type!= 'application/xml' and type!= 'text/x-shellscript' and type!= 'text/plain' and type!= 'text/x-diff' and type!= 'text/x-ruby':
 
-        # remove PASTES_FOLDER from item path (crawled item + submited)
-        if PASTES_FOLDERS in paste:
-            paste = paste.replace(PASTES_FOLDERS, '', 1)
+                print('-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------')
+                print(filename)
+                print(type)
+                print('-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------')
+            '''
 
-        p.populate_set_out(paste)
-        processed_paste+=1
+            p.populate_set_out(paste)
+            processed_paste+=1

Vulnerable code:         dirname = os.path.dirname(filename)
        if not os.path.exists(dirname):
            os.makedirs(dirname)

        decoded = base64.standard_b64decode(gzip64encoded)
        with open(filename, 'wb') as f:
            f.write(decoded)
        '''try:
            decoded2 = gunzip_bytes_obj(decoded)
        except:
            decoded2 =''
        type = magic.from_buffer(decoded2, mime=True)
        if type!= 'text/x-c++' and type!= 'text/html' and type!= 'text/x-c' and type!= 'text/x-python' and type!= 'text/x-php' and type!= 'application/xml' and type!= 'text/x-shellscript' and type!= 'text/plain' and type!= 'text/x-diff' and type!= 'text/x-ruby':
            print('-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------')
            print(filename)
            print(type)
            print('-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------')
        '''
        # remove PASTES_FOLDER from item path (crawled item + submited)
        if PASTES_FOLDERS in paste:
            paste = paste.replace(PASTES_FOLDERS, '', 1)
        p.populate_set_out(paste)
        processed_paste+=1
Fixed code:     # get and sanityze PASTE DIRECTORY
    PASTES_FOLDERS = os.path.join(os.path.realpath(PASTES_FOLDERS), '')
        # remove PASTES_FOLDER from item path (crawled item + submited)
        if PASTES_FOLDERS in paste:
            paste = paste.replace(PASTES_FOLDERS, '', 1)

        filename = os.path.realpath(filename)
        # incorrect filename
        if not os.path.commonprefix([filename, PASTES_FOLDER]) == PASTES_FOLDER:
            print('Path traversal detected {}'.format(filename))
            publisher.warning('Global; Path traversal detected')
        else:
            dirname = os.path.dirname(filename)
            if not os.path.exists(dirname):
                os.makedirs(dirname)
            decoded = base64.standard_b64decode(gzip64encoded)
            with open(filename, 'wb') as f:
                f.write(decoded)
            '''try:
                decoded2 = gunzip_bytes_obj(decoded)
            except:
                decoded2 =''
            type = magic.from_buffer(decoded2, mime=True)
            if type!= 'text/x-c++' and type!= 'text/html' and type!= 'text/x-c' and type!= 'text/x-python' and type!= 'text/x-php' and type!= 'application/xml' and type!= 'text/x-shellscript' and type!= 'text/plain' and type!= 'text/x-diff' and type!= 'text/x-ruby':
                print('-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------')
                print(filename)
                print(type)
                print('-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------')
            '''
            p.populate_set_out(paste)
            processed_paste+=1