Introduction


Web Security.

In 1989, Tim Berners-Lee proposed the World Wide Web at CERN to help researchers share information. Documents could link to other documents, letting readers follow connections between information stored on different computers. What began as a way to share research grew into a platform for shopping, banking, work, entertainment, and just about everything else we have managed to squeeze into a browser!

The web runs over the internet through exchanges between clients and servers. Your browser is a client: it uses a URL to identify a resource and sends an HTTP request to a server, which sends a response back. For a web page, HTML describes the content and links, and JavaScript adds behavior by running code in your browser. The server can run code of its own to look up an account, save a message, or process a payment. Shared protocols and formats let the same browser interact with sites built by people who have never met.

All of this puts a lot of software, data, and authority within reach of a web request. Servers accept input from strangers, browsers execute code supplied by websites, and applications decide who gets access to private information. Those interactions have to remain safe even when someone deliberately sends the unexpected. Throughout this dojo, you'll look beneath the pages and buttons, follow the messages and code that make them work, and find out what happens when the assumptions behind them break.



Shell

Getting a web application to execute arbitrary commands gives you a foothold on the server. From there, you can do local reconnaissance: find out which account you're running as and look through the application's files to see what that access lets you do. Sending a new HTTP request for every command makes this investigation awkward. A shell gives you a way to keep working as you follow up on what you find.

With a bind shell, the target listens on a TCP port and attaches a shell when you connect. You send commands over that connection and read their output, while the shell itself runs on the target.

The server is already listening on 127.0.0.1, TCP port 1337. Open the challenge terminal and connect using Netcat (nc). Here, 127.0.0.1 refers to the challenge environment, so the client needs to run there too. Once connected, read /flag through the shell.

In the last challenge, you could reach the target's listening port. Now suppose a firewall allows new incoming connections only to the web port, 443, while permitting the server to make outgoing connections. The website is reachable, but a bind shell listening on 1337 is not.

With a reverse shell, you run the listener and the target connects back with its shell attached. A stateful firewall can allow traffic in both directions over that established connection while still rejecting new incoming connections to other ports. Your listener must be reachable from the target, and the target must be allowed to connect to it.

The challenge server is already trying to connect to 127.0.0.1 on TCP port 1337. Check Netcat's help for its listening option and start it in the challenge terminal. Once the connection arrives, use the shell to read /flag.


URL & Encoding

When you send a command or other data through a URL, the query parser may interpret part of it as URL syntax. Suppose you want to send fish&chips as the value of order. In /?order=fish&chips, a typical query parser takes & as the start of another parameter, leaving order with just fish.

Percent-encoding represents a byte as % followed by two hexadecimal digits. Writing the ampersand as %26 gives /?order=fish%26chips: the parser splits the parameters before decoding the values, so order contains fish&chips. A literal % is encoded as %25.

Other characters need care too. In query parameters, + represents a space, so a literal plus must be encoded as %2B. Spaces can also be encoded as %20. A # starts a URL fragment, which the client does not send to the server. Encode it as %23 when it belongs in the value.

Read /challenge/server to find the expected order, then send a percent-encoded version as the sole order query parameter in a GET request to https://challenge.internal/ to retrieve the flag. Extra or repeated query parameters are rejected. The server compares the decoded order, so every character must survive URL parsing and percent-decoding. When using a shell, quote the URL so it doesn't interpret the payload as its special characters.

You've sent a short value through a URL. What if you want to send an entire ELF executable, with null bytes and other non-printable data? This server will run a program you supply, but the whole file has to arrive in a URL.

Those bytes can still be percent-encoded, but escaping a byte takes three characters. For this much binary data, base64 is a convenient alternative: each group of three bytes becomes four printable characters.

Here the server uses Flask's parsed query parameters, so a literal + becomes a space. Any + in standard base64 would need to be sent as %2B. URL-safe base64 avoids that extra step by replacing + and / with - and _:

>>> import base64
>>> base64.b64encode(b"\xfb\xff")
b'+/8='
>>> base64.urlsafe_b64encode(b"\xfb\xff")
b'-_8='

Encode your executable with URL-safe base64 and send it in the elf query parameter of a GET request to https://challenge.internal/. The decoded executable must be no larger than 1 MiB. The server applies a two-second execution timeout.

The server returns the program's standard output and standard error in the stdout and stderr fields of its JSON response. A nonzero exit status returns HTTP 400 with both fields. Send a program that reads /flag and prints it.


30-Day Scoreboard:

This scoreboard reflects solves for challenges in this module after the module launched in this dojo.

Rank Hacker Badges Score