Skip to content

MASTG-DEMO-0155: WKNavigationDelegate Accepting Any Server Certificate

Download MASTG-DEMO-0155 IPA Open MASTG-DEMO-0155 Folder Build MASTG-DEMO-0155 IPA

Sample

The code below implements WKNavigationDelegate with a webView(_:didReceive:completionHandler:) override that calls completionHandler(.useCredential, URLCredential(trust: serverTrust)) without first calling SecTrustEvaluateWithError. This accepts any certificate the server presents in a WKWebView, regardless of whether it is expired, self-signed, or issued for the wrong hostname.

The WebView is used to load self-signed.badssl.com, which serves a self-signed certificate that is not trusted by the iOS system trust store. A correctly implemented delegate would cancel this connection.

MastgTest.swift
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
import UIKit
import WebKit

// SUMMARY: This sample demonstrates a custom WKNavigationDelegate that bypasses server certificate validation
// for WebView connections by accepting any certificate without calling SecTrustEvaluateWithError.

class InsecureWKNavigationDelegate: NSObject, WKNavigationDelegate {
    // FAIL: [MASTG-TEST-0397] Accepts any certificate in WebView without evaluating server trust
    func webView(_ webView: WKWebView, didReceive challenge: URLAuthenticationChallenge,
                 completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        guard let serverTrust = challenge.protectionSpace.serverTrust else {
            completionHandler(.cancelAuthenticationChallenge, nil)
            return
        }
        // Insecure: creates a credential from serverTrust without ever calling SecTrustEvaluateWithError.
        // This accepts expired, self-signed, or wrong-hostname certificates in the WebView.
        completionHandler(.useCredential, URLCredential(trust: serverTrust))
    }
}

struct MastgTest {
    private static var navigationDelegate: InsecureWKNavigationDelegate?

    static func mastgTest(completion: @escaping (String) -> Void) {
        DispatchQueue.main.async {
            let navDelegate = InsecureWKNavigationDelegate()
            MastgTest.navigationDelegate = navDelegate

            let webView = WKWebView(frame: .zero)
            webView.navigationDelegate = navDelegate

            let vc = UIViewController()
            vc.view = webView

            guard let presenter = topViewController() else {
                completion("Failed to present: no view controller.")
                return
            }

            presenter.present(vc, animated: true) {
                guard let url = URL(string: "https://self-signed.badssl.com/") else {
                    completion("Invalid URL")
                    return
                }
                // self-signed.badssl.com serves a self-signed certificate not trusted by the system.
                // A correctly implemented delegate would cancel this connection.
                webView.load(URLRequest(url: url))
                completion("The self-signed certificate was accepted because the delegate did not call SecTrustEvaluateWithError.")
            }
        }
    }

    private static func topViewController(base: UIViewController? = nil) -> UIViewController? {
        let root = base ?? UIApplication.shared.connectedScenes
            .compactMap { $0 as? UIWindowScene }
            .flatMap { $0.windows }
            .first { $0.isKeyWindow }?.rootViewController

        if let nav = root as? UINavigationController {
            return topViewController(base: nav.visibleViewController)
        }
        if let tab = root as? UITabBarController {
            return topViewController(base: tab.selectedViewController)
        }
        if let presented = root?.presentedViewController {
            return topViewController(base: presented)
        }
        return root
    }
}

Steps

  1. Extract the app ( Exploring the App Package) and locate the main binary ./Payload/MASTestApp.app/MASTestApp.
  2. Run radare2 (iOS) with the script to identify the WKNavigationDelegate authentication challenge handler and determine whether SecTrustEvaluateWithError is called.
webview_auth_challenge.r2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
e scr.color=false
e asm.bytes=false
e asm.var=false

?e Custom authentication-challenge handlers (functions referencing NSURLAuthenticationChallenge):
is~NSURLAuthenticationChallenge

?e

?e Accessors into the challenge protection space (protectionSpace / serverTrust):
is~protectionSpace,serverTrust

?e

