Skip to content

Commit 2b52a44

Browse files
authored
Merge pull request github#11210 from geoffw0/alamofire2
Swift: Add Alamofire model to swift/cleartext-transmission
2 parents 146d246 + 556d68a commit 2b52a44

File tree

5 files changed

+266
-0
lines changed

5 files changed

+266
-0
lines changed

swift/ql/lib/codeql/swift/elements/expr/ApplyExpr.qll

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@ class ApplyExpr extends Generated::ApplyExpr {
1919
/** Gets the method qualifier, if this is applying a method */
2020
Expr getQualifier() { none() }
2121

22+
/**
23+
* Gets the argument of this `ApplyExpr` called `label` (if any).
24+
*/
25+
final Argument getArgumentWithLabel(string label) {
26+
result = this.getAnArgument() and
27+
result.getLabel() = label
28+
}
29+
2230
override string toString() {
2331
result = "call to " + this.getStaticTarget().toString()
2432
or

swift/ql/src/queries/Security/CWE-311/CleartextTransmission.ql

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,24 @@ class Url extends Transmitted {
5454
}
5555
}
5656

57+
/**
58+
* An `Expr` that transmitted through the Alamofire library.
59+
*/
60+
class AlamofireTransmitted extends Transmitted {
61+
AlamofireTransmitted() {
62+
// sinks are the first argument containing the URL, and the `parameters`
63+
// and `headers` arguments to appropriate methods of `Session`.
64+
exists(CallExpr call, string fName |
65+
call.getStaticTarget().(MethodDecl).hasQualifiedName("Session", fName) and
66+
fName.regexpMatch("(request|streamRequest|download)\\(.*") and
67+
(
68+
call.getArgument(0).getExpr() = this or
69+
call.getArgumentWithLabel(["headers", "parameters"]).getExpr() = this
70+
)
71+
)
72+
}
73+
}
74+
5775
/**
5876
* A taint configuration from sensitive information to expressions that are
5977
* transmitted over a network.

swift/ql/test/query-tests/Security/CWE-311/CleartextTransmission.expected

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
edges
2+
| testAlamofire.swift:150:45:150:45 | password : | testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... |
3+
| testAlamofire.swift:152:51:152:51 | password : | testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... |
4+
| testAlamofire.swift:154:38:154:38 | email : | testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... |
25
| testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : | file://:0:0:0:0 | [summary] to write: return (return) in init(_:) : |
36
| testSend.swift:33:14:33:32 | call to init(_:) : | testSend.swift:37:19:37:19 | data2 |
47
| testSend.swift:33:19:33:19 | passwordPlain : | testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : |
@@ -13,6 +16,12 @@ edges
1316
| testURL.swift:16:55:16:55 | credit_card_no : | testURL.swift:16:22:16:55 | ... .+(_:_:) ... |
1417
nodes
1518
| file://:0:0:0:0 | [summary] to write: return (return) in init(_:) : | semmle.label | [summary] to write: return (return) in init(_:) : |
19+
| testAlamofire.swift:150:13:150:45 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
20+
| testAlamofire.swift:150:45:150:45 | password : | semmle.label | password : |
21+
| testAlamofire.swift:152:19:152:51 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
22+
| testAlamofire.swift:152:51:152:51 | password : | semmle.label | password : |
23+
| testAlamofire.swift:154:14:154:46 | ... .+(_:_:) ... | semmle.label | ... .+(_:_:) ... |
24+
| testAlamofire.swift:154:38:154:38 | email : | semmle.label | email : |
1625
| testSend.swift:5:5:5:29 | [summary param] 0 in init(_:) : | semmle.label | [summary param] 0 in init(_:) : |
1726
| testSend.swift:29:19:29:19 | passwordPlain | semmle.label | passwordPlain |
1827
| testSend.swift:33:14:33:32 | call to init(_:) : | semmle.label | call to init(_:) : |
@@ -36,6 +45,9 @@ subpaths
3645
| 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(_:) : |
3746
| 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(_:) : |
3847
#select
48+
| 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 |
49+
| 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 |
50+
| 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 |
3951
| 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 |
4052
| 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 |
4153
| 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 |

swift/ql/test/query-tests/Security/CWE-311/SensitiveExprs.expected

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,13 @@
1+
| testAlamofire.swift:150:45:150:45 | password | label:password, type:credential |
2+
| testAlamofire.swift:152:51:152:51 | password | label:password, type:credential |
3+
| testAlamofire.swift:154:38:154:38 | email | label:email, type:private information |
4+
| testAlamofire.swift:159:26:159:26 | email | label:email, type:private information |
5+
| testAlamofire.swift:171:35:171:35 | email | label:email, type:private information |
6+
| testAlamofire.swift:177:35:177:35 | email | label:email, type:private information |
7+
| testAlamofire.swift:187:65:187:65 | password | label:password, type:credential |
8+
| testAlamofire.swift:195:64:195:64 | password | label:password, type:credential |
9+
| testAlamofire.swift:205:62:205:62 | password | label:password, type:credential |
10+
| testAlamofire.swift:213:65:213:65 | password | label:password, type:credential |
111
| testCoreData.swift:48:15:48:15 | password | label:password, type:credential |
212
| testCoreData.swift:51:24:51:24 | password | label:password, type:credential |
313
| testCoreData.swift:58:15:58:15 | password | label:password, type:credential |
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
2+
// --- Foundation stubs ---
3+
4+
class NSObject {
5+
}
6+
7+
struct URL {
8+
}
9+
10+
struct URLRequest {
11+
}
12+
13+
class URLResponse: NSObject {
14+
}
15+
16+
class HTTPURLResponse : URLResponse {
17+
}
18+
19+
// --- Alamofire stubs ---
20+
21+
protocol URLConvertible {
22+
}
23+
24+
extension String: URLConvertible {
25+
}
26+
27+
struct HTTPMethod {
28+
static let get = HTTPMethod(rawValue: "GET")
29+
static let post = HTTPMethod(rawValue: "POST")
30+
31+
init(rawValue: String) {}
32+
}
33+
34+
struct HTTPHeaders {
35+
init(_ dictionary: [String: String]) {}
36+
37+
mutating func add(name: String, value: String) {}
38+
mutating func update(name: String, value: String) {}
39+
}
40+
41+
extension HTTPHeaders: ExpressibleByDictionaryLiteral {
42+
public init(dictionaryLiteral elements: (String, String)...) {}
43+
}
44+
45+
typealias Parameters = [String: Any]
46+
47+
protocol ParameterEncoding {
48+
}
49+
50+
struct URLEncoding: ParameterEncoding {
51+
static var `default`: URLEncoding { URLEncoding() }
52+
}
53+
54+
protocol ParameterEncoder {
55+
}
56+
57+
class URLEncodedFormParameterEncoder: ParameterEncoder {
58+
static var `default`: URLEncodedFormParameterEncoder { URLEncodedFormParameterEncoder() }
59+
}
60+
61+
protocol RequestInterceptor {
62+
}
63+
64+
class Request {
65+
}
66+
67+
class DataRequest: Request {
68+
}
69+
70+
final class DataStreamRequest: Request {
71+
}
72+
73+
class DownloadRequest: Request {
74+
struct Options: OptionSet {
75+
let rawValue: Int
76+
77+
init(rawValue: Int) {
78+
self.rawValue = rawValue
79+
}
80+
}
81+
82+
typealias Destination =
83+
(_ temporaryURL: URL, _ response: HTTPURLResponse) ->
84+
(destinationURL: URL, options: Options)
85+
}
86+
87+
class Session {
88+
static let `default` = Session()
89+
90+
typealias RequestModifier = (inout URLRequest) throws -> Void
91+
92+
func request(
93+
_ convertible: URLConvertible,
94+
method: HTTPMethod = .get,
95+
parameters: Parameters? = nil,
96+
encoding: ParameterEncoding = URLEncoding.default,
97+
headers: HTTPHeaders? = nil,
98+
interceptor: RequestInterceptor? = nil,
99+
requestModifier: RequestModifier? = nil) -> DataRequest {
100+
return DataRequest()
101+
}
102+
103+
func request<Parameters: Encodable>(
104+
_ convertible: URLConvertible,
105+
method: HTTPMethod = .get,
106+
parameters: Parameters? = nil,
107+
encoder: ParameterEncoder = URLEncodedFormParameterEncoder.default,
108+
headers: HTTPHeaders? = nil,
109+
interceptor: RequestInterceptor? = nil,
110+
requestModifier: RequestModifier? = nil) -> DataRequest {
111+
return DataRequest()
112+
}
113+
114+
func streamRequest(
115+
_ convertible: URLConvertible,
116+
method: HTTPMethod = .get,
117+
headers: HTTPHeaders? = nil,
118+
automaticallyCancelOnStreamError: Bool = false,
119+
interceptor: RequestInterceptor? = nil,
120+
requestModifier: RequestModifier? = nil) -> DataStreamRequest {
121+
return DataStreamRequest()
122+
}
123+
124+
func download(
125+
_ convertible: URLConvertible,
126+
method: HTTPMethod = .get,
127+
parameters: Parameters? = nil,
128+
encoding: ParameterEncoding = URLEncoding.default,
129+
headers: HTTPHeaders? = nil,
130+
interceptor: RequestInterceptor? = nil,
131+
requestModifier: RequestModifier? = nil,
132+
to destination: DownloadRequest.Destination? = nil) -> DownloadRequest {
133+
return DownloadRequest()
134+
}
135+
136+
// (there are many more variants of `request`, `streamRequest` and `download`)
137+
}
138+
139+
let AF = Session.default
140+
141+
// --- tests ---
142+
143+
struct MyEncodable: Encodable {
144+
let value: String
145+
}
146+
147+
func test1(username: String, password: String, email: String, harmless: String) {
148+
// sensitive data in URL
149+
150+
AF.request("http://example.com/login?p=" + password) // BAD
151+
AF.request("http://example.com/login?h=" + harmless) // GOOD (not sensitive)
152+
AF.streamRequest("http://example.com/login?p=" + password) // BAD
153+
AF.streamRequest("http://example.com/login?h=" + harmless) // GOOD (not sensitive)
154+
AF.download("http://example.com/" + email + ".html") // BAD
155+
AF.download("http://example.com/" + harmless + ".html") // GOOD (not sensitive)
156+
157+
// sensitive data in parameters
158+
159+
let params1 = ["value": email]
160+
let params2 = ["value": harmless]
161+
162+
AF.request("http://example.com/", parameters: params1) // BAD [NOT DETECTED]
163+
AF.request("http://example.com/", parameters: params2) // GOOD (not sensitive)
164+
AF.request("http://example.com/", parameters: params1, encoding: URLEncoding.default) // BAD [NOT DETECTED]
165+
AF.request("http://example.com/", parameters: params2, encoding: URLEncoding.default) // GOOD (not sensitive)
166+
AF.request("http://example.com/", parameters: params1, encoder: URLEncodedFormParameterEncoder.default) // BAD [NOT DETECTED]
167+
AF.request("http://example.com/", parameters: params2, encoder: URLEncodedFormParameterEncoder.default) // GOOD (not sensitive)
168+
AF.download("http://example.com/", parameters: params1) // BAD [NOT DETECTED]
169+
AF.download("http://example.com/", parameters: params2) // GOOD (not sensitive)
170+
171+
let params3 = ["values": ["...", email, "..."]]
172+
let params4 = ["values": ["...", harmless, "..."]]
173+
174+
AF.request("http://example.com/", method:.post, parameters: params3) // BAD [NOT DETECTED]
175+
AF.request("http://example.com/", method:.post, parameters: params4) // GOOD (not sensitive)
176+
177+
let params5 = MyEncodable(value: email)
178+
let params6 = MyEncodable(value: harmless)
179+
180+
AF.request("http://example.com/", parameters: params5) // BAD [NOT DETECTED]
181+
AF.request("http://example.com/", parameters: params6) // GOOD (not sensitive)
182+
183+
// request headers
184+
// - in real usage a password here would normally be base64 encoded for transmission
185+
// - the risk is greatly reduced (but not eliminated) if HTTPS is used
186+
187+
let headers1: HTTPHeaders = ["Authorization": username + ":" + password]
188+
let headers2: HTTPHeaders = ["Value": harmless]
189+
190+
AF.request("http://example.com/", headers: headers1) // BAD [NOT DETECTED]
191+
AF.request("http://example.com/", headers: headers2) // GOOD (not sensitive)
192+
AF.streamRequest("http://example.com/", headers: headers1) // BAD [NOT DETECTED]
193+
AF.streamRequest("http://example.com/", headers: headers2) // GOOD (not sensitive)
194+
195+
let headers3 = HTTPHeaders(["Authorization": username + ":" + password])
196+
let headers4 = HTTPHeaders(["Value": harmless])
197+
198+
AF.request("http://example.com/", headers: headers3) // BAD [NOT DETECTED]
199+
AF.request("http://example.com/", headers: headers4) // GOOD (not sensitive)
200+
AF.download("http://example.com/", headers: headers1) // BAD [NOT DETECTED]
201+
AF.download("http://example.com/", headers: headers2) // GOOD (not sensitive)
202+
203+
var headers5 = HTTPHeaders([:])
204+
var headers6 = HTTPHeaders([:])
205+
headers5.add(name: "Authorization", value: username + ":" + password)
206+
headers6.add(name: "Data", value: harmless)
207+
208+
AF.request("http://example.com/", headers: headers5) // BAD [NOT DETECTED]
209+
AF.request("http://example.com/", headers: headers6) // GOOD (not sensitive)
210+
211+
var headers7 = HTTPHeaders([:])
212+
var headers8 = HTTPHeaders([:])
213+
headers7.update(name: "Authorization", value: username + ":" + password)
214+
headers8.update(name: "Data", value: harmless)
215+
216+
AF.request("http://example.com/", headers: headers7) // BAD [NOT DETECTED]
217+
AF.request("http://example.com/", headers: headers8) // GOOD (not sensitive)
218+
}

0 commit comments

Comments
 (0)