---
title: Không dùng value(forKeyPath:) với input người dùng - tránh KVC injection
impact: HIGH
impactDescription: Truyền string người dùng vào value(forKeyPath:) hoặc NSPredicate(value:) dạng keyPath expression cho phép attacker truy cập private properties hoặc trigger unintended method calls.
tags: swift, ios, kvc, keypathinjection, nspredicate, dynamic-code, security
---

## Không dùng value(forKeyPath:) với input người dùng - tránh KVC injection

Key-Value Coding (KVC) trong Objective-C runtime cho phép truy cập bất kỳ property nào theo tên string. Truyền input người dùng vào `value(forKeyPath:)` hoặc dùng KVC trong NSPredicate format với keyPath không được validate là nguy hiểm.

**Incorrect (KVC với input người dùng):**

```swift
import Foundation

class ProfileViewController: UIViewController {
    var user: User!

    // !! KVC với field name từ user input - truy cập private properties!
    func displayField(fieldName: String) {
        // Nếu fieldName = "privateSSN" hoặc "password" → lộ dữ liệu nhạy cảm
        let value = user.value(forKeyPath: fieldName)
        print("Field value: \(value ?? "nil")")
    }

    // !! NSPredicate với dynamic keyPath từ server
    func filterUsers(sortField: String, sortOrder: String, users: [User]) -> [User] {
        // sortField từ server response không được validate
        let predicate = NSPredicate(format: "%K == %@", sortField, "active")
        return (users as NSArray).filtered(using: predicate) as? [User] ?? []
    }
}
```

**Correct (whitelist keyPath được phép):**

```swift
import Foundation

// SAFE: Whitelist các field được phép truy cập
enum UserDisplayField: String, CaseIterable {
    case displayName = "displayName"
    case email = "email"
    case bio = "bio"
    case avatarURL = "avatarURL"
    // Không có: password, ssn, internalId, etc.
}

class ProfileViewController: UIViewController {
    var user: User!

    // SAFE: Validate field name trước khi dùng
    func displayField(fieldName: String) {
        guard let allowedField = UserDisplayField(rawValue: fieldName) else {
            // Field không được phép - log và return
            print("Warning: Attempted access to field '\(fieldName)'")
            return
        }
        let value = user.value(forKeyPath: allowedField.rawValue)
        displayLabel.text = value as? String
    }

    // SAFE: Enum-based sort column, không dùng raw string từ server
    func filterActiveUsers(from users: [User]) -> [User] {
        return users.filter { $0.status == .active }
    }
}

// SAFE: Dùng Swift keypath thay vì KVC string
struct UserSorter {
    enum SortField {
        case name
        case createdAt
        case lastActive
    }

    func sortUsers(_ users: [User], by field: SortField, ascending: Bool) -> [User] {
        switch field {
        case .name:
            return users.sorted { ascending ? $0.name < $1.name : $0.name > $1.name }
        case .createdAt:
            return users.sorted { ascending ? $0.createdAt < $1.createdAt : $0.createdAt > $1.createdAt }
        case .lastActive:
            return users.sorted { ascending ? $0.lastActive < $1.lastActive : $0.lastActive > $1.lastActive }
        }
    }
}
```

**Tools:** OWASP MASVS-CODE-4, SwiftLint custom rule (detect `value(forKeyPath:` with non-literal argument)
