Evaluación y seguimiento de competencias en seguridad
No se puede mejorar lo que no se mide. Antes de lanzar la formación en seguridad, necesita entender el nivel de habilidad actual de su equipo. Después de la formación, necesita verificar que funcionó. La evaluación continua garantiza que las habilidades no se deterioren.
Este artículo cubre métodos prácticos para evaluar el conocimiento de seguridad de los desarrolladores: cuestionarios, ejercicios de revisión de código, competiciones CTF y construcción de mapas de competencias que visualizan los puntos fuertes y débiles del equipo.
Esta es la Parte 2 de la serie de formación en seguridad para desarrolladores:
- Currículo y recursos — qué enseñar
- Evaluación y mapeo de competencias (este artículo) — cómo evaluar las habilidades
- Guía de implementación — cómo desplegar el programa
Por qué importa la evaluación
Identificar brechas antes de la formación. La formación genérica desperdicia tiempo en temas que la gente ya conoce. La evaluación revela lo que cada desarrollador realmente necesita.
Medir la efectividad de la formación. ¿Funcionó la formación? Compare las puntuaciones antes y después de la formación para saberlo con certeza.
Seguir la mejora a lo largo del tiempo. El conocimiento de seguridad se deteriora sin práctica. La evaluación regular detecta la regresión de habilidades de forma temprana.
Justificar la inversión. La dirección quiere saber si la formación está funcionando. La evaluación proporciona métricas concretas.
Motivar a los desarrolladores. Los niveles de competencia claros dan a los desarrolladores objetivos hacia los que trabajar. La mejora es visible y gratificante.
Métodos de evaluación
Método 1: Cuestionario de conocimientos de seguridad
El enfoque más simple. Cree preguntas de opción múltiple y respuesta corta que cubran los temas clave de seguridad.
Principios de diseño del cuestionario:
| Principio | Por qué importa |
|---|---|
| Relevante para el rol | No evalúe a los desarrolladores de backend sobre seguridad en iOS |
| Progresión de dificultad | L1 (básico) → L2 (intermedio) → L3 (avanzado) |
| Enfoque práctico | Ejemplos de código en lugar de definiciones |
| Sin preguntas trampa | Evalúe el conocimiento, no la comprensión lectora |
| Explique las respuestas | Oportunidad de aprendizaje incluso durante la evaluación |
Estructura de la evaluación:
Duración: 45-60 minutos. Formato: En línea, supervisado o por sistema de honor.
Secciones:
- Conceptos básicos de seguridad (10 preguntas) — todos los roles
- OWASP Top 10 (10 preguntas) — todos los roles
- Específico del lenguaje (10 preguntas) — según el rol
- Autenticación/Autorización (5 preguntas) — backend/fullstack
- Pruebas de seguridad (5 preguntas) — todos los roles
Puntuación: 90 %+ = L3 (Experto) · 70-89 % = L2 (Practicante) · 50-69 % = L1 (Consciente) · Menos del 50 % = Formación requerida
Método 2: Ejercicio de revisión de código seguro
Más práctico que los cuestionarios. Proporcione a los desarrolladores código con vulnerabilidades intencionales y pídale que encuentren los problemas.
Cómo funciona:
- Prepare muestras de código con 5-10 vulnerabilidades
- Establezca un límite de tiempo (30-45 minutos)
- Pida al desarrollador que identifique y explique los problemas
- Puntúe en función de las vulnerabilidades encontradas y la calidad de la explicación
Ejercicio de ejemplo:
# Code Review Exercise: Find the security issues
# Time limit: 30 minutes
# Identify all security vulnerabilities and suggest fixes
from flask import Flask, request, render_template_string
import sqlite3
import pickle
import os
app = Flask(__name__)
SECRET_KEY = "super_secret_key_12345" # Issue #1
@app.route('/login', methods=['POST'])
def login():
username = request.form['username']
password = request.form['password']
conn = sqlite3.connect('users.db')
cursor = conn.cursor()
# Issue #2
query = f"SELECT * FROM users WHERE username='{username}' AND password='{password}'"
cursor.execute(query)
user = cursor.fetchone()
if user:
return f"Welcome {username}!" # Issue #3
return "Invalid credentials"
@app.route('/profile/<user_id>')
def profile(user_id):
# Issue #4
user_data = open(f'profiles/{user_id}.json').read()
return user_data
@app.route('/search')
def search():
query = request.args.get('q', '')
# Issue #5
template = f"<h1>Results for: {query}</h1>"
return render_template_string(template)
@app.route('/load-session')
def load_session():
data = request.cookies.get('session_data')
# Issue #6
session = pickle.loads(bytes.fromhex(data))
return str(session)
@app.route('/execute')
def execute():
cmd = request.args.get('cmd')
# Issue #7
result = os.popen(cmd).read()
return result
if __name__ == '__main__':
app.run(debug=True) # Issue #8
Clave de respuestas:
| # | Vulnerabilidad | Ubicación | Gravedad |
|---|---|---|---|
| 1 | Secreto hardcodeado | Línea 8 | Alta |
| 2 | Inyección SQL | Línea 18 | Crítica |
| 3 | XSS potencial | Línea 21 | Media |
| 4 | Path traversal | Línea 26 | Alta |
| 5 | Inyección de plantillas del lado del servidor | Línea 32 | Crítica |
| 6 | Deserialización insegura | Línea 38 | Crítica |
| 7 | Inyección de comandos | Línea 43 | Crítica |
| 8 | Modo debug en producción | Línea 47 | Media |
Puntuación:
- Vulnerabilidad encontrada: 1 punto
- Impacto explicado correctamente: +1 punto
- Corrección válida sugerida: +1 punto
- Puntuación máxima: 24 puntos
Método 3: Capture The Flag (CTF)
Evaluación gamificada donde los desarrolladores explotan aplicaciones vulnerables para encontrar «banderas» (cadenas ocultas).
Beneficios:
- Atractivo y divertido
- Evalúa habilidades prácticas
- Fomenta el aprendizaje mediante la exploración
- Puede ser en equipo para la colaboración
Plataformas CTF:
| Plataforma | Mejor para | Coste | Enlace |
|---|---|---|---|
| CTFd | CTF personalizado auto-alojado | Gratuito | ctfd.io |
| OWASP Juice Shop | Desafíos preconstruidos | Gratuito | owasp.org/www-project-juice-shop |
| HackTheBox | Máquinas realistas | De pago | hackthebox.com |
| PicoCTF | Amigable para principiantes | Gratuito | picoctf.org |
Organizar un CTF interno:
Duración: 2-4 horas (puede distribuirse en días).
Configuración: Implemente OWASP Juice Shop o desafíos personalizados. Cree equipos de 2-3 personas, mezcle niveles de experiencia, prepare pistas para los equipos atascados.
Desafíos por dificultad:
- Fácil (1 estrella): 5 desafíos — XSS básico, robots.txt
- Medio (2-3 estrellas): 5 desafíos — SQLi, bypass de autenticación
- Difícil (4-5 estrellas): 3 desafíos — deserialización, SSRF
Puntuación: Puntos basados en la dificultad. Bonus por resoluciones tempranas. Sin penalización por pistas: fomenta el aprendizaje sobre la competición.
Después del evento: Explicación de soluciones para todos los desafíos. Reconocimiento para los mejores equipos. Notas individuales de habilidades basadas en la participación.
Método 4: Ejercicio de inyección de vulnerabilidades
Evaluación realista: usted añade intencionalmente vulnerabilidades a un PR y verifica si los desarrolladores las detectan durante la revisión.
Cómo funciona:
- Cree una rama con vulnerabilidades intencionales
- Pida al desarrollador que revise el PR
- Rastree qué vulnerabilidades encuentran
- Proporcione retroalimentación sobre los problemas no detectados
Vulnerabilidades de ejemplo para inyectar:
# Intentional vulnerabilities for testing
# 1. SQL Injection (obvious)
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# 2. Command injection (less obvious)
os.system(f"convert {filename} output.pdf")
# 3. SSRF (requires understanding)
requests.get(user_provided_url)
# 4. Hardcoded secret
API_KEY = "sk_live_123456789"
# 5. Insecure deserialization
data = pickle.loads(user_input)
# 6. Path traversal
file_path = f"uploads/{user_filename}"
with open(file_path, 'r') as f:
content = f.read()
Creación de preguntas para cuestionarios
Uso de IA para generar preguntas
Puede usar asistentes de IA para ayudar a crear preguntas de evaluación. Aquí tiene una plantilla de prompt:
Create 5 multiple-choice security questions for [ROLE] developers
working with [TECHNOLOGY STACK].
Requirements:
- Include code examples where applicable
- Vary difficulty (2 L1, 2 L2, 1 L3)
- Focus on [SPECIFIC TOPIC: e.g., SQL injection, authentication]
- Provide correct answer and explanation
- Make wrong answers plausible but clearly incorrect
Format each question as:
**Q[N] ([Level]):** Question text
[Code if applicable]
a) Option A
b) Option B
c) Option C
d) Option D
**Answer:** [Letter]
**Explanation:** [Why this is correct and others are wrong]
Preguntas de ejemplo por tema
Inyección SQL (backend, datos)
P1 (L1): ¿Cuál de estos es vulnerable a inyección SQL?
# Option A
cursor.execute("SELECT * FROM users WHERE id = %s", (user_id,))
# Option B
cursor.execute(f"SELECT * FROM users WHERE id = {user_id}")
# Option C
User.objects.get(id=user_id)
a) Solo A b) Solo B c) A y B d) Todos
Respuesta: b Explicación: La opción B usa interpolación con f-string, incrustando directamente la entrada del usuario en la consulta. La opción A usa consultas parametrizadas (segura). La opción C usa el ORM de Django (segura).
P2 (L2): ¿Es este código seguro contra la inyección SQL?
if user_id.isdigit():
User.objects.raw(f"SELECT * FROM users WHERE id = {user_id}")[0]
a) Sí, sigue siendo vulnerable a inyección SQL b) No, la validación isdigit() lo hace seguro c) No, el ORM de Django previene la inyección SQL d) Depende del tipo de base de datos
Respuesta: b (pero discuta los casos límite) Explicación: isdigit() garantiza que solo haya dígitos, haciendo imposible la inyección. Sin embargo, este patrón es frágil: mejor usar consultas parametrizadas como defensa en profundidad. raw() con f-strings es un indicador de código problemático.
P3 (L3): Revise esta consulta parametrizada. ¿Es segura?
table_name = request.args.get('table')
cursor.execute(f"SELECT * FROM {table_name} WHERE id = %s", (user_id,))
a) Sí, las consultas parametrizadas previenen la inyección SQL b) No, el nombre de la tabla sigue siendo inyectable c) Sí, el marcador de posición protege todas las entradas d) Depende del driver de la base de datos
Respuesta: b Explicación: La parametrización solo funciona para valores, no para identificadores (nombres de tabla/columna). El table_name sigue siendo vulnerable. Solución: use una lista blanca de nombres de tabla permitidos.
XSS (frontend, backend)
P1 (L1): ¿Qué significa XSS? a) Cross-Site Scripting b) Cross-Server Security c) Client-Side Scripting d) Cross-Site Security
Respuesta: a
P2 (L2): En React, ¿cuál es vulnerable a XSS?
// Option A
<div>{userInput}</div>
// Option B
<div dangerouslySetInnerHTML={{__html: userInput}} />
// Option C
<input value={userInput} />
a) Solo A b) Solo B c) A y B d) Todos
Respuesta: b Explicación: React escapa el contenido en JSX por defecto (A y C son seguras). dangerouslySetInnerHTML omite esta protección y es vulnerable si userInput contiene HTML/JavaScript malicioso.
P3 (L2): ¿Qué cabecera Content-Security-Policy previene mejor el XSS? a) Content-Security-Policy: default-src * b) Content-Security-Policy: default-src 'self' c) Content-Security-Policy: default-src 'self'; script-src 'unsafe-inline' d) Content-Security-Policy: script-src 'none'
Respuesta: b Explicación: 'self' permite solo recursos del mismo origen. La opción a permite todo (sin protección). La opción c permite scripts en línea (riesgo de XSS). La opción d bloquea todos los scripts, incluidos los propios.
Autenticación (todos los roles)
P1 (L1): ¿Dónde debe almacenar tokens JWT en un navegador? a) localStorage b) sessionStorage c) Cookie HttpOnly d) Parámetros de URL
Respuesta: c Explicación: Las cookies HttpOnly no son accesibles mediante JavaScript, protegiéndose contra el robo de tokens por XSS. localStorage/sessionStorage son accesibles mediante JavaScript. Los parámetros de URL se registran y se almacenan en caché.
P2 (L2): ¿Qué está mal con este almacenamiento de contraseñas?
import hashlib
password_hash = hashlib.md5(password.encode()).hexdigest()
a) MD5 es demasiado lento b) MD5 está criptográficamente roto y no se usa sal c) Debería usar base64 en lugar de hexdigest d) Nada, esto es correcto
Respuesta: b Explicación: MD5 es rápido y tiene vulnerabilidades de colisión: no es adecuado para contraseñas. Las contraseñas deben usar bcrypt, Argon2 o scrypt con salado automático.
P3 (L3): Revise esta validación de JWT. ¿Cuál es el problema de seguridad?
const decoded = jwt.verify(token, secret);
if (decoded.userId) {
// Grant access
}
a) Ningún problema — jwt.verify comprueba todo b) Falta la especificación del algoritmo — vulnerable a confusión de algoritmos c) Debería usar jwt.decode en su lugar d) Falta la comprobación de la audiencia
Respuesta: b
Explicación: Sin especificar los algoritmos, el atacante podría crear un token con «alg»: «none» o cambiar de RS256 a HS256 usando la clave pública como secreto. Siempre especifique: jwt.verify(token, secret, { algorithms: ['HS256'] })
Autorización (backend)
P1 (L1): ¿Qué es IDOR?
a) Insecure Direct Object Reference — acceder a recursos adivinando IDs b) Internal Data Object Routing — patrón de API interna c) Integrated Development Object Repository — término de control de versiones d) Indirect Object Reference Design — patrón de codificación segura
Respuesta: a
P2 (L2): Este endpoint recupera datos de usuario. ¿Qué falta?
@app.route('/api/users/<user_id>/profile')
def get_profile(user_id):
user = User.query.get(user_id)
return jsonify(user.to_dict())
a) Validación de entrada en user_id b) Comprobación de autorización — verificar que el solicitante puede acceder a los datos de este usuario c) Limitación de tasa d) Requisito de HTTPS
Respuesta: b
Explicación: Cualquier usuario autenticado podría acceder al perfil de cualquier otro usuario cambiando el user_id. Es necesario verificar: if current_user.id != user_id and not current_user.is_admin: abort(403)
P3 (L3): ¿Cuál es el patrón de autorización más seguro?
a) Verificar la autorización en el controlador después de recuperar los datos b) Filtrar datos en la capa de repositorio/ORM basándose en el usuario actual c) Usar middleware que aplique autorización a todos los endpoints d) Verificar la autorización en la capa de servicio antes del acceso a los datos
Respuesta: b Explicación: El filtrado a nivel de repositorio garantiza que los datos no autorizados nunca salgan de la base de datos. Otros enfoques arriesgan la exposición de datos si se omite la comprobación. Defensa en profundidad: use múltiples capas.
Construcción de mapas de competencias en seguridad para desarrolladores
Los mapas de competencias visualizan las habilidades en todo el equipo e identifican brechas.
Mapa de competencias individual
Desarrollador: Jane Smith | Rol: Desarrolladora senior | Nivel: L2
| Área | Puntuación | Estado |
|---|---|---|
| Codificación segura | 80 % | ✓ |
| Autenticación | 90 % | ✓ |
| Validación de entrada | 100 % | ✓ |
| Criptografía | 40 % | ⚠ Brecha |
| Modelado de amenazas | 50 % | — |
| Pruebas de seguridad | 70 % | ✓ |
| Gestión de dependencias | 80 % | ✓ |
| Respuesta a incidentes | 40 % | ⚠ Brecha |
Recomendado: Curso de criptografía, formación en respuesta a incidentes
Mapa de calor de competencias del equipo
| Nombre | Codif. segura | Autenticación | Valid. entrada | Criptografía | Modelado amenazas | Pruebas | Deps |
|---|---|---|---|---|---|---|---|
| Jane S. | ✓✓ | ✓✓ | ✓✓ | ✗ | ✓ | ✓✓ | ✓✓ |
| John D. | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓ | ✓ |
| Alice K. | ✓ | ✓ | ✓✓ | ✗ | ✗ | ✓ | ✓ |
| Bob M. | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ | ✓✓ |
| Carol L. | ✓ | ✓ | ✓ | ✗ | ✗ | ✗ | ✓ |
| Promedio equipo | 80 % | 75 % | 90 % | 45 % BRECHA | 40 % BRECHA | 60 % | 70 % |
✓✓ = Fuerte (70 %+) · ✓ = En desarrollo (40-70 %) · ✗ = Brecha (menos del 40 %)
Seguimiento de competencias a lo largo del tiempo
Evaluación T1 2024
| Área | Puntuación | Objetivo | Estado |
|---|---|---|---|
| Codificación segura | 80 % | 80 % | Alcanzado |
| Autenticación | 75 % | 80 % | En progreso |
| Criptografía | 45 % | 60 % | Brecha |
| Modelado de amenazas | 40 % | 60 % | Brecha |
| Pruebas de seguridad | 60 % | 70 % | En progreso |
Acciones:
- Módulo de autenticación completado
- Taller de modelado de amenazas (programado en febrero)
- Formación en criptografía para el equipo (programada en marzo)
Objetivos T2: Criptografía por encima del 60 % · Modelado de amenazas por encima del 60 % · Todos los desarrolladores aprueban la evaluación intermedia
Herramientas de seguimiento de competencias
| Enfoque | Mejor para | Esfuerzo |
|---|---|---|
| Hoja de cálculo | Equipos pequeños (menos de 10) | Bajo |
| Notion/Confluence | Organizaciones con mucha documentación | Bajo |
| Plataforma de gestión de habilidades | Equipos grandes | Medio |
| Panel personalizado | Necesidades específicas | Alto |
Plantilla de hoja de cálculo simple:
| Desarrollador | Rol | OWASP Top 10 | Auth/AuthZ | Criptografía | Pruebas | Modelado amenazas | Última evaluación |
|---|---|---|---|---|---|---|---|
| Jane S. | Senior | 4/5 | 5/5 | 2/5 | 3/5 | 3/5 | 2024-01-15 |
| John D. | Medio | 3/5 | 3/5 | 4/5 | 2/5 | 4/5 | 2024-01-15 |
| ... | ... | ... | ... | ... | ... | ... | ... |
Comparativa de plataformas de evaluación
Plataformas gratuitas
| Plataforma | Fortalezas | Limitaciones |
|---|---|---|
| Google Forms | Configuración fácil, gratuito | Sin ejecución de código, análisis básico |
| Microsoft Forms | Integración con Office 365 | Similar a Google Forms |
| Typeform | Mejor experiencia de usuario | Nivel gratuito limitado |
| CTFd auto-alojado | Control total, gamificado | Requiere configuración y mantenimiento |
Plataformas de pago
| Plataforma | Mejor para | Coste aproximado |
|---|---|---|
| Secure Code Warrior | Formación de seguridad empresarial | $$$$ |
| HackEDU | Formación centrada en desarrolladores | $$$ |
| Avatao | Desafíos prácticos | $$$ |
| AppSec Engineer | Formación cloud-native | $$ |
| Kontra | Lecciones interactivas | $$ |
Evaluación hágalo usted mismo
Para equipos pequeños con presupuesto limitado:
- Cree preguntas en Markdown — control de versiones, fácil actualización
- Use Google Forms para la entrega — gratuito, fácil de compartir
- Rastree resultados en una hoja de cálculo — mapeo simple de competencias
- Ejecute CTF con Juice Shop — gratuito, integral
Medición de la mejora
Métricas a rastrear
| Métrica | Cómo medir | Objetivo |
|---|---|---|
| Puntuación media de evaluación | Resultados del cuestionario | Mejora cada trimestre |
| Densidad de vulnerabilidades | Hallazgos de SAST por KLOC | Disminución con el tiempo |
| Tiempo de corrección | Seguimiento en JIRA | Disminución para errores de seguridad |
| Errores de seguridad en producción | Rastreador de errores | Menos en cada versión |
| Tasa de detección en revisión de código | Seguimiento de comentarios de revisión | Más problemas de seguridad detectados |
| Finalización de formación | LMS o hoja de cálculo | 100 % para los obligatorios |
Análisis antes/después
Informe de impacto de la formación: T2 2024
Puntuaciones de evaluación antes y después de la formación:
| Área | Antes de la formación | Después de la formación | Cambio |
|---|---|---|---|
| Prevención de SQLi | 65 % | 88 % | +23 % |
| Buenas prácticas de autenticación | 70 % | 85 % | +15 % |
| Validación de entrada | 60 % | 82 % | +22 % |
Impacto real en el mundo durante el mismo período:
| Métrica | T1 | T2 | Cambio |
|---|---|---|---|
| Hallazgos de SAST | 145 | 89 | −39 % |
| Errores de seguridad en producción | 12 | 5 | −58 % |
| Tiempo medio de corrección (días) | 8 | 4 | −50 % |
La formación está mostrando un ROI medible. Continúe con las evaluaciones trimestrales y centre la formación en las brechas actuales.
Errores comunes
- Evaluar el conocimiento, no las habilidades — Incluya ejercicios prácticos, no solo teoría
- Evaluación de talla única — Personalice para roles y stacks tecnológicos
- Evaluar una vez y olvidar — La evaluación regular detecta la degradación de habilidades
- Penalizar las puntuaciones bajas — Esto desalienta la evaluación honesta
- No actuar sobre los resultados — La evaluación sin formación es inútil
- Hacerla demasiado larga — Máximo 45-60 minutos para los cuestionarios
Preguntas de autoevaluación
- ¿Cuáles son los cuatro métodos para evaluar las habilidades de seguridad de los desarrolladores?
- ¿Por qué el ejercicio de revisión de código es más práctico que un cuestionario?
- ¿Cómo se calculan los niveles de competencia a partir de las puntuaciones de evaluación?
- ¿Qué métricas debe rastrear para medir la efectividad de la formación?
- ¿Con qué frecuencia se debe evaluar a los desarrolladores?
- ¿Cuál es el beneficio de la evaluación al estilo CTF?
Conclusión
La evaluación solo importa si actúa en consecuencia. Una brecha de habilidades que ha medido e ignorado es peor que una que no ha medido: sabía sobre ella y no hizo nada.
Ejecute la línea base. Encuentre las brechas. Úselas para priorizar en qué se forma primero.
Qué sigue
Siguiente: guía de implementación — plan paso a paso para desplegar la formación en seguridad para desarrolladores en todo el equipo.