Fantastic Vulnerabilities and Where to Find Them

TL-DR; I decided to write down some of the interesting security flaws that I encountered during web & mobile app pentest assignments. I hope it is a fun read.

Penetration testing is a fulfilling profession that promotes critical thinking and out-of-the-box ideas. I want to share the interesting and even bizarre software errors that I have come across. Some of these have also provided valuable additions to my checklist.

I am keeping the financial applications out of this post because I have a separate blog post for them here.

And before we begin, a gentle reminder and disclaimer: Testing the security of an application or a system without obtaining prior approval from its owner(s) is unethical, illegal, and can very easily land you in jail. You may also be legally obliged to reimburse the financial and/or reputation losses that your activities have caused. In other words, do not try any of the content in this blog post, unless you are a trained professional and are acting in accordance with the pre-approved terms and conditions of a formal penetration test engagement. All of the information in this blog post is provided “as is”. The content has been sanitized to protect the security of systems. The author of this blog post (Reha Esen) or the company (Sistematik OÜ) cannot be held responsible for any damages in any way.

Account takeover via brute-force, despite an account lock-out policy

I added this case to our standard checklist as soon as I encountered it the first time, and we have been coming across it quite often ever since. I also believe that this little bug is a bit elusive and could go unnoticed in some penetration testing engagements.

The mobile application has a robust account lock-out policy. 4 consecutive incorrect password submissions and the account gets locked. However, if the attacker continues to brute-force the locked account, they observe that the server’s response is slightly different when the correct credentials are submitted. Therefore, the attacker can actually discover the correct password, despite the account being locked.

The best part is that the accounts often get unlocked automatically after some time. This permits the attacker to do a brute-force attack, during which the account gets locked, then continues to brute-force until the correct password is discovered, then wait 15 minutes, so that the account gets unlocked automatically, and then login! That is a trivial account takeover!

Tip: Submit the correct password after locking the account, compare server responses to correct and incorrect password submissions.


The unflushable session – Mysterious case of the misconfigured load balancer

I had completed my test a few hours earlier than planned. But if I am already paid for the whole day, and being an “ethical” hacker, I spend the rest of my time on the project anyway. So, I decided to play around with the web app. I logged off from the app and switched to Burp’s Repeater. I re-sent an authenticated request, got a “403 – Unauthorized” response, as expected. Then I re-submitted, got the same response again. Once more, same response… At the 11th re-submission, I got a “200 – OK” response! Shocked and puzzled, I tried to figure out what was going on, and repeated the submissions 50 more times. I realized that in only a small portion of the requests I was getting the 200 responses.

It took me some time to wrap my head around this phenomenon, but finally figured out that it must be a misconfigured load balancer. After a quick call, the system owner checked and confirmed the hypothesis: some of the servers were not getting the logout request.

Since the sessions never got terminated in one of the application servers, the attacker could continue the brute force attack for an extend period, to guess a valid session token.

This is one of those times where you rightfully think that it is just plain luck that helped you discover such an elusive and weird error. I guess a good habit (on top of diligently following your checklist), is to take time to play around with the application. Get out-of-the-box and go with instinct. If you return empty-handed, no harm done. If you get lucky, then at least you get a new blog post idea…

Tip: After a logout, re-send an authenticated request at least 20 times. Make sure the session is dead for good!


Predictable password reset links – Easy account takeover

Upon clicking the forgot password link and entering their email address, a password reset link is sent to the users. Every password reset link contains a unique, long, and complex value that is associated with the password reset request and the corresponding email address. Upon close examination of the value, I figured that it is actually a Base64-encoded timestamp value in EPOCH, designating when the password reset request was received. This is particularly bad, because the attacker can rapidly send two password reset requests back-to-back, one for his own account and one for the victim. Then can decode and examine the value in his own email. Since the victim’s password reset email was sent right after his own, the hacker should make a simple brute-force attack (Burp Intruder) to guess the correct value. Upon submission, the attacker will set a new password for the victim’s account. Another trivial account takeover!

Tip: Password reset links should always have random values.


Upload a binary and wait until the web server’s job scheduler executes it! – Trivial RCE

In a mobile application penetration test, I discovered that the server-side code allows me to upload executable files. I decided to use Canary Tokens to generate “Custom exe / binary” token and upload that as the file. If at some point the binary gets executed, it sends a beacon to the Canary Tokens server, which sends an email to you.

About 10 minutes after uploading the binary, I received an email from Canary Tokens! I immediately started drafting the high-risk finding, but before I could send it to the client, I received a second email. Then yet a third, and on and on… Apparently a scheduled job was triggering the executables at a fixed interval.

