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
- Do not use user input directly as a file path.
- Combine it with a base directory, normalize the result, and verify that it remains within that directory.
- 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.