WIZER CTF #22: CALCULATOR V2

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 #22

Goal

In this challenge, we're identifying yet another tricky case of a code injection vulnerability.

Description of code

The code below is a version 2 of Challenge #19, which is a simple calculator endpoint. The developer figured that the user of Function c'tor would be safer than just using eval because they read about scopes in JavaScript and noticed that Function() runs in a different scope. Then, the solvers of challenge #19 showed that the code is dangerously vulnerable. Going back to the drawing board, the developer removed the `require` argument and also came up with the following validation, to ensure that the input is valid:


chal22-validate

 

Today we prove that even though a validation was implemented, it's still not safe at all! Take a look at the full app code below.


chal22-code2

What’s wrong with that approach?

There are other applicable ways to bind a library in order to use it in NodeJS. Furthermore, there are multiple bypass options for a blacklist of keywords, especially in flexible mighty languages like Javascript.

What would a successful Command Injection attack look like in this case?

There are multiple ways to bypass the validation, one such way is to use a combination of `eval(atob())` which hides keywords that are being looked for. However, that's not enough since we can't use the `require` command anymore since it's not present on this version, but we could still use `process.binding` to import `fs` and then use the `.internalModuleReadJSON('/etc/passwd')`. Another way around the validation is to break unallowed words into parts and then concatenate them in the code. For instance, we could use `pro` and `cess.binding` to bypass the validation and run eval on the textual command in order to import `fs`.

So what?

Code Injection is an extremely dangerous vulnerability, once an attacker gains access to execute any code and basically any library by controlling user-input, the range of opportunities is broad starting from reading secret keys, through server and infrastructure take over.

Main Takeaways:

  • Never use `eval` nor `String(Function())` involving user input:
    Almost every person who solved this challenge has found a different, creative way to bypass the validation and run code on the server, this means that trying to capture all the methods of bypassing the validation is almost a lost cause. Avoid the use of `eval` and Function constructors with user input. There's almost no good reason to use them, and if you think you have one, you're probably wrong. However, in the unlikely event that you do have a good reason to use them, make sure to create a very strong validation structure, preferably using a whitelisting of what can be done rather than a blacklisting of what can't.
  • Be sure you understand the scope of the validation which needs to be made:
    In this case, the developer thought that by validating the input against a few keywords and removing the `require` option would be safe. However, there are other ways to run cryptic code which bypasses the validation and to import libraries into the code, hence, the validation was not enough to prevent the code injection.
  • Apply a least privilege approach upon deployment of the code:
    Make sure that the code is running with the least amount of privileges possible. In this case, the code could have been running in a container with limited access to the host machine. If you aren't the guy who's responsible for the deployment, make sure to communicate the potential risks to the person who is.

 

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

CODE WIZER!

Past Challenges

CTFs For Developers