To date, this is by far the most trivial remote code execution vulnerability I have ever encountered. Needless to say, this made an interesting addition to the checklist.

Tip: Use Canary Tokens. It is a perfect tool. It is good for discovering SSRF, tracking social engineering test results and, oh yes, getting shells.


Fun with unrestricted GraphQL

We are seeing more and more of GraphQL used in applications. Undeniably, such technologies provide better scalability and improved response times, which are improvements. However, I have been growing cautious about them, because some implementations contain query and transaction parameters in the client request, and if the back-end authorization checks are not robust, this can allow the end-user to alter the query statements to access unauthorized data from the back-end.

In  one of the tests, this was exactly the case; as long as your query is valid, the backend was responding with the data the client asked for. So, all that the attacker has to do is to come up with the right syntax, and the backend responds positively.

I spent some time brute-forcing table and column names, locating juicy information and downloading some sample data to demonstrate the capability of the attack and show the severity of the situation. An error like this could easily lead to mass downloading of end-user data or may even cause data integrity issues.

Tip: Alter and play around with the client-side queries, of either GraphQL or of similar middle-ware components. Too much trust on the client-side may be abused.


It is not SQL Injection, but is kinda similar: Database Enumeration

This may be another simple vulnerability that often goes undetected. While testers are more inclined to finding SQL injection vulnerabilities, here we take advantage of the “sort” functionality on a page that lists some records. We use the sort function to enumerate the data in columns that are not even exposed to the end user. It is loads of fun. We will have a separate, more detailed blog post for this issue soon, however a sneak peek goes like this:

Imagine a web or a mobile application with some records listed on a particular screen, and the user is allowed to order the list by clicking on column headers. If this ordering triggers a new request from the client side to the server side, then it means that “order by” criteria of the query gets sent to the server within a particular request parameter. After locating the parameter, the first step of the attack is to enumerate the column names in the table(s) by a long list of common column names. I used the “common column names” text file within SQLMap’s source code and Burp’s Intruder for brute-forcing the column names. Enumeration may give you a list of valid column names, and some of those may not even be shown on the screen. Such columns may contain juicy information like “password”, “home address”, “gender” or “annual income”. After discovering the column names, the attacker decides on a column that is worthy of enumerating. It should be noted that, for a successful enumeration, the attacker should be able to control a particular record on the table and be able to change the value of the column to enumerate (let’s call this the “target column”). Then, the third step is to order the records based on the “target column” and observe the changes to the order of your own record. Then alter the value of the “target column” of your record and see how its order changes. By doing a binary search, one can enumerate character-by-character the entire value of the “target column” value of any record.

The fun bit of this little technique is that it does not contain a SQL injection payload, because we do not try to break out of the data context. This means that the attacker can stay under the radar, even when a WAF or a similar security product is in play.

Tip: Playing around with sorting function can help you enumerate data!.


Phone number verification in a mobile app

This interesting bug was discovered in the new user registration function of a mobile application.

Most mobile apps send an SMS to the registered number for verification. Instead of doing this, the application in our scope was triggering the user’s phone to send itself a verification SMS. Since the mobile application has permission to directly read the incoming SMS messages, it can verify whether or not the number is correct. This way, if the number that the user is trying to register belongs to someone else, the SMS never arrives, and the verification step fails. This aims to kill two birds with one stone; provide security while avoiding SMS costs.

As clever as this method is, it misses a fundamental feat of authentication security: you should not rely on a device that you have no control over. The received SMS message is regarded as a proof of identity (owning that phone number) and the server trusts the authentication information provided by the client.

If the attacker can “somehow” bypass the client-side control and succeeds to enroll with someone else’s number, it will be a successful account takeover and the attacker will read the victim’s messages.

The attack involved a little bit of an “inception moment” with our test phone: we used Frida to change the designated phone number in the phone’s memory, during run-time. We altered the phone number right before the user registration step was finalized, so that the attacker’s own number got verified, but the number reported to the back-end server as the verified number was the one that we injected. And this was easier than changing the number during transport, because the number is transmitted with asymmetric encryption and circumventing that altogether was an easier route.

Tip: Never implement the authentication mechanism on the client-side, simply because the user has total control over the hardware and the OS, therefore the application owner can never reliably protect the software running on it.

I hope you enjoyed reading this. If you have any feedback, do not hesitate to reach out to me via Also don’t forget to check our blog post on testing financial web applications.