Skip to main content

App Security Best Practices

As long as there have been applications, there have been threat actors with licenses to bypass, IP to lift, data to steal, and actions to subvert. Application owners developed mitigations against these threats and then threat actors countered with new tools and processes to bypass the mitigations. That cat and mouse cycle continues to this day. And while some specific techniques no longer work, there are general principles that remain valid in the ongoing fight to protect applications from unintended use.

At Digital.ai, we've been developing and refining these Best Practices for more than 2 decades. And while they are generally applicable, some are at odds with each other, and some only make sense in certain scenarios.

The most important thing to remember is to be intentional by considering the threats and then applying relevant mitigations. After that, see what happens and then make changes, because variation is one of the key underlying principles. So, whether you practice OODA (Observe, Orient, Decide, Act) or PDCA (Plan, Do, Check, Act), don’t let your protection stagnate. You’ve made an investment in getting your application to do something valuable; don’t stop short of maintaining the integrity of its operation.

And remember, you own the moral high ground, not the threat actors. You made the investment in developing your application, not them. You get to dictate how your application should be used, not them. Anything that you do to enforce your wishes is your prerogative. Most users—the “normal” ones who play by the rules—are fine with that. The few users who refuse to abide by your intentions think they are entitled to take what’s yours for themselves.

What follows is a list of principles and ideas. Pick the ones that work for you but do it consciously. If you want to ignore an idea, that’s fine. Just make sure you have a valid reason for doing so.

Mindset Principles

Think Like a Psychologist

Threat actors are humans with the same tendencies and biases that we all share. When thinking about protecting your application, pay attention to possible motivations, competing desires, typical behaviors, and whatever other human characteristics play into a person’s desire to attack your application. Consider Maslow’s Motivation Model, cognitive biases, and other factors that prompt ways of thinking and use them to your advantage.

Know Your Adversary

Knowing what a threat actor wants and why they want it may help you design the right protection for your application. Regarding Maslow’s Motivation Model, a threat actor may want to monetize an attack to help meet their most basic needs. A different threat actor may want “street cred” and so may be looking for esteem near the top of the hierarchy. The former may be convinced to stop attacking your application if there’s a similar application from a different organization that is easier to successfully attack. In the latter case, the threat actor may thrive on attacking the greatest known challenge, so that if/when they succeed, they can claim elite status. Each one has a different concept of “Return” and “Investment”. So, understanding the ROI model of your adversary can help you plan your protection.

Frustrate Your Adversary

In general, the more your protections can frustrate threat actors, the more likely they are going to give up their attack. Banging one’s head against the wall too many times eventually causes one to question the benefit of continuing.

Preparatory Principles

Model Your Threats

Start by enumerating the threats against your application, then rank them in priority order. You’ll need this ranking to know what threats to address first or what mitigation should win if two threats have competing mitigations. This list will also help if you need to do any performance tuning. Strength of security can be adjusted to provide more cycles for your application logic, but not at the expense of ignoring the most impactful threats.

Investigate Attack Tools

If you want to understand how easy it is to attack an application, look at the OWASP MASTG (Mobile Application Security Testing Guide). It lists several threat tools and examples how to use them to test application resiliency. Doing analyses before and after protection will give you confidence that you’re addressing the most important threats.

note

Be careful with jailbreaks, root kits, and attack frameworks as they require disabling security controls and can themselves contain nasty payloads!

Be the Villain

Before developing a protection, attack your application (at least in thought) to understand what a threat actor may try. Developing an adversarial mindset is important to creating effective protections. Many guards and protections won't make sense until you understand the attack vector that they protect against.

Do Your Part Before Protecting

Application protection products can enhance the security of your application, but please start with secure coding practices and use all other applicable tools (SAST, DAST, SCA, etc.) to look for other vulnerabilities early and often. Take advantage of MASTG and MASVS for information on secure coding practices, good platform usage, etc.

Basic Protection Principles

Multiply the Options

