---
title: Encode output khi render nội dung người dùng trong WKWebView
impact: HIGH
impactDescription: Render chuỗi người dùng trực tiếp vào HTML trong WKWebView dẫn đến XSS - attacker có thể đọc dữ liệu nhạy cảm, thực hiện localStorage/cookie theft hoặc gọi native bridge.
tags: swift, ios, xss, wkwebview, output-encoding, javascript, security
---

## Encode output khi render nội dung người dùng trong WKWebView

Khi inject nội dung từ người dùng vào WKWebView bằng `evaluateJavaScript` hoặc `loadHTMLString`, phải escape các ký tự HTML/JS đặc biệt. Không tin vào bất kỳ chuỗi nào từ server response hoặc user input khi render vào web context.

**Incorrect (inject thẳng vào HTML/JS):**

```swift
import WebKit

class ChatViewController: UIViewController {
    var webView: WKWebView!

    // !! Inject tên người dùng thẳng vào JS - XSS!
    func displayMessage(_ message: String, from sender: String) {
        // Nếu sender = "</script><script>alert(1)</script>" thì XSS!
        let js = "displayMessage('\(message)', '\(sender)')"
        webView.evaluateJavaScript(js, completionHandler: nil)
    }

    // !! loadHTMLString với nội dung không được sanitize
    func renderUserProfile(bio: String) {
        let html = "<html><body><p>\(bio)</p></body></html>"  // XSS!
        webView.loadHTMLString(html, baseURL: nil)
    }
}
```

**Correct (escape trước khi inject):**

```swift
import WebKit

extension String {
    // Escape cho JavaScript string context
    var jsEscaped: String {
        var result = self
            .replacingOccurrences(of: "\\", with: "\\\\")
            .replacingOccurrences(of: "'", with: "\\'")
            .replacingOccurrences(of: "\"", with: "\\\"")
            .replacingOccurrences(of: "\n", with: "\\n")
            .replacingOccurrences(of: "\r", with: "\\r")
        return result
    }

    // Escape cho HTML context
    var htmlEscaped: String {
        return self
            .replacingOccurrences(of: "&", with: "&amp;")
            .replacingOccurrences(of: "<", with: "&lt;")
            .replacingOccurrences(of: ">", with: "&gt;")
            .replacingOccurrences(of: "\"", with: "&quot;")
            .replacingOccurrences(of: "'", with: "&#x27;")
    }
}

class ChatViewController: UIViewController {
    var webView: WKWebView!

    // SAFE: Escape JS string trước khi inject
    func displayMessage(_ message: String, from sender: String) {
        let safeMessage = message.jsEscaped
        let safeSender = sender.jsEscaped
        let js = "displayMessage('\(safeMessage)', '\(safeSender)')"
        webView.evaluateJavaScript(js, completionHandler: nil)
    }

    // SAFER: Truyền data qua postMessage thay vì concat string
    func sendDataToWebView(data: [String: Any]) {
        guard let jsonData = try? JSONSerialization.data(withJSONObject: data),
              let jsonStr = String(data: jsonData, encoding: .utf8) else { return }
        // JSON tự escape, không bị inject
        let js = "window.dispatchEvent(new CustomEvent('nativeMessage', {detail: \(jsonStr)}))"
        webView.evaluateJavaScript(js, completionHandler: nil)
    }

    // SAFE: loadHTMLString với escaping
    func renderUserProfile(bio: String) {
        let safeBio = bio.htmlEscaped
        let html = """
        <html><body>
        <p>\(safeBio)</p>
        </body></html>
        """
        webView.loadHTMLString(html, baseURL: nil)
    }
}
```

**Tools:** SwiftLint, OWASP MASVS-PLATFORM-2, Burp Suite (intercept WebView traffic)