?e xrefs to WKNavigationDelegate challenge handler implementation:
axff @@ `f~WKNavigationDelegate~didReceiveAuthenticationChallenge`~+didreceive

?e

?e SecTrustEvaluateWithError calls  empty output confirms server trust is never evaluated:
is~SecTrustEvaluateWithError

?e

?e xrefs to SecTrustEvaluateWithError:

axt @ sym.imp.SecTrustEvaluateWithError

pdf @ sym.MASTestApp.InsecureWKNavigationDelegate.webView.allocator.didReceive.completionHandler_...o15NSURLCredentialCSgtctF_ > InsecureWKNavigationDelegate.asm
run.sh
1
2
#!/bin/bash
r2 -q -i webview_auth_challenge.r2 -A MASTestApp > output.txt

Observation

The output contains five sections followed by the disassembly file for the handler:

  • Custom authentication-challenge handlers: lists every function whose signature references NSURLAuthenticationChallenge. InsecureWKNavigationDelegate (0x00004000) appears here because it implements a custom challenge handler. This is the broad signal that the app has taken over part of the server trust evaluation, regardless of whether it does so correctly.
  • Accessors into the challenge protection space: the objc_msgSend$protectionSpace (0x00015980) and objc_msgSend$serverTrust (0x000159e0) stubs confirm the app reaches into challenge.protectionSpace.serverTrust, an indication of manual server trust handling.
  • xrefs to WKNavigationDelegate challenge handler implementation: axff on the ObjC challenge handler method shows its call to the Swift implementation. InsecureWKNavigationDelegate's ObjC method (0x41f8) calls the Swift implementation at 0x00004000.
  • SecTrustEvaluateWithError calls: this section is empty. SecTrustEvaluateWithError is not imported into the binary, confirming it is never called by any challenge handler.
  • xrefs to SecTrustEvaluateWithError: empty for the same reason.

Reviewing the disassembled code ( Reviewing Disassembled Objective-C and Swift Code), the disassembly and AI-reversed Swift below confirm the insecure handler:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
Custom authentication-challenge handlers (functions referencing NSURLAuthenticationChallenge):
50   0x00004000 0x00004000 GLOBAL FUNC 0        _$s10MASTestApp28InsecureWKNavigationDelegateC7webView_10didReceive17completionHandlerySo05WKWebG0C_So28NSURLAuthenticationChallengeCySo016NSURLSessionAuthN11DispositionV_So15NSURLCredentialCSgtctF                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    MASTestApp.InsecureWKNavigationDelegate.webView.allocator.didReceive.completionHandler(...o15NSURLCredentialCSgtctF)
51   0x00017b5c 0x00017b5c GLOBAL FUNC 0        _$s10MASTestApp28InsecureWKNavigationDelegateC7webView_10didReceive17completionHandlerySo05WKWebG0C_So28NSURLAuthenticationChallengeCySo016NSURLSessionAuthN11DispositionV_So15NSURLCredentialCSgtctFTq                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  MASTestApp.InsecureWKNavigationDelegate.webView.allocator.didReceive.completionHandler(...o15NSURLCredentialCSgtctFTq)
80   0x000041f8 0x000041f8 LOCAL  FUNC 0        _$s10MASTestApp28InsecureWKNavigationDelegateC7webView_10didReceive17completionHandlerySo05WKWebG0C_So28NSURLAuthenticationChallengeCySo016NSURLSessionAuthN11DispositionV_So15NSURLCredentialCSgtctFTo                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  MASTestApp.InsecureWKNavigationDelegate.webView.allocator.didReceive.completionHandler(...o15NSURLCredentialCSgtctFTo)

Accessors into the challenge protection space (protectionSpace / serverTrust):
451  0x00015980 0x00015980 LOCAL  FUNC 0        _objc_msgSend$protectionSpace
454  0x000159e0 0x000159e0 LOCAL  FUNC 0        _objc_msgSend$serverTrust

