flawopen.com/Cross-Site Scripting/Swift

Cross-Site Scripting in Swift

High CWE-79 Draft — pending review
ELI5

Imagine a guestbook where visitors write a public note. Stored XSS is like someone writing a note that isn't just text — it's a hidden trick that makes the guestbook page itself start doing things, like stealing the next visitor's login session, the moment anyone opens the page to read it.

Key terms on this page
output encoding
Converting characters that have special meaning in HTML (like < and >) into harmless equivalents before inserting untrusted text into a page, so it displays as text instead of running as markup or script.
WKWebView
Apple's embedded browser component for rendering HTML/JS inside an iOS app — including, when misused, untrusted content that can carry the same XSS risk as a normal browser tab.

What's happening

On iOS, XSS shows up when a WKWebView loads untrusted HTML via loadHTMLString() without prior sanitization. The risk compounds when the same WebView also registers a WKUserContentController message handler, giving injected script a potential path back into native Swift code.

Real-world impact

In 2005, an 19-year-old user exploited a stored XSS flaw on MySpace to create the "Samy" worm — a script that added itself to every profile that viewed it, infecting over a million profiles within about 20 hours and forcing MySpace offline to contain it. It remains one of the most-cited demonstrations of how quickly stored XSS can self-propagate.

Source: widely documented in security industry retrospectives — see References below.

Vulnerable vs. fixed

VULNERABLE
// untrusted HTML loaded with no sanitization
webView.loadHTMLString(
  untrustedHtml, baseURL: nil
)
FIXED
// sanitize before loading
let safeHtml = sanitize(untrustedHtml)
webView.loadHTMLString(
  safeHtml, baseURL: nil
)

Why the fix works

Sanitizing the HTML string against a strict allow-list of tags and attributes before it reaches loadHTMLString() strips out any <script> tags or event-handler attributes it contains. loadHTMLString() itself performs no sanitization — it renders exactly the markup it's given, script included.

Swift-specific gotchas

WKUserContentController message handlers are a bridge, like Android's addJavascriptInterface

window.webkit.messageHandlers.someHandler.postMessage(...) lets page JavaScript call back into native Swift code. If untrusted script runs in the same WebView, it can potentially invoke any registered handler — scope handlers narrowly and never register one on a WebView that loads untrusted content.

evaluateJavaScript() with interpolated values is a second injection point

Building a JS string to run via webView.evaluateJavaScript("doSomething('\(userValue)')") can let a value with an embedded quote break out of the intended JS string literal — a variant of the same class of bug, in the native-to-web direction instead of web-to-native.

The deprecated UIWebView has no sanitization either

Older code still targeting UIWebView (deprecated in favor of WKWebView) carries identical risk — migrating away from it is a security improvement as well as a modernization one.

Common misconceptions

"This is a native app, not a website — XSS doesn't apply"

Any WKWebView rendering HTML carries the same fundamental risk as a browser tab, and a registered message handler can make the impact worse than typical browser XSS.

"Swift's strong type system protects me here"

Type safety and output-encoding safety are unrelated properties — a well-typed String can still carry an injected script.

How to check if you're affected

grep -rn "loadHTMLString(" --include="*.swift" . grep -rn "WKUserContentController\|evaluateJavaScript(" --include="*.swift" .
For each hit, trace whether the HTML/JS string ever includes untrusted content without an intervening sanitization step.

Prevention checklist

FAQ

Is this the same bug as SQL injection?

Same underlying shape — untrusted data mixed into a command's structure without encoding — but the target and damage differ: SQL injection targets the database, XSS targets a rendered web context, potentially with a native bridge on iOS.

Does loading a remote URL instead of a raw HTML string avoid this?

Only if that URL and everything it can redirect to is trusted — restricting navigation to an explicit allow-list of origins is the safer default for any WebView that might otherwise load attacker-influenced content.

References

View in: Python JavaScript Go Java PHP C# Ruby C/C++ Rust Kotlin Swift Solidity (N/A)