lcimeni / tiktok-ios

0 stars 0 forks source link

NowSecure dynamic analysis: Failure to Validate Certificate Hostname Allows Data to be Exposed and Modified Remotely #94

Open lcimeni opened 2 years ago

lcimeni commented 2 years ago

Finding Description

The application does not have proper hostname validation implemented.

A remote attacker with access to the local or upstream network as the user could use a certificate from a valid certificate authority to intercept and modify traffic from these endpoints.

Steps to Reproduce

There are several tools a developer can use to evaluate network issues from the a mobile app perspective. One tool that is useful for troubleshooting these process is Android Studio's Network Profiler: https://developer.android.com/studio/profile/network-profiler. Another option is to use Mitmproxy, a web proxy designed to help developers troubleshoot and test their apps: https://mitmproxy.org/.

In addition to the use of a network debugging tool, developers will need access to a valid Certificate Authority-issued certificate for a domain that does not match the app's endpoints. This allows testers to determine if the app properly checks to see if the certificate is signed by a CA and is valid (versus a self-signed cert) but then fails to ensure the cert is configured for that specific hostname. Interception of the traffic and follow-on injection of a valid certification with the incorrect hostname will show if the app is vulnerable to this flaw.

Business Impact

Applications with this vulnerability will experience a substantially larger risk to data integrity and confidentiality. When Hostname Validation is not performed, an attacker will be able to observe the data sent by users and modify what they send and receive from the backend. This can lead to stolen user information and malicious changes to the app.

Remediation Resources

Recommended Fix

While SSL/TLS was observed protecting the application's sensitive data in transit, issues relating to improperly validating the TLS handshake were discovered. The application did not validate the domain name before establishing the TLS session. This process is known as Hostname Verification.

Properly validate the SSL/TLS certificate to ensure it is signed by a trusted certificate authority (CA) as well as contains the correct hostname. More details and code snippets can be found at https://developer.android.com/training/articles/security-ssl or https://developer.android.com/training/articles/security-config for Android.

For applications that must include compromised Certificate Authorities and experience complex phishing attacks against their users, additional security controls may be necessary to provide network protections. One such approach is to use certificate pinning to mitigate the possibility of SSL/TLS weaknesses. Certificate pinning ensures that the client checks the server's certificate against a known copy of that certificate. Bundling the server's certificate inside the application and ensuring any SSL/TLS requests first validate that the server's certificate exactly matches the bundle's certificate is a method of accomplishing certificate pinning.

For some apps, certificate pinning may be impossible to perform. If the app allows users to enter in their own domain names to connect to services, then no opportunity exists to embed a certificate. However, if the app is intended to connect to a known server or set of servers, all of the information is available to guarantee that the client is indeed talking directly to the server and without a man in the middle eavesdropping. Please note that certificate pinning may not be suitable for organizations who can not control the server side certificate used in TLS validation or are not able to perform the certificate rotations in a timely manner to accommodate certificate expiration requirements.

Details and code snippets can be found at https://developer.android.com/training/articles/security-ssl.

Certificate transparency is an alternative to certificate pinning that can also be used to accomplish similar security protections without the same operational work. Certificate transparency is used to audit that a certificate has been issued legitimately by a certificate authority. This method prevents scenarios where a certificate was issued to a malicious actor of a domain the attacker does not own. Additional information can be found at https://github.com/babylonhealth/certificate-transparency-android.

Code Samples

Bad Code Sample (.swift)

func makeRequest() {
let url = URL(string: "https://badname.domain.com")!
let request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 60.0)
self.session.dataTask(with: request) { (data, response, error) in
if let error = error as NSError? {
NSLog("NSerror %@ / %d", error.domain, error.code)
return
}
let response = response as! HTTPURLResponse
let data = data!
NSLog("response received %d bytes with status code %d", data.count, response.statusCode)
}.resume()
}

func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let trust = challenge.protectionSpace.serverTrust!
NSLog("self-signed certificate: %@", trust.isSelfSigned.flatMap { "\($0)" } ?? "unknown" )
}
completionHandler(.performDefaultHandling, nil)
}
extension SecTrust {

var isSelfSigned: Bool? {
guard SecTrustGetCertificateCount(self) == 1 else {
return false
}
guard let cert = SecTrustGetCertificateAtIndex(self, 0) else {
return nil
}
return cert.isSelfSigned
}
}

extension SecCertificate {

var isSelfSigned: Bool? {
guard
let subject = SecCertificateCopyNormalizedSubjectSequence(self),
let issuer = SecCertificateCopyNormalizedIssuerSequence(self)
else {
return nil
}
return subject == issuer
}
}

Good Code Sample (.swift)

//Certificate Pinning
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
if let serverTrust = challenge.protectionSpace.serverTrust {
var secresult = SecTrustResultType.invalid
let status = SecTrustEvaluate(serverTrust, &secresult)

if (errSecSuccess == status) {
if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
let serverCertificateData = SecCertificateCopyData(serverCertificate)
let data = CFDataGetBytePtr(serverCertificateData);
let size = CFDataGetLength(serverCertificateData);
let cert1 = NSData(bytes: data, length: size)
let file_der = Bundle.main.path(forResource: "name-of-cert-file", ofType: "cer")

if let file = file_der {
if let cert2 = NSData(contentsOfFile: file) {
if cert1.isEqual(to: cert2 as Data) { completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust:serverTrust))
return
}
}
}
}
}
}
}

// Pinning failed completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
}

//Alamofire
let pathToCert = Bundle.main.path(forResource: "name-of-cert-file", ofType: "cer")
let localCertificate : NSData = NSData(contentsOfFile: pathToCert! )!

let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates : [SecCertificateCreateWithData(nil, localCertificate) !],
validateCertificateChain : true,
validateHost : true
)

let serverTrustPolicies = [
"my-server.com" : serverTrustPolicy
]

let sessionManager = SessionManager (
serverTrustPolicyManager : ServerTrustPolicyManager(policies : serverTrustPolicies)
)

Additional Guidance

Risk and Regulatory Information

Severity: high CVSS: 7

Application

See more detail in the NowSecure Report