← Back to Writeups
CUSTOMEasyWeb

Точность+ (Tochnost+)

XESXOR9/2/20263 min read
#web#custom#easy#hex_literal_validation_bypass#inconsistent_number_parsing#javascript_type_confusion

Точность+ (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
{"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:

MethodInput "0x1000"Result
parseInt("0x1000")Parses hex prefix4096
Number("0x1000")Evaluates hex literal4096
parseFloat("0x1000")Stops at x0
String comparison "0x1000" <= "1000"Lexicographictrue (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 to 1 by the validator (likely parseInt("1e9") returns 1), so the average stayed low

Solution

The bypass uses a hexadecimal literal "0x1000" which:

  1. Passes validation: The range check sees it as a valid number within bounds (depending on the parsing method used for validation)
  2. Evaluates to 4096: When the server computes the arithmetic mean using Number(), 0x1000 = 4096 ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Exploit

bash
curl -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") = 4096
  • num2 = 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