Unsafe Cookie

ID

swift.unsafe_cookie

Severity

high

Resource

Misconfiguration

Language

Swift

Tags

CWE:1004, CWE:315, CWE:539, CWE:614, NIST.SP.800-53, OWASP:2021:A05, PCI-DSS:6.5.10

Description

Unsafe cookie handling encompasses multiple vulnerabilities related to the improper management of cookies, which can lead to security issues such as disclosure of sensitive information or session hijacking.

Relevant weaknesses include improper storage (CWE-315), expired session management (CWE-539), insufficient transport security (CWE-614), and exposure to cross-site scripting risks (CWE-1004).

In iOS applications, cookies are commonly used by web services, WKWebView components, and server-side Swift frameworks like Vapor. Insecure cookie configurations can expose sensitive session data to attackers through man-in-the-middle attacks, cross-site scripting (XSS), or unauthorized access to stored cookies.

Rationale

Cookies are often used to store session identifiers and other sensitive information. Several potential vulnerabilities arise if cookies are not handled securely:

  • CWE-315: Cleartext Storage of Sensitive Information in a Cookie: Storing sensitive information in cookies without encryption can lead to unauthorized disclosure.

  • CWE-539: Use of Persistent Cookies Containing Sensitive Information: Persistent cookies that remain valid after a session can be exploited if not handled properly.

  • CWE-614: Sensitive Cookie in HTTPS Session without 'Secure' Attribute: Cookies without the Secure attribute can be transmitted over unencrypted connections, exposing them to interception.

  • CWE-1004: Sensitive Cookie without 'HttpOnly' Flag: Cookies accessible to client-side scripts can be stolen via cross-site scripting attacks.

Consider these examples illustrating unsafe practices in Swift:

Foundation HTTPCookie (iOS/macOS)

import Foundation

class CookieManager {
    // VULNERABLE: Creating insecure cookie with Foundation
    func createUnsafeCookie() -> HTTPCookie? {
        // FLAW: Missing .secure (CWE-614) and .httpOnly  (CWE-1004) flags
        let properties: [HTTPCookiePropertyKey: Any] = [
            .name: "sessionId",
            .value: "abc123xyz",
            .domain: "example.com",
            .path: "/", // FLAW - too broad path
            .expires: Date().addingTimeInterval(86400), // FLAW: Persistent cookie (CWE-539)
        ]

        return HTTPCookie(properties: properties)
    }
}

Vapor Framework (Server-Side Swift)

import Vapor

class VaporCookieHandler {
    // VULNERABLE: Insecure cookie in Vapor
    func createUnsafeCookie_vulnerable3(req: Request) throws -> Response {
        let cookie = HTTPCookies.Value(
            name: "SESSIONID",   // FLAW: Sensitive name pattern
            value: "user_session_12345",
            expires: Date().addingTimeInterval(3600 * 24 * 7),  // FLAW: Persistent (CWE-539)
            domain: nil,         // Using default domain, ok
            path: "/",           // FLAW: Root path
            isSecure: false,     // FLAW: No Secure flag (CWE-614)
            isHTTPOnly: false,   // FLAW: No HttpOnly flag (CWE-1004)
            sameSite: nil        // Missing SameSite protection, but not enforced
        )

        var response = Response(status: .ok)
        response.cookies[cookie.name] = cookie
        return response
    }

    // VULNERABLE: Cookie with default insecure settings
    func createDefaultCookie_vulnerable4(req: Request) -> Response {
        var response = Response(status: .ok)

        // FLAW: Using minimal cookie configuration, defaults to insecure
        // values for path: "/", secure: false, httpOnly: false
        let cookie = HTTPCookies.Value(
            name: "userData",
            value: "sensitive_info"
        )

        response.cookies[cookie.name] = cookie
        return response
    }
}

In these examples:

  1. Persistent cookies: Using expires or maxAge creates persistent cookies that survive browser restarts, increasing the window for exploitation (CWE-539).

  2. Missing Secure flag: Cookies without isSecure: true or .secure can be transmitted over unencrypted HTTP connections, exposing them to interception (CWE-614).

  3. Missing HttpOnly flag: Cookies without isHTTPOnly: true or .httpOnly can be accessed by JavaScript, making them vulnerable to XSS attacks (CWE-1004).

  4. Overly broad scope: Using root domain (.com) and root path (/) exposes cookies to more requests than necessary.

  5. Sensitive naming: Cookie names like "SESSIONID", "SESSION", "SESSID" indicate session management cookies that require strict security.

Remediation

To secure cookies in web applications, implement the following practices:

  1. Use the Secure Attribute: Always set the Secure attribute on cookies if your application supports HTTPS. This ensures cookies are only sent over secure channels.

  2. Set the HttpOnly Attribute: Apply the HttpOnly attribute to cookies that store sensitive data, preventing access from client-side scripts and mitigating XSS risks.

  3. Avoid Storing Sensitive Data in Cookies: Encrypt any sensitive data stored in cookies and, where possible, avoid storing information like passwords or sensitive session data directly.

  4. Manage Cookie Expirations Wisely: Use session cookies rather than persistent ones for sensitive information, ensuring they expire appropriately and reduce the risk of exploitation.

  5. Regularly Audit Cookie Usage: Review cookies in use on your website to ensure best practices are consistently applied.

