Programación y Hacking de Interfaces API – Parte N°1

Programación básica de una interfaz API para pentesting

Hacer una lista con las pruebas de seguridad es fácil, pero reproducirlas es otra historia, así que decidí programar un conjunto de API endpoints basados en Python y Flask para comprender mejor su funcionamiento, abordar algunos conceptos y compartir.

Antes de pasar al código, destaco que las interfaces API pueden ser de diferentes tipos y diseños, ya que al actuar e interceder entre microservicios o participar como tal, poseen la facilidad de estar ubicadas en cualquier lugar de una estructura tecnológica.

Por otro lado, estos artículos no pretenden cumplir un rol educativo sobre desarrollo, es algo que trata de ser simple, no requiere el uso de Git (por ahora) y menos aún profundos conocimientos de programación. También, utilizaré prácticas no recomendadas como el uso de credenciales por defecto, la declaración de conexión a base de datos en un mismo archivo, etc. Es una suerte de estudio personal, no pretende ser una cátedra, y recomiendo estudiar programación y desarrollo seguro.

Asumiendo que trabajamos en una máquina basada en Linux:

  1. Verificar y/o instalar Python 3.x.x, pip3 y, virtualenv (respectivamente)
  2. Creamos un directorio para nuestro proyecto.
  3. Generamos un entorno de desarrollo nuevo ($ virtualenv nombredelentorno)
  4. Ejecutamos el entorno ($ source entorno/bin/activate)
  5. Instalamos, finalmente, Flask ($ pip3 install flask)
  6. Se recomienda el uso de algún sistema proxy o parecido a Burp Suite, Postman, Charles, entre otros.
  7. Procedemos a programar nuestra API para pentesting; 2 de las formas más comunes de crear nuestra app, son:
#!/usr/bin/python3
#_*_ coding: utf8 _*_

from flask import Flask

app = Flask(__name__)

@app.route("/",methods=["GET"])
def HomePage():
    return "Bienvenido al Home"

if __name__ == "__main__":
    app.run(debug=True)

Ejemplo N°1 de base

#!/usr/bin/python3
#_*_ coding: utf8 _*_

import flask

app = flask.Flask(__name__)
app.config["DEBUG"] = True

@app.route("/",methods=["GET"])
def HomePage():
    return "Bienvenido al Home"

app.run()

Ejemplo N°2 de base

Por defecto, con Flask, se habilita un servidor local para desarrollo utilizando el puerto 5000, y esto con el modo debugging activado (para no tener que arrancar el servidor con cada cambio en el código); todas las rutas sin establecer un método HTTP con Flask, interactúan con GET. Es decir, queda así: http://127.0.0.1:5000/

Tras ejecutar nuestra app de base ($ python3 app.py), obtendremos lo siguiente:

Response válido de nuestra base para la API de pentesting

En el response figuran cabeceras con versiones de tecnologías, y como tenemos el control a nivel de desarrollo, modificamos inmediatamente el banner del servidor Flask para empezar a mover los dedos con Python, utilizando make_response:

#!/usr/bin/python3
#_*_ coding: utf8 _*_

from flask import Flask,make_response

app = Flask(__name__)

@app.route("/",methods=["GET"])
def HomePage():
    response = make_response("Bienvenido al Home")
    response.headers["Server"] = "Servidor API Pentesting 1.0"
    return response

if __name__ == "__main__":
    app.run(debug=True)
Cabecera personalizada “Server API Pentesting 1.0”

Control de métodos o verbos HTTP

Una manera común y rápida es usando la función request (no confundir con el módulo requests):

#!/usr/bin/python3
#_*_ coding: utf8 _*_

from flask import Flask,make_response,request

app = Flask(__name__)

@app.route("/",methods=["GET","POST"])
def HomePage():
    if request.method == "GET":
        response = make_response("Método GET")
        response.headers["Server"] = "Servidor API Pentesting 1.0"
        return response
    else:
        response = make_response("Método POST")
        response.headers["Server"] = "Servidor API Pentesting 1.0"
        return response

if __name__ == "__main__":
    app.run(debug=True)

O sin usar la función request:

#!/usr/bin/python3
#_*_ coding: utf8 _*_

from flask import Flask,make_response

app = Flask(__name__)

@app.route("/",methods=["GET"])
def HomePageGet():
    response = make_response("Método GET")
    response.headers["Server"] = "Servidor API Pentesting 1.0"
    return response

@app.route("/",methods=["POST"])
def HomePagePost():
    response = make_response("Método POST")
    response.headers["Server"] = "Servidor API Pentesting 1.0"
    return response

if __name__ == "__main__":
    app.run(debug=True)

Con ambos códigos, el resultado es el mismo:

Servicio y conexión a base de datos con Flask-MySQL

Ahora que ya tenemos lista nuestra app de base, necesitamos una fuente de información a la cual recurrir y con su ayuda, habilitar, controlar y formular los conceptos de algunas vulnerabilidades informáticas, programando algunos endpoint. No obstante, como dije, utilizaré algunas prácticas no recomendadas porque es algo simple y básico (esto es una prueba de concepto, por favor, no usen credenciales por defecto para conexión a bases de datos, no declaren todo en un mismo archivo, etc.); busquen información sobre desarrollo seguro.

