Author Note: This write-up will not examine any new vulnerability. Rather, it explores a common methodology used in trivially hacking iOS apps, in which you perform a man-in-the-middle (MitM) attack on yourself.
Additionally, you will need a perfunctory knowledge of man-in-the-middle attacks, SSL, and the HTTP protocol. It was written for a general audience, so some examples have been simplified for the purposes of the article.
Common design patterns
As an iOS developer, you often want to push updates/changes at a faster rate than Apple will let app updates through. Additionally, often there will just be small changes (literally a couple bytes) that would require all your users to download the updated binary, which could be hundreds of megabytes in size.
If there are certain parameters that you change often, then it might make sense to pull them remotely. A simple change to a config file on your server allows you to propagate your changes quickly and efficiently. However, if the parameters being set in-app are critical, it could open your app to vulnerabilities and MitM attacks.
This is most commonly found in mobile games, apps with p2p requirements, and apps that have aspects that require quick updates (news, social media, etc.)
Intercepting the requests
As a mobile device developer, one might not initially think that intercepting their network requests would be easy, and that simply using https would be enough to stop anyone from snooping around. However, when you are dealing with interconnected models that require frequent updates, you open yourself up to an entirely new vector of attack — when users perform man-in-the-middle attacks on themselves.
Although there is no method to completely prevent users from intercepting their own connections, there are a few design patterns that can mitigate this risk.
As a proof of concept, take a look at Tap Tap Reborn. Roughly 500k users, with an active community outside of the game, and comparatively expensive in-app-purchases (20$ for premium, and $10 for 750 “gems”).
To intercept iOS network requests, I’d recommend following the guide by PortSwigger. Additionally, in newer versions of iOS you will need to allow full trust for root certificates, by going to Settings -> General -> About -> Certificate Trust Settings.
On initial load of the app, it attempts to download configuration files from an EC2 instance — no verification or validation of the input. These dictate everything from default settings, available songs, point levels required, and “rewards” for each action.
There is not any sort of verification of the contents of these configuration files. Additionally, each time the app is run it updates the server with a large JSON object containing all of your details. Again, no verification or encryption.
The real kicker is that the app, on-load, will update the server with progress you’ve made since last signing it. It doesn’t do these updates as event based, either (as in, if you played 3 songs it submits 3 events with the details of the songs, and tallies up your XP) — instead, it just accepts a large JSON profile object.
The server blindly accepts anything that is sent through. Changing values here reflect accurately server-side!
Therefore, intercepting a single request and modifying the values, then clearing the app and re-signing in through Facebook will allow you to set your gems, level, and XP to anything you’d like. You can also set yourself to premium, as an alpha user, and your high score for each song.
Through this method, one is easy able to become the #1 player in the world, and give themselves an arbitrary amount of in-game currency.
That amount of gems is equivalent to $187,000. Now of course it’s not actually worth anywhere near that amount, but it’s easy to imagine how, on a more critical or SaaS product you could easily rack up charges due to non-verification of payment or purchase.
How should one fix this problem? We will explore a few common ways that are used to mitigate this attack vector.
Hash the contents and verify
Difficulty to break: Trivial
Difficulty w/ embedded salt: Medium
One of the first solutions to this problem was to include a hash along with the data, and would look somewhat like this:
This is great for testing the validity of your information and that there was not corruption, but it falls short of being a valid security method — all you would need is to rehash the information and replace the hash before forwarding the packet to the device.
This becomes somewhat more viable when you have a have a salt that is embedded in your binary. This is more formally known as “shared key” types of encryption. For instance, if you appended “My$ecure$@lt” to the end of your data before you verified it against the hash, it would prevent any pure man-in-the-middle attack. The attacker would have to decompile and inspect the binary, which is feasible but requires more effort and knowledge than a simple MitM attack.
Through the use of an obfuscator, like Obfuscator-iOS, this method can become even more secure. It still would not stop a dedicated attacker, however.
Difficulty to break: Medium/Hard
This is more along the lines of obfuscation than clear and open design, but it will provide a non-trivial roadblock to most attackers. This pattern would involve encrypting the contents of your data somehow. This could range from a simple encoding (base-64) all the way to PGP, or even rolling your own encryption (which I do not recommend — a system is not secure until it has been exhaustively attacked). This would again require some form of key hidden in the binary, which is not impossible to reverse engineer and extract.
This will thwart all but the most dedicated attackers. A pure MitM attack will not suffice, and will require significant knowledge to figure out how to decrypt.
Cons to this approach are that it’ll require significantly more computing resources, and that it adds another layer of abstraction to your system.
Difficulty to break: Difficult
Certificate pinning involves verifying that the certificate in which your information is encrypted/returns matches a known set of certificates — that is, it has not been “swapped out” in between. This is arguably one of the most secure methods to prevent MitM attacks — the device will not accept any connection that is not signed by the embedded certificates.
The only way to bypass this would be to jailbreak the device, and manually disable SSL verification or replace the embedded certificates with Burp Suite’s. This would require a much more sophisticated level of attack, and if your device is jailbroken/rooted then there are multiple other attack vectors to take into consideration.
Better Architectural Design
The best methodology for a secure system is to design it from the beginning with security in mind. This means securing your endpoints, and making conscious decisions for what information should be renewed each session, as well as how many levels of verification should be required at each stage. It involves having a more abstract, security-conscious view of your application, and what attack vectors it may have. In the example above, minimizing these vectors would mean requiring as little information as possible be refreshed each session, and not pull certain critical components from live-config.
Applicability and Further Research
Man-in-the-middle attacks are not anything new —this is more of an application of a security paradigm than a ground-breaking revelation. However, as a developer you are often more focused on preventing an outside attacker from compromising your users data integrity than from a MitM attack performed by your users themselves.
Snapchat and Facebook already implement certificate pinning, as it is one of the most reliable and secure methods of preventing any 3rd party from modifying the data in the middle of the connection (as of the writing of this article). It’s not impossible to circumvent (see iOS Kill Switch for jailbroken devices), but at that point an attacker would have access to a lot more than a single HTTP connection.
This write-up was meant to illustrate a common bad design pattern, and on how relying solely on SSL/TLS for your iOS app can lead to consequences, ranging from loss of revenue to full compromise of your application. This is especially true on iOS, where it is easy to simply rely on Swift’s HTTP requests without considering a user replacing the certificate with one of their own.