Here are secure implementations in Swift:

Foundation HTTPCookie (Secure Configuration)

import Foundation

class SecureCookieManager {
    // SECURE: Creating secure cookie with Foundation
    func createSecureCookie_secure1() -> HTTPCookie? {
        let properties: [HTTPCookiePropertyKey: Any] = [
            .name: "authToken",
            .value: "secure_token_value",
            .domain: "api.example.com",         // Specific domain
            .path: "/api/v1",                   // Specific path
            .secure: true,                      // SECURE: Only sent over HTTPS
            .init(rawValue: "HttpOnly"): true,  // SECURE: Not accessible to JavaScript
            // No .expires or .maximumAge = session cookie (non-persistent)
        ]

        return HTTPCookie(properties: properties)
    }

    // SECURE: Session cookie with SameSite protection
    func createSessionCookie_secure2() -> HTTPCookie? {
        let properties: [HTTPCookiePropertyKey: Any] = [
            .name: "session",
            .value: UUID().uuidString,
            .domain: "myapp.com",
            .path: "/",
            .secure: true,                          // HTTPS only
            .init(rawValue: "HttpOnly"): true,      // JavaScript cannot access
            .sameSitePolicy: "Strict",              // CSRF protection
            // Session cookie: no expiration
        ]

        return HTTPCookie(properties: properties)
    }
}

Vapor Framework (Secure Configuration)

import Vapor

class SecureVaporCookieHandler {
    // SECURE: Properly configured secure cookie in Vapor
    func createSecureCookie_secure3(req: Request) throws -> Response {
        let cookie = HTTPCookies.Value(
            name: "sessionToken",
            value: generateSecureToken(),
            // No expires/maxAge = session cookie (non-persistent)
            domain: req.url.host,          // Specific to current host
            path: req.url.path,            // Specific path
            isSecure: true,                // SECURE: HTTPS only (CWE-614 fixed)
            isHTTPOnly: true,              // SECURE: XSS protection (CWE-1004 fixed)
            sameSite: .strict              // SECURE: CSRF protection
        )

        var response = Response(status: .ok)
        response.cookies[cookie.name] = cookie
        return response
    }

    // SECURE: Using Vapor's HTTPCookies builder with all security flags
    func createCookieViaBuilder_secure4(req: Request) -> Response {
        var response = Response(status: .ok)

        response.cookies["secure_session"] = HTTPCookies.Value(
            name: "secure_session",
            value: generateSecureToken(),
            isSecure: true,    // Only HTTPS
            isHTTPOnly: true,  // No script access
            sameSite: .lax     // Balance between security and usability
        )

        return response
    }

    private func generateSecureToken() -> String {
        // ... cryptographically secure random token generation
    }
}

Best Practice for Foundation HTTPCookie: Add extension method to create cookies enforcing a strict configuration.

extension HTTPCookie {
    /// Factory method to create secure session cookies
    static func secureSessionCookie(name: String, value: String, domain: String, path: String) -> HTTPCookie? {
        let properties: [HTTPCookiePropertyKey: Any] = [
            .name: name,
            .value: value,
            .domain: domain,
            .path: path,
            .secure: true,           // HTTPS only
            .setByJavaScript: true,  // No JavaScript access
            .sameSitePolicy: "Lax",  // some CSRF protection
            // No expiration = session cookie
        ]

        return HTTPCookie(properties: properties)
    }
}

// Usage
if let cookie = HTTPCookie.secureSessionCookie(
    name: "session",
    value: sessionId,
    domain: "api.example.com",
    path: "/api"
) {
    HTTPCookieStorage.shared.setCookie(cookie)
}

These secure examples demonstrate:

  • Secure flag: isSecure: true or .secure: true ensures cookies are only transmitted over HTTPS

  • HttpOnly flag: isHTTPOnly: true or .httpOnly: true prevents JavaScript access, mitigating XSS

  • Session cookies: Omitting expires/maxAge creates non-persistent cookies that expire with the session

  • SameSite attribute: .sameSite: .strict or .lax helps prevent CSRF attacks

  • Specific scope: Using specific domain and path limits cookie exposure

  • Secure token generation: Using cryptographically secure random values

Configuration

The detector has the following configurable parameters:

  • checkPersistence, that indicates if the persistence of the cookie must be checked.

  • invalidCookieNamePattern, that indicates the pattern used to detect invalid cookie names.

  • invalidDomainPattern, that indicates the pattern used to detect invalid domain names.

  • invalidPathPattern, that indicates the pattern used to detect invalid paths.

  • enforceHttpOnly, that indicates if the HttpOnly flag of the cookie must be checked.

  • enforceSecure, that indicates if the Secure flag of the cookie must be checked.

properties:
  checkPersistence: true
  invalidCookieNamePattern: '.*SESS(ION)?ID.*'
  invalidDomainPattern: '\.[^\.]+'
  invalidPathPattern: '/'
  enforceHttpOnly: true
  enforceSecure: true

This configuration checks too-broad top-level domains and paths, non-persistent cookies, and httpOnly + secure flags set. You may weaken this strict policy or mute issues for specific cases.

References