Vulnerability: Global.py in AIL framework 2.8 allows path traversal.
Vulnerability ID: CVE-2020-8545
CWE: CWE-22
Severity score: 2.9
Project: https://github.com/CIRCL/AIL-framework
Published: 2020-02-03
File: bin/Global.py
Commit message: fix: [Global: filename provided by all feeders] avoid path tranversal
Commit author: Terrtia

Vulnerable version (before fix):
 def rreplace(s, old, new, occurrence):      p = Process(config_section)      PASTES_FOLDER = os.path.join(os.environ['AIL_HOME'], p.config.get("Directories", "pastes"))     PASTES_FOLDERS = PASTES_FOLDER + '/'      # LOGGING #     publisher.info("Feed Script started to receive & publish.") def rreplace(s, old, new, occurrence):             time.sleep(1)             continue          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)          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 version (after fix):
 def rreplace(s, old, new, occurrence):      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.") def rreplace(s, old, new, occurrence):             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)          # 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

Patch:
patch: @@ -45,8 +45,10 @@ def rreplace(s, old, new, occurrence):
 
     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.")
@@ -75,40 +77,46 @@ def rreplace(s, old, new, occurrence):
             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
path: bin/Global.py