App Hardening Tutorial for Android With Kotlin

In this App Hardening Tutorial for Android with Kotlin, you’ll learn how to code securely to mitigate security vulnerabilities. By Kolin Stürt.

Leave a rating/review
Download materials
Save for later
Share

As network communications and OSs become more secure, hackers have shifted their focus from basic eavesdropping to attacking devices and apps. To protect your app from attacks, you need to know how to use app hardening effectively.

From minimizing pointer use to null safety and mutability checks, Kotlin is a great language for secure development. However, that means it’s tempting to forget about security altogether. Even Kotlin has vulnerabilities that you need to protect your app against.

In this tutorial, you’ll learn how to:

  • Avoid code vulnerabilities.
  • Validate input and sanitize output.
  • Secure concurrent code.
Note: This tutorial assumes that you’re already familiar with the basics of Android development and Android Studio. If Android development is new to you, first read through the Beginning Android Development and Kotlin for Android tutorials.

Getting Started

Download and unzip the materials for this tutorial by using the Download Materials button at the top or bottom of this page. You’ll work with the sample app, Snitcher, which you previously cracked. Now it’s time to secure it.

If you didn’t read the previous tutorial, the Snitcher app lets users send anonymous tips about animal crimes to law enforcement. Well, it doesn’t send the information to law enforcement, so feel free to test it out.

Open and run the starter project in Android Studio 3.5.0 or higher. You’ll see a simple sign-up screen. Once you enter an email and any password and choose SIGN UP, you’ll need to enter that password when you launch the app in the future:

Signup screen

After that step, you’ll get a list of wrongdoings to report:

Report list

Tap an entry in the list to proceed to the reporting screen. Right now, the app has an “overflow” of security issues which you will eventually fix!

Introducing Overflows

Overflow Icon

In languages such as C, hackers exploit security vulnerabilities by causing an app to write data to an area it’s not supposed to, such as beyond an expected boundary and into adjacent memory locations.

That’s called an overflow, and it can overwrite important data. In certain environments, this can be an area that contains code the device executes. It’s been a major way for attackers to maliciously change a program. Bug bounty hunters refer to it as “gaining arbitrary code execution.” It’s a very important preoccupation for them.

Overflow Diagram

One example of an overflow in Kotlin is when a recursive function ends up in an infinite loop. Because the size of the stack will run out, you’ll get a StackOverflow exception.

Kotlin provides safety modifiers such as tailrec, which helps avoid the chances of a stack overflow by adding rules and throwing an error if you break them. The rules are:

  • The last operation of the function can only call itself.
  • There cannot be more code after a recursive call.
  • Prohibit use within try/catch/finally blocks.

These rules are helpful in the instances where your implementation changes later and you forget to re-check that it’s still safe.

To implement this in Snitcher, open Timing.kt and add tailrec right after the private modifier in the method definition of factorial. Your modified method definition should look like below:

private tailrec fun factorial(number: Int, accumulator: Int = 1) : Int {

You’ve just added a safety modifier! Also, Android Studio provides important security warnings for potential overflows.

Paying Attention to Warnings

Exceptions and crashes are obvious indicators that something is wrong. Often, a worse problem is an incorrect value that goes undetected for some time. This is what happens with an integer overflow. Kotlin doesn’t throw an exception for a signed integer overflow. Instead, the app continues with the wrong values!

The good news is that Android Studio detects most integer overflows at compile time. To see how this looks, open the ReportDetailActivity.kt file and take a look at the warning by hovering over REPORT_APP_ID * REPORT_PROVIDER_ID on the line right under comment //2. Send report.

Integer overflow warning

Regular numbers defined like this are integers. But adding them together exceeded the maximum size of the container. That’s why it’s the best security practice to treat warnings as errors!

At the top of the file, replace the REPORT_APP_ID and REPORT_PROVIDER_ID declarations with the following:

private const val REPORT_APP_ID = 46341L
private const val REPORT_PROVIDER_ID = 46341L

You’ve now added L to the end of the numbers, which defines them as Long and fixes the warning. That’s because Long is a number that can hold a much larger value.

Another vulnerable area is interacting with languages that use pointers. Pointers allow you to access raw memory locations, making it easier to read and write to the wrong area.

Kotlin is much safer than many languages because it mostly does away with pointers, but it still allows you to interface with C using the CPointer and COpaquePointer objects.

Note: You can read more about C Interop in Kotlin on the official website.

While native code is beyond the scope of this article, here are a few things to keep in mind if you’ll be working with NDK:

  • It’s extremely important when interfacing with C to do bounds checking on the input to make sure it’s within range.
  • Don’t store or return pointers for later use.
  • Avoid unsafe casts using .reinterpret() or .toLong() and .toCPointer() methods.

Because vulnerabilities can manipulate data in your app, another possible place for vulnerabilities is when your app passes data for further processing to a server. To make sure this is secure, you should sanitize all data that leaves your app.

Sanitizing Output

Some people are terribly afraid of germs they can’t see.

Worried person

For app developers, that’s a good quality.

If the app sends data in the text fields to a server, then sanitizing that input reduces the potential of an attack. The most basic technique is to limit the amount of input that you can enter into your fields. This reduces the likelihood that a specific code snippet or payload can get through.

To do this, open the activity_main.xml file and make sure you’re in the XML editing view. Add the following to the first EditText element, with the id login_email:

android:maxLength="254"

Email addresses can have a max of 254 characters.

Now, add this to the next two EditText elements, with the ids login_password and login_confirm_password:

android:maxLength="32"

You’ve set the maximum password length to be 32 characters.

Now, open the activity_report_detail.xml file. Add the following to the EditText field, with id details_edtxtview:

android:maxLength="512"

You made the maximum character limit 512 for the report.

Try out your changes. Build and run the app, entering a large amount of text into the password field. You’ll see you’ve made your app a bit safer.

Next, you’ll want to remove dangerous characters for the language that your server is using. This prevents command injection attacks — when you pass data to an environment that should store it but instead executes it as commands. The app’s underlying datastore uses an SQLite database, while the pretend server is SQL.