Точность+ (Tochnost+)
Точность+ (Tochnost+)
Platform: CUSTOM | Category: Web | Type: Challenge | Difficulty: Easy | OS: N/A | **Author: XESXOR Techniques: hex_literal_validation_bypass, inconsistent_number_parsing, javascript_type_confusion
Summary
Walkthrough of the CUSTOM Web Точность+ (Tochnost+). Task: Express.js calculator computes arithmetic mean of two numbers, requiring result > 1000 while inputs must be 1-1000. Solution: JavaScript type confusion — hex literal '0x1000' passes range valida
Description
Самый точный калькулятор, правда, пока только бета-версия. Нужно его протестировать.
English summary: The most accurate calculator, though still only a beta version. You need to test it. The app computes the arithmetic mean of two numbers. The goal is to get a result greater than 1000, but inputs are supposedly restricted to the range 1–1000 (with a winking hint "на первый взгляд 😉" — "at first glance").
Semantic Clues
- "Точность+" (Accuracy+/Precision+) — hints at numeric precision and type handling edge cases
- "бета-версия" (beta version) — suggests incomplete/buggy validation
- "на первый взгляд 😉" (at first glance 😉) — explicit hint that the 1–1000 restriction can be bypassed; things are not what they seem
Analysis
The target at http://31.207.77.216:4023 is an Express.js calculator application. The homepage presents a form to enter two numbers and compute their arithmetic mean.
Request format
The form sends a POST request to /calculate with a JSON body:
json read-only • copy allowed{"num1": "500", "num2": "500"}
Validation logic
The server validates that each number falls within the range 1–1000. However, the validation and the actual arithmetic computation use different parsing methods — a classic JavaScript type confusion vulnerability.
The key insight is that JavaScript has multiple ways to parse numbers:
| Method | Input "0x1000" | Result |
|---|---|---|
parseInt("0x1000") | Parses hex prefix | 4096 |
Number("0x1000") | Evaluates hex literal | 4096 |
parseFloat("0x1000") | Stops at x | 0 |
String comparison "0x1000" <= "1000" | Lexicographic | true (starts with "0") |
The server likely uses one parsing method for validation (e.g., parseFloat which returns 0 for hex, or string/loose comparison) and another for computation (e.g., Number() which correctly evaluates 0x1000 as 4096).
Failed attempts
- Plain large numbers (
"5000","2000") — rejected by the range check, which correctly identifies them as > 1000 - Scientific notation (
"1e9") — coerced to1by the validator (likelyparseInt("1e9")returns1), so the average stayed low
Solution
The bypass uses a hexadecimal literal "0x1000" which:
- Passes validation: The range check sees it as a valid number within bounds (depending on the parsing method used for validation)
- Evaluates to 4096: When the server computes the arithmetic mean using
Number(),0x1000= 4096
Exploit
bash read-only • copy allowedcurl -X POST http://31.207.77.216:4023/calculate \ -H "Content-Type: application/json" \ -d '{"num1":"0x1000","num2":"500"}'
The server computes:
num1=Number("0x1000")=4096num2=Number("500")=500- Average =
(4096 + 500) / 2=2298
Since 2298 > 1000, the condition is satisfied and the flag is returned.
Response
Average: 2298 > 1000 — BugCTF{REDACTED}
Key Takeaways / Lessons
- Mastering hex_literal_validation_bypass, inconsistent_number_parsing, javascript_type_confusion was key to this challenge.
- Always check for common misconfigurations and verify assumptions.
Crafted by XESXOR Research • 2026-05-29 • CUSTOM • Точность+ (Tochnost+) • Difficulty: Easy