Para esto, Flask-MySQL es una excelente opción, aunque hay alternativas como MySQL Connector/Python. Entonces, luego de su instalación ($ pip3 install flask-mysql), creamos una base de datos (en este ejemplo, utilizando XAMPP y phpMyAdmin), y siguiendo el ejemplo del repositorio oficial, establecemos la conexión al servicio, creamos una tabla en la base de datos y en esta última, una key de índice primario para determinar IDs únicas a cada línea, y así evitar el uso de enumeraciones al insertar información en la base de datos. Columnas: id, sku, price, title, customer, email y password:

#!/usr/bin/python3
#_*_ coding: utf8 _*_

from flask import Flask,make_response,request
from flaskext.mysql import MySQL

app = Flask(__name__)

app.config["MYSQL_DATABASE_HOST"] = "localhost"
app.config["MYSQL_DATABASE_DB"] = "db_api_pentesting"
app.config["MYSQL_DATABASE_USER"] = "root"
app.config["MYSQL_DATABASE_PASSWORD"] = ""

mysql = MySQL()
mysql.init_app(app)

@app.route("/",methods=["GET"])
def HomePage():
    dbcursor = mysql.get_db().cursor()
    dbcursor.execute("CREATE TABLE orders (id INT AUTO_INCREMENT PRIMARY KEY, sku VARCHAR(255), price VARCHAR(255), title VARCHAR(255), customer VARCHAR(255), email VARCHAR(255), password VARCHAR(255))")
    response = make_response("Método GET")
    response.headers["Server"] = "Servidor API Pentesting 1.0"
    return response

if __name__ == "__main__":
    app.run(debug=True)
Tabla orders con la key primaria ID

Generación y almacenaje de información en la base de datos

Bacán. Hasta aquí, ya sólo necesito generar la información que posteriormente consultaré por medio de los API endpoints. Desde un comienzo pensé en una base de datos simple pero que fuera similar a la de un comercio; verifico entonces que sea posible la inserción de datos en la tabla:

#!/usr/bin/python3
#_*_ coding: utf8 _*_

from flask import Flask,make_response,request
from flaskext.mysql import MySQL

app = Flask(__name__)

app.config["MYSQL_DATABASE_HOST"] = "localhost"
app.config["MYSQL_DATABASE_DB"] = "db_api_pentesting"
app.config["MYSQL_DATABASE_USER"] = "root"
app.config["MYSQL_DATABASE_PASSWORD"] = ""

mysql = MySQL()
mysql.init_app(app)

@app.route("/",methods=["GET"])
def HomePage():
    dbcursor = mysql.get_db().cursor()
    dbcursor.execute("INSERT INTO orders (sku, price, title, customer, email, password) VALUES ('Data 1', 'Data 2', 'Data 3', 'Data 4', 'Data 5', 'Data 6')")
    mysql.get_db().commit()
    response = make_response("Método GET")
    response.headers["Server"] = "Servidor API Pentesting 1.0"
    return response

if __name__ == "__main__":
    app.run(debug=True)

Ahora sí: programo un script que genere y almacene 5.999 datos en la tabla:

#!/usr/bin/python3
#_*_ coding: utf8 _*_

from flask import Flask
from flaskext.mysql import MySQL

app = Flask(__name__)

app.config["MYSQL_DATABASE_HOST"] = "localhost"
app.config["MYSQL_DATABASE_DB"] = "db_api_pentesting"
app.config["MYSQL_DATABASE_USER"] = "root"
app.config["MYSQL_DATABASE_PASSWORD"] = ""

mysql = MySQL()
mysql.init_app(app)

def Q(s,p,t,c,e,w):
    dbcursor = mysql.get_db().cursor()
    dbcursor.execute("INSERT INTO orders (sku,price,title,customer,email,password) VALUES ('"+s+"','"+p+"','"+t+"','"+c+"', '"+e+"','"+w+"')")
    mysql.get_db().commit()

@app.route("/",methods=["GET"])
def HomePage():
    for x in range(0,5999):
        x = str(x)
        sp = "Some product"
        cn = "Customer name"
        ce = "cutomer@somedomain.com"
        cp = "s3cr3tp4ssw0rd!@"
        if len(x) == 1:
            Q("ABC-000"+x,"20."+x,sp,cn,ce,cp)
        elif len(x) == 2:
            Q("CDE-00"+x,"90.0"+x,sp,cn,ce,cp)
        elif len(x) == 3:
            Q("FGH-0"+x,"15."+x,sp,cn,ce,cp)
        else:
            Q("IJK-"+x,"40."+x,sp,cn,ce,cp)
    return "Nothing to see here!"

if __name__ == "__main__":
    app.run(debug=True)
Base de datos lista para la API

Si bien es cierto se recomienda la utilización de un ORM como SQLAlchemy para agregar versatilidad y una capa de abstracción de la base de datos, hasta acá ya tenemos lista una app de base para comenzar a realizar pruebas de seguridad.

Parte N°2 – Programación y Hacking de Interfaces API