Application 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.
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.
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.
Follow Standards
First, if there are general standards like OWASP MASVS (https://mas.owasp.org/MASVS/11-MASVSRESILIENCE/) or guidelines that pertain to your industry or your distribution model, use them while
building and prioritizing threats so that you don’t inadvertently miss something on those checklists. Here
are some specific guard types mapped to the OWASP MASVS Resilience categories.
Mindset Principles
Think Like a Psychologist
Next, remember that 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 the OWASP MASTG and the OWASP 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. Combine Virtual Control
Detection 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 guardspec or 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 that protect 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 defensein-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,
guardspecs/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
Finally, 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.
Go Forward, Securely!
Remember that protecting applications from evolving threat actors is an enduring challenge. The
continuous cat-and-mouse cycle between application owners and malicious actors requires that you
remain proactive and adaptable in the face of evolving threats. Digital.ai is your partner in this cycle, and
we encourage you to use the tools we provide within the framework of the advice outlined in this paper in
order to preserve your assets. When in doubt, reach out! We are here to help.