miscfreemedium

Not Posixtive

HackTheBox

Luigi is not posixtive we can challenge his scripting abilities. He's convinced we cannot understand the secret hidden inside his l33t coding abilities. We can't let that slide!

$ ls tags/ techniques/
python_hash_collisionbitwise_not_bypassreturn_code_exploitation

Not Posixtive — HackTheBox

Description

Luigi is not posixtive we can challenge his scripting abilities. He's convinced we cannot understand the secret hidden inside his l33t coding abilities. We can't let that slide!

Analysis

Server Structure

The challenge provides a Python server with several key components:

  1. Input validation functions:

    • check_stricter_values() - allows max 4 chars, only alphabetic and dots
    • check_values() - allows max 13 chars, only alphabetic and dots
    • check_operands() - blocks operators +, -, *, /, %, =, x, o, b but allows ~
  2. Execute function:

    • Runs subprocess.run([bin, switch, compl])
    • Returns returncode * mode
  3. Win condition:

if debug[0] != debug[1] and str(debug[0]) != str(debug[1]) and hash(debug[0]) == hash(debug[1]) and isinstance(debug[0], type(debug[1])): print("What an awesome player! You have beaten the competitor, you deserve this:", open('flag.txt').read())

Vulnerability Analysis

The win condition requires finding two values that:

  • Are not equal (!=)
  • Have different string representations
  • Have the same hash
  • Are the same type

Key Insight: Python Hash Collision

In Python, hash(-1) == hash(-2) == -2. This is a known implementation detail of Python's hash function where -1 is reserved as an error indicator, so hash(-1) returns -2 instead.

>>> hash(-1) -2 >>> hash(-2) -2 >>> hash(-1) == hash(-2) True

Solution

Exploitation Strategy

  1. Mode = ~0

    • The - operator is blocked, but ~ (bitwise NOT) is not
    • ~0 = -1 in Python
    • This passes through eval() in check_operands()
  2. Binary = grep

    • 4 characters, alphabetic only
    • grep pattern file returns:
      • 0 if match found
      • 1 if no match
      • 2 if error (file not found)
  3. Arguments = server.py,nonexistent

    • server.py exists on the server
    • nonexistent does not exist
  4. Switches = zzz,zzz

    • Pattern that won't match anything in server.py

Result Calculation

  • Command 1: grep zzz server.py -> return code 1 -> 1 * (-1) = -1
  • Command 2: grep zzz nonexistent -> return code 2 -> 2 * (-1) = -2

Verification

debug = [-1, -2] debug[0] != debug[1] # True: -1 != -2 str(debug[0]) != str(debug[1]) # True: "-1" != "-2" hash(debug[0]) == hash(debug[1]) # True: hash(-1) == hash(-2) == -2 isinstance(debug[0], type(debug[1])) # True: both are int

Exploit Script

#!/usr/bin/env python3 from pwn import * r = remote('94.237.61.202', 47412) r.recvuntil(b'> ') r.sendline(b'1') # Create mode r.recvuntil(b'(mode)> ') r.sendline(b'~0') # mode = -1 r.recvuntil(b'> ') r.sendline(b'2') # Add bin r.recvuntil(b'(bin)> ') r.sendline(b'grep') r.recvuntil(b'> ') r.sendline(b'3') # Research arguments r.recvuntil(b'(arg1,arg2)> ') r.sendline(b'server.py,nonexistent') r.recvuntil(b'> ') r.sendline(b'4') # Switches r.recvuntil(b'(switch1,switch2)> ') r.sendline(b'zzz,zzz') r.recvuntil(b'> ') r.sendline(b'5') # Beat the competitor print(r.recvall(timeout=5).decode())

References

  • Python hash implementation: hash(-1) returns -2 as -1 is reserved for errors
  • POSIX exit codes: 0=success, 1=general error, 2=misuse/file not found
  • Bitwise NOT: ~x = -(x+1), so ~0 = -1

$ cat /etc/motd

Liked this one?

Pro unlocks every writeup, every flag, and API access. $9/mo.

$ cat pricing.md

$ grep --similar

Similar writeups