Skip to content

MASTG-DEMO-0109: ATS TLS Policy Exceptions in Info.plist

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

Sample

The code below shows an insecure ATS configuration in an Info.plist file. NSAllowsArbitraryLoads is explicitly set to false to keep the focus on the two per-domain TLS policy exceptions:

  • tls-v1-0.badssl.com: lowers the minimum TLS version to TLS 1.0 via NSExceptionMinimumTLSVersion, applying to all subdomains via NSIncludesSubdomains.
  • static-rsa.badssl.com: disables the forward secrecy requirement via NSExceptionRequiresForwardSecrecy = false, allowing cipher suites without ephemeral key exchange (such as RSA key exchange).

Note that if NSAllowsArbitraryLoads were set to true, ATS would be disabled for all domains not explicitly listed in NSExceptionDomains, allowing all connections to those domains regardless of TLS version or cipher suite. Domains listed in NSExceptionDomains would still have their per-domain settings applied.

 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
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
    <dict>
        <key>NSAppTransportSecurity</key>
        <dict>
            <key>NSAllowsArbitraryLoads</key>
            <false/>
            <key>NSExceptionDomains</key>
            <dict>

                <key>static-rsa.badssl.com</key>
                <dict>
                    <key>NSExceptionMinimumTLSVersion</key>
                    <string>TLSv1.2</string>
                    <key>NSExceptionRequiresForwardSecrecy</key>
                    <false/>
                </dict>

                <key>tls-v1-0.badssl.com</key>
                <dict>
                    <key>NSExceptionMinimumTLSVersion</key>
                    <string>TLSv1.0</string>
                    <key>NSIncludesSubdomains</key>
                    <true/>
                </dict>

            </dict>
        </dict>
    </dict>
</plist>
  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
 97
 98
 99
100
101
102
103
104
105
106
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>BuildMachineOSBuild</key>
    <string>25E253</string>
    <key>CFBundleDevelopmentRegion</key>
    <string>en</string>
    <key>CFBundleExecutable</key>
    <string>MASTestApp</string>
    <key>CFBundleIdentifier</key>
    <string>org.owasp.mastestapp.MASTestApp-iOS-2</string>
    <key>CFBundleInfoDictionaryVersion</key>
    <string>6.0</string>
    <key>CFBundleName</key>
    <string>MASTestApp</string>
    <key>CFBundlePackageType</key>
    <string>APPL</string>
    <key>CFBundleShortVersionString</key>
    <string>1.0</string>
    <key>CFBundleSupportedPlatforms</key>
    <array>
        <string>iPhoneOS</string>
    </array>
    <key>CFBundleVersion</key>
    <string>1</string>
    <key>DTCompiler</key>
    <string>com.apple.compilers.llvm.clang.1_0</string>
    <key>DTPlatformBuild</key>
    <string>23E252</string>
    <key>DTPlatformName</key>
    <string>iphoneos</string>
    <key>DTPlatformVersion</key>
    <string>26.4</string>
    <key>DTSDKBuild</key>
    <string>23E252</string>
    <key>DTSDKName</key>
    <string>iphoneos26.4</string>
    <key>DTXcode</key>
    <string>2641</string>
    <key>DTXcodeBuild</key>
    <string>17E202</string>
    <key>LSRequiresIPhoneOS</key>
    <true/>
    <key>MinimumOSVersion</key>
    <string>17.4</string>
    <key>NSAppTransportSecurity</key>
    <dict>
        <key>NSAllowsArbitraryLoads</key>
        <false/>
        <key>NSExceptionDomains</key>
        <dict>
            <key>static-rsa.badssl.com</key>
            <dict>
                <key>NSExceptionMinimumTLSVersion</key>
                <string>TLSv1.2</string>
                <key>NSExceptionRequiresForwardSecrecy</key>
                <false/>
            </dict>
            <key>tls-v1-0.badssl.com</key>
            <dict>
                <key>NSExceptionMinimumTLSVersion</key>
                <string>TLSv1.0</string>
                <key>NSIncludesSubdomains</key>
                <true/>
            </dict>
        </dict>
    </dict>
    <key>UIApplicationSceneManifest</key>
    <dict>
        <key>UIApplicationSupportsMultipleScenes</key>
        <true/>
        <key>UISceneConfigurations</key>
        <dict/>
    </dict>
    <key>UIApplicationSupportsIndirectInputEvents</key>
    <true/>
    <key>UIDeviceFamily</key>
    <array>
        <integer>1</integer>
        <integer>2</integer>
    </array>
    <key>UILaunchScreen</key>
    <dict>
        <key>UILaunchScreen</key>
        <dict/>
    </dict>
    <key>UIRequiredDeviceCapabilities</key>
    <array>
        <string>arm64</string>
    </array>
    <key>UISupportedInterfaceOrientations~ipad</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationPortraitUpsideDown</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
    <key>UISupportedInterfaceOrientations~iphone</key>
    <array>
        <string>UIInterfaceOrientationPortrait</string>
        <string>UIInterfaceOrientationLandscapeLeft</string>
        <string>UIInterfaceOrientationLandscapeRight</string>
    </array>
