Skip to content

path-traversal

A Path traversal vulnerability (AKA directory traversal) may allow an attack to access files and directories that are not normally authorized (especially outside of the scope of the Web App directory). Allowing user intput to be passed directly (or to partial influence the path to files) it may be possible to access arbitrary files and directories stored on file system including application source code or configuration and critical system files.

Of particular interest related to this type of vulnerability is to keep in mind that the attacker may use the “dot-dot-slash (../)” notation and its variations or by specify an absolute file path (starting with /), which may allow to escape of the current working directory.

Another variations of Path traversal can also occur in the context of archive decompression, where a user-provided archive can lead to files being written (or overwritten) outside of the intended target director. This can occur when the archive contains files that are specified using relative paths (i.e. dot-dot-slash ../). This could allow attacker to drop a Web shell or to modify the Web App and/or system configuration files. You can read more about the so-called "Zip slip" vulnerability class in this article.

Examples

Insecure Example

import flask
import json

app = flask.Flask(__name__)

@app.route("/view_file")
def view_file():
    filename = request.args.get("filename")
    return open(filename, "r").read()
import (
    "archive/zip"
    "fmt"
    "io"
    "log"
    "os"
    "path/filepath"
)

func unsafe_unzip(target string) error {

    reader, err := zip.OpenReader("example.zip")

    if err := os.MkdirAll(target, 0750); err != nil {
        return err
    }

    for _, file := range reader.File {
        // using the name of the archive file may lead to path traversal
        // Imagine example.zip contains a file with a path of `../../../../../etc/resolv.conf`
        path := filepath.Join(target, file.Name)
        if file.FileInfo().IsDir() {
            os.MkdirAll(path, file.Mode()) 
            continue
        }

        fileReader, err := file.Open()

        defer fileReader.Close()

        targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
        if err != nil {
            return err
        }
        defer targetFile.Close()

        if _, err := io.Copy(targetFile, fileReader); err != nil {
            return err
        }
    }

    return nil
}

Secure Example

import flask
import json

app = flask.Flask(__name__)

@app.route("/view_file")
def view_file():
    filename = request.args.get("filename")
    # Always prefer using an allowlist of files, rather than a blocklist of files (or overly simplistic regular expression)
    if filename in "allowed-file1.txt" or filename == "allowed-file2.txt":
        return open(filename, "r").read()
    else:
        return "Error - cannot read this file"
import (
    "archive/zip"
    "fmt"
    "io"
    "log"
    "os"
    "path/filepath"
)

func safe_unzip(target string) error {

    reader, err := zip.OpenReader("example.zip")

    if err := os.MkdirAll(target, 0750); err != nil {
        return err
    }

    for _, file := range reader.File {
        path := filepath.Join(target, file.Name) 

        // Detect relative paths that would "break out" of the target directory
        if !strings.HasPrefix(path, filepath.Clean(target) + string(os.PathSeparator)){
            return filenames, fmt.Errorf("%s is an illegal filepath", path)
        }

        if file.FileInfo().IsDir() {
            os.MkdirAll(path, file.Mode()) 
            continue
        }

        fileReader, err := file.Open()

        defer fileReader.Close()

        targetFile, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
        if err != nil {
            return err
        }
        defer targetFile.Close()

        if _, err := io.Copy(targetFile, fileReader); err != nil {
            return err
        }
    }

    return nil
}