The London Bridge
The London Bridge is falling down. - by Sharib
Last updated
The London Bridge is falling down. - by Sharib
Last updated
The following post by 0xb0b is licensed under CC BY 4.0
In the challenge The London Bridge we started with an Nmap scan which revealed SSH and a web server running on port 8080. We continued with a directory scan and manual inspection of the website. Initially we attempted attacks such as XSS and SSTI on a contact page and file upload bypasses on the upload page, but were met with working countermeasures. However, by discovering a hidden directory and fuzzing parameters, we find and exploit an SSRF vulnerability to access internal services. From there, we use local file access to compromise the system and then escalate our privileges on the machine using a kernel exploit. Along the way, we demonstrate in this writeup multiple paths to root, including an unintended method, while also harvesting user passwords from Firefox profile data.
We start with an Nmap scan and find two open ports. Port 22
on which SSH is running and a web server on port 8080
.
We first set up a directory scan to enumerate all the directories as we check the site manually. From our initial scan, we find nothing out of the ordinary except for a contact form page and an upload page. Everything we would find if we visited the site.
We are greeted with a welcome page on the index page. Other than that, we find nothing unusual in the source.
In the gallery, next to the pictures of the London Bridge, there is an upload form. This could be interesting.
We carry on and try to use the sites we have found to get a foothold on the machine.
Below we show every single attempt, including those that were unsuccessful. If you want to skip this, you can jump directly to Discover The Hidden Directory
.
We have a contact form on the page /contact
. We insert the following payloads into this to check for XSS, for a potential session hijack, even though we did not find anything like a login page from our first scan. We set up a Python web server and hope to connect to it if a user opens our contact request. Using the different directories in our payload we could determine which field was vulnerable.
Unfortunately, we do not receive any feedback. However, we can see that the contents of the name
field are being reflected. And we can see in the source that sanitising is taking place.
Since the name is reflected, let's try again with a simple Server Side Template Injection (SSTI) payload.
But to no avail. Let's move on to the next page.
As mentioned above, there is an upload form on the gallery page. Perhaps this could be exploited to upload malicious images that could enable a web shell or XXE via SVGs.
In the source we find a clue that could be related to the clue in the room description.
We examine the request to upload an image and try several exploits like a file upload bypass without success. Fuzzing other parameters does not reveal anything.
Let's start again, we may not have found all the directories. This time we choose a larger wordlist. The directory-list-lowercase-2.3-medium.txt
. And we find what we are looking for. We had not seen /dejaview
before.
Here, we seem to be able to enter URLs to load images. This literally asks for Server Side Request Forgery (SSRF).
We intercept a sample query of an existing image from the Gallery using Burp Suite...
... and see that the image is loaded.
We now try using the localhost address and port of the server we know, but get no content. SSRF does not seem to work for this parameter.
Recalling the hint of the room and the source of the /gallery
page, we might need to fuzz for other parameters.
We use FuFF to find the parameter. Alternatively, you could Arjun. But FFUF is quite sufficient here. Unfortunately, we were not successful with our first wordist: we cannot find another parameter.
Again, we use the wordlist that we have already had success with. And we find the parameter www
.
We immediately check for SSRF, set up a web server and test to see if a connection is possible.
Lo and behold, we see the contents of our directory.
We will now look at what we can do with the parameter. And we want to start by identifying the different responses that are triggered by different selected inputs. That way we can see if we are going in the right direction.
If no url is given, we get an internal server error.
If we enter the localhost address 127.0.0.1
, we receive a message that we do not have the authorization for access. That is interesting.
The same applies to the localhost address with the service 8080
known to us.
There seems to be a filter in place that filters requests for 127.0.0.1
. There are different representations of localhost to bypass a filter like that. As we saw at the beginning, we are allowed to access other resources or urls that are not localhost. We will use the following source to find a way around the filter.
Since these payloads are provided for port 80
, but we only know that 8080
is actually a running service, we modify the list and provide it here:
Next, we fuzz for the possible representations and find a several that works.
For now, we will use the 127.1:8080/
representation.
Now we try to enumerate all internal open ports with the localhost representation 127.1
. Another service runs on port 80
.
When we access this page using the SSRF, we get a different index page.
Next, we enumerate all the possible directories on port 80 and initially find three. These are templates
, uploads
and static
.
Templates
shows that SSTI might be possible here. This is probably the template for the gallery
page. The original idea was to create a file that has an SSTI payload in its filename and then be uploaded to the gallery. The file, as well its name is shown on the page. After some time, it turned out that there was a strong sanitisation mechanism in place that prevented this. Unfortunately, SSTI is not possible. But it would certainly be a more interesting attack vector than the one we are about to find next. But up to this point, the machine is already very good.
What the machine teaches us is not always to trust our first scan result. We scan for further directories. This time with a different wordlist. At this point a shout out to jaxafed for the sanity check at this point. Thank you very much!
With the new wordllist in use, we continue to find ‘directories’. It seems as if we are in a home directory. We also see an .ssh
folder.
This was the slightly disappointing part of the challenge, I really would have liked the SSTI. Although experienced pentesters and bug bounty hunters might have skipped the enumeration path and would be successful by manipulating the filename in the fileupload form first. But even that could have been avoided with something not directly enumerable that instead could be found using SSRF. Nevertheless, a really nice exploit chain so far.
We look at the contents of the .ssh
folder and find the auhtorized_key
file and even a private key id_rsa
.
We request the authorized_key
file to determine the possible user. It is beth
.
Next, we request the id_rsa
and copy its content.
We copy the content of the private key to our machine and adjust the permissions of our id_rsa
file. We then use the key to gain access to the machine via SSH as beth
. At first glance, we can't find the first flag. We find another user charles
in /etc/passwd
and are afraid that we will have to extend our privileges to this user. But this is not the case.
We search for user.txt
and are able to locate it at /home/beth/__pycache__/user.txt
. A not so common location.
With the user beth
we now run the enumeration script LinPeas and even find some vectors that could be used. The service of the app on port 8080
calls things from the home directory of beth
. If we had the authorization to restart the service, we would actually be able escalate our privileges, as the service runs in the context of root
. Unfortunately this is not the case. We also cannot find a password for beth
, nor can we brute-force it to see what privileges we might have with sudo
.
There was a picture in /uploads
(not in /home/beth/uploads
) that gave me the idea, just as an aside. After really many attempts, the question came to me. Why the room got its name.
And there it struck me, so this is just an assumption. But metaphorically speaking, the kernel is the bridge between the hardware and the software. So the intended or somewhat intended way is a kernel exploit. Something we are not that used to at challenge machines on TryHackMe.
So from our LinPeas scan, we already have a suggestion. But the exploit initially tried here did not fully work.
Let's pursue this systematically and not rely entirely on linpeas. Using uname -a
, we output all the necessary information to find a suitable kernel exploit. We have a linux kernel 4.5.0-122
and we are running Ubuntu.
Next, we google for a specific exploit and actually find one.
Checking the search result, it seems to fit our target. At least the version.
We clone the repository and serve the content using a Python web server.
Next, we get all the contents of the cloned repository on the target machine to compile it there.
Now all we need to do is call make to compile it properly.
After running the exploit, we are root
and have access to the root flag at /root/.root.txt
.
The authors' approach was to show that, although often overlooked in SUID permissions, an improperly configured D-Bus service can pose a significant security risk, highlighting the need for careful consideration in system configuration. From our LinPeas scan we get the following suggestion:
This was an attempt made before the initial approach which did not yield to anything fruitful.
By looking for some SUID binaries, we see the /usr/lib/dbus-1.0/dbus-daemon-launch-helper
.
We find the exploit fitting our kernel version at the following repository:
We clone the repository and make it available using a Python web server.
On the target machine we download the clone repository recursively using wget
.
Finally, we just need to add execute permissions to exploit.dbus.sh
and run it. After a successful execution, we are root
.
For charles password we probably need to be the user root
. There was no path to escalate from beth
to charles
. In the home directory of charles we find an mozilla folder.
Maybe there is a Firefox profile with a few passwords hidden in it. We can find out how to extract these in the following writeup. Essentially, we only need the files key4.db
and logins.json
. But for the showcased script to work, we seem to need the whole profile.
We dig down to the profile folder and set up a Python web server.
Next, we recursively download the profile data to our machine.
Then we get the script.
We select this and specify the profile folder. After execution, we receive the credentials for the page https://buckinghampalace.com
. That password is also the answer to the final question of the challenge.
In addition to the two ways of obtaining a root shell shown above, there is another way to solve this challenge. This was the original discovery of Jaxafed. Shout out to Jaxafed for sharing this way. Don't forget to check out his great writeups:
Don't miss Jaxafed's detailed explanation of how to leverage this arbritrary file read to obtain a root shell:
We are now on the system using a shell as beth
. Our Linpeas scan also shows the following:
The web server that we reach on port 8080
is running in the context of root
. How can we utilise this? Let's take a closer look at the app
.
This defines an upload folder, which is the one in beth
's home directory.
This folder is used in the gallery. We remember our insights from http://127.1:80/templates
. It is used here. More precisely, it is the /home/beth/templates/index.html
template.
In the template we see that every file from the folder is included and displayed.
Furthermore, we have full control over this folder. The idea now is that we let the app
use other folders instead of the uploads
folder in the home directory. The app
itself only looks for a file called uploads
. So we can use a symlink that has the name uploads
and points to any folder we want to read.
Let's create a symlink to /root
.
Reloading the gallery page now gives us the content of the /root
folder.
Inspecting the source of the gallery gives us the links to access those files.
And we are able to retrieve the .root.txt
flag.
We can also solve charles Password's task in this way. We keep creating symlinks to slowly work our way through the folder structure.
From /home/charles
to /home/charles/.mozilla
.
From /home/charles/.mozilla
to /home/charles/.mozilla/firefox
.
Until we finally reach its profile: ./home/charles/.mozilla/firefox/8k3bf3zp.charles
.
To download the file we inspect the source of the /gallery
page, copy its contents and let chatGPT do the work of creating us a wget request to download each part of the profile.
Now we just need to run the script and get the password for charles
.
Tib3rius has utilised the file upload function in a similar way to Jaxafed. We are able to write to other files outside the upload folder using symlinks. The server runs in the context of root
, so an arbitrary file write is available. We can write to /etc/passwd
as root
and set a new alias for the root
user with the uid 0
adding a password of our choice. From 03:40:48
you can follow his approach. What follows is a step-by-step guide.
The prerequisite is that we already have Beth's private SSH key, and we already have a session.
We capture a request for the file upload. We know, we are only allowed to upload images from the source code.
Next, we create a symlink to a testfile outside the uploads
folder. The symlink is called like the file we upload. So the content gets written to the file it points to.
Recalling the source of app.py
:
Symlinks work to write to files outside the upload folder because the code does not validate or restrict file paths when saving uploaded files. When we upload a file with the name testfile.png
, the application uses os.path.join()
to combine the upload directory path uploads
with the filename passwd.png
, but if passwd.png
is a symbolic link, it resolves to the target file /home/beth/testfile
. Therefore when the file.save(file_path)
function is called, it follows the symlink and writes to the linked file instead of a new file in the uploads directory.
We now upload an arbitrary image with the filename of the symlink, we can edit this in our request.
And we can confirm that we have written successfully outside /home/beth/uploads
.
Now the idea is to write to the /etc/passwd
. Adding a new alias for root and replacing the value x
with a password to be able to change to the root user using that alias and generated password.
To generate the password, we need to use openssl on the target machine. We chose the password password
.
We confirm that only root
is allowed to write to the /etc/passwd
file.
Next, we copy the contents of the /etc/passwd
file and add a new line with the alias root2
and the password hash.
Now, we add a symlink in /home/beth/uploads
pointing to /etc/passwd
.
Next, we use our intercepted request, edit the filename to be passwd.png
, the name of the symlink and append the new content of the /etc/passwd
to the image content.
After uploading the file we see the fragment and the changes of the upload in /etc/passwd
.
We are now able to switch to the user root
using the alias root2
. We are able to switch to the root2
user because it has the same UID and GID as the root user (both set to 0), granting it root privileges. The username is just an alias, and the system treats root2
the same as root
for permission purposes.