</dict>
</plist>
 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
import Foundation

struct MastgTest {
    // SUMMARY: This sample demonstrates two ATS TLS policy exceptions in Info.plist:
    // 1. NSExceptionMinimumTLSVersion = TLSv1.0 for tls-v1-0.badssl.com (allows TLS 1.0, including subdomains).
    // 2. NSExceptionRequiresForwardSecrecy = false for static-rsa.badssl.com (disables the PFS requirement).

    // FAIL: [MASTG-TEST-0x01] NSExceptionMinimumTLSVersion = TLSv1.0 with NSIncludesSubdomains = true.
    static let tls10Endpoint = "https://tls-v1-0.badssl.com:1010/"

    // FAIL: [MASTG-TEST-0x01] NSExceptionRequiresForwardSecrecy = false.
    static let noPfsEndpoint = "https://static-rsa.badssl.com/"

    static func mastgTest(completion: @escaping (String) -> Void) {
        var result = "Testing ATS TLS policy exceptions:\n\n"
        let group = DispatchGroup()

        for endpoint in [tls10Endpoint, noPfsEndpoint] {
            guard let url = URL(string: endpoint) else {
                result += "Invalid URL: \(endpoint)\n"
                continue
            }

            group.enter()
            URLSession.shared.dataTask(with: url) { _, response, error in
                if let error = error as NSError? {
                    result += "\(endpoint) failed: \(error.localizedDescription)\n"
                } else if let httpResponse = response as? HTTPURLResponse {
                    result += "\(endpoint) returned status: \(httpResponse.statusCode)\n"
                } else {
                    result += "\(endpoint) completed without HTTP response.\n"
                }
                group.leave()
            }.resume()
        }

        group.notify(queue: .main) {
            completion(result)
        }
    }
}

Steps

  1. Extract the app ( Exploring the App Package) and locate the Info.plist file inside the app bundle (which we'll name Info_reversed.plist).
  2. Convert the Info.plist to pretty-printed JSON ( Convert Plist Files to JSON).
  3. Extract NSAllowsArbitraryLoads and any NSExceptionMinimumTLSVersion, NSTemporaryExceptionMinimumTLSVersion, or NSExceptionRequiresForwardSecrecy keys from the NSAppTransportSecurity configuration. In this case we use gron to transform the JSON into a greppable format and egrep to search for those keys.
run.sh
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#!/bin/bash

plutil -convert json -o Info_reversed.json Info_reversed.plist

# Format JSON output
jq . Info_reversed.json > Info_reversed.json.tmp && mv Info_reversed.json.tmp Info_reversed.json

gron -m Info_reversed.json \
| egrep 'json\.NSAppTransportSecurity\.(NSAllowsArbitraryLoads|NSExceptionDomains\["[^"]+"\]\.(NSExceptionMinimumTLSVersion|NSTemporaryExceptionMinimumTLSVersion|NSExceptionRequiresForwardSecrecy))' \
> output.txt

Observation

The output shows the NSAllowsArbitraryLoads setting and the TLS policy exceptions found in Info_reversed.plist:

output.txt
1
2
3
4
json.NSAppTransportSecurity.NSAllowsArbitraryLoads = false;
json.NSAppTransportSecurity.NSExceptionDomains["static-rsa.badssl.com"].NSExceptionMinimumTLSVersion = "TLSv1.2";
json.NSAppTransportSecurity.NSExceptionDomains["static-rsa.badssl.com"].NSExceptionRequiresForwardSecrecy = false;
json.NSAppTransportSecurity.NSExceptionDomains["tls-v1-0.badssl.com"].NSExceptionMinimumTLSVersion = "TLSv1.0";

Evaluation

The test case fails because two per-domain TLS policy exceptions are configured:

  • NSExceptionMinimumTLSVersion = "TLSv1.0" for tls-v1-0.badssl.com allows connections using the deprecated TLS 1.0 protocol. Because NSIncludesSubdomains = true, the exception also applies to all subdomains of tls-v1-0.badssl.com.
  • NSExceptionRequiresForwardSecrecy = false for static-rsa.badssl.com disables the ATS requirement for Perfect Forward Secrecy (PFS), allowing cipher suites such as RSA key exchange that don't provide forward secrecy. Past sessions can be decrypted if the server's private key is later compromised. Note that NSAllowsArbitraryLoads is set to false, so we don't consider it as a failure condition here. If it were true, the test would fail on that basis alone, because ATS would be disabled for all connections to domains not listed in NSExceptionDomains. Domains listed in NSExceptionDomains would still have their per-domain settings applied even in that case.