diff --git a/rules/swift/security/swift-webview-config-allows-js-swift.yml b/rules/swift/security/swift-webview-config-allows-js-swift.yml new file mode 100644 index 00000000..eb67b0d2 --- /dev/null +++ b/rules/swift/security/swift-webview-config-allows-js-swift.yml @@ -0,0 +1,110 @@ +id: swift-webview-config-allows-js-swift +severity: warning +language: swift +message: >- + Webviews were observed that do not have JavaScript disabled. Consider + disabling JavaScript wherever the functionality is not required, following + the principle of least privelege. +note: >- + [CWE-272] Least Privilege Violation. + [REFERENCES] + - https://mas.owasp.org/MASVS/controls/MASVS-PLATFORM-2/ +utils: + match_pattern_upgradeKnownHostsToHTTPS: + kind: assignment + all: + - has: + stopBy: neighbor + kind: directly_assignable_expression + all: + - has: + stopBy: end + kind: simple_identifier + pattern: $F + - has: + stopBy: end + kind: navigation_suffix + has: + stopBy: neighbor + kind: simple_identifier + regex: '^javaScriptEnabled|allowsContentJavaScript$' + - has: + stopBy: neighbor + regex: '^=$' + - has: + stopBy: neighbor + kind: boolean_literal + regex: '^true$' + - follows: + stopBy: end + kind: property_declaration + all: + - has: + stopBy: end + kind: pattern + has: + stopBy: neighbor + kind: simple_identifier + pattern: $F + - has: + stopBy: neighbor + kind: call_expression + any: + - pattern: WKWebpagePreferences() + - pattern: WKPreferences() + - not: + follows: + stopBy: neighbor + kind: assignment + all: + - has: + stopBy: neighbor + kind: directly_assignable_expression + all: + - has: + stopBy: end + kind: simple_identifier + pattern: $F + - has: + stopBy: end + kind: navigation_suffix + has: + stopBy: neighbor + kind: simple_identifier + regex: '^(javaScriptEnabled|allowsContentJavaScript)$' + - has: + stopBy: neighbor + regex: '^=$' + - has: + stopBy: neighbor + kind: boolean_literal + regex: '^true$' + - not: + precedes: + stopBy: end + kind: assignment + all: + - has: + stopBy: neighbor + kind: directly_assignable_expression + all: + - has: + stopBy: end + kind: simple_identifier + pattern: $F + - has: + stopBy: end + kind: navigation_suffix + has: + stopBy: neighbor + kind: simple_identifier + regex: '^(javaScriptEnabled|allowsContentJavaScript)$' + - has: + stopBy: neighbor + regex: '^=$' + - has: + stopBy: neighbor + pattern: $$$ +rule: + kind: assignment + matches: match_pattern_upgradeKnownHostsToHTTPS \ No newline at end of file diff --git a/rules/swift/security/swift-webview-config-base-url-swift.yml b/rules/swift/security/swift-webview-config-base-url-swift.yml new file mode 100644 index 00000000..284395d2 --- /dev/null +++ b/rules/swift/security/swift-webview-config-base-url-swift.yml @@ -0,0 +1,73 @@ +id: swift-webview-config-base-url-swift +severity: warning +language: swift +message: >- + UIWebView instances were observed where the baseURL is misconfigured as + nil, which allows for origin abuse within the webview. In order to remove + the effective origin, the application should explicitly set the baseURL to + `about:blank` or similar. +note: >- + [CWE-272] Least Privilege Violation. + [REFERENCES] + - https://mas.owasp.org/MASVS/controls/MASVS-PLATFORM-2/ +utils: + matches_patttern_loadHTMLString_&_load: + kind: call_expression + all: + - has: + stopBy: end + kind: navigation_expression + all: + - has: + stopBy: end + kind: simple_identifier + pattern: $W + - has: + stopBy: end + kind: navigation_suffix + has: + stopBy: end + kind: simple_identifier + regex: '^loadHTMLString|load$' + - has: + stopBy: end + kind: call_suffix + has: + stopBy: end + kind: value_argument + all: + - has: + stopBy: end + kind: simple_identifier + regex: '^baseURL$' + - has: + stopBy: end + regex: '^nil$' + - inside: + stopBy: end + kind: source_file + has: + stopBy: end + kind: property_declaration + all: + - has: + stopBy: end + kind: pattern + has: + stopBy: neighbor + kind: simple_identifier + pattern: $W + - has: + stopBy: neighbor + kind: call_expression + all: + - has: + stopBy: neighbor + kind: simple_identifier + regex: '^UIWebView$' + - has: + stopBy: neighbor + kind: call_suffix +rule: + kind: call_expression + matches: matches_patttern_loadHTMLString_&_load \ No newline at end of file diff --git a/rules/swift/security/swift-xxe-prevention-swift.yml b/rules/swift/security/swift-xxe-prevention-swift.yml new file mode 100644 index 00000000..110555c7 --- /dev/null +++ b/rules/swift/security/swift-xxe-prevention-swift.yml @@ -0,0 +1,114 @@ +id: swift-xxe-prevention-swift +severity: warning +language: swift +message: >- + Usage of Apple's native XML Parser was observed where the parser is + explicitly instructed to resolve external entities. This can lead to XXE + attacks if untrusted input is parsed. Consider disabling this + functionality where feasible. +note: >- + [CWE-611] Improper Restriction of XML External Entity Reference. + [REFERENCES] + - https://developer.apple.com/library/archive/documentation/Security/Conceptual/SecureCodingGuide/Articles/ValidatingInput.html + - https://mas.owasp.org/MASVS/controls/MASVS-CODE-4/ + - https://owasp.org/www-community/vulnerabilities/XML_External_Entity_(XXE)_Processing +utils: + match_pattern_upgradeKnownHostsToHTTPS: + kind: assignment + all: + - has: + stopBy: neighbor + kind: directly_assignable_expression + all: + - has: + stopBy: end + kind: simple_identifier + pattern: $F + - has: + stopBy: end + kind: navigation_suffix + has: + stopBy: neighbor + kind: simple_identifier + regex: '^shouldResolveExternalEntities$' + - has: + stopBy: neighbor + regex: '^=$' + - has: + stopBy: neighbor + kind: boolean_literal + regex: '^true$' + - follows: + stopBy: end + kind: property_declaration + all: + - has: + stopBy: end + kind: pattern + has: + stopBy: neighbor + kind: simple_identifier + pattern: $F + - has: + stopBy: neighbor + kind: call_expression + pattern: XMLParser($$$) + - not: + follows: + stopBy: end + kind: assignment + all: + - has: + stopBy: neighbor + kind: directly_assignable_expression + all: + - has: + stopBy: end + kind: simple_identifier + pattern: $F + - has: + stopBy: end + kind: navigation_suffix + has: + stopBy: neighbor + kind: simple_identifier + regex: '^shouldResolveExternalEntities$' + - has: + stopBy: neighbor + regex: '^=$' + - has: + stopBy: neighbor + kind: boolean_literal + regex: '^true$' + - not: + precedes: + stopBy: end + kind: assignment + all: + - has: + stopBy: neighbor + kind: directly_assignable_expression + all: + - has: + stopBy: end + kind: simple_identifier + pattern: $F + - has: + stopBy: end + kind: navigation_suffix + has: + stopBy: neighbor + kind: simple_identifier + regex: '^shouldResolveExternalEntities$' + - has: + stopBy: neighbor + regex: '^=$' + - has: + stopBy: neighbor + any: + - has: + stopBy: neighbor + pattern: $$$ +rule: + kind: assignment + matches: match_pattern_upgradeKnownHostsToHTTPS diff --git a/tests/__snapshots__/swift-webview-config-allows-js-swift-snapshot.yml b/tests/__snapshots__/swift-webview-config-allows-js-swift-snapshot.yml new file mode 100644 index 00000000..37445aeb --- /dev/null +++ b/tests/__snapshots__/swift-webview-config-allows-js-swift-snapshot.yml @@ -0,0 +1,153 @@ +id: swift-webview-config-allows-js-swift +snapshots: + ? | + let preferences = WKPreferences() + preferences.javaScriptEnabled = true + preferences.javaScriptCanOpenWindowsAutomatically = false + : labels: + - source: preferences.javaScriptEnabled = true + style: primary + start: 34 + end: 70 + - source: preferences + style: secondary + start: 34 + end: 45 + - source: javaScriptEnabled + style: secondary + start: 46 + end: 63 + - source: .javaScriptEnabled + style: secondary + start: 45 + end: 63 + - source: preferences.javaScriptEnabled + style: secondary + start: 34 + end: 63 + - source: = + style: secondary + start: 64 + end: 65 + - source: 'true' + style: secondary + start: 66 + end: 70 + - source: preferences + style: secondary + start: 4 + end: 15 + - source: preferences + style: secondary + start: 4 + end: 15 + - source: WKPreferences() + style: secondary + start: 18 + end: 33 + - source: let preferences = WKPreferences() + style: secondary + start: 0 + end: 33 + ? | + let prefs = WKWebpagePreferences() + prefs.allowsContentJavaScript = false + prefs.allowsContentJavaScript = true + let config = WKWebViewConfiguration() + config.defaultWebpagePreferences = prefs + let webView = WKWebView(frame: .zero, configuration: config) + : labels: + - source: prefs.allowsContentJavaScript = true + style: primary + start: 73 + end: 109 + - source: prefs + style: secondary + start: 73 + end: 78 + - source: allowsContentJavaScript + style: secondary + start: 79 + end: 102 + - source: .allowsContentJavaScript + style: secondary + start: 78 + end: 102 + - source: prefs.allowsContentJavaScript + style: secondary + start: 73 + end: 102 + - source: = + style: secondary + start: 103 + end: 104 + - source: 'true' + style: secondary + start: 105 + end: 109 + - source: prefs + style: secondary + start: 4 + end: 9 + - source: prefs + style: secondary + start: 4 + end: 9 + - source: WKWebpagePreferences() + style: secondary + start: 12 + end: 34 + - source: let prefs = WKWebpagePreferences() + style: secondary + start: 0 + end: 34 + ? | + let prefs = WKWebpagePreferences() + prefs.allowsContentJavaScript = true + let config = WKWebViewConfiguration() + config.defaultWebpagePreferences = prefs + : labels: + - source: prefs.allowsContentJavaScript = true + style: primary + start: 35 + end: 71 + - source: prefs + style: secondary + start: 35 + end: 40 + - source: allowsContentJavaScript + style: secondary + start: 41 + end: 64 + - source: .allowsContentJavaScript + style: secondary + start: 40 + end: 64 + - source: prefs.allowsContentJavaScript + style: secondary + start: 35 + end: 64 + - source: = + style: secondary + start: 65 + end: 66 + - source: 'true' + style: secondary + start: 67 + end: 71 + - source: prefs + style: secondary + start: 4 + end: 9 + - source: prefs + style: secondary + start: 4 + end: 9 + - source: WKWebpagePreferences() + style: secondary + start: 12 + end: 34 + - source: let prefs = WKWebpagePreferences() + style: secondary + start: 0 + end: 34 diff --git a/tests/__snapshots__/swift-webview-config-base-url-swift-snapshot.yml b/tests/__snapshots__/swift-webview-config-base-url-swift-snapshot.yml new file mode 100644 index 00000000..5e2fae4a --- /dev/null +++ b/tests/__snapshots__/swift-webview-config-base-url-swift-snapshot.yml @@ -0,0 +1,514 @@ +id: swift-webview-config-base-url-swift +snapshots: + ? | + let webview = UIWebView(...) + webview.loadHTMLString(someHtmlString, baseURL: nil) + : labels: + - source: 'webview.loadHTMLString(someHtmlString, baseURL: nil)' + style: primary + start: 29 + end: 81 + - source: webview + style: secondary + start: 29 + end: 36 + - source: loadHTMLString + style: secondary + start: 37 + end: 51 + - source: .loadHTMLString + style: secondary + start: 36 + end: 51 + - source: webview.loadHTMLString + style: secondary + start: 29 + end: 51 + - source: baseURL + style: secondary + start: 68 + end: 75 + - source: nil + style: secondary + start: 77 + end: 80 + - source: 'baseURL: nil' + style: secondary + start: 68 + end: 80 + - source: '(someHtmlString, baseURL: nil)' + style: secondary + start: 51 + end: 81 + - source: webview + style: secondary + start: 4 + end: 11 + - source: webview + style: secondary + start: 4 + end: 11 + - source: UIWebView + style: secondary + start: 14 + end: 23 + - source: (...) + style: secondary + start: 23 + end: 28 + - source: UIWebView(...) + style: secondary + start: 14 + end: 28 + - source: let webview = UIWebView(...) + style: secondary + start: 0 + end: 28 + - source: | + let webview = UIWebView(...) + webview.loadHTMLString(someHtmlString, baseURL: nil) + style: secondary + start: 0 + end: 82 + ? | + let webview10 = UIWebView(frame: self.view.bounds) + let text = "This is a test." + let data = text.data(using: .utf8)! + webview10.load(data, mimetype: "text/plain", textEncodingName: "UTF-8", baseURL: nil) + self.view.addSubview(webview10) + : labels: + - source: 'webview10.load(data, mimetype: "text/plain", textEncodingName: "UTF-8", baseURL: nil)' + style: primary + start: 116 + end: 201 + - source: webview10 + style: secondary + start: 116 + end: 125 + - source: load + style: secondary + start: 126 + end: 130 + - source: .load + style: secondary + start: 125 + end: 130 + - source: webview10.load + style: secondary + start: 116 + end: 130 + - source: baseURL + style: secondary + start: 188 + end: 195 + - source: nil + style: secondary + start: 197 + end: 200 + - source: 'baseURL: nil' + style: secondary + start: 188 + end: 200 + - source: '(data, mimetype: "text/plain", textEncodingName: "UTF-8", baseURL: nil)' + style: secondary + start: 130 + end: 201 + - source: webview10 + style: secondary + start: 4 + end: 13 + - source: webview10 + style: secondary + start: 4 + end: 13 + - source: UIWebView + style: secondary + start: 16 + end: 25 + - source: '(frame: self.view.bounds)' + style: secondary + start: 25 + end: 50 + - source: 'UIWebView(frame: self.view.bounds)' + style: secondary + start: 16 + end: 50 + - source: 'let webview10 = UIWebView(frame: self.view.bounds)' + style: secondary + start: 0 + end: 50 + - source: | + let webview10 = UIWebView(frame: self.view.bounds) + let text = "This is a test." + let data = text.data(using: .utf8)! + webview10.load(data, mimetype: "text/plain", textEncodingName: "UTF-8", baseURL: nil) + self.view.addSubview(webview10) + style: secondary + start: 0 + end: 234 + ? | + let webview12 = UIWebView(frame: self.view.bounds) + let externalHtml = "
" + webview12.loadHTMLString(externalHtml, baseURL: nil) + self.view.addSubview(webview12) + : labels: + - source: 'webview12.loadHTMLString(externalHtml, baseURL: nil)' + style: primary + start: 146 + end: 198 + - source: webview12 + style: secondary + start: 146 + end: 155 + - source: loadHTMLString + style: secondary + start: 156 + end: 170 + - source: .loadHTMLString + style: secondary + start: 155 + end: 170 + - source: webview12.loadHTMLString + style: secondary + start: 146 + end: 170 + - source: baseURL + style: secondary + start: 185 + end: 192 + - source: nil + style: secondary + start: 194 + end: 197 + - source: 'baseURL: nil' + style: secondary + start: 185 + end: 197 + - source: '(externalHtml, baseURL: nil)' + style: secondary + start: 170 + end: 198 + - source: webview12 + style: secondary + start: 4 + end: 13 + - source: webview12 + style: secondary + start: 4 + end: 13 + - source: UIWebView + style: secondary + start: 16 + end: 25 + - source: '(frame: self.view.bounds)' + style: secondary + start: 25 + end: 50 + - source: 'UIWebView(frame: self.view.bounds)' + style: secondary + start: 16 + end: 50 + - source: 'let webview12 = UIWebView(frame: self.view.bounds)' + style: secondary + start: 0 + end: 50 + - source: | + let webview12 = UIWebView(frame: self.view.bounds) + let externalHtml = "" + webview12.loadHTMLString(externalHtml, baseURL: nil) + self.view.addSubview(webview12) + style: secondary + start: 0 + end: 231 + ? | + let webview13 = UIWebView(frame: self.view.bounds) + let mixedContent = "