⚡ Sangsakala Proxy 🏠 Home
Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions unified/ql/lib/ext/legacy-swift.model.yml
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,18 @@ extensions:
- ["", "Realm.Configuration", true, "init(deleteRealmIfMigrationNeeded:encryptionKey:fileURL:inMemoryIdentifier:migrationBlock:objectTypes:readOnly:schemaVersion:seedFilePath:shouldCompactOnLaunch:syncConfiguration:)", "", "", "Argument[seedFilePath:]", "path-injection", "manual"]
- ["", "Realm.Configuration", true, "fileURL", "", "", "PostUpdate", "path-injection", "manual"]
- ["", "Realm.Configuration", true, "seedFilePath", "", "", "PostUpdate", "path-injection", "manual"]
- ["", "WKWebView", true, "evaluateJavaScript(_:)", "", "", "Argument[0]", "code-injection", "manual"]
- ["", "WKWebView", true, "evaluateJavaScript(_:completionHandler:)", "", "", "Argument[0]", "code-injection", "manual"]
- ["", "WKWebView", true, "evaluateJavaScript(_:in:in:completionHandler:)", "", "", "Argument[0]", "code-injection", "manual"]
- ["", "WKWebView", true, "evaluateJavaScript(_:in:contentWorld:)", "", "", "Argument[0]", "code-injection", "manual"]
- ["", "WKWebView", true, "callAsyncJavaScript(_:arguments:in:in:completionHandler:)", "", "", "Argument[0]", "code-injection", "manual"]
- ["", "WKWebView", true, "callAsyncJavaScript(_:arguments:in:contentWorld:)", "", "", "Argument[0]", "code-injection", "manual"]
- ["", "WKUserContentController", true, "addUserScript(_:)", "", "", "Argument[0]", "code-injection", "manual"]
- ["", "UIWebView", true, "stringByEvaluatingJavaScript(from:)", "", "", "Argument[from:]", "code-injection", "manual"]
- ["", "WebView", true, "stringByEvaluatingJavaScript(from:)", "", "", "Argument[from:]", "code-injection", "manual"]
- ["", "JSContext", true, "evaluateScript(_:)", "", "", "Argument[0]", "code-injection", "manual"]
- ["", "JSContext", true, "evaluateScript(_:withSourceURL:)", "", "", "Argument[0]", "code-injection", "manual"]
- ["", "", false, "JSEvaluateScript(_:_:_:_:_:_:)", "", "", "Argument[1]", "code-injection", "manual"]
- ["", "Digest", true, "md5(_:)", "", "", "Argument[0]", "weak-hash-input-MD5", "manual"]
- ["", "Digest", true, "sha1(_:)", "", "", "Argument[0]", "weak-hash-input-SHA1", "manual"]
- ["", "Digest", true, "sha2(_:variant:)", "", "", "Argument[0]", "weak-password-hash-input-SHA2", "manual"]
Expand Down
32 changes: 32 additions & 0 deletions unified/ql/src/queries/security/CWE-094/UnsafeJsEval.qhelp
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<!DOCTYPE qhelp PUBLIC
"-//Semmle//qhelp//EN"
"qhelp.dtd">
<qhelp>
<overview>
<p>Evaluating JavaScript that contains a substring from a remote origin may lead to remote code execution. Code written by an attacker can execute unauthorized actions, including exfiltration of local data through a third party web service.</p>

</overview>
<recommendation>

<p>When loading JavaScript into a web view, evaluate only known, locally-defined source code. If part of the input comes from a remote source, do not inject it into the JavaScript code to be evaluated. Instead, send it to the web view as data using an API such as <code>WKWebView.callAsyncJavaScript</code> with the <code>arguments</code> dictionary to pass remote data objects.</p>

</recommendation>
<example>

<p>In the following (bad) example, a call to <code>WKWebView.evaluateJavaScript</code> evaluates JavaScript source code that is tainted with remote data, potentially introducing a code injection vulnerability.</p>

<sample src="UnsafeJsEvalBad.swift" />

<p>In the following (good) example, we sanitize the remote data by passing it using the <code>arguments</code> dictionary of <code>WKWebView.callAsyncJavaScript</code>. This ensures that untrusted data cannot be evaluated as JavaScript source code.</p>

<sample src="UnsafeJsEvalGood.swift" />

</example>
<references>

<li>
Apple Developer Documentation: <a href="https://developer.apple.com/documentation/webkit/wkwebview/callasyncjavascript(_:arguments:in:contentworld:)">WKWebView.callAsyncJavaScript(_:arguments:in:contentWorld:)</a>
</li>

</references>
</qhelp>
47 changes: 47 additions & 0 deletions unified/ql/src/queries/security/CWE-094/UnsafeJsEval.ql
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* @name JavaScript Injection
* @description Evaluating JavaScript code containing a substring from a remote source may lead to remote code execution.
* @kind path-problem
* @problem.severity warning
* @security-severity 9.3
* @precision medium
* @id unified/swift/unsafe-js-eval
* @tags security
* external/cwe/cwe-094
* external/cwe/cwe-095
* external/cwe/cwe-749
*/

