SQL Injection: Riesgos de Seguridad, Medidas de Protección y Mejores Prácticas
Esta guía te ofrece un análisis completo de SQL Injection, incluyendo riesgos de seguridad, medidas de protección, Prepared Statements, frameworks ORM y validación de entrada con ejemplos prácticos.
Resumen Rápido
SQL Injection es una vulnerabilidad crítica que permite a los atacantes inyectar comandos SQL maliciosos. La protección se logra mediante Prepared Statements, validación de entrada y frameworks ORM.
Descripción Técnica Concisa
SQL Injection es una técnica de ataque donde los atacantes inyectan comandos SQL maliciosos a través de campos de entrada para obtener acceso no autorizado a bases de datos o manipular datos.
Vectores de ataque:
- Formularios de login: Evasión de autenticación
- Campos de búsqueda: Extracción de datos sensibles
- Parámetros de URL: Manipulación de consultas a la base de datos
- Cargas de archivos: Inyección a través de nombres de archivo
- Endpoints de API: Manipulación directa de la base de datos
Medidas de protección:
- Prepared Statements: Consultas parametrizadas
- Validación de entrada: Verificación basada en listas blancas
- Frameworks ORM: Capa de abstracción para acceso a bases de datos
- Least Privilege: Derechos mínimos en la base de datos
- Manejo de errores: Sin exposición de errores de base de datos
Puntos Clave para el Examen
- SQL Injection: Inyección de comandos SQL a través de campos de entrada
- Objetivos de ataque: Extracción, manipulación o destrucción de datos
- Vulnerabilidades: Formularios de login, campos de búsqueda, parámetros de URL
- Medidas de protección: Prepared Statements, validación de entrada, ORM
- Prepared Statements: Consultas parametrizadas con marcadores de posición
- Frameworks ORM: Hibernate, Entity Framework, SQLAlchemy
- Least Privilege: Derechos mínimos para acceso a bases de datos
- Relevancia profesional: Riesgo de seguridad crítico en aplicaciones web
Componentes Principales
- Vectores de ataque: Posibles puntos de entrada para inyección
- Técnicas de inyección: Basadas en Union, Boolean y Time
- Medidas de protección: Prepared Statements, validación, ORM
- Frameworks ORM: Capa de abstracción para acceso a bases de datos
- Validación de entrada: Verificación de entrada basada en listas blancas
- Manejo de errores: Gestión segura de excepciones
- Security Headers: Capas de protección adicionales
- Monitoreo: Detección de intentos de ataque
Ejemplos Prácticos
1. Ataques de SQL Injection y Contramedidas
import java.sql.*;
import java.util.Scanner;
import java.util.regex.Pattern;
// Implementación insegura (vulnerable a SQL Injection)
class UnsafeLoginService {
private Connection connection;
public UnsafeLoginService(Connection connection) {
this.connection = connection;
}
public boolean loginUnsafe(String username, String password) {
// MUY PELIGROSO - Concatenación directa de strings
String query = "SELECT * FROM users WHERE username = '" + username +
"' AND password = '" + password + "'";
System.out.println("Query: " + query);
try (Statement stmt = connection.createStatement();
ResultSet rs = stmt.executeQuery(query)) {
return rs.next(); // Login exitoso si hay resultado
} catch (SQLException e) {
System.err.println("Error de base de datos: " + e.getMessage());
return false;
}
}
}
// Implementación segura con Prepared Statements
class SafeLoginService {
private Connection connection;
public SafeLoginService(Connection connection) {
this.connection = connection;
}
public boolean loginSafe(String username, String password) {
// SEGURO - Prepared Statement con marcadores de posición
String query = "SELECT * FROM users WHERE username = ? AND password = ?";
try (PreparedStatement pstmt = connection.prepareStatement(query)) {
// Establecer parámetros (escapados automáticamente)
pstmt.setString(1, username);
pstmt.setString(2, password);
try (ResultSet rs = pstmt.executeQuery()) {
return rs.next();
}
} catch (SQLException e) {
System.err.println("Error de base de datos: " + e.getMessage());
return false;
}
}
// Con validación de entrada
public boolean loginWithValidation(String username, String password) {
// Validar entrada antes de consultar la base de datos
if (!isValidUsername(username) || !isValidPassword(password)) {
System.err.println("Entrada inválida");
return false;
}
return loginSafe(username, password);
}
private boolean isValidUsername(String username) {
// Lista blanca: solo caracteres alfanuméricos y guión bajo
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_]{3,20}$");
return pattern.matcher(username).matches();
}
private boolean isValidPassword(String password) {
// Mínimo 8 caracteres, máximo 100
return password != null &&
password.length() >= 8 &&
password.length() <= 100;
}
}
// Medidas de seguridad avanzadas
class AdvancedSecurityService {
private Connection connection;
public AdvancedSecurityService(Connection connection) {
this.connection = connection;
}
// Con limitación de intentos
private Map<String, Integer> loginAttempts = new java.util.concurrent.ConcurrentHashMap<>();
private static final int MAX_ATTEMPTS = 5;
private static final long LOCK_TIME_MS = 30000; // 30 minutos
public boolean loginWithRateLimit(String username, String password) {
String clientIp = getClientIp(); // Implementación requerida
// Verificar limitación de intentos
if (isBlocked(clientIp)) {
System.err.println("IP bloqueada por demasiados intentos");
return false;
}
boolean loginSuccess = loginSafe(username, password);
if (!loginSuccess) {
incrementAttempts(clientIp);
} else {
clearAttempts(clientIp);
}
return loginSuccess;
}
private boolean isBlocked(String clientIp) {
Integer attempts = loginAttempts.get(clientIp);
return attempts != null && attempts >= MAX_ATTEMPTS;
}
private void incrementAttempts(String clientIp) {
loginAttempts.merge(clientIp, 1, Integer::sum);
}
private void clearAttempts(String clientIp) {
loginAttempts.remove(clientIp);
}
private String getClientIp() {
// Implementación para extraer la IP
return "127.0.0.1"; // Marcador
}
// Con registro y monitoreo
public boolean loginWithMonitoring(String username, String password) {
long startTime = System.currentTimeMillis();
String clientIp = getClientIp();
try {
boolean success = loginSafe(username, password);
// Registrar el intento de login
logLoginAttempt(username, clientIp, success,
System.currentTimeMillis() - startTime);
return success;
} catch (Exception e) {
logSecurityEvent("Error de login", username, clientIp, e.getMessage());
return false;
}
}
private void logLoginAttempt(String username, String clientIp,
boolean success, long duration) {
String logLevel = success ? "INFO" : "WARN";
System.out.printf("[%s] Login %s: user=%s, ip=%s, duration=%dms%n",
logLevel, success ? "exitoso" : "fallido",
username, clientIp, duration);
}
private void logSecurityEvent(String event, String username,
String clientIp, String details) {
System.out.printf("[SECURITY] %s: user=%s, ip=%s, details=%s%n",
event, username, clientIp, details);
}
}
// Demo de SQL Injection
public class SQLInjectionDemo {
public static void main(String[] args) {
System.out.println("=== Demo SQL Injection ===");
try {
// Conexión a base de datos en memoria H2
Connection conn = DriverManager.getConnection("jdbc:h2:mem:testdb", "sa", "");
// Crear tabla de prueba
setupTestDatabase(conn);
// Crear servicios
UnsafeLoginService unsafeService = new UnsafeLoginService(conn);
SafeLoginService safeService = new SafeLoginService(conn);
AdvancedSecurityService advancedService = new AdvancedSecurityService(conn);
// Intentos normales de login
System.out.println("\n--- Intentos Normales de Login ---");
testLogin(unsafeService, "admin", "password123", "Inseguro");
testLogin(safeService, "admin", "password123", "Seguro");
// Ataques de SQL Injection
System.out.println("\n--- Ataques de SQL Injection ---");
// Ataque de inyección clásico
String injectionUsername = "admin' OR '1'='1";
String injectionPassword = "anything";
System.out.println("Username de inyección: " + injectionUsername);
System.out.println("Password de inyección: " + injectionPassword);
boolean unsafeResult = unsafeService.loginUnsafe(injectionUsername, injectionPassword);
System.out.println("Login inseguro (inyección): " +
(unsafeResult ? "EXITOSO (¡Peligro!)" : "fallido"));
boolean safeResult = safeService.loginSafe(injectionUsername, injectionPassword);
System.out.println("Login seguro (inyección): " +
(safeResult ? "exitoso" : "fallido"));
// Medidas de seguridad avanzadas
System.out.println("\n--- Medidas de Seguridad Avanzadas ---");
// Con validación de entrada
boolean validationResult = safeService.loginWithValidation("admin", "password123");
System.out.println("Login con validación: " +
(validationResult ? "exitoso" : "fallido"));
boolean invalidResult = safeService.loginWithValidation("admin'; DROP TABLE users; --", "password");
System.out.println("Login con entrada inválida: " +
(invalidResult ? "exitoso" : "fallido"));
// Con limitación de intentos
boolean rateLimitedResult = advancedService.loginWithRateLimit("admin", "password123");
System.out.println("Login con limitación de intentos: " +
(rateLimitedResult ? "exitoso" : "fallido"));
// Con monitoreo
boolean monitoredResult = advancedService.loginWithMonitoring("admin", "password123");
System.out.println("Login con monitoreo: " +
(monitoredResult ? "exitoso" : "fallido"));
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
private static void testLogin(Object service, String username, String password, String type) {
try {
boolean result;
if (service instanceof UnsafeLoginService) {
result = ((UnsafeLoginService) service).loginUnsafe(username, password);
} else if (service instanceof SafeLoginService) {
result = ((SafeLoginService) service).loginSafe(username, password);
} else {
result = false;
}
System.out.printf("%s Login (%s, %s): %s%n",
type, username, password,
result ? "exitoso" : "fallido");
} catch (Exception e) {
System.out.printf("%s Login: Error - %s%n", type, e.getMessage());
}
}
private static void setupTestDatabase(Connection conn) throws SQLException {
try (Statement stmt = conn.createStatement()) {
// Crear tabla de usuarios
stmt.execute("DROP TABLE users IF EXISTS");
stmt.execute("""
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""");
// Insertar datos de prueba
stmt.execute("INSERT INTO users (username, password, email) VALUES " +
"('admin', 'password123', 'admin@example.com'), " +
"('user1', 'user123', 'user1@example.com'), " +
"('user2', 'user456', 'user2@example.com')");
System.out.println("Base de datos de prueba creada");
}
}
}
2. Medidas de seguridad en ORM
import javax.persistence.*;
import java.util.List;
import java.util.regex.Pattern;
// JPA Entity
@Entity
@Table(name = "users")
class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(unique = true, nullable = false)
private String username;
@Column(nullable = false)
private String password;
private String email;
// Getters y setters
public Long getId() { return id; }
public String getUsername() { return username; }
public void setUsername(String username) { this.username = username; }
public String getPassword() { return password; }
public void setPassword(String password) { this.password = password; }
public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}
// Clase service segura con JPA
class SecureUserService {
private EntityManager em;
public SecureUserService(EntityManager em) {
this.em = em;
}
// Autenticación segura con JPA
public User authenticate(String username, String password) {
// Validación de entrada
if (!isValidInput(username) || !isValidInput(password)) {
throw new IllegalArgumentException("Entrada inválida");
}
try {
// Query JPA con parámetros nombrados (segura automáticamente)
TypedQuery<User> query = em.createQuery(
"SELECT u FROM User u WHERE u.username = :username AND u.password = :password",
User.class);
query.setParameter("username", username);
query.setParameter("password", password);
List<User> results = query.getResultList();
return results.isEmpty() ? null : results.get(0);
} catch (Exception e) {
// Manejo seguro de errores
throw new RuntimeException("Autenticación fallida", e);
}
}
// Búsqueda segura con Criteria API de JPA
public List<User> searchUsers(String searchTerm) {
if (!isValidSearchTerm(searchTerm)) {
throw new IllegalArgumentException("Término de búsqueda inválido");
}
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> user = cq.from(User.class);
// Búsqueda segura con Criteria API (sin concatenación de strings)
Predicate searchPredicate = cb.or(
cb.like(user.get("username"), "%" + searchTerm + "%"),
cb.like(user.get("email"), "%" + searchTerm + "%")
);
cq.where(searchPredicate);
return em.createQuery(cq).getResultList();
}
// Paginación segura
public List<User> getUsersPaginated(int page, int size) {
if (page < 0 || size <= 0 || size > 100) {
throw new IllegalArgumentException("Parámetros de paginación inválidos");
}
TypedQuery<User> query = em.createQuery("SELECT u FROM User u ORDER BY u.username", User.class);
query.setFirstResult(page * size);
query.setMaxResults(size);
return query.getResultList();
}
private boolean isValidInput(String input) {
// Validación por whitelist
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_@.-]{3,100}$");
return input != null && pattern.matcher(input).matches();
}
private boolean isValidSearchTerm(String term) {
// Los términos de búsqueda permiten más caracteres
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_@.-\\s]{1,50}$");
return term != null && pattern.matcher(term).matches();
}
}
// Medidas avanzadas con Spring Data JPA
@Repository
interface UserRepository extends JpaRepository<User, Long> {
// Consultas seguras con nombres de métodos
Optional<User> findByUsernameAndPassword(String username, String password);
@Query("SELECT u FROM User u WHERE u.username LIKE %:search% OR u.email LIKE %:search%")
List<User> findByUsernameOrEmailContaining(@Param("search") String search);
// Query nativa con binding de parámetros
@Query(value = "SELECT * FROM users WHERE email = :email", nativeQuery = true)
Optional<User> findByEmailNative(@Param("email") String email);
}
// Service con Spring Data JPA
@Service
@Transactional
class UserServiceWithSpring {
private UserRepository userRepository;
public UserServiceWithSpring(UserRepository userRepository) {
this.userRepository = userRepository;
}
public Optional<User> authenticate(String username, String password) {
// Validación de entrada
validateInput(username, "username");
validateInput(password, "password");
// Spring Data JPA, automáticamente segura
return userRepository.findByUsernameAndPassword(username, password);
}
public List<User> searchUsers(String searchTerm) {
validateSearchTerm(searchTerm);
return userRepository.findByUsernameOrEmailContaining(searchTerm);
}
private void validateInput(String input, String fieldName) {
if (input == null || input.trim().isEmpty()) {
throw new IllegalArgumentException(fieldName + " no puede estar vacío");
}
if (input.length() > 100) {
throw new IllegalArgumentException(fieldName + " es demasiado largo");
}
// Reglas de validación adicionales...
}
private void validateSearchTerm(String term) {
if (term == null || term.trim().isEmpty()) {
throw new IllegalArgumentException("El término de búsqueda no puede estar vacío");
}
if (term.length() > 50) {
throw new IllegalArgumentException("El término de búsqueda es demasiado largo");
}
}
}
// Demo de seguridad en ORM
public class ORMSecurityDemo {
public static void main(String[] args) {
System.out.println("=== ORM Security Demo ===");
// En una aplicación real, esto sería gestionado por Spring/DI
EntityManagerFactory emf = Persistence.createEntityManagerFactory("testdb");
EntityManager em = emf.createEntityManager();
try {
// Crear datos de prueba
setupTestData(em);
// Crear el service
SecureUserService service = new SecureUserService(em);
// Autenticación normal
System.out.println("\n--- Autenticación normal ---");
User user = service.authenticate("admin", "password123");
System.out.println("Autenticación: " + (user != null ? "exitosa" : "fallida"));
// Intento de inyección (bloqueado por ORM)
System.out.println("\n--- Intento de inyección ---");
try {
User injectedUser = service.authenticate("admin' OR '1'='1", "anything");
System.out.println("Autenticación por inyección: " +
(injectedUser != null ? "exitosa (¡Peligro!)" : "fallida"));
} catch (Exception e) {
System.out.println("Autenticación por inyección: fallida (protegida)");
}
// Búsqueda segura
System.out.println("\n--- Búsqueda segura ---");
List<User> searchResults = service.searchUsers("admin");
System.out.println("Resultados de búsqueda: " + searchResults.size() + " usuarios");
// Paginación
System.out.println("\n--- Paginación ---");
List<User> paginatedUsers = service.getUsersPaginated(0, 2);
System.out.println("Resultados paginados: " + paginatedUsers.size() + " usuarios");
} finally {
em.close();
emf.close();
}
}
private static void setupTestData(EntityManager em) {
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
// Crear usuarios de prueba
User admin = new User();
admin.setUsername("admin");
admin.setPassword("password123");
admin.setEmail("admin@example.com");
em.persist(admin);
User user1 = new User();
user1.setUsername("user1");
user1.setPassword("user123");
user1.setEmail("user1@example.com");
em.persist(user1);
tx.commit();
System.out.println("Datos de prueba creados");
} catch (Exception e) {
if (tx.isActive()) {
tx.rollback();
}
e.printStackTrace();
}
}
}
3. Ejemplos en PHP y Python
<?php
// Implementación insegura en PHP (vulnerable)
class UnsafeLoginPHP {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function loginUnsafe($username, $password) {
// PELIGROSO - Concatenación directa de strings
$query = "SELECT * FROM users WHERE username = '" . $username .
"' AND password = '" . $password . "'";
echo "Query: " . $query . "\n";
$stmt = $this->pdo->query($query);
return $stmt->rowCount() > 0;
}
}
// Implementación segura en PHP
class SafeLoginPHP {
private $pdo;
public function __construct($pdo) {
$this->pdo = $pdo;
}
public function loginSafe($username, $password) {
// SEGURO - Prepared Statement
$query = "SELECT * FROM users WHERE username = ? AND password = ?";
$stmt = $this->pdo->prepare($query);
$stmt->execute([$username, $password]);
return $stmt->rowCount() > 0;
}
public function loginWithValidation($username, $password) {
// Validación de entrada
if (!$this->isValidUsername($username) || !$this->isValidPassword($password)) {
throw new InvalidArgumentException("Entrada inválida");
}
return $this->loginSafe($username, $password);
}
private function isValidUsername($username) {
// Validación de whitelist
return preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username);
}
private function isValidPassword($password) {
return strlen($password) >= 8 && strlen($password) <= 100;
}
}
// Demo en PHP
echo "=== PHP SQL Injection Demo ===\n";
try {
$pdo = new PDO('mysql:host=localhost;dbname=test', 'user', 'password');
// Crear servicios
$unsafeService = new UnsafeLoginPHP($pdo);
$safeService = new SafeLoginPHP($pdo);
// Intentos de login normales
echo "\n--- Intentos de login normales ---\n";
$unsafeResult = $unsafeService->loginUnsafe('admin', 'password123');
echo "Login inseguro: " . ($unsafeResult ? 'exitoso' : 'fallido') . "\n";
$safeResult = $safeService->loginSafe('admin', 'password123');
echo "Login seguro: " . ($safeResult ? 'exitoso' : 'fallido') . "\n";
// Ataque de inyección
echo "\n--- Ataque SQL Injection ---\n";
$injectionUsername = "admin' OR '1'='1";
$injectionPassword = "anything";
echo "Usuario de inyección: $injectionUsername\n";
echo "Contraseña de inyección: $injectionPassword\n";
$unsafeInjectionResult = $unsafeService->loginUnsafe($injectionUsername, $injectionPassword);
echo "Login inseguro (inyección): " .
($unsafeInjectionResult ? 'EXITOSO (¡Peligro!)' : 'fallido') . "\n";
$safeInjectionResult = $safeService->loginSafe($injectionUsername, $injectionPassword);
echo "Login seguro (inyección): " .
($safeInjectionResult ? 'exitoso' : 'fallido') . "\n";
} catch (PDOException $e) {
echo "Error de base de datos: " . $e->getMessage() . "\n";
}
?>
# Implementación de SQLAlchemy en Python
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import re
Base = declarative_base()
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
username = Column(String(50), unique=True, nullable=False)
password = Column(String(100), nullable=False)
email = Column(String(100))
def __repr__(self):
return f"<User(username='{self.username}')>"
class SecureUserService:
def __init__(self, session):
self.session = session
def authenticate(self, username, password):
# Validación de entrada
if not self.is_valid_input(username) or not self.is_valid_input(password):
raise ValueError("Entrada inválida")
# Consulta SQLAlchemy - automáticamente segura
user = self.session.query(User).filter(
User.username == username,
User.password == password
).first()
return user
def search_users(self, search_term):
if not self.is_valid_search_term(search_term):
raise ValueError("Término de búsqueda inválido")
# Búsqueda segura con SQLAlchemy
users = self.session.query(User).filter(
(User.username.like(f"%{search_term}%")) |
(User.email.like(f"%{search_term}%"))
).all()
return users
def is_valid_input(self, input_str):
# Validación de whitelist
pattern = r'^[a-zA-Z0-9_@.-]{3,100}$'
return input_str is not None and re.match(pattern, input_str) is not None
def is_valid_search_term(self, term):
# Los términos de búsqueda permiten más caracteres
pattern = r'^[a-zA-Z0-9_@.-\s]{1,50}$'
return term is not None and re.match(pattern, term) is not None
# Demo en Python
def main():
print("=== Python SQLAlchemy Security Demo ===")
# Crear engine de base de datos
engine = create_engine('sqlite:///:memory:')
Base.metadata.create_all(engine)
Session = sessionmaker(bind=engine)
session = Session()
try:
# Crear datos de prueba
admin = User(username='admin', password='password123', email='admin@example.com')
user1 = User(username='user1', password='user123', email='user1@example.com')
session.add(admin)
session.add(user1)
session.commit()
# Crear servicio
service = SecureUserService(session)
# Autenticación normal
print("\n--- Autenticación normal ---")
user = service.authenticate('admin', 'password123')
print(f"Autenticación: {'exitosa' if user else 'fallida'}")
# Intento de inyección
print("\n--- Intento de inyección ---")
try:
injected_user = service.authenticate("admin' OR '1'='1", "anything")
print(f"Autenticación por inyección: {'exitosa (¡Peligro!)' if injected_user else 'fallida'}")
except Exception as e:
print(f"Autenticación por inyección: fallida (protegida)")
# Búsqueda segura
print("\n--- Búsqueda segura ---")
search_results = service.search_users('admin')
print(f"Resultados de búsqueda: {len(search_results)} usuarios")
finally:
session.close()
if __name__ == "__main__":
main()
Técnicas de SQL Injection
Inyección basada en UNION
-- Consulta original
SELECT * FROM users WHERE username = 'admin' AND password = 'password'
-- Inyección
SELECT * FROM users WHERE username = 'admin' UNION SELECT table_name, null, null, null FROM information_schema.tables--' AND password = 'anything'
Inyección basada en booleanos
-- Consulta original
SELECT * FROM products WHERE category = 'electronics'
-- Inyección
SELECT * FROM products WHERE category = 'electronics' AND 1=1--' -- Siempre verdadero
SELECT * FROM products WHERE category = 'electronics' AND 1=0--' -- Siempre falso
Inyección basada en tiempo
-- Query original
SELECT * FROM users WHERE id = 1
-- Inyección con retraso de tiempo
SELECT * FROM users WHERE id = 1 AND (SELECT SLEEP(5))--' -- Retrasa la respuesta si es verdadero
Resumen de medidas de protección
| Medida | Descripción | Efectividad |
|---|---|---|
| Prepared Statements | Consultas parametrizadas | Muy alta |
| ORM Frameworks | Capa de abstracción | Alta |
| Validación de entrada | Verificación basada en whitelist | Media |
| Least Privilege | Permisos mínimos en BD | Alta |
| Manejo de errores | Sin exponer errores de BD | Media |
| Rate Limiting | Limitación de intentos | Media |
Patrones de validación de entrada
Validación por whitelist
// Solo caracteres permitidos
Pattern pattern = Pattern.compile("^[a-zA-Z0-9_]{3,20}$");
boolean isValid = pattern.matcher(input).matches();
Validación de longitud
// Limitación de longitud
if (input == null || input.length() < 3 || input.length() > 100) {
throw new IllegalArgumentException("Longitud inválida");
}
Validación de tipo de contenido
// Verificar tipos de datos esperados
try {
int number = Integer.parseInt(input);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Se esperaba un número");
}
Mejores prácticas de seguridad en base de datos
Principio de Least Privilege
-- Usuario de aplicación web con permisos mínimos
CREATE USER 'webapp'@'localhost' IDENTIFIED BY 'secure_password';
GRANT SELECT, INSERT, UPDATE ON app_db.users TO 'webapp';
GRANT SELECT ON app_db.products TO 'webapp';
-- SIN permisos DROP, ALTER, CREATE
Procedimientos almacenados
-- Procedimiento almacenado seguro
CREATE PROCEDURE authenticate_user(IN p_username VARCHAR(50), IN p_password VARCHAR(100))
BEGIN
SELECT id FROM users WHERE username = p_username AND password = p_password;
END;
-- Invocación desde la aplicación
CALL authenticate_user(?, ?);
Monitoreo y detección
Detección de anomalías
// Reconocer patrones de consulta inusual
public class QueryMonitor {
private Map<String, Integer> queryPatterns = new ConcurrentHashMap<>();
public void monitorQuery(String query, String ip) {
// Analizar patrón de consulta
String pattern = extractPattern(query);
// Verificar frecuencia
int count = queryPatterns.merge(pattern, 1, Integer::sum);
if (count > 100) { // Umbral
alertSecurityTeam("Patrón de consulta sospechoso", ip, pattern);
}
}
private String extractPattern(String query) {
// Normalizar consulta para reconocimiento de patrón
return query.replaceAll("\\d+", "N")
.replaceAll("'[^']*'", "'S'")
.toLowerCase();
}
private void alertSecurityTeam(String message, String ip, String details) {
System.out.printf("[ALERT] %s from %s: %s%n", message, ip, details);
// Enviar a sistema SIEM
}
}
Ventajas y desventajas
Ventajas de las medidas de protección
- Seguridad: Previene comprometer la base de datos
- Cumplimiento: Satisface estándares de seguridad
- Confianza: Protege los datos del usuario
- Estabilidad: Previene corrupción de datos
Desventajas
- Rendimiento: Pequeña sobrecarga por validación
- Complejidad: Código adicional necesario
- Mantenimiento: Actualizaciones regulares requeridas
- Falsos positivos: Posibles alertas incorrectas
Preguntas frecuentes de examen
-
¿Qué es SQL Injection y cómo funciona? Inyectar comandos SQL a través de campos de entrada concatenando strings en consultas.
-
¿Cuál es la mejor defensa contra SQL Injection? Los Prepared Statements con parametrización son la medida más efectiva.
-
¿Por qué son más seguros los ORM Frameworks? Abstraen las consultas SQL y utilizan automáticamente Prepared Statements.
-
¿Cuál es la diferencia entre validación whitelist y blacklist? Whitelist solo permite caracteres definidos, blacklist bloquea patrones conocidos como peligrosos.
Recursos principales
- https://owasp.org/www-community/attacks/SQL_Injection
- https://www.w3schools.com/sql/sql_injection.asp
- https://portswigger.net/web-security/sql-injection



