🗄️ Unzip a file in Go
Table of Contents
To unzip a compressed archive in Go using the standard library, you need to use archive/zip package and open the file with the zip.OpenReader(name string) function. Extracting a ZIP file in this way involves iterating through all the archive files. For each of them, we create a new empty file or directory in the target path and then uncompress its byte content there.
You can also check our example on how to zip a file in Go
| |
How it works #
Open the zip file #
| |
To unzip the file, first, open it with the zip.OpenReader(name string) function. As always, when working with files, remember to close it if you no longer need it, using the ReadCloser.Close() method in this case.
Get the absolute destination path #
| |
Convert our relative destination path to the absolute representation, which will be needed in the step of Zip Slip vulnerability checking.
Iterate over zip files inside the archive and unzip each of them #
| |
The actual process of unzipping files in Go using archive/zip is to iterate through the files of the opened ZIP file and unpack each one individually to its final destination.
Check if file paths are not vulnerable to Zip Slip #
| |
The first step of an individual file unzipping function is to check whether the path of this file does not make use of the Zip Slip vulnerability, which was discovered in 2018 and affected thousands of projects. With a specially crafted archive that holds directory traversal filenames, e.g., ../../evil.sh, an attacker can gain access to parts of the file system outside of the target folder in which the unzipped files should reside. The attacker can then overwrite executable files and other sensitive resources, causing significant damage to the victim machine.
To detect this vulnerability, prepare the target file path by combining the destination and the name of the file inside the ZIP archive. It can be done using filepath.Join() function. Then we check if this final file path contains our destination path as a prefix. If not, the file may be trying to access the part of the file system other than the destination and should be rejected.
For example, when we want to unzip our file into the /a/b/ directory:
err := unzipSource("testFolder.zip", "/a/b")
if err != nil {
log.Fatal(err)
}
and in the archive there is a file with a name ../../../../evil.sh, then the output of
filepath.Join("/a/b", "../../../../evil.sh")
is
/evil.sh
In this way, the attacker can unzip the evil.sh file in the root directory /, which should not be allowed with our check.
Create a directory tree #
| |
For each file or directory in the ZIP archive, we need to create a corresponding directory in the destination path, so that the resulting directory tree of the extracted files matches the directory tree inside the ZIP. We use os.MkdirAll() function to do this. For directories, we create the corresponding folder in the destination path, and for files, we create the base directory of the file. Note that we return from the function when the file is a directory as only files need to be unzipped, which we will do in the next steps.
Create a destination file for unzipped content #
| |
Before uncompressing a ZIP archive file, we need to create a target file where the extracted content could be saved. Since the mode of this target file should match the mode of the file inside the archive, we use os.OpenFile() function, where we can set the mode as an argument.
Unzip the content of a file and copy it to the destination file #
| |
In the last step, we open an individual ZIP file and copy its content to the file created in the previous step. Opening with zip.File.Open() gives access to the uncompressed data of the archive file while copying.