From d5bec9384c95b8d5e0d2491938d0ee8e7a3ba3f7 Mon Sep 17 00:00:00 2001 From: Lewis Date: Fri, 11 Aug 2023 01:26:10 -0700 Subject: [PATCH] Add Swift rules (#3034) * Add Swift rules * add nosemgrep * add category * add nosemgrep * fix name conflict --- .../sensitive-storage-userdefaults.swift | 35 +++++ .../sensitive-storage-userdefaults.yaml | 144 ++++++++++++++++++ swift/sqllite/sqllite-injection-audit.swift | 66 ++++++++ swift/sqllite/sqllite-injection-audit.yaml | 43 ++++++ swift/webview/webview-js-window.swift | 16 ++ swift/webview/webview-js-window.yaml | 58 +++++++ 6 files changed, 362 insertions(+) create mode 100644 swift/lang/storage/sensitive-storage-userdefaults.swift create mode 100644 swift/lang/storage/sensitive-storage-userdefaults.yaml create mode 100644 swift/sqllite/sqllite-injection-audit.swift create mode 100644 swift/sqllite/sqllite-injection-audit.yaml create mode 100644 swift/webview/webview-js-window.swift create mode 100644 swift/webview/webview-js-window.yaml diff --git a/swift/lang/storage/sensitive-storage-userdefaults.swift b/swift/lang/storage/sensitive-storage-userdefaults.swift new file mode 100644 index 0000000000..12d5eacb1b --- /dev/null +++ b/swift/lang/storage/sensitive-storage-userdefaults.swift @@ -0,0 +1,35 @@ +let username = getUsername() +let passphrase = getPass() + + +// okid: swift-user-defaults +UserDefaults.standard.set(username, forKey: "userName") +// ruleid: swift-user-defaults +UserDefaults.standard.set(passphrase, forKey: "passphrase") +// ruleid: swift-user-defaults +UserDefaults.standard.set(passWord, forKey: "userPassword") + +// ruleid: swift-user-defaults +UserDefaults.standard.set("12717-127163-a71367-127ahc", forKey: "apiKey") + +let apiKey = "12717-127163-a71367-127ahc" +// ruleid: swift-user-defaults +UserDefaults.standard.set(apiKey, forKey: "GOOGLE_TOKEN") + + +let key = "1sdad3SADSD33131" +// ruleid: swift-user-defaults +UserDefaults.standard.set(key, forKey: "cryptoKey") + + +let key = "foobar" +// ruleid: swift-user-defaults +UserDefaults.standard.set(key, forKey: "clientSecret") + + +let key = "foobar" +// ruleid: swift-user-defaults +UserDefaults.standard.set(key, forKey: "rsaPrivateKey") + +// ruleid: swift-user-defaults +UserDefaults.standard.set(passphrase, forKey: "pass_phrase") \ No newline at end of file diff --git a/swift/lang/storage/sensitive-storage-userdefaults.yaml b/swift/lang/storage/sensitive-storage-userdefaults.yaml new file mode 100644 index 0000000000..bd00b498eb --- /dev/null +++ b/swift/lang/storage/sensitive-storage-userdefaults.yaml @@ -0,0 +1,144 @@ +rules: +- id: swift-user-defaults + message: >- + Potentially sensitive data was observed to be stored in UserDefaults, which is not adequate protection + of sensitive information. For data of a sensitive nature, applications should leverage the Keychain. + severity: WARNING + metadata: + likelihood: LOW + impact: HIGH + confidence: MEDIUM + category: security + cwe: + - 'CWE-311: Missing Encryption of Sensitive Data' + masvs: + - 'MASVS-STORAGE-1: The app securely stores sensitive data' + owasp: + - A03:2017 - Sensitive Data Exposure + - A04:2021 - Insecure Design + references: + - https://developer.apple.com/library/archive/documentation/Security/Conceptual/SecureCodingGuide/Articles/ValidatingInput.html + - https://mas.owasp.org/MASVS/controls/MASVS-STORAGE-1/ + subcategory: + - vuln + technology: + - ios + - macos + languages: + - swift + options: + taint_propagation: true + patterns: + - pattern-either: + - patterns: + - pattern-either: + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: "$KEY") + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: $KEY) + - pattern: | + UserDefaults.standard.set($VALUE, forKey: "$KEY") + - pattern: | + UserDefaults.standard.set($VALUE, forKey: $KEY) + - metavariable-regex: + metavariable: $VALUE + regex: (?i).*(passcode|password|pass_word|passphrase|pass_code|pass_word|pass_phrase)$ + - focus-metavariable: $VALUE + - patterns: + - pattern-either: + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: "$KEY") + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: $KEY) + - pattern: | + UserDefaults.standard.set($VALUE, forKey: "$KEY") + - pattern: | + UserDefaults.standard.set($VALUE, forKey: $KEY) + - metavariable-regex: + metavariable: $KEY + regex: (?i).*(passcode|password|pass_word|passphrase|pass_code|pass_word|pass_phrase)$ + - focus-metavariable: $KEY + - patterns: + - pattern-either: + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: "$KEY") + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: $KEY) + - pattern: | + UserDefaults.standard.set($VALUE, forKey: "$KEY") + - pattern: | + UserDefaults.standard.set($VALUE, forKey: $KEY) + - metavariable-regex: + metavariable: $VALUE + regex: (?i).*(api_key|apikey)$ + - focus-metavariable: $VALUE + - patterns: + - pattern-either: + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: "$KEY") + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: $KEY) + - pattern: | + UserDefaults.standard.set($VALUE, forKey: "$KEY") + - pattern: | + UserDefaults.standard.set($VALUE, forKey: $KEY) + - metavariable-regex: + metavariable: $KEY + regex: (?i).*(api_key|apikey)$ + - focus-metavariable: $KEY + - patterns: + - pattern-either: + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: "$KEY") + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: $KEY) + - pattern: | + UserDefaults.standard.set($VALUE, forKey: "$KEY") + - pattern: | + UserDefaults.standard.set($VALUE, forKey: $KEY) + - metavariable-regex: + metavariable: $VALUE + regex: (?i).*(secretkey|secret_key|secrettoken|secret_token|clientsecret|client_secret)$ + - focus-metavariable: $VALUE + - patterns: + - pattern-either: + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: "$KEY") + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: $KEY) + - pattern: | + UserDefaults.standard.set($VALUE, forKey: "$KEY") + - pattern: | + UserDefaults.standard.set($VALUE, forKey: $KEY) + - metavariable-regex: + metavariable: $KEY + regex: (?i).*(secretkey|secret_key|secrettoken|secret_token|clientsecret|client_secret)$ + - focus-metavariable: $KEY + - patterns: + - pattern-either: + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: "$KEY") + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: $KEY) + - pattern: | + UserDefaults.standard.set($VALUE, forKey: "$KEY") + - pattern: | + UserDefaults.standard.set($VALUE, forKey: $KEY) + - metavariable-regex: + metavariable: $VALUE + regex: (?i).*(cryptkey|cryptokey|crypto_key|cryptionkey|symmetrickey|privatekey|symmetric_key|private_key)$ + - focus-metavariable: $VALUE + - patterns: + - pattern-either: + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: "$KEY") + - pattern: | + UserDefaults.standard.set("$VALUE", forKey: $KEY) + - pattern: | + UserDefaults.standard.set($VALUE, forKey: "$KEY") + - pattern: | + UserDefaults.standard.set($VALUE, forKey: $KEY) + - metavariable-regex: + metavariable: $KEY + regex: (?i).*(cryptkey|cryptokey|crypto_key|cryptionkey|symmetrickey|privatekey|symmetric_key|private_key)$ + - focus-metavariable: $KEY diff --git a/swift/sqllite/sqllite-injection-audit.swift b/swift/sqllite/sqllite-injection-audit.swift new file mode 100644 index 0000000000..62ab22b89a --- /dev/null +++ b/swift/sqllite/sqllite-injection-audit.swift @@ -0,0 +1,66 @@ +let username = someField.text() +let password = a.text() + +let sql = "SELECT * FROM semgrep_users WHERE username = '\(username)' AND password = '\(password)'" + +// ruleid:swift-potential-sqlite-injection +let result = sqlite3_exec(db, sql, nil, nil, nil) +sqlite3_close(db) + +let sql = "SELECT * FROM semgrep_users WHERE username = 'admin' AND password = '\(password)'" +// ruleid:swift-potential-sqlite-injection +let result = sqlite3_exec(db, sql, nil, nil, nil) +sqlite3_close(db) + + +let sql = "SELECT * FROM semgrep_users WHERE username = ? AND password = ?" +var stmt: OpaquePointer? +// okid:swift-potential-sqlite-injection +if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK { + sqlite3_bind_text(stmt, 1, username, -1, nil) + sqlite3_bind_text(stmt, 2, password, -1, nil) + if sqlite3_step(stmt) == SQLITE_DONE { + // SUCCESS + } +} + +sqlite3_finalize(stmt) +sqlite3_close(db) + +let sql = "SELECT * FROM semgrep_users WHERE username = 'admin' AND password = 'admin'" +// okid:swift-potential-sqlite-injection +let result = sqlite3_exec(db, sql, nil, nil, nil) +sqlite3_close(db) + + +let theUsername = "admin" +let sql = "SELECT * FROM semgrep_users WHERE username = '" + theUsername + "' AND password = 'admin'" +// FP but cant do much about this I dont think +// ruleid:swift-potential-sqlite-injection +let result = sqlite3_exec(db, sql, nil, nil, nil) +sqlite3_close(db) + +let newUser = getUsernameFromServer() +let sql = "SELECT * FROM semgrep_users WHERE username = '" + newUser + "' AND password = 'admin'" +// ruleid:swift-potential-sqlite-injection +let result = sqlite3_exec(db, sql, nil, nil, nil) +sqlite3_close(db) + + +let sql = "SELECT * FROM semgrep_users WHERE username = 'admin' AND password = '" + password + "'" +// ruleid:swift-potential-sqlite-injection +let result = sqlite3_exec(db, sql, nil, nil, nil) +sqlite3_close(db) + + +let sql = "SELECT * FROM semgrep_users WHERE username = ? AND password = '" + password + "'" +// ruleid:swift-potential-sqlite-injection +if sqlite3_prepare_v2(db, sql, -1, &stmt, nil) == SQLITE_OK { + sqlite3_bind_text(stmt, 1, username, -1, nil) + if sqlite3_step(stmt) == SQLITE_DONE { + // SUCCESS + } +} + +sqlite3_finalize(stmt) +sqlite3_close(db) \ No newline at end of file diff --git a/swift/sqllite/sqllite-injection-audit.yaml b/swift/sqllite/sqllite-injection-audit.yaml new file mode 100644 index 0000000000..3603f4b733 --- /dev/null +++ b/swift/sqllite/sqllite-injection-audit.yaml @@ -0,0 +1,43 @@ +rules: +- id: swift-potential-sqlite-injection + message: >- + Potential Client-side SQL injection which has different impacts depending on the SQL use-case. The + impact may include the circumvention of local authentication mechanisms, obtaining of sensitive data + from the app, or manipulation of client-side behavior. It wasn't possible to make certain that the + source is untrusted, but the application should avoid concatenating dynamic data into SQL queries + and should instead leverage parameterized queries. + severity: WARNING + metadata: + likelihood: MEDIUM + impact: MEDIUM + confidence: LOW + category: security + cwe: + - "CWE-89: Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')" + masvs: + - 'MASVS-CODE-4: The app validates and sanitizes all untrusted inputs.' + references: + - https://developer.apple.com/library/archive/documentation/Security/Conceptual/SecureCodingGuide/Articles/ValidatingInput.html + subcategory: + - vuln + technology: + - ios + - macos + languages: + - swift + mode: taint + pattern-sources: + - pattern-either: + - pattern: | + "...\($X)..." + - pattern: | + $SQL = "..." + $X + - pattern: | + $SQL = $X + "..." + pattern-sinks: + - patterns: + - pattern-either: + - pattern: sqlite3_exec($DB, $SQL, ...) + - pattern: sqlite3_prepare_v2($DB, $SQL, ...) + - focus-metavariable: + - $SQL diff --git a/swift/webview/webview-js-window.swift b/swift/webview/webview-js-window.swift new file mode 100644 index 0000000000..8e59a95a93 --- /dev/null +++ b/swift/webview/webview-js-window.swift @@ -0,0 +1,16 @@ +let prefs = WKPreferences() +// ruleid: swift-webview-config-allows-js-open-windows +prefs.JavaScriptCanOpenWindowsAutomatically = true +let config = WKWebViewConfiguration() +config.defaultWebpagePreferences = prefs + +WKWebView(frame: .zero, configuration: config) + +let prefs2 = WKPreferences() +prefs2.JavaScriptCanOpenWindowsAutomatically = true +// okid: swift-webview-config-allows-js-open-windows +prefs2.JavaScriptCanOpenWindowsAutomatically = false +let config = WKWebViewConfiguration() +config.defaultWebpagePreferences = prefs2 + +WKWebView(frame: .zero, configuration: config) \ No newline at end of file diff --git a/swift/webview/webview-js-window.yaml b/swift/webview/webview-js-window.yaml new file mode 100644 index 0000000000..a3b22ca534 --- /dev/null +++ b/swift/webview/webview-js-window.yaml @@ -0,0 +1,58 @@ +rules: +- id: swift-webview-config-allows-js-open-windows + message: >- + Webviews were observed that explictly allow JavaScript in an WKWebview to open windows automatically. + Consider disabling this functionality if not required, following the principle of least privelege. + severity: WARNING + metadata: + likelihood: LOW + impact: LOW + confidence: HIGH + category: security + cwe: + - 'CWE-272: Least Privilege Violation' + masvs: + - 'MASVS-PLATFORM-2: The app uses WebViews securely' + references: + - https://mas.owasp.org/MASVS/controls/MASVS-PLATFORM-2/ + - https://developer.apple.com/documentation/webkit/wkpreferences/1536573-javascriptcanopenwindowsautomati + subcategory: + - audit + technology: + - ios + - macos + languages: + - swift + patterns: + - pattern: | + $P = WKPreferences() + ... + - pattern-either: + - patterns: + - pattern-inside: | + $P.JavaScriptCanOpenWindowsAutomatically = $FALSE + ... + $P.JavaScriptCanOpenWindowsAutomatically = $TRUE + # nosemgrep + - pattern-not-inside: | + ... + $P.JavaScriptCanOpenWindowsAutomatically = $TRUE + ... + $P.JavaScriptCanOpenWindowsAutomatically = $FALSE + - pattern: | + $P.JavaScriptCanOpenWindowsAutomatically = true + - metavariable-regex: + metavariable: $TRUE + regex: ^(true)$ + - metavariable-regex: + metavariable: $TRUE + regex: (.*(?!true)) + - patterns: + - pattern: | + $P.JavaScriptCanOpenWindowsAutomatically = true + # nosemgrep + - pattern-not-inside: | + ... + $P.JavaScriptCanOpenWindowsAutomatically = ... + ... + $P.JavaScriptCanOpenWindowsAutomatically = ...