Buenas Prácticas de Escritura de Código

Juan Vera del Campo - juan.vera@professor.universidadviu.com

Como decíamos ayer...

  • El Threat Modeling permite identificar amenazas y tratarlas en el momento del diseño
  • ¿Podemos mejorar la seguridad de una aplicación simplemente escribiendo buen código?

Hoy hablamos de...

  1. Mayores vulnerabilidades de código
  2. Secure System Design Principles
  3. Recomendaciones y ejemplos
  4. Proyectos, librerías y dependencias
  5. Ejemplos
  6. Referencias

Mayores vulnerabilidades de código

OWASP: Top 10

center

https://owasp.org/www-project-top-ten/

center

  1. Broken access control: usuarios capaces de hacer cosas a las que no deberían estar autorizados. Defensa: denegación por defecto
  2. Cryptographic failures: protección definiciente de los datos en tránsito y almacenados. Defensa: políticas de datos
  3. Injection: el usuario puede forzar la ejecución de comandos. Defensa: librerías especializadas, validación de entradas
  4. Diseño inseguro. Defensa: Threat Modeling.
  5. Security Misconfiguration Defensa: system hardening
  1. Vulnerable components: uso de librerías vulnerables. Defensa: auditorias
  2. Identification failures. Atacantes entrando por fuerza bruta. Defensa: 2FA
  3. Integrity Failures No comprobar si los plugins son maliciosos. Defensa: firma digital
  4. Monitoring Failures No registrar las acciones de los usuarios. Defensa: sistemas de gestión de logs
  5. Server-Side Request Forgery Obligar al servidor a acceder a datos en su nombre. Defensa: validación de entrada

Otros proyectos

OWASP tiene otros proyectos

Otras recomendaciones: SANS Top25

https://www.sans.org/top25-software-errors/

Otras recomendaciones: MITRE

Rank ID Name Score 2020 Rank Change
[1] CWE-787 Out-of-bounds Write 65.93 +1
[2] CWE-79 Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting') 46.84 -1
[3] CWE-125 Out-of-bounds Read 24.9 +1
[4] CWE-20 Improper Input Validation 20.47 -1
[5] CWE-78 Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection') 19.55 +5
[6] CWE-89 Improper Neutralization of Special Elements used in an SQL Command ('SQL Injection') 19.54 0
[7] CWE-416 Use After Free 16.83 +1
[8] CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') 14.69 +4
[9] CWE-352 Cross-Site Request Forgery (CSRF) 14.46 0
[10] CWE-434 Unrestricted Upload of File with Dangerous Type 8.45 +5

https://cwe.mitre.org/top25/archive/2023/2023_top25_list.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)

https://es.wikipedia.org/wiki/Principio_KISS

Valida lo que dice el usuario

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:

Secure by default

La configuración por defecto tiene que ser "acceso prohibido"

Ejemplo:

Complete Mediation

Open Design

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

Evidence Production

Aprende con malos ejemplos

Bad Example: Javascript

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

Referencias y resúmenes

Recomendaciones y ejemplos

Recomendaciones

  1. 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!
  2. 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
  1. Least privilege
    • Valida los permisos en cada petición
    • Crea tests que validan permisos
    • Mantén las autorizaciones en el nivel mínimo posible
  2. Gestión de librerías seguras y probadas
  3. Usable
  4. Acceso denegado por defecto
  5. 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'])

# Salida: ['María', 'Eva', 'Alberto', 'Jorge']
import yaml

input = '!!python/object/new:sys.exit [42]'
yaml.load(input, Loader=yaml.UnsafeLoader)

¿Cuál es la salida de este comando?

https://theconversation.com/what-is-log4j-a-cybersecurity-expert-explains-the-latest-internet-vulnerability-how-bad-it-is-and-whats-at-stake-173896

Loguea todo

No uses print(), sino 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://www.geeksforgeeks.org/logging-in-python/

Pide permisos

Prefiero perdir 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

Archivos temporales

No los gestiones tú: usa las librerías del propio lenguaje

from tempfile import TemporaryFile

with TemporaryFile() as tmp:
    tmp.write(...)

No hagas:

with open('borrame.temp') as tmp:
    tmp.write(...)
os.unlink('borrame.temp')

https://rules.sonarsource.com/python/type/Vulnerability/RSPEC-5445

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);

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

center

Ejemplos:

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

Ejemplo: Pytoileur

center

  • Librería publicada en Pypi el 27 de mayo de 2024 (Link)
  • Descripción genérica "pystob allows for API Authorization Management and allows to build REST-Based API with custom JSON syntax. "

Historia completa: https://www.sonatype.com/blog/pypi-crypto-stealer-targets-windows-users-revives-malware-campaign

Si descargas el código, solo tiene un archivo setup.py, que parece completamente vacío y limpio a primera vista...

center

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)

center

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)

center

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

Cuidado con la inteligengia artificial

Asistentes de código:

  • ChatGPT, escribe código a partir de lenguaje natural
  • CoPilot, propone código a partir de un esqueleto que escribimos nosotros

ChatGPT propone código con SQL Injection y CoPilot mete vulnerabilidades

https://www.elladodelmal.com/2022/12/chatgpt-hace-codigo-con-sql-injection.html?m=1
https://www.elladodelmal.com/2022/09/copilot-y-su-codigo-inseguro-o-como-la.html

Ejemplos

center

from fastapi import FastAPI
import sqlite3

app = FastAPI()


@app.get("/create")
async def create(name, password):
    conn = sqlite3.connect('users.db')
    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:

  • SQL injection
  • Los errores no se manejan
  • Contraseñas en claro
from fastapi import FastAPI
import sqlite3
import bcrypt

app = FastAPI()


@app.get("/create")
async def create(name, password):
    conn = sqlite3.connect('users.db')
    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:

  • Los errores no se manejan
  • Código poco legible
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):
        # TODO: check uniqueness
        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:

  • No hay validación de entrada
@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}"}
  • Ejecución: uvicorn server05:app --reload
  • Visita: localhost:8000/help
  • Problemas: cualquiera puede crear un nuevo usuario

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

Referencias

Para practicar: Bwapp

  • Aplicación web con múltiples problemas de seguridad
  • ¡No incluye explicaciones!
  • Tres niveles de dificultad

http://www.itsecgames.com/

  1. docker run --rm -p 8080:80 raesene/bwapp
  2. Go to: http://localhost:8080/install.php and click on "install"

Para practicar: WebGoat

  • Web de aprendizaje de OWASP
  • Incluye lecciones y ejercicios de la lista de OWASP

https://owasp.org/www-project-webgoat/

  1. docker run --rm -p 8080:8080 -p 9090:9090 -t -e TZ=Europe/Amsterdam webgoat/goatandwolf
  2. Ve a http://localhost:8080/WebGoat y registra un nuevo usuario

¡Gracias!

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

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.

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."

## 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

--- ![center w:40em](images/coding/demos1.png) --- ![center w:25em](images/coding/demos2.png)

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