Insecure Temporary File
ID |
swift.insecure_temporary_file |
Severity |
high |
Resource |
Api |
Language |
Swift |
Tags |
CWE:377, MASVS:STORAGE-1, NIST.SP.800-53, PCI-DSS:6.5.6 |
Description
Insecure temporary file creation can allow unauthorized access to sensitive information or unexpected code execution.
In iOS, macOS, and server-side Swift applications, insecure temporary file creation can occur through:
-
Use of legacy C functions (
mktemp,tmpnam,tmpfile,tempnam) via Swift’s C interop - these are always insecure. -
File operations in hardcoded temp directories (
/tmp,/var/tmp,/private/tmp) with predictable filenames. -
Creating temp files with predictable names (non-UUID) in any temporary directory.
FileManager.default.temporaryDirectory and NSTemporaryDirectory() are NOT flagged on their own.
These are recommended APIs when used correctly with UUID-based filenames and atomic operations, under platforms with app sandbox.
The detector only flags file operations that use predictable filenames.
|
Rationale
Insecure temporary file creation in Swift exposes applications to several attack vectors:
The following example demonstrates different patterns:
-
a classic race condition or Time-of-check to Time-of-use (TOC-TOU) vulnerability using the deprecated
mktemp()C function:
import Foundation
// VULNERABLE: Using deprecated mktemp() function
func createTempConfig() {
var template = "/tmp/myapp_config_XXXXXX"
// ISSUE: mktemp() creates predictable filename without creating the file
// Window for race condition between name generation and file creation
template.withCString { cString in
let result = mktemp(UnsafeMutablePointer(mutating: cString))
if let path = String(cString: result, encoding: .utf8) {
// ISSUE: Attacker can create file or symlink at this path
// before the application creates it
let data = "sensitive config".data(using: .utf8)!
try? data.write(to: URL(fileURLWithPath: path))
}
}
}
-
Creating a temporary file with predictable name using
NSTemporaryDirectoryandFileManager.createFile()allow attackers to perform a symlink attack leveraging the race condition between path construction and file creation:
import Foundation
func createLogFile(sessionId: String) {
let tempDir = NSTemporaryDirectory()
// ISSUE: Predictable filename allows attacker to create symlink
let logPath = tempDir + "session_\(sessionId).log"
// Race condition: attacker can create symlink between path construction
// and file creation, causing app to write to unintended location
FileManager.default.createFile(atPath: logPath, contents: nil)
}
-
Direct predictable file creation in shared
/tmp(other processes have access):
import Foundation
func storeTemporaryData(userId: String, data: Data) {
// ISSUE: Hardcoded /tmp directory with predictable name
let tmpPath = "/tmp/user_\(userId)_data.tmp"
// Attacker can predict filename and perform symlink attack
try? data.write(to: URL(fileURLWithPath: tmpPath))
}
Attack Scenarios
-
Symlink Attack: Attacker creates a symbolic link at the predicted temp file location, causing the application to write sensitive data to an attacker-controlled location (e.g., replacing system files or stealing data)
-
File Replacement Attack: Attacker creates a file at the predicted location before the application, potentially injecting malicious content that the application will read
-
Information Disclosure: Temp files created in shared directories like
/tmpmay have overly permissive access rights, allowing other users or processes to read sensitive data -
Denial of Service: Attacker exhausts temp file creation by occupying predicted filenames
Remediation
To fix insecure temporary file handling in Swift, use app sandbox directories with secure naming and atomic operations.
-
Option 1: Use App Sandbox Directories with UUID-based Names. Alternatives follow:
import Foundation
class SecureFileHandler {
// FIXED: Use app's sandbox temporary directory with UUID
func createTempConfig() throws -> URL {
let tmpDir = FileManager.default.temporaryDirectory
// Use UUID for unpredictable filename
let uniqueFilename = UUID().uuidString + "_config.tmp"
let fileURL = tmpDir.appendingPathComponent(uniqueFilename)
// Create file with restricted permissions
let data = "sensitive config".data(using: .utf8)!
try data.write(to: fileURL, options: [.atomic, .completeFileProtection])
return fileURL
}
// FIXED: Use app's Cache directory for session logs
func createLogFile() throws -> URL {
let cacheDir = try FileManager.default.url(
for: .cachesDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
// Use UUID instead of predictable session ID
let logFile = cacheDir.appendingPathComponent("\(UUID().uuidString).log")
// Create with atomic write
FileManager.default.createFile(
atPath: logFile.path,
contents: nil,
attributes: [.posixPermissions: 0o600] // Owner read/write only
)
return logFile
}
// FIXED: Use app's temporary directory with proper isolation
func storeTemporaryData(data: Data) throws -> URL {
let tmpDir = FileManager.default.temporaryDirectory
// Create unique subdirectory for this session
let sessionDir = tmpDir.appendingPathComponent(UUID().uuidString, isDirectory: true)
try FileManager.default.createDirectory(
at: sessionDir,
withIntermediateDirectories: true,
attributes: [.posixPermissions: 0o700] // Owner-only access
)
// Create temp file with atomic write
let tempFile = sessionDir.appendingPathComponent(UUID().uuidString + ".tmp")
try data.write(to: tempFile, options: [.atomic, .completeFileProtection])
return tempFile
}
}
-
Option 2: Use FileManager’s recommended directories.
import Foundation
class SecureFileManager {
// Use appropriate directory based on data persistence requirements
func createPersistentTempFile() throws -> URL {
// For data that should survive app termination
let documentsDir = try FileManager.default.url(
for: .documentDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
let filename = UUID().uuidString + ".tmp"
return documentsDir.appendingPathComponent(filename)
}
func createCacheFile() throws -> URL {
// For temporary data that can be deleted by the system
let cacheDir = try FileManager.default.url(
for: .cachesDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
let filename = UUID().uuidString + ".cache"
return cacheDir.appendingPathComponent(filename)
}
func createApplicationSupportFile() throws -> URL {
// For app support files
let appSupportDir = try FileManager.default.url(
for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil,
create: true
)
let filename = UUID().uuidString + ".dat"
return appSupportDir.appendingPathComponent(filename)
}
}
Key Security Practices:
-
Never use legacy C functions: Avoid
mktemp(),tmpnam(),tmpfile()ortempnam(). -
Avoid shared directories: Don’t use
/tmp,/var/tmpdirectly - use app sandbox directories. -
Use unpredictable names: Always use
UUID().uuidStringfor temp file names. -
Set restrictive permissions: Use
.posixPermissions: 0o600for sensitive files (owner read/write only) -
Use atomic writes: Apply
.atomicoption to prevent partial writes during race conditions -
Apply file protection: Use
.completeFileProtectionfor sensitive data on iOS -
Clean up temp files: Always delete temp files when no longer needed
-
Use appropriate sandbox directory:
-
temporaryDirectory: For true temporary files (system may delete) -
cachesDirectory: For cache data (system may delete under storage pressure) -
documentDirectory: For user-visible documents -
applicationSupportDirectory: For app support files-
iOS-Specific Considerations
-
-
On iOS, apps run in a sandbox with isolated directories:
// iOS app directories are isolated per-app
let tmpDir = FileManager.default.temporaryDirectory
// Returns: /var/mobile/Containers/Data/Application/<UUID>/tmp/
// This is already isolated from other apps, but still needs:
// 1. Unpredictable filenames (UUID)
// 2. Proper file permissions
// 3. Atomic operations
// 4. File protection classes for sensitive data
References
-
CWE-377 : Insecure Temporary File.
-
MASVS-STORAGE-1: The app securely stores sensitive data.
-
Creating files safely in Mac apps, good overview on this dense topic.
-
About Files and Directories. In particular, The Role of File Coordinators and Presenters show how to coordinate file access between different processes / threads.