CVE-2017-5887 (published 2017-04-06T14:59Z)

WebSocket.swift in Starscream before 2.0.4 allows an SSL Pinning bypass because pinning occurs in the stream function (this is too late; pinning should occur in the initStreamsWithData function).

Severity: MEDIUM
CVSS v2 base score: 5.0 (AV:N/AC:L/Au:N/C:N/I:P/A:N)
CVSS v3 base score: 7.5, HIGH (CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:H/A:N)

Weakness information:
cwe_id: CWE-295
cwe_name: Improper Certificate Validation
description: The product does not validate, or incorrectly validates, a certificate.
extended_description: When a certificate is invalid or malicious, it might allow an attacker to spoof a trusted entity by interfering in the communication path between the host and client. The product might connect to a malicious host while believing it is a trusted host, or the product might be deceived into accepting spoofed data that appears to originate from a trusted host.
         

Fix information:
repo_url: https://github.com/daltoniam/Starscream
commit_details: repo_url: https://github.com/daltoniam/Starscream
author: daltoniam
author_date: 2017-03-14 13:12:39-05:00
msg: some cleanup and SSL pinning fix
repository: repo_url: https://github.com/daltoniam/Starscream
repo_name: daltoniam/Starscream
description: Websockets in swift for iOS and OSX
repo_language: Swift
owner: daltoniam
file_changes: filename: WebSocket.swift
change_type: ModificationType.MODIFY
diff: @@ -258,7 +258,6 @@ open class WebSocket : NSObject, StreamDelegate {
      Private method that starts the connection.
      */
     private func createHTTPRequest() {
-
         let urlRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, "GET" as CFString,
                                                     url as CFURL, kCFHTTPVersion1_1).takeRetainedValue()
 
@@ -383,14 +382,29 @@ open class WebSocket : NSObject, StreamDelegate {
                 guard !sOperation.isCancelled else { return }
                 out -= 100
                 if out < 0 {
-                    self?.cleanupStream()
+                    WebSocket.sharedWorkQueue.async {
+                        self?.cleanupStream()
+                    }
                     self?.doDisconnect(self?.errorWithDetail("write wait timed out", code: 2))
                     return
                 } else if outStream.streamError != nil {
                     return // disconnectStream will be called.
                 }
             }
-            guard !sOperation.isCancelled else { return }
+            guard !sOperation.isCancelled, let s = self else { return }
+            // Do the pinning now if needed
+            if let sec = s.security, !s.certValidated {
+                let trust = outStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
+                let domain = outStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
+                s.certValidated = sec.isValid(trust, domain: domain)
+                if !s.certValidated {
+                    WebSocket.sharedWorkQueue.async {
+                        let error = s.errorWithDetail("Invalid SSL certificate", code: 1)
+                        s.disconnectStream(error)
+                    }
+                    return
+                }
+            }
             outStream.write(bytes, maxLength: data.count)
         }
         writeQueue.addOperation(operation)
@@ -400,17 +414,6 @@ open class WebSocket : NSObject, StreamDelegate {
      Delegate for the stream methods. Processes incoming bytes
      */
     open func stream(_ aStream: Stream, handle eventCode: Stream.Event) {
-        if let sec = security, !certValidated && [.hasBytesAvailable, .hasSpaceAvailable].contains(eventCode) {
-            let trust = aStream.property(forKey: kCFStreamPropertySSLPeerTrust as Stream.PropertyKey) as! SecTrust
-            let domain = aStream.property(forKey: kCFStreamSSLPeerName as Stream.PropertyKey) as? String
-            if sec.isValid(trust, domain: domain) {
-                certValidated = true
-            } else {
-                let error = errorWithDetail("Invalid SSL certificate", code: 1)
-                disconnectStream(error)
-                return
-            }
-        }
         if eventCode == .hasBytesAvailable {
             if aStream == inputStream {
                 processInputStream()
@@ -945,6 +948,7 @@ open class WebSocket : NSObject, StreamDelegate {
         readyToWrite = false
         mutex.unlock()
         cleanupStream()
+        writeQueue.cancelAllOperations()
     }
 
 }

programming_language: Swift