Merge pull request #11210 from geoffw0/alamofire2

Swift: Add Alamofire model to swift/cleartext-transmission
This commit is contained in:
Geoffrey White
2022-11-23 18:23:44 +00:00
committed by GitHub
5 changed files with 266 additions and 0 deletions

View File

@@ -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

View File

@@ -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.

View File

@@ -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 |

View File

@@ -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 |

View 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)
}