MySejahtera is a Perfectly Good App With No Exploits — Final (Dual Fix Ver )

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

It is a POST request that requires emailOrUserName field.

Sending ForgetPassword Request

Successful Request

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.

main.js from the decompiled apk

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:



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.

Actual `registerNewUser()` function call

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:

  1. As usual, no Rate-Limit, no Authentication Token required. The door is open to everyone!

  2. There is a difference in behaviour between the two baseURL , “MySejahtera” URL will explicitly tell you that the number had been registered and give you 200 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.

  3. 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.

  4. 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:

Look at Title

The information above is valid as of 22 October 2021. I should probably resign from this series too.