MySejahtera is a Perfectly Good App With No Exploits — Final (Dual Fix Ver )
Part 1 — MySejahtera is a Perfectly Good App With No Exploits
Part 2 — MySejahtera is a Perfectly Good App With No Exploits — Part 2
KUALA LUMPUR, Oct 21 — Health Minister Khairy Jamaluddin today said that the ministry is conducting a “dual-fix” method to address the issue of unsolicited one-time passwords (OTP) and spam emails being sent out from the MySejahtera system, to the public, whose details are registered with the app. — Malay Mail
With “Dual-Fix”, can we expect all the public API endpoints to be secure now? We’ll have to find that out for ourselves.
This time around, I thought the better entry point would be from the “MySejahtera” app itself since my brief manual test gave me a hint of what could go wrong given their solid track record.
Our final play starts with Man In The Middle Attack (MITM). The idea is to route all HTTPS traffic to a proxy and have a self-signed CA certificate installed into the android emulator. HTTP Toolkit makes everything easy via the ADB command.
However, I found out that “MySejahtera” app implements SSL-Pinning, which increases the difficulty of the attack by ensuring that communications are only possible with the dedicated trustful servers. Of course, we can use Frida or Objection to tinker around with the app, but I doubt it's worth the effort.
On the verge of giving up, I got the idea that the early version of “MySejahtera” app might not have SSL-Pinning. It's an idea worth exploring. I quickly get MySejahtera v1.0.9
apk from APKFun. Install it into the android emulator and proxy the traffic via Burp Suite.
Tadaa! My hunch was correct, SSL-Pinning was not implemented in the early version of the app (For obvious reason, since the app was probably rushed to production).
I found out that the “Reset Password” HTTP endpoint is actually not protected (solid consistency on security here) and is available at https://mysejahtera.malaysia.gov.my/register/forgotPassword?locale=en_US
It is a POST
request that requires emailOrUserName
field.
Now, you can easily run the following curl
command to trigger resetPasswordLink
SMS to any phone number or email address that had registered with “MySejahtera”. Just update the emailOrUserName
field to an appropriate value.
My next strategy is to de-compile the apk to look at the source code (extremely messed up version). After getting “MySejahtera” apk from APKMirror, I use Apktool to decompile by running.
apktool d mysejahtera.apk
which gives the following folder structure:
The code is pretty messed up for readability due to minification and bundling.
We can use tools like codebeautify to beautify the minified code and increase the readability.
The first thing I did is to confirm if there is any change in the baseURL
as compared to the previous two articles. A quick search with https://
keywords is good enough to point us right back to main.js
.
With the help of codebeautify, we now know that there are two domain names:
https://covid-19malaysia.kpisoft.com
https://mysejahtera.malaysia.gov.my
we’ll see much more of `devLink` too
Using the semi-guess method, let's continue to look for some endpoint by searching around. I started the search with /api
and saw two functions, resendOTP()
and resendEmailOTP()
. Looking at the concatenation of the URL
, we can kind of guess what is e
. It is either contactNumber
or email
.
While testing out, the /register/api/mobileApp/resendOtp
endpoint does work and will send OTP
to any number without any authentication or rate-limiting. However, it seems to get taken down at the time of writing and now return 403 Forbidden
. Bravo to the dev for trying to fix the vulnerable endpoint.
The /register/api/mobileApp/resendEmailOtp
endpoint is still live, but it is not as vulnerable since there is nothing much one can do with sending OTP
to random emails.
The next interesting function I found is the registerNewUser()
function. With a little digging, we’re able to identify what is being passed to registerNewUser()
. It is taking a javascript object with contactNumber
and countryCode
field.
That is the same payload as the endpoint in discovered Part 2 (It got fixed with the implementation of reCAPTCHA
).
So its POSTMAN
time!
The result is the same with Part 2, now you’re able to send OTP
to any phone number that has yet to register in MySejahtera
The issue with the above endpoint:
As usual, no Rate-Limit, no Authentication Token required. The door is open to everyone!
There is a difference in behaviour between the two
baseURL
, “MySejahtera” URL will explicitly tell you that the number had been registered and give you200 OK
, while the “KPISoft” has inconsistent behaviour. My wild guess is that it is the old URL that is used in early 2020 and got desynced as “MySejahtera” URL got live.A bad actor could use this to filter which phone number had been used to register “MySejahtera”. If it is not registered, an
OTP
will be sent.Who is paying for the
OTP
?
In conclusion, the APIs exploit throughout the series fit the description of some of the OWASP API Security Top 10
API2:2019 Broken User Authentication
API4:2019 Lack of Resources & Rate Limiting
API9:2019 Improper Assets Management
API10:2019 Insufficient Logging & Monitoring
Proudly brought to you by:
The information above is valid as of 22 October 2021. I should probably resign from this series too.