Like many people, threat actors are likely to take the path of least resistance when faced with multiple possibilities. So, create many paths by using many guards with different reactions to lead threat actors toward dead ends or honey pots or “a maze of twisty little passages, all alike.” Even without adding honey pots, etc., adding more hay to the haystack always makes it harder to find the needle.

Break Cause and Effect

Cause and effect are valuable tools to the threat actor. Every time they make a change and see a reaction, they learn more about when your application performs its checks and how it responds to the tamper events it sees. Breaking the link between cause and effect—or at least relaxing it a bit—is an effective countermeasure. So,while it is tempting—and makes some sense—to immediately crash or exit an application when an attack is detected, a better approach is to subtly break the running app. If attackers trip a detection but don’t know that they did, they are likely going to try the next thing. If they are several steps into a chain of events that comprise their attack before they realize they’ve been caught, their job of isolating the single point of failure is that much harder. That makes it harder for attackers to find and disable your guards.

Remove Single Points of Failure

Single points of failure make your protection fragile, so link multiple guards together and establish a threshold level of what indicates tampering. This is especially true when guards use heuristics. As an example, Virtual Control Detection uses heuristic-based techniques to identify whether interaction with the device is likely a person or not. As with any heuristic-based decision, Virtual Control Detection is approximate and might not always reflect the true source of the interaction. It is recommended that the result of Virtual Control Detection be combined with other inputs when determining how to react.

Imitate the Mosquito

A larger number of smaller guards is preferable to a smaller number of larger guards. Smaller guards are harder to find than larger guards, plus they provide a greater ability to overlap each other giving a greater measure of defense-in-depth. Also, small, hidden tamper actions that accumulate can be used to kill an attack by a thousand stings.

Use Guard Networks

Guard networks are groups of guards that work together to protect the application and the other guards in the network. By creating a tangle of protections, you can make it very difficult to strip out any single guard. In this case, the greater the tangle the better, up to the point where additional guards would have an adverse effect on performance.

Use Targeted Guards

Apply guards to protect your most important assets and consider the attack vectors that are most dangerous. For example, if license removal is a concern, your protections should focus on your licensing logic, licensing code paths, license argument validation, and license routine return values.

Change and Agitate Regularly

Protections are semi-random based on your code, your blueprint, the product version, and the protection seed. Change your seed for every release (and save it) so that you can be confident that your protection is different every time. Doing so will agitate the attacker by making them start from scratch for every release. There’s no reason to make the attacker’s job any easier than necessary.

Don’t Provide Any Freebies

Never release an application to production with any of the guards or other features set to debug mode. Debug messages can expose what kind of protection is applied and where it is injected. You’re trying to make it hard for the attacker, so don’t let your guard down.

Start Simple and Iterate

When first integrating protection into your application, take advantage of any automatic protection options (default configurations, zero-config, etc.) to do the initial integration of the protection into your CI pipeline. This will allow you to integrate the protection more quickly into your Software Development Lifecycle. You can later customize the protection for your specific security and performance needs. Remember to come back and customize your protection to the appropriate level as identified by your initial threat assessment.

Advanced Protection Principles

Introduce Non-Deterministic Behavior

Beyond ensuring that many of your guards only react in invisible ways, also consider guards that only trigger some of the time. Using execution probabilities or wall clock time thresholds or combinations of guard outputs can introduce some mystery about what your application is doing. Use tamper actions that only subtly modify your code but still in a way that protects your original intent.

Report Everything

If you use some of these subtle reaction techniques, it is still important to report every tamper event to App Aware. This valuable data can be studied for patterns that can help you improve your future protections—especially to help distinguish between normal users, exploit developers, and exploit users.

Separate Guard Networks

Take the concept of a guard network to the next level. Use multiple guard networks that are independent from each other, so that if a threat actor does identify one guard network, you have others that are waiting in the wings to detect the attack.

Use Non-Targeted Guards

