For Developers

WIZER CTF #26: Messages App Level 2

Written by Itzik Spitzen | Jul 25, 2024 1:27:03 PM

If you're a returning visitor to our CTF Recaps, feel free to dive straight into the insights! For first-time explorers, let us quickly introduce you to the essence of these recaps. Wizer CTFs were introduced to challenge developers, encouraging them to adopt a hacker's mindset and thereby code more securely. This initiative is a pivotal part of our new security awareness training, specially crafted for development teams - Wizer's Secure Code Training for Developers!

After a challenge retires, our Wizer Wizard and CTO, Itzik Spitzen, crafts takeaways that offer valuable insights into the challenge, focusing on the defensive perspective for your script. Curious to test-drive a CTF before delving into the notes? Visit wizer-ctf.com – it's free, and there's something for all skill levels!

Link to challenge #26

Goal

In this challenge, we're identifying a Stored DOM-based XSS vulnerability caused by an incomplete error handling structure.

Description of code

The code below showcases a message creating and viewing app. The front-end code below represents the two main user interfaces: message creation and message retrieval / viewing pages. When we want to show a message on the page, we fetch the messages from the API and then place the messageId, firstname, lastname and the message itself in the browser's DOM. For the first three, innerText is used, however for the message itself, innerHTML is used. The developer(s) of Challenge #10 has revised the app and added the field `attn` (attention), furthermore, in order to mitigate the original vulnerability, they added a server side sanitization using the well known library DOMPurify.


 

What’s wrong with that approach?

If compared to Challenge #10, the sanitization structure which was implemented is definitely a good step forward, it is indeed an improved version since it's done upon save function on the server side and not only on the frontend client code side. However, the flaw of the change is within the error handling structure below, which can be exploited to change the flow of the code and affect the results. The other effective way to solve it would have been to encode the output, and prevent the XSS from being triggered upon /showMessage.


 

Another effective way to solve it would have been to encode the output, and prevent the XSS from being triggered upon /showMessage.

What would a successful Stored XSS attack look like?

By not providing a 4th argument (causing `req.body[3]` to be null), an error will be caused and the error handling structure in place, which is currently merely printing the error message but not exiting the function, will allow the attacker to continue the code flow, skipping the DOMPurify sanitization and injecting a script tag into the message. Then once that message is saved in the system, the attacker would use some method such as a phishing email or any other social engineering trick, to make someone hit a link which includes that ID, as demonstrated below:

https://https://chal26-j89j45fg8.vercel.app/views/showMessage?messageId=[malicious id]

This vulnerability is a specific subtype of XSS named Stored DOM-based XSS.

 

So what?

While the code injection required to capture the flag is absolutely harmless, once an XSS attack is possible, by using a phishing strategies or other social engineering techniques, they could cause someone to click the link with the payload and execute an attack to hijack session cookies and/or perform actions on their behalf. Attackers can make requests authenticated as the user (transfer money in a banking application for example) or send data back to them (make API calls and send all the resulting data back). It could escalate and be immensely harmful as well since once attackers are able to run Javascript within the context of a logged-in user it could serve as an entry point, they can then identify and exploit other vulnerabilities such as broken access control (a.k.a. IDOR), weak encryption / hashing and others to execute wider attacks such as taking over an admin account.

Main Takeaways:

  • Error handling flow matters:
    Always make sure that the error handling structure is properly implemented. In this case, the developer didn't properly handle the error and hence, the attacker was able to change the flow of the code by causing an error. The DOMPurify sanitization was skipped. Make sure to always review the code flow caused by an error and ensure it either exists the function or properly starts an error handling routine which doesn't allow unintended behaviors.
  • Code flow should always be tightly implemented and properly tested:
    Developers should always stop and think about the code flow including the options which could be caused by using the code in different ways, and not only the intended or straightforward way. Unit tests should cover 'negative' cases (meaning what the code shouldn't do) and not only 'positive' cases (meaning what the code should do). It is highly recommended to spend time creating better unit tests which ideally cover all the possible cases.
  • Minimize your exposure to XSS by avoiding DOM changes:
    While the developer likely thought it would be nice to easily support HTML tags within messages, if the requirements do not specifically say it's required, it's always better to minimize the exposure. If the use of HTML tags isn't specifically needed, the developer could have replaced the innerHTML with innerText which would have prevented the option of an XSS attack altogether. Minimizing the exposure could have also been done by encoding the output, and preventing the XSS from being triggered upon /showMessage.

 

Wanna join us on our next challenge? Sign up for our mailing list at wizer-ctf.com.

CODE WIZER!

Past Challenges

CTFs For Developers