xrefs to WKNavigationDelegate challenge handler implementation:
ICOD 0x00004290 0x00004000 sym.MASTestApp.InsecureWKNavigationDelegate.webView.allocator.didReceive.completionHandler_...o15NSURLCredentialCSgtctF_
CALL 0x00004298 0x00004000 sym.MASTestApp.InsecureWKNavigationDelegate.webView.allocator.didReceive.completionHandler_...o15NSURLCredentialCSgtctF_

SecTrustEvaluateWithError calls — empty output confirms server trust is never evaluated:

xrefs to SecTrustEvaluateWithError:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
            ;-- section.0.__TEXT.__text:
            ;-- pc:
            ; CALL XREFS from func.000041f8 @ 0x4290(x), 0x4298(x) ; method.MASTestApp.InsecureWKNavigationDelegate.webView:didReceiveAuthenticationChallenge:completionHandler:
            ; ICOD XREF from func.00004584 @ 0x46a8(x) ; sym.MASTestApp.MastgTest.mastg.completion_...FZ_
 348: NSURLCredential.allocator__GenericAccessorSgtctF_..partial.apply (int64_t arg1, int64_t arg2, int64_t arg3, void *arg4, int64_t arg_a0h);
           0x00004000      sub sp, sp, 0xa0                           ; [00] -r-x section size 69084 named 0.__TEXT.__text
           0x00004004      stp x20, x19, [var_80h]
           0x00004008      stp x29, x30, [var_90h]
           0x0000400c      add x29, sp, 0x90
           0x00004010      mov x8, x0                                 ; arg1
           0x00004014      mov x0, x1                                 ; arg2
           0x00004018      str x2, [var_28h]                          ; arg3
           0x0000401c      str x3, [arg0]                             ; arg4
           0x00004020      stur xzr, [x29, -0x18]
           0x00004024      stur xzr, [x29, -0x20]
           0x00004028      stur xzr, [x29, -0x30]
           0x0000402c      stur xzr, [x29, -0x28]
           0x00004030      stur xzr, [x29, -0x38]
           0x00004034      stur xzr, [x29, -0x40]
           0x00004038      stur x8, [x29, -0x18]
           0x0000403c      mov x8, x0
           0x00004040      stur x8, [x29, -0x20]
           0x00004044      stur x2, [x29, -0x30]                      ; arg3
           0x00004048      stur x3, [x29, -0x28]                      ; arg4
           0x0000404c      stur x20, [x29, -0x38]
           0x00004050      bl sym._objc_msgSend_protectionSpace
           0x00004054      mov x29, x29
           0x00004058      bl sym.imp.objc_retainAutoreleasedReturnValue ; void objc_retainAutoreleasedReturnValue(void *instance)
           0x0000405c      ldr x1, [var_38h]
           0x00004060      str x0, [var_40h]
           0x00004064      bl sym._objc_msgSend_serverTrust
           0x00004068      mov x29, x29
           0x0000406c      bl sym.imp.objc_retainAutoreleasedReturnValue ; void objc_retainAutoreleasedReturnValue(void *instance)
           0x00004070      mov x8, x0
           0x00004074      ldr x0, [var_40h]
           0x00004078      str x8, [var_48h]
           0x0000407c      adrp x8, segment.__DATA_CONST              ; 0x20000
           0x00004080      ldr x8, [x8, 0x120]                        ; [0x20120:8]=0x8010000000000022 ; "\""
           0x00004084      blr x8
           0x00004088      ldr x0, [var_48h]
       ┌─< 0x0000408c      cbz x0, 0x4120
      ┌──< 0x00004090      b 0x4094
      ││   ; CODE XREF from func.00004000 @ 0x4090(x)
      └──> 0x00004094      ldr x8, [var_48h]
          0x00004098      str x8, [var_20h]
      ┌──< 0x0000409c      b 0x40a0
      ││   ; CODE XREF from func.00004000 @ 0x409c(x)
      └──> 0x000040a0      ldr x0, [arg0]
          0x000040a4      ldr x8, [var_20h]
          0x000040a8      str x8, [var_18h]
          0x000040ac      stur x8, [x29, -0x40]
          0x000040b0      bl sym.imp.swift_retain
          0x000040b4      mov x0, 0                                  ; int64_t arg_20h
          0x000040b8      str x0, [var_8h]
          0x000040bc      bl sym....sSo15NSURLCredentialCMa          ; func.0000415c
          0x000040c0      mov x20, x0
          0x000040c4      ldr x0, [var_18h]
          0x000040c8      adrp x8, segment.__DATA_CONST              ; 0x20000
          0x000040cc      ldr x8, [x8, 0x128]                        ; [0x20128:8]=0x8010000000000023 ; "#"
          0x000040d0      blr x8
          0x000040d4      ldr x0, [var_18h]                          ; int64_t arg1
          0x000040d8      bl sym.__C.NSURLCredential                 ; func.000041bc
          0x000040dc      ldr x20, [arg0]
          0x000040e0      ldr x8, [var_28h]
          0x000040e4      mov x1, x0
          0x000040e8      ldr x0, [var_8h]
          0x000040ec      str x1, [var_10h]
          0x000040f0      blr x8
          0x000040f4      ldr x0, [var_10h]
          0x000040f8      adrp x8, segment.__DATA_CONST              ; 0x20000
          0x000040fc      ldr x8, [x8, 0x120]                        ; [0x20120:8]=0x8010000000000022 ; "\""
          0x00004100      blr x8
          0x00004104      ldr x0, [arg0]                             ; void *arg0
          0x00004108      bl sym.imp.swift_release                   ; void swift_release(void *arg0)
          0x0000410c      ldr x0, [var_18h]
          0x00004110      adrp x8, segment.__DATA_CONST              ; 0x20000
          0x00004114      ldr x8, [x8, 0x120]                        ; [0x20120:8]=0x8010000000000022 ; "\""
          0x00004118      blr x8
      ┌──< 0x0000411c      b 0x414c
      ││   ; CODE XREF from func.00004000 @ 0x408c(x)
      │└─> 0x00004120      ldr x20, [arg0]
          0x00004124      mov x0, x20
          0x00004128      bl sym.imp.swift_retain
          0x0000412c      ldr x8, [var_28h]
          0x00004130      mov w9, 2
          0x00004134      mov x0, x9
          0x00004138      mov x1, 0
          0x0000413c      blr x8
          0x00004140      ldr x0, [arg0]                             ; void *arg0
          0x00004144      bl sym.imp.swift_release                   ; void swift_release(void *arg0)
      │┌─< 0x00004148      b 0x414c
      ││   ; CODE XREFS from func.00004000 @ 0x411c(x), 0x4148(x)
      └└─> 0x0000414c      ldp x29, x30, [var_90h]
           0x00004150      ldp x20, x19, [var_80h]
           0x00004154      add sp, sp, 0xa0                           ; 0x178000
           0x00004158      ret
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import Foundation
import WebKit

class InsecureWKNavigationDelegate: NSObject, WKNavigationDelegate {
    func webView(
        _ webView: WKWebView,
        didReceive challenge: URLAuthenticationChallenge,
        completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void
    ) {
        if let serverTrust = challenge.protectionSpace.serverTrust {
            let credential = URLCredential(trust: serverTrust)
            completionHandler(.useCredential, credential)
        } else {
            completionHandler(.cancelAuthenticationChallenge, nil)
        }
    }
}

Evaluation

InsecureWKNavigationDelegate surfaces in the "Custom authentication-challenge handlers" section, so it has taken control of the server trust evaluation and warrants manual review.

The test case fails because SecTrustEvaluateWithError is not imported into the binary at all — the "SecTrustEvaluateWithError calls" section in output.txt is empty.

The disassembly confirms this:

  • serverTrust is obtained at 0x00004064.
  • the only check is a nil guard at 0x0000408c (cbz x0, 0x4120).
  • NSURLCredential is created directly at 0x000040d8 with no call to SecTrustEvaluateWithError anywhere in the function.

The AI-reversed Swift makes the pattern explicit: any non-nil trust object presented to the WKWebView is accepted unconditionally.