Table of Contents
Hello mates! I played VishwaCTF 2023 which was happened from 31 Mar to 3 Apr. I played it with the team Invaders0x1. These are the challenges i have solved during CTF.
WEB-Eeezzy
The challenge is at https://ch41272110703.ch.eng.run. It is a login page with fields username and password.
First, I did ctrl+u
😉. It shows a view.php
in onclick function.
Visiting https://ch41272110703.ch.eng.run/view.php results the following code
<?php
session_start();
$_SESSION['status']=null;
$flag="";
try {
if (isset($_GET['username']) && isset($_GET['password'])) {
if (strcmp($_GET['username'], $flag)==0 && strcmp($_GET['password'], $flag)==0)
$_SESSION['status']=$flag;
else
$_SESSION['status']="Invalid username or password";
}
} catch (Throwable $th) {
$_SESSION['status']=$flag;
}
?>
This php code is checking for the username and password parameters which are entered in the login page. The username and flag are compared using strcmp()
and similarly password and flag. If the condition satisfied we can get the flag.
Exploiting strcmp() vulnerabilities.
If I set $_GET['password']
equal to an empty array, then strcmp would return a NULL. Due to some inherent weaknesses in PHP's comparisons, NULL == 0 will return true.
Tried by inputing some default credentials in the login page. It says incorrect details, then i captured the request headers. I found that this https://ch41272110703.ch.eng.run/view.php?username=admin&password=1234&submit=Login
has been sent to the server for the authentication.
The payload would be : ?username=admin&password[]=34&submit=Login
https://ch41272110703.ch.eng.run/view.php??username=admin&password[]=34&submit=Login returns an error in the login page with the flag.
Flag : VishwaCTF{5t0p_c0mp4r1ng}
Steganography-Guatemala
Attached files : a gif file named AV
I tried exiftool to view metadata of the gif.
┌─[intruder@parrot]─[/Guatemala]
└──╼ $exiftool AV
ExifTool Version Number : 12.16
File Name : AV
Directory : .
File Size : 1086 KiB
File Modification Date/Time : 2023:03:31 17:17:04+05:30
File Access Date/Time : 2023:04:04 10:58:55+05:30
File Inode Change Date/Time : 2023:04:04 10:58:51+05:30
File Permissions : rwxrwx---
File Type : GIF
File Type Extension : gif
MIME Type : image/gif
GIF Version : 89a
Image Width : 498
Image Height : 498
Has Color Map : Yes
Color Resolution Depth : 8
Bits Per Pixel : 8
Background Color : 0
Animation Iterations : Infinite
Comment : dmlzaHdhQ1RGe3ByMDczYzdfdXJfM1gxRn0=
Frame Count : 17
Duration : 2.04 s
Image Size : 498x498
Megapixels : 0.248
It looks there is a base64 encoded text in the comments. By decoding it we can get the flag.
┌─[intruder@parrot]─[/Guatemala]
└──╼ $echo "dmlzaHdhQ1RGe3ByMDczYzdfdXJfM1gxRn0=" | base64 -d
vishwaCTF{pr073c7_ur_3X1F}
Flag : vishwaCTF{pr073c7_ur_3X1F}
Steganography-Can you see me?
Attached files : havealook.jpg
First of all, lets try to extract if any data is present in the image using steghide.
┌─[✗]─[intruder@parrot]─[/canyouseeme]
└──╼ $steghide extract -sf havealook.jpg
Enter passphrase:
steghide: could not extract any data with that passphrase!
Okay, we dont have password. Lets use unzip if we can extract data out of it without password.
┌─[intruder@parrot]─[/canyouseeme]
└──╼ $unzip havealook.jpg
Archive: havealook.jpg
warning [havealook.jpg]: 134855 extra bytes at beginning or within zipfile
(attempting to process anyway)
inflating: hereissomething.wav
Yes, we got an audio file hereissomething.wav. The audio is not understandable by the humans. It seems to be a morsecode. Then i used this site to decode the morse code. In the sonic visualizer i found the flag.
Flag : vishwaCTF{n0w_y0u_533_m3}
Forensics - The Sender Conundrum
Attached files : TheEmail.eml and unzipme.zip
The zip file is password protected. So, lets analyze email raw data for the password.
There is a riddle sent by the sender.
<p></p>Hello Marcus Cooper,<br>
You are one step behind from finding your flag. <br>
Here is a Riddle: <br>
I am a noun and not a verb or an adverb.<br>
I am given to you at birth and never taken away,<br>
You keep me until you die, come what may.<br>
What am I?<br>
I am too bad to solve riddles, so I found a solution on google as Name
Tried Name as the password to extract zip file. Its the wrong password. Then i tried few names present in the eml file. I found the correct name on this line Return-Path: BrandonLee@anonymousemail.me. I extracted the flag file from the zip with the password BrandonLee.
┌─[intruder@parrot]─[thesenderconundrum]
└──╼ $unzip unzipme.zip
Archive: unzipme.zip
creating: unzipme/
[unzipme.zip] unzipme/flag.txt password:
extracting: unzipme/flag.txt
┌─[intruder@parrot]─[/thesenderconundrum]
└──╼ $cat unzipme/flag.txt
vishwaCTF{1d3n7i7y_7h3f7_is_n0t_4_j0k3}
Flag : vishwaCTF{1d3n7i7y_7h3f7_is_n0t_4_j0k3}
REV-Phi-Calculator
Attached files : Phi_Calculator.py
#============================================================================#
#============================Phi CALCULATOR===============================#
#============================================================================#
import hashlib
from cryptography.fernet import Fernet
import base64
# GLOBALS --v
arcane_loop_trial = True
jump_into_full = False
full_version_code = ""
username_trial = "vishwaCTF"
bUsername_trial = b"vishwaCTF"
key_part_static1_trial = "VishwaCTF{m4k3_it_possibl3_"
key_part_dynamic1_trial = "xxxxxxxx"
key_part_static2_trial = "}"
key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial
print(len(key_full_template_trial))
star_db_trial = {
"Sharuk Khan": 4.38,
"Bollywood Star": 5.95,
"Rohan 16": 6.57,
"WISH 0855-0714": 7.17,
"Tiger 007": 7.78,
"Lalande 21185": 8.29,
"UV Ceti": 8.58,
"Sirius": 8.59,
"Boss 154": 9.69,
"Yin Sector CL-Y d127": 9.86,
"Duamta": 9.88,
"Ross 248": 10.37,
"WISE 1506+7027": 10.52,
"Epsilon Eridani": 10.52,
"Lacaille 9352": 10.69,
"Ross 128": 10.94,
"EZ Aquarii": 11.10,
"61 Cygni": 11.37,
"Procyon": 11.41,
"Struve 2398": 11.64,
"Groombridge 34": 11.73,
"Epsilon Indi": 11.80,
"SPF-LF 1": 11.82,
"Tau Ceti": 11.94,
"YZ Ceti": 12.07,
"WISE 0350-5658": 12.09,
"Luyten's Star": 12.39,
"Teegarden's Star": 12.43,
"Kapteyn's Star": 12.76,
"Talta": 12.83,
"Lacaille 8760": 12.88
}
def intro_trial():
print("\n===============================================\n\
Welcome to the Phi Calculator, " + username_trial + "!\n")
print("This is the trial version of Phi Calculator.")
print("The full version may be purchased in person near\n\
the galactic center of the Milky Way galaxy. \n\
Available while supplies last!\n\
=====================================================\n\n")
def menu_trial():
print("___Phi Calculator___\n\n\
Menu:\n\
(1) Estimate Projection Burn\n\
(2) [LOCKED] Estimate Slingshot Approach Vector\n\
(3) Enter License Key\n\
(4) Exit Phi Calculator")
choice = input("What would you like to do, "+ username_trial +" (1/2/3/4)? ")
if not validate_choice(choice):
print("\n\nInvalid choice!\n\n")
return
if choice == "1":
estimate_burn()
elif choice == "2":
locked_estimate_vector()
elif choice == "3":
enter_license()
elif choice == "4":
global arcane_loop_trial
arcane_loop_trial = False
print("Bye!")
else:
print("That choice is not valid. Please enter a single, valid \
lowercase letter choice (1/2/3/4).")
def validate_choice(menu_choice):
if menu_choice == "1" or \
menu_choice == "2" or \
menu_choice == "3" or \
menu_choice == "4":
return True
else:
return False
def estimate_burn():
print("\n\nSOL is detected as your nearest star.")
target_system = input("To which system do you want to travel? ")
if target_system in star_db_trial:
ly = star_db_trial[target_system]
mana_cost_low = ly**2
mana_cost_high = ly**3
print("\n"+ target_system +" will cost between "+ str(mana_cost_low) \
+" and "+ str(mana_cost_high) +" stone(s) to project to\n\n")
else:
# TODO : could add option to list known stars
print("\nStar not found.\n\n")
def locked_estimate_vector():
print("\n\nYou must buy the full version of this software to use this \
feature!\n\n")
def enter_license():
user_key = input("\nEnter your license key: ")
user_key = user_key.strip()
global bUsername_trial
if check_key(user_key, bUsername_trial):
decrypt_full_version(user_key)
else:
print("\nKey is NOT VALID. Check your data entry.\n\n")
def check_key(key, username_trial):
global key_full_template_trial
if len(key) != len(key_full_template_trial):
return False
else:
# Check static base key part --v
i = 0
for c in key_part_static1_trial:
if key[i] != c:
return False
i += 1
# TODO : test performance on toolbox container
# Check dynamic part --v
if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[5]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[3]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[6]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[2]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[7]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[1]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[8]:
return False
return True
def decrypt_full_version(key_str):
key_base64 = base64.b64encode(key_str.encode())
f = Fernet(key_base64)
try:
with open("keygenme.py", "w") as fout:
global full_version
global full_version_code
full_version_code = f.decrypt(full_version)
fout.write(full_version_code.decode())
global arcane_loop_trial
arcane_loop_trial = False
global jump_into_full
jump_into_full = True
print("\nFull version written to 'keygenme.py'.\n\n"+ \
"Exiting trial version...")
except FileExistsError:
sys.stderr.write("Full version of keygenme NOT written to disk, "+ \
"ERROR: 'keygenme.py' file already exists.\n\n"+ \
"ADVICE: If this existing file is not valid, "+ \
"you may try deleting it and entering the "+ \
"license key again. Good luck")
def ui_flow():
intro_trial()
while arcane_loop_trial:
menu_trial()
# Encrypted blob of full version
full_version = \
b"""
gAAAAABgT_nvHAwPaWal_64Giubfb7I87ML4ANp4g-eUbMTqsc4asWygnpXcaJ5FLahXXDcul9xPDqIPPytiZ9aMm25S6dgfi4ZPvM5IUSnnNjk6dxYAKsX5Yd72BV4ERrqdNNn2jZrphzlV4a4gY-XV_0ZHovFlHhEpPQnTtG_5RTETId0xAD5K5iActkI9a3P4sx6ExBQ082EuPFlnWtUGl0dsEDHher3xT_lZe9JP5UAcOJsoC9AJ7N3Y1KjWXATzaBkXw6XTnzqDHu9Ycffw-i-GfQP-16hF_f2WBE9nQqniFu6THNuAvqwg0XBnsfvV0Fo3MTpON6HpeI3eIXqd4tLtsfhNcPa99ugrucf0l19Z5eFvrtMMgmfW_9lgvO7UcCft79ShvQWEHjhVeiDKBZo7TgTJ-1wpB92obH_bFGJpMcsp1w42tDEJmavnRSKXl39ph9-cgVXUKTfsjUbJCgtZfR8yj28JFCdmETu2kkt_dW4aLN8BTeRHLUpCEod9xBUFxzQJZNxey6ISn2j-PTR-yxCXrC2_A3TCBcqwUJYviP6emLKPSBRJB8dkRlWmylnMH4aYd6YXPnY457tk6UpGO6Ezw4K4DEhFtMSO4Vq2UhAS85j8kokc9_GG2v315uqVZ-TY7nU7xkhsrtEFK0h-0jiiLbTKLvOb3zpXq0ELdX9_WEK681LsIuFErhsvvPmmXx1K7IDlmjIWkYw--7lqpXPVrl9LalI-7npOF4MYet3jlH9v5Y83K3VDCrNZjH8uqK4pTKo0_I4HOmEtfe8pghAYDldmQ8wvphHRh4UEM34QcgCJa3VH1XAu2MRDwbEWcnXxumt_xL2wXBTFAPZWxrnioRunzw5HnrqW6Nzi871XiJ0OHQzt_ulvgxDmFMxAiSpzm9YJoxspTG1hpSqLe5IUICBXEhofgTAhHePGff-Qi20rDYQMQio8zoyV2ZPRjKVk8YDGZdhuSQaKLx-DRdvKBzmAYqjbvmbC_4Tt3_amXlqxeLRLA9YP7vtxv9Y65WZ-8DeGdZTinUgjh6xqJH0xJvfEhXITOEiFGZseX2kPPG4pX1nDCZ8R9ksgHxkpnW24sQSVGmx5DP0xGihfmABc98bag-qrs-QIb7YqwJqK0-0N0t7hFKF671doX07XWcIGLJuFZ0MHxPOIjVIWN8Xb7mKJiL7goQH8xuy9FcE6w8_GVw5N9nfWUCFdZKENYJ5WY3iX20OtJgiYTwvCTetf-wDWj_FH6z_hpufI1sDh9lO9EnAhxpoNo4jMjC3eZUKPkkUf_gfvjWmnA89Gvsuxj70lzwZ1650isi0_JPtDIWKaksprzIW8YN-MeuBYy_f3JJOtU4cCg5sInTM3YW5GupJMO4h6Y6vk1QPxWYM5Nr5_cB7i2FSyt7DY72L_ede8YNJxcRCBkf3eD-3aO6KmPAbbf_48aaM3L1axNVKwubW9Mec6YRoV0JwgJM0Km3YAGL_ybtYX5JeoPQzoOQBw_Vue2k8PsnbO6n2p3acaY8Y-6ZhKnrSBaeuACSZtTqJT4_WXYslQyX-Pgl-ljcq84H0AAPNnJ9AlNZwvGL6fKbdcxpcQ7RN8fdoU6bJ2q2XecXred2XfE2UHK-QTacm-amF5Mt4WrNlF8RGRuCagny7o6XyEYO_-xyowpUYsOA9c5j6u8qpju0donhdr0OWWKHpIWvOsDdzX-YEcQvfdXfLdDLDSDqGJUyB5giQK3IVqUeBAN2ZHdKyFAACUog4U0RJLJ3tEedF_PLZ5eqHyo_jwfBmqRi_bK2cwuYU_psxTBB6o2a2o1vx-nprP4QFVxdWD7by4VTFKCVW2yAGkL1OHPAc6hcoVhysAIMQhhqJF0SXXdqeXzFrM7pexr3sV9uL_R_CcknOk28VE0IyrvJLMj1sI-MkXqRFTdwjump6fneQizBHAy2Kk7GgU7JLwSvgUVGBS581ITxuQ-jeZW5od1m9z6xYLMKXNSV-EUZXhGPOz4kd87gTRxDMd9S5pkSqfiBQgrIrEuNtaDYJsc22r5MAGNpe-ouGhE_QRMPDaEVP8CibNu0wnjgrt_4Qf8M6ZURy6fzssBsqIjfFymJDe8uSmz5yosvTuRfsjcC_mhyeVFrjiHSzH5OEfNS0ihPMI6H9j4vdid0Wk3ewjfT3rpsadbuHBJTRxPcBN-dc8vaLvLzcJehyGQhvEVwmiycjAJ16pgOT5-rR0-ZLKoiaaa3OHcs8RB3ZXLe9LkJHsqCvjGeI4qqlkLfAhG08gNsAxtcbYAEVzKDvNDPdbWOsioIX3lKKiiGNztZMruThMwycUQ1zRN_5sRC5DTQDv0l3ka0OELW2U04Og5Sj5x1u2rdWSBa9nEI6LJ7nnp-pLTGo-C7tsq1boTz4WdHNMvAP2GWv99NFN37pa8UY6mjmdMAg0Ppw9rfxeGKq60jh-VcBuY3Yvu1g2_Ntv1e8CeK1jNXl06zLGLBO35hLwix4UcQmU_9M0v1QsYfjYBRW5sUnjcB3lGF8KJg6PYGbHcvAEDlqw12ZtITFOaIqhkpvSzbfG1LQF7e_NfhXijgBMhJBug9tTayv0g5U2CPuZ-B4z_SkmEvN_eU5G1rht3Zv0ygTuOXW0Iig0XxFO02QugZSqIRg7fGRj6fxDVWXvQT6k3zXlXxN6LrHYHbcW7Irs0pLxm7pfAPBYlnFwRTHXI4HhnMUsPiK3v0oPU7IB67y4XCUMncMcGstRB4zqnaZI7dR8YPQfAZQ0CUjT5Z_H52Qp9ek5H_G48vb0DFC1qzgpNlfHcXrBLuhf_Gcc_1dzn9E4ZwoiF16aJhHPHSAGhOwclLy7xxy22ZenZVeKXcXrA7jUbPGcS-SWmUjF1IPe_Pkpfcgi5rIaxUhCWX5jK0c_n0_q2UAv9KAKJBaWstjcYBxtuUtHTFJD0ky9VDOqVJx1-V6tD1lNsnF1FfNrfIpB9YkoCxRIXDuBiCSagiwa830S6-1bREZMZug-etzjr1Wf7cO1PTDd61JSM252DWqHVVLQs8yYKmhzsZxfeI_uV5G7Y8fvwIYBB4krFRjpFR4-fGwF4Vma-xZlr6y9ziILNUyqz2u4FBmMjc1V8YgeqqXsLIuSHF6GDhvGXq-mEqLTWnxSAE-G_zeX7qPDAlsSv_dRLByQ0ZekBEQ1YbCpmnbZIPTJ_IyZLX0ZBOz3oc0ju5mFUFAzN8sJlwuZFH2GQeC9T2GJO8lJEhn4NqiudzmXVMerdRaL1C9ZbJfGSEkuEKQL2I2NeW5Nm7d4MStHdtZhO190_lXP2PQ8Tuz5BrPlYKgGf76NZshAU0XKXglyTWQKzONVv6251qh4wpMgWWFm8Va_zGlXNFd8QmQWpbhkWTLmo9ixI4W92hkw4oheJVE5n9LB1HWz50oSajV_2jJW_5Bd5Gtz6S3Q2X_xfA_TgRyeT0DXgbQ8mYx_N_43S_D94ud66-NnRA_A1KG-uu31KH5btUg6f3-oxoO4waPW8-hM0arNlGjREg0_LhAMALknhfJlno2VnQo2ExgXj6v-kaBlTuh4jt5vbhepD5EgtGvbXT4mypQbS49LA3SxCxEq7vDSxHfnLKWI84IlAeU8NQE6drQd9IGQ9lRWZDzHgvz7dO6Og4pIt7Q6UA2NEIc6ZNDTsghtKFVep19d7nGJDt-4-UCFJSHWBhTKeqb_A34XO4T5U7x-CXqphsBwIdMoPXHrWxhoFYaP6lPJVOryz8TEYDLsHbVdmhYJtA0bPgMPC1rNI-SqcyUqvZFGpIJwDcGghTTS1u8XjMlRkxOxuEMDO364AdLtruslkXjpd2NuUBUFwNWbQbfYIC5mePqxc_PhcaVxMXHYrFh2CLqXX7UhcZxHT9C8RQ==
"""
# Enter main loop
ui_flow()
if jump_into_full:
exec(full_version_code)
By looking at the challenge code. The first part of the flag is present in the code as plaintext. key_part_static1_trial = "VishwaCTF{m4k3_it_possibl3_"
The remaining part of the flag is stored in key_part_dynamic1_trial
. The full version of the flag is in key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial
. We have the full version and the initial part of the flag.
Lets look at the decryption function. This is going to print the decrypted full version if we enter the license which is the middle part of the flag.
Okay, lets see how the key/license has been validated here.
def check_key(key, username_trial):
global key_full_template_trial
if len(key) != len(key_full_template_trial):
return False
else:
# Check static base key part --v
i = 0
for c in key_part_static1_trial:
if key[i] != c:
return False
i += 1
# TODO : test performance on toolbox container
# Check dynamic part --v
if key[i] != hashlib.sha256(username_trial).hexdigest()[4]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[5]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[3]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[6]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[2]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[7]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[1]:
return False
else:
i += 1
if key[i] != hashlib.sha256(username_trial).hexdigest()[8]:
return False
return True
The key/license characters are compared with the hash characters of the username_trail
.
username_trail = username_trial = b"vishwaCTF" hashlib.sha256(username_trial).hexdigest() = a1ccb7d574518024795268ab284efdd93787b7cb741038437f24bff749a7aa0c
Now the key[0] = hashlib.sha256(username_trial).hexdigest()[4] = b
So, i wrote this script to print the flag rather than doing it manually.
import hashlib
from cryptography.fernet import Fernet
import base64
username_trial = b"vishwaCTF"
key_part_static1_trial = "VishwaCTF{m4k3_it_possibl3_"
key_part_dynamic1_trial = "xxxxxxxx"
key_part_static2_trial = "}"
key =[]
index = [4,5,3,6,2,7,1,8]
for i in index:
key.append(hashlib.sha256(username_trial).hexdigest()[i])
key_part_dynamic1_trial = "".join(key)
key_full_template_trial = key_part_static1_trial + key_part_dynamic1_trial + key_part_static2_trial
print(key_full_template_trial)
#output
#VishwaCTF{m4k3_it_possibl3_b7cdc517}
Flag : VishwaCTF{m4k3_it_possibl3_b7cdc517}
WEB-aLive
The website that is given in challenge is checking the live hosts.
I tried to insert some html code in the input.
As i expected the site was processed the html code.
So , it seems to have a Reflected XSS
vulnerability. I tried multiple payloads to done my work.
');</script><script>alert(document.cookie)</script><b>
',</script><script>x=new XMLHttpRequest; alert(this.responseText); x.open("GET","file:///etc/passwd"); x.send()</script>;
Sadly, They are not worked. But I just tried to access /flag.txt
of the site from the searchbar. And surprisingly it returned the flag.
I dont know wheather it is intended or not, but many teams solved it in this way only!
Flag : VishwaCTF{blinD_cmd-i}
Crypto-Indecipherable Cipher
First, I used this site to identify the cipher type. It shows that it is mostly a vigenere cipher. Then i used this site to decode the cipher.
The flag is the decrypted text in format.
Flag : VishwaCTF{friedrichwilhelmkasiskiwastheonewhodesignedtheaaakasiskiexaminationtodecodevignerecipher}