Background
This article shows my approach on selection of the challenges I was able to solve during the competition. For the complete set I recommend reading the write-ups of the winner of this CTF ๐ฅgynvael (who by the way completely dominated the scoreboard).
During this 2 day (or more like 36hrs) I was capable of solving 4 out of 10 challenges (2 easy, 1 medium and 1 hard) which gave me 12th place among ~100 participants. Fortunately, this CTF didn't promote quick solves, so it only counted whether you were able to submit the flag or not. For me, it is a perfect approach because it is much less stressful knowing you can deal with your personal life and come back "loosing" only time (and because you can do nothing about it yet, I'm trying to neglect that).
Challenge: postgres
You have access to the Postgres console. Just read the flag from flag table.
Difficulty: hard (429/500 points)
Given: telnet service, one request per session/connection with 5s timeout
Very pleasant task, that description sounded obviously too easy.
>> select * from flag;
( 'Close', 'but not there yet')
So, my next step was to try to enumerate as much as possible about this database to find the other tables or maybe some metadata about the tables. Because of the way how the task was written, my idea was that that flag
table is in the other schema, or flag is in the flag
table metadata. Unfortunately, during the recon, I've discovered that I cannot easily read the names of databases and tables - which means I cannot also read the names of the columns. So, I've tried to look in other ways.
>> select * from pg_database;
permission denied for table pg_database
>> select * from pg_tables;
permission denied for view pg_tables
>> select * from current_schema;
('public',)
>> select * from current_schemas(true);
(['pg_catalog', 'public'],)
>> SHOW ALL;
('allow_in_place_tablespaces', 'off', 'Allows tablespaces directly inside pg_tblspc, for testing.')
>> SHOW search_path
('"$user", public',)
Then I've stumbled upon this answer on Stack Exchange, and voilร .
>> SELECT to_json((SELECT t FROM public.flag t LIMIT 1))
({'val1': 'Close', 'CTF_a2*****************************4dd89': 'but not there yet'},)
Table flag
has flag as a title on one of its columns.
Reading:
Challenge: deobf
Deobfuscate the code, or call appropriate function after executing it, to get the flag.
Difficulty: medium (275/500 points)
Given: obfuscated JS code
These I like. I've watched many times John Hammond dealing with ransomware stagers/payloads, and I've done similar challenges before. But man, this one gave me a headache. JavaScript tends to be less understandable than PowerShell due to its caveats, like clousure and some weird inline arrays within parenthesis calls. First step I did is obviously to put that stack of meaningless words through beautifier.io to get "stack of meaningless words" - but prettier.
Dealing with that was really pain in the ass and it took me way too long to realize that according to the task description I can just call the appropriate function to get the flag, so wasting no more time I've slapped a few console.log(..)
here and there (focusing on values returned from functions) and got the flag in the output.
console.log(window[_0x553b6f(0xca, 'xKir')](_0x553b6f(0xc2, 'rW2u')))
Challenge: traversal
Try using the Path traversal vulnerability
Difficulty: easy (50/500 points)
Given: shared instance web application and partial source code
Description and source code made that challenge pretty trivial.
[Route("")]
public class HomeController : Controller
{
[HttpGet("/download")]
public async Task<IActionResult> Download([Required] string filename)
{
if (!ModelState.IsValid)
{
return BadRequest();
}
if (filename.Contains(".."))
{
return BadRequest();
}
var model = new HomeModel();
var fullPath = Path.Combine(model.BaseDirectory, filename);
var file = await System.IO.File.ReadAllBytesAsync(fullPath);
return File(file, System.Net.Mime.MediaTypeNames.Text.Plain);
}
public IActionResult Index()
{
return View(new HomeModel());
}
}
I know that when the second argument in Path.Combine
is rooted path, the first one is ignored (source. This and similar working methods can be found in other languages, environments or their versions, so it is always good to verify that, even if that means executing it blindly.
Also, doing it that way I bypass the double-dot (..
) filter.
So, when the first (or last, depending how to look at it) part was done: now's the time on determining how to make the server execute the Path.Combine
with the /tmp/flag
. I have fired up the Burp, intercept the GET request and played a bit with it in the repeater. Fortunately, simple URL encoding was enough, so I've got the flag.
Challenge: math-basic
To get the flag automate sending answers to simple math questions
Difficulty: easy (50/500 points)
Given: telnet server, 15 seconds session
This is "more coding, less hacking" type of challenge that resolves to reading lines with telnetlib
(or other, see the gynvael's solution) library and send solves accordingly. I've used parts of the script from Google 2021 challenge, perfect square definition and prime number searching algorithm.
#!/usr/bin/env python3`
import string
import math
from telnetlib import Telnet
host = "192.46.238.159"
port = 1337
def tcSend(tc, msg):
tc.write(msg.encode('ascii') + b"\n")
def tcReadUntil(tc, end):
return tc.read_until(end.encode('ascii'), 5)
def tcReadNextTask(tc):
lines = []
try:
tcOutput = tcReadUntil(tc, ">>")
lines = tcOutput.splitlines()
return lines[-2]
except:
print(lines)
return ''
# https://geekflare.com/prime-number-in-python/
def isPrime(n):
for i in range(2,int(math.sqrt(n))+1):
if (n%i) == 0:
return False
return True
def solveTask(text):
tokens = [x.decode('ascii') for x in text.split()]
try:
if 'perfect' in tokens:
n = int(tokens[1])
return math.sqrt(n).is_integer()
if 'prime?' in tokens:
n = int(tokens[1])
return isPrime(n)
if 'divisible' in tokens:
n1 = int(tokens[1])
n2 = int(tokens[4][:-1])
return n1 % n2 == 0
else:
return print(tokens)
except Exception as e:
print(tokens)
print(e)
if __name__ == "__main__":
accepted_characters = ''
with Telnet(host, port) as tc:
while True:
taskText = tcReadNextTask(tc)
if taskText == '':
break
answer = 'Y' if solveTask(taskText) else 'N'
tcSend(tc, answer)