Along with targeted guards, add some guards at unusual or unimportant parts of your application. This keeps your most important and most heavily protected code from standing out too much. It also may catch attacks that you didn’t consider when you built your protection. This is another case where defense-in-depth shines.

Include Expected Failure Cases

If you have a function that evaluates some condition and then chooses between the happy path and the unhappy path, a threat actor may try to replace the evaluation function with a function that always returns true. If you add a call to that function with arguments that should cause it to fail, but it still passes, you know something is wrong. Note that this secure programming practice does not require application protection to work. However, adding this code and protecting this code adds to your application’s overall defense-in-depth posture, so both are recommended.

Use Threads Strategically

Consider running some of your guards on background threads. Doing everything on a single thread makes the attacker’s job simpler. Forcing an attacker to watch several threads makes their job harder.

Beware Threading Issues

When installing guards or other protections in multithreaded code (recommended!), be sure that the features that you are using are thread safe. If a guard type is not thread safe out of the box, consider putting all the guards of one type on one thread or add a mutex to synchronize multiple threads.

Match Patterns Carefully

When using regular expressions to identify invocation locations or protected ranges, carefully review your protection logs regularly to make sure you’re matching what you expect to match. Unintended overmatching can have consequences like severe performance degradation, extreme code bloat, and can even introduce failures. Use matching capabilities to make your life easier, not harder when you need to debug obscure errors.

Vary Custom Tamper Reaction Functions

Using several tamper reaction functions instead of one is another way to remove single points of failure. Give the attacker more work not less.

Use Non-Tamper Reactions to Do Important Stuff

If you only use tamper reactions, it’s easier for an attacker to remove entire guards. Putting meaningful operations in non-tamper reactions causes good things to not happen if an attacker removes a guard. At every point, you want to make the attacker use a scalpel, not an axe.

Use Damaged and Repaired Code and Data Where Possible

Some platforms, like Windows, allow you to damage and repair and re-damage code to make static and dynamic analysis harder. If that is possible with your platform, do it! Other platforms, like iOS, don’t permit changing code, but you can still change data. On restrictive platforms, consider damaging, repairing, and re-damaging data that drives your code execution. A switch statement containing both real and bogus code that is selected by a data value can provide some of the protection value provided on less restrictive platforms.

Choose Good Invocation Locations Where Possible

When using protection products that permit specifying invocation locations, mix it up. Don’t trigger all your guards in one spot. Put some invocations at function starts. Put some invocations inside functions. Put some invocations at startup. Put some invocations on intervals. Variation can increase confusion.

Maintenance Principles

Debug Smarter

Our protection products have features to assist with debugging protection-time and runtime issues. Check the documentation for details.

Help Support Help You

When asking customer support for help, provide as much information as you can including logs, blueprints, unprotected applications, debug information, mapfiles, protection commands, versions of protection products used, etc. Having everything needed to reproduce an issue always leads to the fastest resolution.

Cryptography Principles

Use White-Box Crypto

If your application processes sensitive data, hopefully you already use cryptography. Although, if you ship keys in your application, they often can be found and copied using one of the many attack tools created for that purpose. Using a white-box crypto solution like Digital.ai Key & Data Protection can ensure that your keys never exist in memory, while still allowing your application to perform the required cryptographic operations.

White-box cryptography replaces canonical keys with code so that they cannot easily be found and lifted. And even though your keys are in a different form, they still permit your application to interact with endpoints using normal cryptographic implementations like OpenSSL.

White-box cryptography protects your keys because the equivalent code is non-reversible to classical key format. At the same time, Digital.ai Key & Data white-box cryptography keys are effectively bound tightly to the statically linked, white-box algorithm implementation. Since these white-box cryptography keys can be easily rotated by linking a replacement library, your applications gain extra resilience against threat actors.

For more information, see What is White-Box Cryptography?.

Follow Standards

Finally, if there are general standards like OWASP MASVS or guidelines that pertain to your industry or your distribution model, use them while building and prioritizing threats so that you don’t inadvertently fall victim to the OWASP MASVS Resilience categories.