demo
ios
MASTG-TEST-0347
MASTG-DEMO-0113: Runtime Monitoring of Text Input Fields Not Hiding Sensitive Data
Download MASTG-DEMO-0113 IPA
Open MASTG-DEMO-0113 Folder
Build MASTG-DEMO-0113 IPA
Sample
This demo uses the same sample as Text Input Fields Not Hiding Sensitive Data .
../MASTG-DEMO-0112/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
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
107
108
109
110
111 import SwiftUI
import UIKit
// SUMMARY : This sample demonstrates the use of both secure and insecure text input fields in an iOS app . It includes a login alert with a username field ( insecure ), a password field ( insecure ), and an OTP field ( secure ) . Additionally , it presents a SwiftUI view for entering a second OTP , which is also secure . The test checks for the presence of text input fields and whether they are configured to mask sensitive information .
struct MastgTest {
@inline ( never ) @_optimize ( none )
public static func mastgTest ( completion : @escaping ( String ) -> Void ) {
DispatchQueue . main . async {
let alert = UIAlertController (
title : "Login" ,
message : "Please enter your credentials." ,
preferredStyle : . alert
)
alert . addTextField { tf in
tf . placeholder = "Username"
tf . autocorrectionType = . no
tf . accessibilityIdentifier = "username_field"
}
// FAIL : [ MASTG - TEST - 0x57 ] The password field has isSecureTextEntry set to false .
alert . addTextField { tf in
tf . placeholder = "Password"
tf . isSecureTextEntry = false
tf . accessibilityIdentifier = "password_field"
}
// PASS : [ MASTG - TEST - 0x57 ] The OTP 1 field has isSecureTextEntry set to true .
alert . addTextField { tf in
tf . placeholder = "OTP 1"
tf . isSecureTextEntry = true
tf . keyboardType = . numberPad
tf . accessibilityIdentifier = "otp_1_field"
}
alert . addAction ( UIAlertAction ( title : "Next" , style : . default , handler : { _ in
let username = alert . textFields ? [ 0 ] . text ?? ""
let otp1 = alert . textFields ? [ 2 ] . text ?? ""
let baseMessage = "Logged in as: \(username), OTP 1: \(otp1)"
let otpView = OTPView { otp2 in
if let presenter = topViewController () {
presenter . dismiss ( animated : true ) {
completion ( "\(baseMessage), OTP 2: \(otp2)" )
}
} else {
completion ( "\(baseMessage), OTP 2: \(otp2)" )
}
}
let hosting = UIHostingController ( rootView : otpView )
if let presenter = topViewController () {
presenter . present ( hosting , animated : true )
}
}))
if let presenter = topViewController () {
presenter . present ( alert , animated : true )
} else {
completion ( "Failed to present alert." )
}
}
}
struct OTPView : View {
@State private var otp2 : String = ""
var onSubmit : ( String ) -> Void
var body : some View {
VStack ( spacing : 20 ) {
Text ( "Enter OTP 2" )
SecureField ( "OTP 2" , text : $ otp2 )
. keyboardType ( . numberPad )
. textFieldStyle ( RoundedBorderTextFieldStyle ())
. accessibilityIdentifier ( "otp_2_field" )
. padding ()
Button ( "Submit" ) {
onSubmit ( otp2 )
}
}
. padding ()
}
}
private static func topViewController (
base : UIViewController ? = {
let scenes = UIApplication . shared . connectedScenes
. compactMap { $ 0 as ? UIWindowScene }
let keyWindow = scenes
. flatMap { $ 0. windows }
. first { $ 0. isKeyWindow }
return keyWindow ? . rootViewController
}()
) -> UIViewController ? {
if let nav = base as ? UINavigationController {
return topViewController ( base : nav . visibleViewController )
}
if let tab = base as ? UITabBarController {
return topViewController ( base : tab . selectedViewController )
}
if let presented = base ? . presentedViewController {
return topViewController ( base : presented )
}
return base
}
}
Steps
Make sure Frida (iOS) is installed on your machine and the frida-server running on the device.
Run run.sh to spawn the app with Frida.
Fill in the login form: enter text in all fields, tap Next , enter a value in the OTP 2 field, and tap Submit .
Stop the script by pressing Ctrl+C.
The script hooks into text input controls at runtime and monitors when they lose focus. For each interaction, it captures the entered text, the input field class, accessibility identifier when available, placeholder text when available, and the isSecureTextEntry attribute. Based on this value, it reports whether the input is masked or exposed.
Observation
output.txt Exposed [ isSecureTextEntry = false , class = _UIAlertControllerTextField , aid = username_field , placeholder = Username ]: "username"
Exposed [ isSecureTextEntry = false , class = _UIAlertControllerTextField , aid = password_field , placeholder = Password ]: "password"
Masked [ isSecureTextEntry = true , class = _UIAlertControllerTextField , aid = otp_1_field , placeholder = OTP 1 ]: "1234"
Masked [ isSecureTextEntry = true , class = UITextField , aid = null , placeholder = OTP 2 ]: "5678"
The output contains all text that was entered in every text input, along with its masking status.
Evaluation
The test case fails because the output shows the password field with isSecureTextEntry set to false, meaning it is exposed — and this field contains sensitive data.
The password input (password_field) has isSecureTextEntry=false and contains sensitive data.
The username input (username_field) has isSecureTextEntry=false but is not considered sensitive.
The OTP 1 input (otp_1_field) has isSecureTextEntry=true, masking the sensitive data.
The OTP 2 input (OTP 2) is a SwiftUI SecureField which always masks input. Notice that its aid is null because SwiftUI's SecureField does not propagate the accessibilityIdentifier to the underlying UITextField. However, placeholder correctly shows OTP 2 and isSecureTextEntry=true confirms masking of the data.
Note
Exposed fields display typed characters in plain text, while masked fields show bullet characters, so the test can also be verified visually by observing the on-screen behavior.