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 NSTemporaryDirectory and FileManager.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

  1. 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)

  2. File Replacement Attack: Attacker creates a file at the predicted location before the application, potentially injecting malicious content that the application will read

  3. Information Disclosure: Temp files created in shared directories like /tmp may have overly permissive access rights, allowing other users or processes to read sensitive data

  4. 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:

  1. Never use legacy C functions: Avoid mktemp(), tmpnam(), tmpfile() or tempnam().

  2. Avoid shared directories: Don’t use /tmp, /var/tmp directly - use app sandbox directories.

  3. Use unpredictable names: Always use UUID().uuidString for temp file names.

  4. Set restrictive permissions: Use .posixPermissions: 0o600 for sensitive files (owner read/write only)

  5. Use atomic writes: Apply .atomic option to prevent partial writes during race conditions

  6. Apply file protection: Use .completeFileProtection for sensitive data on iOS

  7. Clean up temp files: Always delete temp files when no longer needed

  8. 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