import unified

/**
* A taint configuration from taint sources to sinks for this query.
*/
module UnsafeJsEvalConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node node) { Models::isSource(node, _) }

predicate isSink(DataFlow::Node node) { Models::isSink(node, "code-injection") }

predicate isBarrier(DataFlow::Node node) {
// TODO: Port the numeric-type barrier from original Swift's UnsafeJsEvalExtensions.qll.
none()
}

predicate isAdditionalFlowStep(DataFlow::Node nodeFrom, DataFlow::Node nodeTo) {
// TODO: Port the base-address flow step from original Swift's UnsafeJsEvalExtensions.qll.
none()
}

predicate observeDiffInformedIncrementalMode() { any() }
}

/**
* Detect taint flow of taint sources to sinks for this query.
*/
module UnsafeJsEvalFlow = TaintTracking::Global<UnsafeJsEvalConfig>;

import UnsafeJsEvalFlow::PathGraph

from UnsafeJsEvalFlow::PathNode source, UnsafeJsEvalFlow::PathNode sink
where UnsafeJsEvalFlow::flowPath(source, sink)
select sink.getNode(), source, sink, "Evaluation of uncontrolled JavaScript from a remote source."
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
let webview: WKWebView
let remoteData = try String(contentsOf: URL(string: "http://example.com/evil.json")!)

...

_ = try await webview.evaluateJavaScript("console.log(" + remoteData + ")") // BAD
10 changes: 10 additions & 0 deletions unified/ql/src/queries/security/CWE-094/UnsafeJsEvalGood.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
let webview: WKWebView
let remoteData = try String(contentsOf: URL(string: "http://example.com/evil.json")!)

...

