USSD Hacks aren’t patched, you can still play around with it. (Google Chrome Research-CVE-2021–30589)

Product: Google Chrome (Stable | 88.0.4324.150 | Windows 10) & Chrome Android (Dev | 90.0.4413.0 | Android 9.0)

What is the USSD code?
Unstructured Supplementary Service Data is referred to as “USSD codes” or feature codes which allow to access hidden features like IMEI information, service provider balance, user statistics, etc.

What is the Click-to-call of Chrome?
Nowadays, browsers like Chrome, Safari, etc. allow you to send the Phone Number from the computer to your device. When it comes to Chrome, they use Click-to-call for “tel:” URIs. Currently, the click-to-call doesn’t allow user to send any numbers from Desktop to Android like it allowed us to do by using the Click-to-call Flag.

Vulnerability Details: Due to lack of checks for the chars like “asterisk (*)”, “Hash (#)”, and “Percentage (%)”, an attacker was successful in crafting an exploit with “tel:” URI which can allow attackers to send the USSD to Android devices which allowed him/her to perform nefarious works like erasing the victim’s device, transferring funds/performing banking through USSD, etc.

Vulnerability story: After my CVE-2021–21187 was patched by Chrome folk Meacers (Mustafa) in less than 90-minutes just before the new year, I got fascinated by browsers security and the way security folks in browser security work. While I was learning about the URIs specifically “tel:”, I came through an interesting comment of my best friend Eric Lawrence in the Chrome bug discussion when he was on the Chrome team. From the discussion, it can be said that they are blocking only “asterisk (*)” and “Hash (#)” but what about URL encoded? From here, I began to create a few test cases and also tried to understand what the click-to-call was actually doing. I didn’t succeed playing around with “iframes” because they can render “tel:” URIs in it and can revoke applications without UI (on the desktop). Chrome has a policy for Android browsers that it blocks “tel:” URIs inside the “iframes” and the remaining attack surfaces will be marked as Won’tFix because they require a higher number of UI(s). The next idea which comes to my mind was about the function which sends numbers from Desktop to Mobile. With the hope that the numbers aren’t being checked and are sent to the dialer, I created a few test cases to check them one by one. From what HomeSen has said to me, “Always try your hack practically if it doesn’t work ask why it didn’t, and learn from it. There is always someone better than you who might be knowing how to bypass it, why can’t you try to be the one?”. With the same hope and a bit of discussion with Eric Lawrence and Patrick Walker, I eliminated the first way of embedding “tel:” URI in the IMG tag (<img>) the reason why we’d to eliminate was that it requires the user to view the broken image (by doing a right-click-> View Image) even though images load automatically but when you embed any URIs in it, you need to click and view it to trigger but there have been many exploits for example in the exploit of UXSS by Abdulrhman Alquabandi, he has used the idea to execute JS obviously the finding is neat as it is Universal-XSS. If you’re curious how we chef (s) cooked up the exploit for this hack, please go through the “How exploit was constructed?”.

Vulnerability analysis from source code: Covered in the exploit construction and patch analysis.

How exploit was constructed?

On the very first stage, while we review the discussion of Mozilla (Gecko)and Chrome (Chromium) folks in a combined case on Github, it can be said that they actually wanted to block USSD codes when the “tel:” URI is used despite being on any “operating system”. The reason behind that is, browsers like Chrome had the ability to send the number to your device and that’s what the click-to-call was actually implemented for. Moving ahead, we first decided to play around with the below test case:

data:text/html,<a href="tel:*#06#">clickhere</a> /// USSD code to open IMEI Number

Firstly, our target was to know why it didn’t share the button at all when we were adding “Hash (#)” every time after the “asterisk (*)” in the tel: URI. The next thing which we needed here was to find the real problem why Hash wasn’t being allowed after the asterisk (was it protecting against USSD there?). After a few test-cases, we began to play with URL encoding as in the Chrome bug discussion, it was explicitly mentioned by their folk(s) that they are trying to block “asterisk (*)” and “Hash (#)” but not the URL-Encoding.

So, we should probably begin to encode from the char where it was being blocked/stripped out i.e. “#” so, it will be:


We’ll use the above-encoded part. Now, it's time to put all the pieces together and build a working PoC. The exploit which was cooked up by the exploit chef (s) was:

data:text/html,<a href="tel:*%252306%2523">Click Me</a> /// USSD Code to open IMEI Number

Now, let’s know the reason why the former exploit didn’t work but the latter did:

In the latter test case, we used “%25” which is actually the “%” that wasn’t being stripped out by the GURL::Getcontent(). The part after that is “%2306%23" which ends up as “#06#” (As per the %23->#) here our exploit opened up the IMEI number. Later on, the below exploit allowed us to open Test History:

data:text/html,<a href="tel:"*%252307%2523">Click Me</a> /// To open test history

Similarly, one can adjust the exploit to erase the victim’s device.

How vulnerability was patched (Code analysis)?

bool IsUrlSafeForClickToCall(const GURL& url) {
// Get the unescaped content as this is what we’ll end up sending to the
// Android dialer.
std::string unescaped = GetUnescapedURLContent(url);
// We don’t allow any number that contains any of these characters as they
// might be used to create USSD codes.
return !unescaped.empty() &&
std::none_of(unescaped.begin(), unescaped.end(),
[](char c) { return c == ‘#’ || c == ‘*’ || c == ‘%’; });

When we review the patch commit, it is can be seen that to patch such USSD code vulnerability, the Chrome team is blocking the characters like “asterisk (*)”, “Hash (#)”, and “Percentage (%)” which will eventually block the USSD codes from being sent to the Android dialer even after URL-encoding it. Great work by the Chrome folks to strip out the complete vulnerability!

How did Chrome folks anticipate the next problem and patch it beforehand with 100 IQ?

In the Commit, we can see three steps taken by Chrome folks to protect users from USSD attacks. USSD codes are usually ‘*’ and ‘#’, why is Chrome stripping out ‘%’ also? If we didn’t have the exploit test case, the below reason is sufficient to know why it was required to blacklist “%”.

That’s what the clever patch is all about, and hats-off to the Chrome Security team for anticipating the bypass for the patch i.e. when you encode USSD code, it starts with “%” and when you will encode a “%”, it will end up into “%25” so every time the patch seeing anything encoded starting with “%” will not allow it to be triggered. However, if there are any other bypasses present over there that I am not aware of, feel free to send hacks to the team. But luckily, the Chrome team has also removed the “#click-to-call-ui” which means you can’t send the Phone number to your device when clicking on a “tel:” handler, anymore. Previously, it was available and can be enabled by using the “chrome://flags/#click-to-call-ui”. As “Click-to-Call” functionality is removed, it means that the “callto:” and “tel:” are currently working in the same manner i.e. asking you if you want to open an application. Previously, “callto:!=tel:” because “callto:” URI is for applications like Skype whereas the “tel:” URI is used to send to the dialer of your device. The “tel:” and the “callto:” works the same when you will use any special blacklisted characters with the “tel:” URI in the hyperlink.

Screenshot from chrome://flags

The above screenshot is from Google Chrome ( 95.0.4638.69 | Stable| Revision | Windows 10 ). From the above screenshot, it can be inferred that Chrome is currently delivering the browser without Click-to-call functionality (by default) due to which, users won’t be able to use the function to send “Phone Numbers” from desktop to device also, one can’t enable it as it has been removed completely.

Vulnerability credits: Patrick Walker, Eric Lawrence, and Kirtikumar Anandrao Ramchandani

Patch & reviewer credits: Richard Knoll, Peter Beverloo, Istiaque Ahmed, Gayane Petrosyan, David Jacobo, Robert Kaplow, and Mike West

Hon’ble mentions: Amy Ressler, Adrian Taylor, Andrew Whally, Abdulrhman Alquabandi, Jun Kokatsu, Christoph Diehl, and Yan for inspiring me to get into browser security.

Key takeaways:

1. Always see the other side of a feature like you see both sides of a coin.
2. Before you start to hack, take a look at the past bugs.
3. Keep asking as many questions as you can.
4. Don’t give up too early, there’s always a solution/bypass.

Advisory/Stable Channel Update:

Bug-Introducing CL: Unknown


Feb 20, 2021: Case created
Feb 22, 2021: Case triaged
March 29, 2021: Vulnerability root cause found by Richard Knoll (Chromium Folk)
April 22, 2021: Patch committed
April 26, 2021: Issue Patched and it was landed into Canary build
May 20, 2021: Boilerplate remainder by Amy Ressler
July 20, 2021: Vulnerability Acknowledged as CVE-2021–30589 in Monthly Google Chrome Stable Channel Update

Upcoming Blog: “.onion” URLs can be leaked. :)