mirror of
https://github.com/github/codeql.git
synced 2026-07-21 03:08:25 +02:00
Merge pull request #11210 from geoffw0/alamofire2
Swift: Add Alamofire model to swift/cleartext-transmission
This commit is contained in:
@@ -19,6 +19,14 @@ class ApplyExpr extends Generated::ApplyExpr {
|
||||
/** Gets the method qualifier, if this is applying a method */
|
||||
Expr getQualifier() { none() }
|
||||
|
||||
/**
|
||||
* Gets the argument of this `ApplyExpr` called `label` (if any).
|
||||
*/
|
||||
final Argument getArgumentWithLabel(string label) {
|
||||
result = this.getAnArgument() and
|
||||
result.getLabel() = label
|
||||
}
|
||||
|
||||
override string toString() {
|
||||
result = "call to " + this.getStaticTarget().toString()
|
||||
or
|
||||
|
||||
@@ -54,6 +54,24 @@ class Url extends Transmitted {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An `Expr` that transmitted through the Alamofire library.
|
||||
*/
|
||||
class AlamofireTransmitted extends Transmitted {
|
||||
AlamofireTransmitted() {
|
||||
// sinks are the first argument containing the URL, and the `parameters`
|
||||
// and `headers` arguments to appropriate methods of `Session`.
|
||||
exists(CallExpr call, string fName |
|
||||
call.getStaticTarget().(MethodDecl).hasQualifiedName("Session", fName) and
|
||||
fName.regexpMatch("(request|streamRequest|download)\\(.*") and
|
||||
(
|
||||
call.getArgument(0).getExpr() = this or
|
||||
call.getArgumentWithLabel(["headers", "parameters"]).getExpr() = this
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A taint configuration from sensitive information to expressions that are
|
||||
* transmitted over a network.
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
edges
|
||||
| testAlamofire.swift:150:45:150:45 | password : | testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... |
|
||||
| testAlamofire.swift:152:51:152:51 | password : | testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... |
|
||||
| testAlamofire.swift:154:38:154:38 | email : | testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... |
|
||||
| testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : | file://:0:0:0:0 | [summary] to write: return (return) in init(_:) : |
|
||||
| testSend.swift:33:14:33:32 | call to init(_:) : | testSend.swift:37:19:37:19 | data2 |
|
||||
| testSend.swift:33:19:33:19 | passwordPlain : | testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : |
|
||||
@@ -13,6 +16,12 @@ edges
|
||||
| testURL.swift:16:55:16:55 | credit_card_no : | testURL.swift:16:22:16:55 | ... .+(_:_:) ... |
|
||||
nodes
|
||||
| file://:0:0:0:0 | [summary] to write: return (return) in init(_:) : | semmle.label | [summary] to write: return (return) in init(_:) : |
|
||||
| testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||
| testAlamofire.swift:150:45:150:45 | password : | semmle.label | password : |
|
||||
| testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||
| testAlamofire.swift:152:51:152:51 | password : | semmle.label | password : |
|
||||
| testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
|
||||
| testAlamofire.swift:154:38:154:38 | email : | semmle.label | email : |
|
||||
| testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : | semmle.label | [summary param] 0 in init(_:) : |
|
||||
| testSend.swift:29:19:29:19 | passwordPlain | semmle.label | passwordPlain |
|
||||
| testSend.swift:33:14:33:32 | call to init(_:) : | semmle.label | call to init(_:) : |
|
||||
@@ -36,6 +45,9 @@ subpaths
|
||||
| testSend.swift:33:19:33:19 | passwordPlain : | testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : | file://:0:0:0:0 | [summary] to write: return (return) in init(_:) : | testSend.swift:33:14:33:32 | call to init(_:) : |
|
||||
| testSend.swift:47:17:47:17 | password : | testSend.swift:41:10:41:18 | data : | testSend.swift:41:45:41:45 | data : | testSend.swift:47:13:47:25 | call to pad(_:) : |
|
||||
#select
|
||||
| testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | testAlamofire.swift:150:45:150:45 | password : | testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:150:45:150:45 | password : | password |
|
||||
| testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | testAlamofire.swift:152:51:152:51 | password : | testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:152:51:152:51 | password : | password |
|
||||
| testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | testAlamofire.swift:154:38:154:38 | email : | testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | This operation transmits '... .+(_:_:) ...', which may contain unencrypted sensitive data from $@. | testAlamofire.swift:154:38:154:38 | email : | email |
|
||||
| testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | testSend.swift:29:19:29:19 | passwordPlain | This operation transmits 'passwordPlain', which may contain unencrypted sensitive data from $@. | testSend.swift:29:19:29:19 | passwordPlain | passwordPlain |
|
||||
| testSend.swift:37:19:37:19 | data2 | testSend.swift:33:19:33:19 | passwordPlain : | testSend.swift:37:19:37:19 | data2 | This operation transmits 'data2', which may contain unencrypted sensitive data from $@. | testSend.swift:33:19:33:19 | passwordPlain : | passwordPlain |
|
||||
| testSend.swift:52:27:52:27 | str1 | testSend.swift:45:13:45:13 | password : | testSend.swift:52:27:52:27 | str1 | This operation transmits 'str1', which may contain unencrypted sensitive data from $@. | testSend.swift:45:13:45:13 | password : | password |
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
| testAlamofire.swift:150:45:150:45 | password | label:password, type:credential |
|
||||
| testAlamofire.swift:152:51:152:51 | password | label:password, type:credential |
|
||||
| testAlamofire.swift:154:38:154:38 | email | label:email, type:private information |
|
||||
| testAlamofire.swift:159:26:159:26 | email | label:email, type:private information |
|
||||
| testAlamofire.swift:171:35:171:35 | email | label:email, type:private information |
|
||||
| testAlamofire.swift:177:35:177:35 | email | label:email, type:private information |
|
||||
| testAlamofire.swift:187:65:187:65 | password | label:password, type:credential |
|
||||
| testAlamofire.swift:195:64:195:64 | password | label:password, type:credential |
|
||||
| testAlamofire.swift:205:62:205:62 | password | label:password, type:credential |
|
||||
| testAlamofire.swift:213:65:213:65 | password | label:password, type:credential |
|
||||
| testCoreData.swift:48:15:48:15 | password | label:password, type:credential |
|
||||
| testCoreData.swift:51:24:51:24 | password | label:password, type:credential |
|
||||
| testCoreData.swift:58:15:58:15 | password | label:password, type:credential |
|
||||
|
||||
218
swift/ql/test/query-tests/Security/CWE-311/testAlamofire.swift
Normal file
218
swift/ql/test/query-tests/Security/CWE-311/testAlamofire.swift
Normal file
@@ -0,0 +1,218 @@
|
||||
|
||||
// --- Foundation stubs ---
|
||||
|
||||
class NSObject {
|
||||
}
|
||||
|
||||
struct URL {
|
||||
}
|
||||
|
||||
struct URLRequest {
|
||||
}
|
||||
|
||||
class URLResponse: NSObject {
|
||||
}
|
||||
|
||||
class HTTPURLResponse : URLResponse {
|
||||
}
|
||||
|
||||
// --- Alamofire stubs ---
|
||||
|
||||
protocol URLConvertible {
|
||||
}
|
||||
|
||||
extension String: URLConvertible {
|
||||
}
|
||||
|
||||
struct HTTPMethod {
|
||||
static let get = HTTPMethod(rawValue: "GET")
|
||||
static let post = HTTPMethod(rawValue: "POST")
|
||||
|
||||
init(rawValue: String) {}
|
||||
}
|
||||
|
||||
struct HTTPHeaders {
|
||||
init(_ dictionary: [String: String]) {}
|
||||
|
||||
mutating func add(name: String, value: String) {}
|
||||
mutating func update(name: String, value: String) {}
|
||||
}
|
||||
|
||||
extension HTTPHeaders: ExpressibleByDictionaryLiteral {
|
||||
public init(dictionaryLiteral elements: (String, String)...) {}
|
||||
}
|
||||
|
||||
typealias Parameters = [String: Any]
|
||||
|
||||
protocol ParameterEncoding {
|
||||
}
|
||||
|
||||
struct URLEncoding: ParameterEncoding {
|
||||
static var `default`: URLEncoding { URLEncoding() }
|
||||
}
|
||||
|
||||
protocol ParameterEncoder {
|
||||
}
|
||||
|
||||
class URLEncodedFormParameterEncoder: ParameterEncoder {
|
||||
static var `default`: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() }
|
||||
}
|
||||
|
||||
protocol RequestInterceptor {
|
||||
}
|
||||
|
||||
class Request {
|
||||
}
|
||||
|
||||
class DataRequest: Request {
|
||||
}
|
||||
|
||||
final class DataStreamRequest: Request {
|
||||
}
|
||||
|
||||
class DownloadRequest: Request {
|
||||
struct Options: OptionSet {
|
||||
let rawValue: Int
|
||||
|
||||
init(rawValue: Int) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
}
|
||||
|
||||
typealias Destination =
|
||||
(_ temporaryURL: URL, _ response: HTTPURLResponse) ->
|
||||
(destinationURL: URL, options: Options)
|
||||
}
|
||||
|
||||
class Session {
|
||||
static let `default` = Session()
|
||||
|
||||
typealias RequestModifier = (inout URLRequest) throws -> Void
|
||||
|
||||
func request(
|
||||
_ convertible: URLConvertible,
|
||||
method: HTTPMethod = .get,
|
||||
parameters: Parameters? = nil,
|
||||
encoding: ParameterEncoding = URLEncoding.default,
|
||||
headers: HTTPHeaders? = nil,
|
||||
interceptor: RequestInterceptor? = nil,
|
||||
requestModifier: RequestModifier? = nil) -> DataRequest {
|
||||
return DataRequest()
|
||||
}
|
||||
|
||||
func request<Parameters: Encodable>(
|
||||
_ convertible: URLConvertible,
|
||||
method: HTTPMethod = .get,
|
||||
parameters: Parameters? = nil,
|
||||
encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
|
||||
headers: HTTPHeaders? = nil,
|
||||
interceptor: RequestInterceptor? = nil,
|
||||
requestModifier: RequestModifier? = nil) -> DataRequest {
|
||||
return DataRequest()
|
||||
}
|
||||
|
||||
func streamRequest(
|
||||
_ convertible: URLConvertible,
|
||||
method: HTTPMethod = .get,
|
||||
headers: HTTPHeaders? = nil,
|
||||
automaticallyCancelOnStreamError: Bool = false,
|
||||
interceptor: RequestInterceptor? = nil,
|
||||
requestModifier: RequestModifier? = nil) -> DataStreamRequest {
|
||||
return DataStreamRequest()
|
||||
}
|
||||
|
||||
func download(
|
||||
_ convertible: URLConvertible,
|
||||
method: HTTPMethod = .get,
|
||||
parameters: Parameters? = nil,
|
||||
encoding: ParameterEncoding = URLEncoding.default,
|
||||
headers: HTTPHeaders? = nil,
|
||||
interceptor: RequestInterceptor? = nil,
|
||||
requestModifier: RequestModifier? = nil,
|
||||
to destination: DownloadRequest.Destination? = nil) -> DownloadRequest {
|
||||
return DownloadRequest()
|
||||
}
|
||||
|
||||
// (there are many more variants of `request`, `streamRequest` and `download`)
|
||||
}
|
||||
|
||||
let AF = Session.default
|
||||
|
||||
// --- tests ---
|
||||
|
||||
struct MyEncodable: Encodable {
|
||||
let value: String
|
||||
}
|
||||
|
||||
func test1(username: String, password: String, email: String, harmless: String) {
|
||||
// sensitive data in URL
|
||||
|
||||
AF.request("http://example.com/login?p=" + password) // BAD
|
||||
AF.request("http://example.com/login?h=" + harmless) // GOOD (not sensitive)
|
||||
AF.streamRequest("http://example.com/login?p=" + password) // BAD
|
||||
AF.streamRequest("http://example.com/login?h=" + harmless) // GOOD (not sensitive)
|
||||
AF.download("http://example.com/" + email + ".html") // BAD
|
||||
AF.download("http://example.com/" + harmless + ".html") // GOOD (not sensitive)
|
||||
|
||||
// sensitive data in parameters
|
||||
|
||||
let params1 = ["value": email]
|
||||
let params2 = ["value": harmless]
|
||||
|
||||
AF.request("http://example.com/", parameters: params1) // BAD [NOT DETECTED]
|
||||
AF.request("http://example.com/", parameters: params2) // GOOD (not sensitive)
|
||||
AF.request("http://example.com/", parameters: params1, encoding: URLEncoding.default) // BAD [NOT DETECTED]
|
||||
AF.request("http://example.com/", parameters: params2, encoding: URLEncoding.default) // GOOD (not sensitive)
|
||||
AF.request("http://example.com/", parameters: params1, encoder: URLEncodedFormParameterEncoder.default) // BAD [NOT DETECTED]
|
||||
AF.request("http://example.com/", parameters: params2, encoder: URLEncodedFormParameterEncoder.default) // GOOD (not sensitive)
|
||||
AF.download("http://example.com/", parameters: params1) // BAD [NOT DETECTED]
|
||||
AF.download("http://example.com/", parameters: params2) // GOOD (not sensitive)
|
||||
|
||||
let params3 = ["values": ["...", email, "..."]]
|
||||
let params4 = ["values": ["...", harmless, "..."]]
|
||||
|
||||
AF.request("http://example.com/", method:.post, parameters: params3) // BAD [NOT DETECTED]
|
||||
AF.request("http://example.com/", method:.post, parameters: params4) // GOOD (not sensitive)
|
||||
|
||||
let params5 = MyEncodable(value: email)
|
||||
let params6 = MyEncodable(value: harmless)
|
||||
|
||||
AF.request("http://example.com/", parameters: params5) // BAD [NOT DETECTED]
|
||||
AF.request("http://example.com/", parameters: params6) // GOOD (not sensitive)
|
||||
|
||||
// request headers
|
||||
// - in real usage a password here would normally be base64 encoded for transmission
|
||||
// - the risk is greatly reduced (but not eliminated) if HTTPS is used
|
||||
|
||||
let headers1: HTTPHeaders = ["Authorization": username + ":" + password]
|
||||
let headers2: HTTPHeaders = ["Value": harmless]
|
||||
|
||||
AF.request("http://example.com/", headers: headers1) // BAD [NOT DETECTED]
|
||||
AF.request("http://example.com/", headers: headers2) // GOOD (not sensitive)
|
||||
AF.streamRequest("http://example.com/", headers: headers1) // BAD [NOT DETECTED]
|
||||
AF.streamRequest("http://example.com/", headers: headers2) // GOOD (not sensitive)
|
||||
|
||||
let headers3 = HTTPHeaders(["Authorization": username + ":" + password])
|
||||
let headers4 = HTTPHeaders(["Value": harmless])
|
||||
|
||||
AF.request("http://example.com/", headers: headers3) // BAD [NOT DETECTED]
|
||||
AF.request("http://example.com/", headers: headers4) // GOOD (not sensitive)
|
||||
AF.download("http://example.com/", headers: headers1) // BAD [NOT DETECTED]
|
||||
AF.download("http://example.com/", headers: headers2) // GOOD (not sensitive)
|
||||
|
||||
var headers5 = HTTPHeaders([:])
|
||||
var headers6 = HTTPHeaders([:])
|
||||
headers5.add(name: "Authorization", value: username + ":" + password)
|
||||
headers6.add(name: "Data", value: harmless)
|
||||
|
||||
AF.request("http://example.com/", headers: headers5) // BAD [NOT DETECTED]
|
||||
AF.request("http://example.com/", headers: headers6) // GOOD (not sensitive)
|
||||
|
||||
var headers7 = HTTPHeaders([:])
|
||||
var headers8 = HTTPHeaders([:])
|
||||
headers7.update(name: "Authorization", value: username + ":" + password)
|
||||
headers8.update(name: "Data", value: harmless)
|
||||
|
||||
AF.request("http://example.com/", headers: headers7) // BAD [NOT DETECTED]
|
||||
AF.request("http://example.com/", headers: headers8) // GOOD (not sensitive)
|
||||
}
|
||||
Reference in New Issue
Block a user