All Articles

N1CTF 2021 Writeup

This page has been machine-translated from the original page.

I participated in n1CTF, which ran from November 20, 2021.

Unfortunately I could not solve any of the problems, so this article is entirely about reviewing what I missed.

I am going through the challenges using other participants’ writeups as references.

Table of Contents

Signin (Web)

You can access a website running the following PHP script:

<?php 
//flag is /flag
$path=$_POST['path'];
$time=(isset($_GET['time'])) ? urldecode(date(file_get_contents('php://input'))) : date("Y/m/d H:i:s");
$name="/var/www/tmp/".time().rand().'.txt';
$black="f|ht|ba|z|ro|;|,|=|c|g|da|_";
$blist=explode("|",$black);
foreach($blist as $b){
    if(strpos($path,$b) !== false){
        die('111');
    }
}
if(file_put_contents($name, $time)){
echo "<pre class='language-html'><code class='language-html'>logpath:$name</code></pre>";
}
$check=preg_replace('/((\s)*(\n)+(\s)*)/i','',file_get_contents($path));
if(is_file($check)){
echo "<pre class='language-html'><code class='language-html'>".file_get_contents($check)."</code></pre>";
}

The Flag is located at the path /flag, but the path data sent via POST is filtered by a blacklist.

My original assumption was that this involved crafting an eval-like statement using characters not on the blacklist so that $check would ultimately contain /flag — but that turned out to be wrong.

The key to obtaining the Flag is focusing on this line: $time=(isset($_GET['time'])) ? urldecode(date(file_get_contents('php://input'))) : date("Y/m/d H:i:s");

isset($_GET[‘time’])

This expression checks whether a variable exists — specifically, whether the GET query contains time.

Reference: PHP: isset - Manual

filegetcontents(‘php://input’)

This script is called when the GET query contains time.

file_get_contents('php://input') retrieves the raw request body below the HTTP headers.

Reference: ajax - PHP “php://input” vs $_POST - Stack Overflow

Reference: What is PHP filegetcontents()? - Qiita

Bypassing date()

As described above, the request data obtained as a string via file_get_contents('php://input') is passed to the date() function.

Reading the official documentation reveals this format sample:

<?php
// Assuming today is March 10th, 2001, 5:16:18 pm, and that we are in the
// Mountain Standard Time (MST) Time Zone

$today = date("F j, Y, g:i a");                 // March 10, 2001, 5:16 pm
$today = date("m.d.y");                         // 03.10.01
$today = date("j, n, Y");                       // 10, 3, 2001
$today = date("Ymd");                           // 20010310
$today = date('h-i-s, j-m-y, it is w Day');     // 05-16-18, 10-03-01, 1631 1618 6 Satpm01
$today = date('\i\t \i\s \t\h\e jS \d\a\y.');   // it is the 10th day.
$today = date("D M j G:i:s T Y");               // Sat Mar 10 17:16:18 MST 2001
$today = date('H:m:s \m \i\s\ \m\o\n\t\h');     // 17:03:18 m is month
$today = date("H:i:s");                         // 17:16:18
$today = date("Y-m-d H:i:s");                   // 2001-03-10 17:16:18 (the MySQL DATETIME format)
?>

As you can see from date('\i\t \i\s \t\h\e jS \d\a\y.');, prefixing characters with a backslash prevents date() from treating them as date-format tokens, so the backslash-escaped characters are output literally.

Using this behavior, sending \/\f/l\/a/g as POST data causes $time to ultimately contain /flag, from which the Flag can be retrieved.

Summary

I really wanted to review the Rev problems too, but I could not find any writeups for them.

I will update this article if I find some.