Previous slide Next slide Toggle fullscreen Open presenter view
Como decíamos ayer...
El modelado de amenazas permite identificar amenazas y tratarlas en el momento del diseño
¿Podemos mejorar la seguridad de una aplicación simplemente escribiendo buen código?
Errores de código más comunes
Otros proyectos
OWASP tiene otros proyectos
Otras recomendaciones: MITRE
Rank
ID
Name
[1]
CWE-79
Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
[2]
CWE-787
Out-of-bounds Write
[3]
CWE-89
Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection')
[4]
CWE-352
Cross-Site Request Forgery (CSRF)
[5]
CWE-22
Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
[6]
CWE-125
Out-of-bounds Read
[7]
CWE-78
Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')
[8]
CWE-416
Use After Free
[9]
CWE-862
Missing Authorization
[10]
CWE-434
Unrestricted Upload of File with Dangerous Type
https://cwe.mitre.org/top25/archive/2024/2024_cwe_top25.html
Secure System Design Principles
"The Protection of Information in Computer Systems", JEROME H. SALTZER and MICHAEL D. SCHROEDER, 1974
Sistemas simples
As systems get more complex, security will get worse (Bruce Schneier)
Keeping it simple, stupid! (simpler said than done)
Diseños lo más simples y pequeños posible (Principio KISS)
Reducir el número de componentes utilizados, conservando sólo aquellos que sean esenciales
Servicios y aplicaciones deshabilitados de forma predeterminada
CWE:
https://es.wikipedia.org/wiki/Principio_KISS
Guías de estilo (linters)
Tienes que entender el código de otro para poder decidir si es seguro o no
En dos semanas, no entenderás tu propio código
Los lenguajes que dan mucha libertad pueden provocar que cada programador escriba de una manera
Todos los programadores del equipo deben seguir las mismas reglas
Ejemplos:
Valida lo que dice el usuario
Secure by default
La configuración por defecto tiene que ser "acceso prohibido"
Ejemplo:
Open Design
La seguridad del sistema no debe depender de su secreto
Evita la "seguridad por oscuridad"
En software comercial: reviews internas del código
CWE:
SECRET_PASSWORD = "SuperSecreta123" .reverse()
def autenticar (usuario, contrasena ):
if usuario == "admin" and contrasena == SECRET_PASSWORD:
return True
return False
Isolated compartments
Contenedores que gestionen o impidan la comunicación entre componentes y la fuga de información y el control.
Restringe la comunicación autorizada entre componentes a rutas observables con interfaces definidas
Aislamiento de procesos y memoria, particiones de disco, virtualización, protecciones de software, zonas, puertas de enlace, firewalls, docker, kubernetes
CWE:
Least Privilege
Usa los privilegios mínimos necesarios para una tarea y durante el menor tiempo necesario
Usa revocación de privilegios cuando ya no sean necesarios
Los roles deben ser revisados, acordados y auditados periódicamente
CWE:
function authenticateUsers (username, password ) {
var accounts = apiService.sql ("SELECT * FROM users" );
for (var i=0 , i<accounts.length ; i++) {
var account = account[i];
if (account.username === username && account.password === password) {
return true ;
}
if ("true" === "true" ) {
return false ;
}
}
}
$("#login" ).click (function ( ) {
var username = $("#username" ).val ();
var password = $("#password" ).val ();
var authenticated = authenticatedUser (username, password);
if (authenticated === true ) {
$.cookie ('loggedin' , 'yes' , {expires : 1 });
} else if (authenticated === false ) {
$("error_message" ).show (LogIn Failed );
}
});
https://twitter.com/hot_girl_spring/status/1853430439022670236
Aprende con malos ejemplos
Recomendaciones y ejemplos en Python
Recomendaciones
Input Validation : nunca te fíes de tus entradas
Tamaño de la entrada
Caracteres válidos
Formato, tipo (entero, cadena...) -> Sanitizar
Dentro de valores permitidos (máximos, mínimos...)
¡Existente!
Manejo de secretos
Comprueba que los errores de autenticación no incluyen información
Guarda las contraseñas de forma segura en todos los dispositivos
Transmite las contraseñas de forma segura
https://github.com/OWASP/wrongsecrets
Least privilege
Valida los permisos en cada petición
Crea tests que validan permisos
Mantén las autorizaciones en el nivel mínimo posible
Gestión de librerías seguras y probadas
Usable
Acceso denegado por defecto
Control de calidad (ver siguiente temas)
https://codesigningstore.com/secure-coding-practices-to-implement
No te fíes de nadie
Siempre tienes que "sanitizar" cualquier input del usuario
import os
filename = input ('Select a file for deletion: ' )
os.system('rm %s' )
¿Qué pasa si el usario introduce -rf / ; dd if=/dev/random of=/dev/sda ?
Mejor:
import os
filename = input ('Select a file for deletion: ' )
os.remove(filename)
Pero...
¿Qué pasa si el usario introduce * ?
Ataques de templates
import yaml
input = '''
title: Título de Prueba
alumnos:
- María
- Eva
- Alberto
- Jorge
'''
clase = yaml.load(input , yaml.BaseLoader)
print (clase['alumnos' ])
Loguea todo
No uses print(), sinó el módulo logging (Java: log4j )
import logging
logger = logging.getLogger()
logger.warning('Esto es un mensaje de warning %d' , 5 )
logger.info('Esto es un mensaje de info' )
logger.info('Esto es un mensaje de error' )
Estas librerías especializadas permiten configurar la salida de log. Por ejemplo: errores consola y archivo, info solo a archivo, colores, incluir fechas...
https://cwe.mitre.org/data/definitions/778.html
https://www.geeksforgeeks.org/logging-in-python/
Comprueba permisos
Prefiero pedir perdón que permiso:
try :
file = open (path_to_file)
except PermissionError:
return None
with file:
return file.read()
Mira antes de cruzar:
if os.path(path_to_file, os.R_OK):
with open (Path_to_file) as file:
return file.read()
return None
SQL injection
txtUserId = getRequestString("UserId");
txtSQL = "SELECT * FROM Users WHERE UserId = " + txtUserId;
Sim, por ejemplo, UserId=5, el comando devolverá los datos del usuario 5
Si un atacante es capaz de asignar UserId= "105 OR 1=1"...
Se ejecutará: SELECT * FROM Users WHERE UserId = 105 OR 1=1;
¡Este comando devuelve toda la base de datos!
https://www.w3schools.com/sql/sql_injection.asp
Command injection
<?php
$command = "ls " .$_GET ['modifiers' ];
$output = exec ($command );
Cosas a tener en cuenta
No guardes contraseñase en claro en la base de datos
No guardes la contraseña de conexión a la base de datos en el código
Comprueba si los usuarios tienen autorización para realizar las acciones
Equivocarse es fácil: escribe las cosas solo una vez y reutiliza código
Aprovecha los mecanismos que ya incluyen las librerías que uses
Ejemplo inicial
DB_CONFIG = {
'user' : 'tu_usuario_mysql' ,
'password' : 'tu_contraseña_mysql' ,
'host' : '127.0.0.1' ,
'database' : 'test_python_db'
}
@app.get("/create" )
async def create (name, password ):
conn = mysql.connector.connect(**DB_CONFIG)
sql = f'INSERT INTO users(name,password) VALUES("{name} ","{password} ")'
cur = conn.cursor()
cur.execute(sql)
conn.commit()
return {"message" : f"I created user {name} " }
Problemas:
DB_CONFIG = {
'user' : 'tu_usuario_mysql' ,
'password' : 'tu_contraseña_mysql' ,
'host' : '127.0.0.1' ,
'database' : 'test_python_db'
}
@app.get("/create" )
async def create (name, password ):
conn = mysql.connector.connect(**DB_CONFIG)
sql = 'INSERT INTO users(name,password) VALUES(?,?)'
hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
cur = conn.cursor()
cur.execute(sql, (name, hashed))
conn.commit()
return {"message" : f"I created user {name} " }
Problemas:
from fastapi import FastAPI
import sqlite3
import bcrypt
app = FastAPI()
class User :
def __init__ (self, name, password ):
self.name = name
self.hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt())
def check (self, password ):
return bcrypt.checkpw(password.encode, self.hashed)
def create (self ):
conn = sqlite3.connect('users.db' )
sql = 'INSERT INTO users(name,password) VALUES(?,?)'
cur = conn.cursor()
cur.execute(sql, (self.name, self.hashed))
conn.commit()
@app.get("/create" )
async def create (name, password ):
user = User(name, password)
user.create()
return {"message" : f"I created user {name} " }
Problemas:
@app.get("/create" )
async def create_user (
name: str = Query(max_length=50 , default=None ),
password: str = Query(min_length=3 , max_length=50 , default=0 ),
age: int = Path(title="The age of the new user" , ge=18 , default=0 ) ):
user = User(name, password)
user.create()
return {"message" : f"I created user {name} " }
def get_current_username (
credentials: Annotated[HTTPBasicCredentials, Depends(security )]
):
current_username_bytes = credentials.username.encode("utf8" )
correct_username_bytes = b"stanleyjobson"
is_correct_username = secrets.compare_digest(
current_username_bytes, correct_username_bytes
)
current_password_bytes = credentials.password.encode("utf8" )
correct_password_bytes = b"swordfish"
is_correct_password = secrets.compare_digest(
current_password_bytes, correct_password_bytes
)
if not (is_correct_username and is_correct_password):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Incorrect email or password" ,
headers={"WWW-Authenticate" : "Basic" },
)
return credentials.username
https://fastapi.tiangolo.com/advanced/security/http-basic-auth/#__tabbed_3_1
Gestión de usuarios logueados
@app.route('/secret_page' )
def secret_page ():
if g.user is None :
return redirect(url_for('login' , next =request.url))
pass
@app.route('/another_secret_page' )
def another_secret_page ():
if g.user is None :
return redirect(url_for('login' , next =request.url))
pass
Uso de decoradores para escribir la lógica solo una vez:
from functools import wraps
from flask import g, request, redirect, url_for
def login_required (f ):
@wraps(f )
def decorated_function (*args, **kwargs ):
if g.user is None :
return redirect(url_for('login' , next =request.url))
return f(*args, **kwargs)
return decorated_function
@app.route('/secret_page' )
@login_required
def secret_page ():
pass
@app.route('/another_secret_page' )
@login_required
def another_secret_page ():
pass
Contraseñas en archivos de configuración
import os
from os.path import join, dirname
from dotenv import load_dotenv
dotenv_path = join(dirname(__file__), '.env' )
load_dotenv(dotenv_path)
SECRET_KEY = os.environ.get("SECRET_KEY" )
DATABASE_PASSWORD = os.environ.get("DATABASE_PASSWORD" )
Cuidado: estos archivos de configuración no deben estar en el control de versiones
Considera leer de memoria: DATABASE_PASSWORD = os.environ.get("DATABASE_PASSWORD")
Conexión a la base de datos dentro del código: https://cwe.mitre.org/data/definitions/540.html
Proyectos, librerías y dependencias
Creación del proyecto
En esta sección crearemos un proyecto Vue directamente dentro de un docker. Esta no es la manera recomendada de crear proyectos Vue, pero lo haremos así para no tener que instalar más cosas en nuestros PCs.
docker run -ti --rm node bash
mkdir project ; cd project
npm install vue
npm install vuetify
npm install vuetify@^3.0.1
npm install eslint
ls node_modules
npm audit
Archivo package.json (JavaScript)
Incluye metadados del proyecto
Lista dependencias, con las versiones exactas
Ventajas: es reproducible en cualquier PC
Desventajas: ocupa mucho más espacio en disco
Archivo Package y Package.lock (Python)
"biplist": {
"hashes": [
"sha256:4c0549764c5fe50b28042ec21aa2e14fe1a2224e239a1dae77d9e7f3932aa4c6"
],
"index": "pypi",
"version": "==1.0.3"
},
¡No re-inventes la rueda!
No re-inventes la rueda: utiliza librerías reconocidas siempre que puedas
Pero recuerda auditar tus librerías
Considera utilizar contextos para la aplicación:
JavaScript: npm / yarn
Python: pip / conda / pipenv
Si descargas el código, solo tiene un archivo setup.py, que parece completamente vacío y limpio a primera vista...
Pero esa línea 17 en realidad oculta otro comando después de muchos espacios. Para poder verlo hay que activar el "word wrap" en tu editor de texto (Alt+Z en VS)
Si analizamos qué hace este código: añade un archivo a Windows que se ejecuta siempre que el usuario abre una nueva sesión en su PC
El atacante recomendaba instalar su librería en respuestas en sourceforge (comentarios borrados actualmente)
Audita tus librerías / dependencias
Auditoría de librerías
Ejemplo de auditoría de librerías, veremos más detalles en las siguientes sesiones
npm audit
Pero... no te fies de nadie
Para practicar: Bwapp
Aplicación web con múltiples problemas de seguridad
¡No incluye explicaciones!
Tres niveles de dificultad
http://www.itsecgames.com/
docker run --rm -p 8080:8080 -p 9090:9090 -t -e TZ=Europe/Amsterdam webgoat/goatandwolf
Ve a http://localhost:8080/WebGoat y registra un nuevo usuario
WARNING: está clase es interactiva. Las transparencias son solo una sugerencia
Ya vimos durante la sesión anterior qué es lo que hace en proyecto OWASP
Entre sus guías, incluye el top ten de vulnerabilidades que debemos evitar en nuestro código
Para forzar las mismas reglas en toda la empresa, puedes utilizar linters: no permitirán que un código compile o se suba a git si no sigue las reglas de la empresa
PEP8 es un ejemplo de reglas. Hay muchos más. Los linters los puedes encontrar para cada lenguaje. Ejemplos en Python: pylama, frake8. Ejemplos en Javascript: eslint
This code snippet contains several issues that indicate a lack of experience, which is likely why it was shared with the caption, "I don't think the intern will last much longer." Here are some specific issues:
Potential SQL Injection: The code uses apiService.sql("SELECT * FROM users") to retrieve user data. If apiService.sql doesn't implement SQL injection protection, this approach could expose the database to attacks. Typically, prepared statements or ORM (Object-Relational Mapping) tools are used to avoid this risk.
Inefficient Authentication Logic: The authenticateUser function iterates through all user accounts in the database (for (var i = 0; i < accounts.length; i++)). In a real-world application, fetching all user records to find a single match is highly inefficient and poses security risks by unnecessarily loading all user data.
Hardcoded Condition Always Returning False: At the end of authenticateUser, there's an if ("true" === "true") { return false; } condition, which always evaluates to true and will always return false, regardless of whether the user credentials are correct. This effectively prevents any user from logging in successfully, making the authentication function useless.
Unsecure Cookie Storage: The code uses $.cookie('loggedin', 'yes', { expires: 1 }); to set a login cookie without any additional security configurations, such as HttpOnly or Secure flags. This makes the cookie vulnerable to client-side manipulation or interception in non-HTTPS contexts.
Potential Incorrect Error Handling: The line $("error_message").show(LogIn Failed) appears to be missing proper syntax for displaying the error message. It should likely be $("#error_message").show("Login Failed");, where # selects the element by ID, and "Login Failed" should be wrapped in quotes.
These issues are commonly made by beginners and can lead to performance problems and significant security vulnerabilities, which is likely why the post humorously suggests that "the intern won't last much longer."
Los ejemplos de esta página son una buena introducción a las "reglas de oro" que se discuten en el resto de la sesión. Es muy recomendable visitar esa página durante la sesión, y los alumnos después de ella.
## Recomendaciones para Python
- Always sanitize external data
- Scan your code
- Be careful when downloading packages
- Review your dependency licenses
- Do not use the system standard version of Python
- Use Python’s capability for virtual environments
- Set DEBUG = False in production
- Be careful with string formatting
- (De)serialize very cautiously
- Use Python type annotations
> https://snyk.io/blog/python-security-best-practices-cheat-sheet/
En el caso particular de esta función, no pasa nada: la función no permite atajos de shell. Pero tienes que tenerlo en cuenta para tus funciones∫
Un ejemplo de esta vulnerabildad que nos tuvo varias semanas pegados a la pantalla en 2021 fue log4j
Y los logs de aplicación puedes fácilmente centralizarlos en un SIEM
Ambas son buenas soluciones, depende de tus preferencias. Pero tienes que implementar alguna de ellas
Fíjate: en caso de error en el segundo ejemplo, el archivo no se borrará nunca. Además, el programa no puede ejecutarse en paralelo: dos ejecuciones concurrentes trabajarán sobre el mismo archivo.
---

---

Esta transparencia la veremos con comandos reales, está aquí solo para referencia
Soluciones:
- https://jaiguptanick.github.io/Blog/blog/Walkthrough_of_bWAPP_solutions_A1_injection_writeups/
- https://dumbmaster.blogspot.com/2017/01/owasp-top-10-bwapp-walkthrough-for-a1_21.html
Nota: no he sido capaz de ejecutar WebWolf desde el docker, quizá prefieras hacerlo en tu propio PC