Path injection in Swift

Path injection in Swift

Description

Using user input directly as a file path can let an attacker use path components such as ../ to read or modify files outside the allowed directory.

Potential impact

  • Reading sensitive files.
  • Deleting or overwriting arbitrary files.
  • Exposing data within the application's sandbox.

Remediation

  1. Do not use user input directly as a file path.
  2. Combine it with a base directory, normalize the result, and verify that it remains within that directory.
  3. Restrict filenames to an allow-list of characters and file extensions.

Examples

Before

swift
let fileName = readLine()!
let contents = try String(contentsOfFile: fileName)

After

swift
import Foundation

enum PathValidationError: Error {
    case invalidFileName
    case outsideBaseDirectory
}

func readUpload(fileName: String, baseDirectory: URL) throws -> String {
    guard !fileName.isEmpty,
          fileName == URL(fileURLWithPath: fileName).lastPathComponent else {
        throw PathValidationError.invalidFileName
    }

    // baseDirectory는 앱만 쓸 수 있는 디렉터리여야 합니다.
    let base = baseDirectory
        .resolvingSymlinksInPath()
        .standardizedFileURL
    let candidate = base
        .appendingPathComponent(fileName, isDirectory: false)
        .resolvingSymlinksInPath()
        .standardizedFileURL

    guard candidate.deletingLastPathComponent() == base else {
        throw PathValidationError.outsideBaseDirectory
    }

    return try String(contentsOf: candidate, encoding: .utf8)
}

Explanation:

  • Before: User input controls the entire file path.
  • After: The example accepts only a single filename, resolves symbolic links in the base directory and candidate path, and reads the file only when its actual parent is the base directory. As assumed in the example, only the application must be able to modify the base directory. Prevent an attacker from replacing the path after validation.

References