_ = try await webview.callAsyncJavaScript(
"console.log(data)",
arguments: ["data": remoteData], // GOOD
contentWorld: .page
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
#select
| UnsafeJsEval.swift:266:22:266:107 | WKUserScript(...) | UnsafeJsEval.swift:204:12:204:66 | String(...) | UnsafeJsEval.swift:266:22:266:107 | WKUserScript(...) | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:266:22:266:107 | WKUserScript(...) | UnsafeJsEval.swift:205:12:205:35 | String(...) | UnsafeJsEval.swift:266:22:266:107 | WKUserScript(...) | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:266:22:266:107 | WKUserScript(...) | UnsafeJsEval.swift:208:30:208:53 | String(...) | UnsafeJsEval.swift:266:22:266:107 | WKUserScript(...) | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:269:22:269:124 | WKUserScript(...) | UnsafeJsEval.swift:204:12:204:66 | String(...) | UnsafeJsEval.swift:269:22:269:124 | WKUserScript(...) | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:269:22:269:124 | WKUserScript(...) | UnsafeJsEval.swift:205:12:205:35 | String(...) | UnsafeJsEval.swift:269:22:269:124 | WKUserScript(...) | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:269:22:269:124 | WKUserScript(...) | UnsafeJsEval.swift:208:30:208:53 | String(...) | UnsafeJsEval.swift:269:22:269:124 | WKUserScript(...) | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:277:26:277:31 | string | UnsafeJsEval.swift:204:12:204:66 | String(...) | UnsafeJsEval.swift:277:26:277:31 | string | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:277:26:277:31 | string | UnsafeJsEval.swift:205:12:205:35 | String(...) | UnsafeJsEval.swift:277:26:277:31 | string | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:277:26:277:31 | string | UnsafeJsEval.swift:208:30:208:53 | String(...) | UnsafeJsEval.swift:277:26:277:31 | string | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:280:26:280:31 | string | UnsafeJsEval.swift:204:12:204:66 | String(...) | UnsafeJsEval.swift:280:26:280:31 | string | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:280:26:280:31 | string | UnsafeJsEval.swift:205:12:205:35 | String(...) | UnsafeJsEval.swift:280:26:280:31 | string | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:280:26:280:31 | string | UnsafeJsEval.swift:208:30:208:53 | String(...) | UnsafeJsEval.swift:280:26:280:31 | string | Evaluation of uncontrolled JavaScript from a remote source. |
| UnsafeJsEval.swift:320:44:320:76 | ... + ... | UnsafeJsEval.swift:318:24:318:87 | String(...) | UnsafeJsEval.swift:320:44:320:76 | ... + ... | Evaluation of uncontrolled JavaScript from a remote source. |
edges
| UnsafeJsEval.swift:204:7:204:66 | try! ... | UnsafeJsEval.swift:265:13:265:18 | string | provenance | |
| UnsafeJsEval.swift:204:7:204:66 | try! ... | UnsafeJsEval.swift:268:13:268:18 | string | provenance | |
| UnsafeJsEval.swift:204:7:204:66 | try! ... | UnsafeJsEval.swift:276:13:276:18 | string | provenance | |
| UnsafeJsEval.swift:204:7:204:66 | try! ... | UnsafeJsEval.swift:279:13:279:18 | string | provenance | |
| UnsafeJsEval.swift:204:12:204:66 | String(...) | UnsafeJsEval.swift:204:7:204:66 | try! ... | provenance | |
| UnsafeJsEval.swift:205:7:205:35 | try! ... | UnsafeJsEval.swift:265:13:265:18 | string | provenance | |
| UnsafeJsEval.swift:205:7:205:35 | try! ... | UnsafeJsEval.swift:268:13:268:18 | string | provenance | |
| UnsafeJsEval.swift:205:7:205:35 | try! ... | UnsafeJsEval.swift:276:13:276:18 | string | provenance | |
| UnsafeJsEval.swift:205:7:205:35 | try! ... | UnsafeJsEval.swift:279:13:279:18 | string | provenance | |
| UnsafeJsEval.swift:205:12:205:35 | String(...) | UnsafeJsEval.swift:205:7:205:35 | try! ... | provenance | |
| UnsafeJsEval.swift:208:7:208:60 | ... + ... | UnsafeJsEval.swift:265:13:265:18 | string | provenance | |
| UnsafeJsEval.swift:208:7:208:60 | ... + ... | UnsafeJsEval.swift:268:13:268:18 | string | provenance | |
| UnsafeJsEval.swift:208:7:208:60 | ... + ... | UnsafeJsEval.swift:276:13:276:18 | string | provenance | |
| UnsafeJsEval.swift:208:7:208:60 | ... + ... | UnsafeJsEval.swift:279:13:279:18 | string | provenance | |
| UnsafeJsEval.swift:208:30:208:53 | String(...) | UnsafeJsEval.swift:208:7:208:60 | ... + ... | provenance | |
| UnsafeJsEval.swift:265:13:265:18 | string | UnsafeJsEval.swift:266:43:266:48 | string | provenance | |
| UnsafeJsEval.swift:266:43:266:48 | string | UnsafeJsEval.swift:266:22:266:107 | WKUserScript(...) | provenance | |
| UnsafeJsEval.swift:268:13:268:18 | string | UnsafeJsEval.swift:269:43:269:48 | string | provenance | |
| UnsafeJsEval.swift:269:43:269:48 | string | UnsafeJsEval.swift:269:22:269:124 | WKUserScript(...) | provenance | |
| UnsafeJsEval.swift:276:13:276:18 | string | UnsafeJsEval.swift:277:26:277:31 | string | provenance | |
| UnsafeJsEval.swift:279:13:279:18 | string | UnsafeJsEval.swift:280:26:280:31 | string | provenance | |
| UnsafeJsEval.swift:318:7:318:16 | remoteData | UnsafeJsEval.swift:320:61:320:70 | remoteData | provenance | |
| UnsafeJsEval.swift:318:24:318:87 | String(...) | UnsafeJsEval.swift:318:7:318:16 | remoteData | provenance | |
| UnsafeJsEval.swift:320:61:320:70 | remoteData | UnsafeJsEval.swift:320:44:320:76 | ... + ... | provenance | |
nodes
| UnsafeJsEval.swift:204:7:204:66 | try! ... | semmle.label | try! ... |
| UnsafeJsEval.swift:204:12:204:66 | String(...) | semmle.label | String(...) |
| UnsafeJsEval.swift:205:7:205:35 | try! ... | semmle.label | try! ... |
| UnsafeJsEval.swift:205:12:205:35 | String(...) | semmle.label | String(...) |
| UnsafeJsEval.swift:208:7:208:60 | ... + ... | semmle.label | ... + ... |
| UnsafeJsEval.swift:208:30:208:53 | String(...) | semmle.label | String(...) |
| UnsafeJsEval.swift:265:13:265:18 | string | semmle.label | string |
| UnsafeJsEval.swift:266:22:266:107 | WKUserScript(...) | semmle.label | WKUserScript(...) |
| UnsafeJsEval.swift:266:43:266:48 | string | semmle.label | string |
| UnsafeJsEval.swift:268:13:268:18 | string | semmle.label | string |
| UnsafeJsEval.swift:269:22:269:124 | WKUserScript(...) | semmle.label | WKUserScript(...) |
| UnsafeJsEval.swift:269:43:269:48 | string | semmle.label | string |
| UnsafeJsEval.swift:276:13:276:18 | string | semmle.label | string |
| UnsafeJsEval.swift:277:26:277:31 | string | semmle.label | string |
| UnsafeJsEval.swift:279:13:279:18 | string | semmle.label | string |
| UnsafeJsEval.swift:280:26:280:31 | string | semmle.label | string |
| UnsafeJsEval.swift:318:7:318:16 | remoteData | semmle.label | remoteData |
| UnsafeJsEval.swift:318:24:318:87 | String(...) | semmle.label | String(...) |
| UnsafeJsEval.swift:320:44:320:76 | ... + ... | semmle.label | ... + ... |
| UnsafeJsEval.swift:320:61:320:70 | remoteData | semmle.label | remoteData |
subpaths
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
query: queries/security/CWE-094/UnsafeJsEval.ql
postprocess:
- utils/test/InlineExpectationsTestQuery.ql
Loading
Loading