Merge pull request #9964 from geoffw0/cwe95

Swift: Query for CWE-79 / CWE-95
This commit is contained in:
Mathias Vorreiter Pedersen
2022-08-05 10:38:33 +01:00
committed by GitHub
8 changed files with 488 additions and 0 deletions

View File

@@ -11,6 +11,9 @@ abstract class RemoteFlowSource extends Node {
abstract string getSourceType();
}
/**
* A data flow source of remote user input that is defined through 'models as data'.
*/
private class ExternalRemoteFlowSource extends RemoteFlowSource {
ExternalRemoteFlowSource() { sourceNode(this, "remote") }

View File

@@ -0,0 +1,32 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Fetching data in a WebView without restricting the base URL may allow an attacker to access sensitive local data, for example using <code>file://</code>. Data can then be extracted from the software using the URL of a machine under the attackers control. More generally, an attacker may use a URL under their control as part of a cross-site scripting attack.</p>
</overview>
<recommendation>
<p>When loading HTML into a web view, always set the <code>baseURL</code> to an appropriate URL that you control, or to <code>about:blank</code>. Do not use <code>nil</code>, as this does not restrict URLs that can be resolved. Also do not use a <code>baseURL</code> that could itself be controlled by an attacker.</p>
</recommendation>
<example>
<p>In the following example, a call to <code>UIWebView.loadHTMLString</code> has the <code>baseURL</code> set to <code>nil</code>, which does not restrict URLs that can be resolved from within the web page.</p>
<sample src="UnsafeWebViewFetchBad.swift" />
<p>To fix the problem, we set the <code>baseURL</code> to <code>about:blank</code>. This ensures that an attacker cannot resolve URLs that point to the local file system, or to web servers under their control.</p>
<sample src="UnsafeWebViewFetchGood.swift" />
</example>
<references>
<li>
<a href="https://www.allysonomalley.com/2018/12/03/ios-bug-hunting-web-view-xss/">iOS Bug Hunting - Web View XSS</a>
</li>
</references>
</qhelp>

View File

@@ -0,0 +1,143 @@
/**
* @name Unsafe WebView fetch
* @description Fetching data in a WebView without restricting the base URL may allow an attacker to access sensitive local data, or enable cross-site scripting attack.
* @kind path-problem
* @problem.severity warning
* @security-severity 6.1
* @precision high
* @id swift/unsafe-webview-fetch
* @tags security
* external/cwe/cwe-079
* external/cwe/cwe-095
* external/cwe/cwe-749
*/
import swift
import codeql.swift.dataflow.DataFlow
import codeql.swift.dataflow.TaintTracking
import codeql.swift.dataflow.FlowSources
import DataFlow::PathGraph
import codeql.swift.frameworks.StandardLibrary.String
/**
* A taint source that is `String(contentsOf:)`.
* TODO: this shouldn't be needed when `StringSource` in `String.qll` is working.
*/
class StringContentsOfUrlSource extends RemoteFlowSource {
StringContentsOfUrlSource() {
exists(CallExpr call, AbstractFunctionDecl f |
call.getFunction().(ApplyExpr).getStaticTarget() = f and
f.getName() = "init(contentsOf:)" and
f.getParam(0).getType().getName() = "URL" and
this.asExpr() = call
)
}
override string getSourceType() { result = "" }
}
/**
* A sink that is a candidate result for this query, such as certain arguments
* to `UIWebView.loadHTMLString`.
*/
class Sink extends DataFlow::Node {
Expr baseUrl;
Sink() {
exists(
AbstractFunctionDecl funcDecl, CallExpr call, string funcName, string paramName, int arg,
int baseUrlArg
|
// arguments to method calls...
exists(string className, ClassDecl c |
(
// `loadHTMLString`
className = ["UIWebView", "WKWebView"] and
funcName = "loadHTMLString(_:baseURL:)" and
paramName = "string"
or
// `UIWebView.load`
className = "UIWebView" and
funcName = "load(_:mimeType:textEncodingName:baseURL:)" and
paramName = "data"
or
// `WKWebView.load`
className = "WKWebView" and
funcName = "load(_:mimeType:characterEncodingName:baseURL:)" and
paramName = "data"
) and
c.getName() = className and
c.getAMember() = funcDecl and
call.getFunction().(ApplyExpr).getStaticTarget() = funcDecl
) and
// match up `funcName`, `paramName`, `arg`, `node`.
funcDecl.getName() = funcName and
funcDecl.getParam(pragma[only_bind_into](arg)).getName() = paramName and
call.getArgument(pragma[only_bind_into](arg)).getExpr() = this.asExpr() and
// match up `baseURLArg`
funcDecl.getParam(pragma[only_bind_into](baseUrlArg)).getName() = "baseURL" and
call.getArgument(pragma[only_bind_into](baseUrlArg)).getExpr() = baseUrl
)
}
/**
* Gets the `baseURL` argument associated with this sink.
*/
Expr getBaseUrl() { result = baseUrl }
}
/**
* A taint configuration from taint sources to sinks (and `baseURL` arguments)
* for this query.
*/
class UnsafeWebViewFetchConfig extends TaintTracking::Configuration {
UnsafeWebViewFetchConfig() { this = "UnsafeWebViewFetchConfig" }
override predicate isSource(DataFlow::Node node) { node instanceof RemoteFlowSource }
override predicate isSink(DataFlow::Node node) {
node instanceof Sink or
node.asExpr() = any(Sink s).getBaseUrl()
}
override predicate isAdditionalTaintStep(DataFlow::Node node1, DataFlow::Node node2) {
// allow flow through `try!` and similar constructs
// TODO: this should probably be part of DataFlow / TaintTracking.
node1.asExpr() = node2.asExpr().(AnyTryExpr).getSubExpr()
or
// allow flow through `!`
// TODO: this should probably be part of DataFlow / TaintTracking.
node1.asExpr() = node2.asExpr().(ForceValueExpr).getSubExpr()
or
// allow flow through string concatenation.
// TODO: this should probably be part of TaintTracking.
node2.asExpr().(AddExpr).getAnOperand() = node1.asExpr()
or
// allow flow through `URL.init`.
exists(CallExpr call, ClassDecl c, AbstractFunctionDecl f |
c.getName() = "URL" and
c.getAMember() = f and
f.getName() = ["init(string:)", "init(string:relativeTo:)"] and
call.getFunction().(ApplyExpr).getStaticTarget() = f and
node1.asExpr() = call.getAnArgument().getExpr() and
node2.asExpr() = call
)
}
}
from
UnsafeWebViewFetchConfig config, DataFlow::PathNode sourceNode, DataFlow::PathNode sinkNode,
Sink sink, string message
where
config.hasFlowPath(sourceNode, sinkNode) and
sink = sinkNode.getNode() and
(
// base URL is nil
sink.getBaseUrl() instanceof NilLiteralExpr and
message = "Tainted data is used in a WebView fetch without restricting the base URL."
or
// base URL is tainted
config.hasFlowToExpr(sink.getBaseUrl()) and
message = "Tainted data is used in a WebView fetch with a tainted base URL."
)
select sink, sourceNode, sinkNode, message

View File

@@ -0,0 +1,6 @@
let webview = UIWebView()
...
webview.loadHTMLString(htmlData, baseURL: nil) // BAD

View File

@@ -0,0 +1,6 @@
let webview = UIWebView()
...
webview.loadHTMLString(htmlData, baseURL: URL(string: "about:blank")) // GOOD

View File

@@ -0,0 +1,80 @@
edges
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : |
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | UnsafeWebViewFetch.swift:120:25:120:39 | call to getRemoteData() |
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : |
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | UnsafeWebViewFetch.swift:167:25:167:39 | call to getRemoteData() |
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | UnsafeWebViewFetch.swift:206:17:206:31 | call to getRemoteData() : |
| UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:94:10:94:37 | try ... : |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:121:25:121:25 | remoteString |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:124:25:124:51 | ... call to +(_:_:) ... |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:135:25:135:25 | remoteString |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:137:25:137:25 | remoteString |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:138:47:138:56 | ...! |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:139:25:139:25 | remoteString |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:139:48:139:57 | ...! |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:140:47:140:57 | ...! |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:141:25:141:25 | remoteString |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:141:48:141:58 | ...! |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:153:85:153:94 | ...! |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:154:86:154:95 | ...! |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:168:25:168:25 | remoteString |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:171:25:171:51 | ... call to +(_:_:) ... |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:182:25:182:25 | remoteString |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:184:25:184:25 | remoteString |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:185:47:185:56 | ...! |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:186:25:186:25 | remoteString |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:186:48:186:57 | ...! |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:187:47:187:57 | ...! |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:188:25:188:25 | remoteString |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:188:48:188:58 | ...! |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:200:90:200:99 | ...! |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | UnsafeWebViewFetch.swift:201:91:201:100 | ...! |
| UnsafeWebViewFetch.swift:206:17:206:31 | call to getRemoteData() : | UnsafeWebViewFetch.swift:210:25:210:25 | htmlData |
| UnsafeWebViewFetch.swift:206:17:206:31 | call to getRemoteData() : | UnsafeWebViewFetch.swift:211:25:211:25 | htmlData |
nodes
| UnsafeWebViewFetch.swift:94:10:94:37 | try ... : | semmle.label | try ... : |
| UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | semmle.label | call to ... : |
| UnsafeWebViewFetch.swift:117:21:117:35 | call to getRemoteData() : | semmle.label | call to getRemoteData() : |
| UnsafeWebViewFetch.swift:120:25:120:39 | call to getRemoteData() | semmle.label | call to getRemoteData() |
| UnsafeWebViewFetch.swift:121:25:121:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:124:25:124:51 | ... call to +(_:_:) ... | semmle.label | ... call to +(_:_:) ... |
| UnsafeWebViewFetch.swift:135:25:135:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:137:25:137:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:138:47:138:56 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:139:25:139:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:139:48:139:57 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:140:47:140:57 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:141:25:141:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:141:48:141:58 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:153:85:153:94 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:154:86:154:95 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:164:21:164:35 | call to getRemoteData() : | semmle.label | call to getRemoteData() : |
| UnsafeWebViewFetch.swift:167:25:167:39 | call to getRemoteData() | semmle.label | call to getRemoteData() |
| UnsafeWebViewFetch.swift:168:25:168:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:171:25:171:51 | ... call to +(_:_:) ... | semmle.label | ... call to +(_:_:) ... |
| UnsafeWebViewFetch.swift:182:25:182:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:184:25:184:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:185:47:185:56 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:186:25:186:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:186:48:186:57 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:187:47:187:57 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:188:25:188:25 | remoteString | semmle.label | remoteString |
| UnsafeWebViewFetch.swift:188:48:188:58 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:200:90:200:99 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:201:91:201:100 | ...! | semmle.label | ...! |
| UnsafeWebViewFetch.swift:206:17:206:31 | call to getRemoteData() : | semmle.label | call to getRemoteData() : |
| UnsafeWebViewFetch.swift:210:25:210:25 | htmlData | semmle.label | htmlData |
| UnsafeWebViewFetch.swift:211:25:211:25 | htmlData | semmle.label | htmlData |
subpaths
#select
| UnsafeWebViewFetch.swift:120:25:120:39 | call to getRemoteData() | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:120:25:120:39 | call to getRemoteData() | Tainted data is used in a WebView fetch without restricting the base URL. |
| UnsafeWebViewFetch.swift:121:25:121:25 | remoteString | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:121:25:121:25 | remoteString | Tainted data is used in a WebView fetch without restricting the base URL. |
| UnsafeWebViewFetch.swift:124:25:124:51 | ... call to +(_:_:) ... | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:124:25:124:51 | ... call to +(_:_:) ... | Tainted data is used in a WebView fetch without restricting the base URL. |
| UnsafeWebViewFetch.swift:139:25:139:25 | remoteString | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:139:25:139:25 | remoteString | Tainted data is used in a WebView fetch with a tainted base URL. |
| UnsafeWebViewFetch.swift:141:25:141:25 | remoteString | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:141:25:141:25 | remoteString | Tainted data is used in a WebView fetch with a tainted base URL. |
| UnsafeWebViewFetch.swift:167:25:167:39 | call to getRemoteData() | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:167:25:167:39 | call to getRemoteData() | Tainted data is used in a WebView fetch without restricting the base URL. |
| UnsafeWebViewFetch.swift:168:25:168:25 | remoteString | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:168:25:168:25 | remoteString | Tainted data is used in a WebView fetch without restricting the base URL. |
| UnsafeWebViewFetch.swift:171:25:171:51 | ... call to +(_:_:) ... | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:171:25:171:51 | ... call to +(_:_:) ... | Tainted data is used in a WebView fetch without restricting the base URL. |
| UnsafeWebViewFetch.swift:186:25:186:25 | remoteString | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:186:25:186:25 | remoteString | Tainted data is used in a WebView fetch with a tainted base URL. |
| UnsafeWebViewFetch.swift:188:25:188:25 | remoteString | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:188:25:188:25 | remoteString | Tainted data is used in a WebView fetch with a tainted base URL. |
| UnsafeWebViewFetch.swift:210:25:210:25 | htmlData | UnsafeWebViewFetch.swift:94:14:94:37 | call to ... : | UnsafeWebViewFetch.swift:210:25:210:25 | htmlData | Tainted data is used in a WebView fetch without restricting the base URL. |

View File

@@ -0,0 +1 @@
queries/Security/CWE-079/UnsafeWebViewFetch.ql

View File

@@ -0,0 +1,217 @@
// --- stubs ---
class NSObject
{
}
class URL
{
init?(string: String) {}
init?(string: String, relativeTo: URL?) {}
}
extension String {
init(contentsOf: URL) throws {
var data = ""
// ...
self.init(data)
}
}
class NSURLRequest : NSObject
{
enum CachePolicy : UInt
{
case useProtocolCachePolicy
}
}
typealias TimeInterval = Double
class URLRequest
{
typealias CachePolicy = NSURLRequest.CachePolicy
init(url: URL, cachePolicy: URLRequest.CachePolicy = .useProtocolCachePolicy, timeoutInterval: TimeInterval = 60.0) {}
}
class Data
{
init<S>(_ elements: S) {}
}
class WKNavigation : NSObject
{
}
class UIResponder : NSObject
{
}
class UIView : UIResponder
{
}
class UIWebView : UIView
{
func loadRequest(_ request: URLRequest) {} // deprecated
func load(_ data: Data, mimeType MIMEType: String, textEncodingName: String, baseURL: URL) {} // deprecated
func loadHTMLString(_ string: String, baseURL: URL?) {} // deprecated
}
class WKWebView : UIView
{
func load(_ request: URLRequest) -> WKNavigation? {
// ...
return WKNavigation()
}
func load(_ data: Data, mimeType MIMEType: String, characterEncodingName: String, baseURL: URL) -> WKNavigation? {
// ...
return WKNavigation()
}
func loadHTMLString(_ string: String, baseURL: URL?) -> WKNavigation? {
// ...
return WKNavigation()
}
}
// --- tests ---
func getRemoteData() -> String {
let url = URL(string: "http://example.com/")
do
{
return try String(contentsOf: url!)
} catch {
return ""
}
}
func testSimpleFlows() {
let webview = UIWebView()
webview.loadHTMLString(try! String(contentsOf: URL(string: "http://example.com/")!), baseURL: nil) // BAD [NOT DETECTED]
let data = try! String(contentsOf: URL(string: "http://example.com/")!)
webview.loadHTMLString(data, baseURL: nil) // BAD [NOT DETECTED]
let url = URL(string: "http://example.com/")
webview.loadHTMLString(try! String(contentsOf: url!), baseURL: nil) // BAD [NOT DETECTED]
}
func testUIWebView() {
let webview = UIWebView()
let localString = "<html><body><p>Local HTML</p></body></html>"
let localStringFragment = "<body><p>Local HTML</p></body>"
let remoteString = getRemoteData()
webview.loadHTMLString(localString, baseURL: nil) // GOOD: the HTML data is local
webview.loadHTMLString(getRemoteData(), baseURL: nil) // BAD: HTML contains remote input, may access local secrets
webview.loadHTMLString(remoteString, baseURL: nil) // BAD
webview.loadHTMLString("<html>" + localStringFragment + "</html>", baseURL: nil) // GOOD: the HTML data is local
webview.loadHTMLString("<html>" + remoteString + "</html>", baseURL: nil) // BAD
webview.loadHTMLString("<html>\(localStringFragment)</html>", baseURL: nil) // GOOD: the HTML data is local
webview.loadHTMLString("<html>\(remoteString)</html>", baseURL: nil) // BAD [NOT DETECTED]
let localSafeURL = URL(string: "about:blank")
let localURL = URL(string: "http://example.com/")
let remoteURL = URL(string: remoteString)
let remoteURL2 = URL(string: "/path", relativeTo: remoteURL)
webview.loadHTMLString(localString, baseURL: localSafeURL!) // GOOD: a safe baseURL is specified
webview.loadHTMLString(remoteString, baseURL: localSafeURL!) // GOOD: a safe baseURL is specified
webview.loadHTMLString(localString, baseURL: localURL!) // GOOD: a presumed safe baseURL is specified
webview.loadHTMLString(remoteString, baseURL: localURL!) // GOOD: a presumed safe baseURL is specified
webview.loadHTMLString(localString, baseURL: remoteURL!) // GOOD: the HTML data is local
webview.loadHTMLString(remoteString, baseURL: remoteURL!) // BAD
webview.loadHTMLString(localString, baseURL: remoteURL2!) // GOOD: the HTML data is local
webview.loadHTMLString(remoteString, baseURL: remoteURL2!) // BAD
let localRequest = URLRequest(url: localURL!)
let remoteRequest = URLRequest(url: remoteURL!)
webview.loadRequest(localRequest) // GOOD: loadRequest is out of scope as it has no baseURL
webview.loadRequest(remoteRequest) // GOOD: loadRequest is out of scope as it has no baseURL
let localData = Data(localString.utf8)
let remoteData = Data(remoteString.utf8)
webview.load(localData, mimeType: "text/html", textEncodingName: "utf-8", baseURL: localSafeURL!) // GOOD: the data is local
webview.load(remoteData, mimeType: "text/html", textEncodingName: "utf-8", baseURL: localSafeURL!) // GOOD: a safe baseURL is specified
webview.load(localData, mimeType: "text/html", textEncodingName: "utf-8", baseURL: remoteURL!) // GOOD: the HTML data is local
webview.load(remoteData, mimeType: "text/html", textEncodingName: "utf-8", baseURL: remoteURL!) // BAD [NOT DETECTED]
}
func testWKWebView() {
let webview = WKWebView()
// note: `WKWebView` is safer than `UIWebView` as it has better security configuration options
// and is more locked down by default.
let localString = "<html><body><p>Local HTML</p></body></html>"
let localStringFragment = "<body><p>Local HTML</p></body>"
let remoteString = getRemoteData()
webview.loadHTMLString(localString, baseURL: nil) // GOOD: the HTML data is local
webview.loadHTMLString(getRemoteData(), baseURL: nil) // BAD
webview.loadHTMLString(remoteString, baseURL: nil) // BAD
webview.loadHTMLString("<html>" + localStringFragment + "</html>", baseURL: nil) // GOOD: the HTML data is local
webview.loadHTMLString("<html>" + remoteString + "</html>", baseURL: nil) // BAD
webview.loadHTMLString("<html>\(localStringFragment)</html>", baseURL: nil) // GOOD: the HTML data is local
webview.loadHTMLString("<html>\(remoteString)</html>", baseURL: nil) // BAD [NOT DETECTED]
let localSafeURL = URL(string: "about:blank")
let localURL = URL(string: "http://example.com/")
let remoteURL = URL(string: remoteString)
let remoteURL2 = URL(string: "/path", relativeTo: remoteURL)
webview.loadHTMLString(localString, baseURL: localSafeURL!) // GOOD: a safe baseURL is specified
webview.loadHTMLString(remoteString, baseURL: localSafeURL!) // GOOD: a safe baseURL is specified
webview.loadHTMLString(localString, baseURL: localURL!) // GOOD: a presumed safe baseURL is specified
webview.loadHTMLString(remoteString, baseURL: localURL!) // GOOD: a presumed safe baseURL is specified
webview.loadHTMLString(localString, baseURL: remoteURL!) // GOOD: the HTML data is local
webview.loadHTMLString(remoteString, baseURL: remoteURL!) // BAD
webview.loadHTMLString(localString, baseURL: remoteURL2!) // GOOD: the HTML data is local
webview.loadHTMLString(remoteString, baseURL: remoteURL2!) // BAD
let localRequest = URLRequest(url: localURL!)
let remoteRequest = URLRequest(url: remoteURL!)
webview.load(localRequest) // GOOD: loadRequest is out of scope as it has no baseURL
webview.load(remoteRequest) // GOOD: loadRequest is out of scope as it has no baseURL
let localData = Data(localString.utf8)
let remoteData = Data(remoteString.utf8)
webview.load(localData, mimeType: "text/html", characterEncodingName: "utf-8", baseURL: localSafeURL!) // GOOD: the data is local
webview.load(remoteData, mimeType: "text/html", characterEncodingName: "utf-8", baseURL: localSafeURL!) // GOOD: a safe baseURL is specified
webview.load(localData, mimeType: "text/html", characterEncodingName: "utf-8", baseURL: remoteURL!) // GOOD: the HTML data is local
webview.load(remoteData, mimeType: "text/html", characterEncodingName: "utf-8", baseURL: remoteURL!) // BAD [NOT DETECTED]
}
func testQHelpExamples() {
let webview = UIWebView()
let htmlData = getRemoteData()
// ...
webview.loadHTMLString(htmlData, baseURL: nil) // BAD
webview.loadHTMLString(htmlData, baseURL: URL(string: "about:blank")) // GOOD
}
testSimpleFlows()
testUIWebView()
testWKWebView()
testQHelpExamples()