- Authentication Bypass
- Union-Based SQL Injection
- Error-Based SQL Injection
- Blind SQL Injection
- Second-Order SQL Injection
- Stored Procedure Injection
// C# - ASP.NET Example
public bool Login(string username, string password)
{
string query = "SELECT * FROM Users WHERE Username = '" + username +
"' AND Password = '" + password + "'";
SqlCommand cmd = new SqlCommand(query, connection);
SqlDataReader reader = cmd.ExecuteReader();
return reader.HasRows; // True si encuentra usuario
}Ataque:
Username: admin' OR '1'='1
Password: cualquiercosa
Query resultante:
SELECT * FROM Users WHERE Username = 'admin' OR '1'='1' AND Password = 'cualquiercosa'✅ Siempre retorna verdadero, bypass completo!
// Opción 1: Prepared Statements (Parametrized Queries)
public bool Login_Secure(string username, string password)
{
string query = "SELECT * FROM Users WHERE Username = @Username AND Password = @Password";
using (SqlCommand cmd = new SqlCommand(query, connection))
{
cmd.Parameters.AddWithValue("@Username", username);
cmd.Parameters.AddWithValue("@Password", password);
SqlDataReader reader = cmd.ExecuteReader();
return reader.HasRows;
}
}
// Opción 2: Stored Procedure
public bool Login_StoredProc(string username, string password)
{
using (SqlCommand cmd = new SqlCommand("sp_Login_Secure", connection))
{
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@Username", username);
cmd.Parameters.AddWithValue("@Password", password);
SqlDataReader reader = cmd.ExecuteReader();
return reader.HasRows;
}
}# Python - Flask Example
@app.route('/product')
def get_product():
product_id = request.args.get('id')
query = f"SELECT ProductName, Price FROM Products WHERE ProductID = {product_id}"
cursor.execute(query)
result = cursor.fetchall()
return jsonify(result)Ataque:
/product?id=1 UNION SELECT CreditCardNumber, SSN FROM SensitiveData--
Query resultante:
SELECT ProductName, Price FROM Products WHERE ProductID = 1
UNION SELECT CreditCardNumber, SSN FROM SensitiveData--💥 Expone datos sensibles de otra tabla!
# Opción 1: Parametrized Query
@app.route('/product')
def get_product_secure():
product_id = request.args.get('id')
# Validación de entrada
if not product_id.isdigit():
return jsonify({"error": "Invalid ID"}), 400
query = "SELECT ProductName, Price FROM Products WHERE ProductID = ?"
cursor.execute(query, (product_id,))
result = cursor.fetchall()
return jsonify(result)
# Opción 2: ORM (SQLAlchemy)
@app.route('/product_orm')
def get_product_orm():
product_id = request.args.get('id', type=int)
if product_id is None:
return jsonify({"error": "Invalid ID"}), 400
product = Product.query.filter_by(ProductID=product_id).first()
if product:
return jsonify({
"name": product.ProductName,
"price": product.Price
})
return jsonify({"error": "Not found"}), 404// PHP Example
<?php
$username = $_GET['username'];
$query = "SELECT * FROM Users WHERE Username = '$username'";
$result = mysqli_query($conn, $query);
if (!$result) {
die("Error: " . mysqli_error($conn)); // Muestra error completo!
}
?>Ataque:
?username=admin' AND 1=CONVERT(int, (SELECT TOP 1 CreditCardNumber FROM SensitiveData))--
Error generado:
Error: Conversion failed when converting the nvarchar value '4532123456789012' to data type int.
💥 El número de tarjeta queda expuesto en el mensaje de error!
<?php
// Prepared Statement con manejo seguro de errores
$username = $_GET['username'];
// Validación de entrada
if (!preg_match('/^[a-zA-Z0-9_]{3,20}$/', $username)) {
die("Invalid username format");
}
$stmt = $conn->prepare("SELECT * FROM Users WHERE Username = ?");
$stmt->bind_param("s", $username);
if (!$stmt->execute()) {
// Log error internamente, no mostrar al usuario
error_log("Database error: " . $stmt->error);
die("An error occurred. Please try again later.");
}
$result = $stmt->get_result();
?>// Node.js - Express Example
app.get('/check-user', (req, res) => {
const username = req.query.username;
const query = `SELECT * FROM Users WHERE Username = '${username}'`;
db.query(query, (err, results) => {
if (err) {
res.send("Error occurred");
} else if (results.length > 0) {
res.send("User exists");
} else {
res.send("User not found");
}
});
});Ataque (Boolean-based):
# Detectar si admin tiene rol Admin
?username=admin' AND Role='Admin'--
# Extraer password caracter por caracter
?username=admin' AND SUBSTRING(Password,1,1)='a'--
?username=admin' AND SUBSTRING(Password,1,1)='b'--
...iterando hasta encontrar cada letra
Ataque (Time-based):
?username=admin' AND IF(1=1, SLEEP(5), 0)--
Si la página tarda 5 segundos, confirma vulnerabilidad!
// Parametrized Query + Rate Limiting
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutos
max: 100 // límite de requests
});
app.use('/check-user', limiter);
app.get('/check-user', async (req, res) => {
const username = req.query.username;
// Validación de entrada
if (!/^[a-zA-Z0-9_]{3,20}$/.test(username)) {
return res.status(400).send("Invalid username format");
}
try {
const [results] = await db.promise().query(
'SELECT Username FROM Users WHERE Username = ?',
[username]
);
// Respuesta constante para prevenir user enumeration
setTimeout(() => {
if (results.length > 0) {
res.send("User exists");
} else {
res.send("User not found");
}
}, 200); // Delay constante
} catch (error) {
console.error('Database error:', error);
res.status(500).send("An error occurred");
}
});// Java - Spring Boot Example
@PostMapping("/register")
public String registerUser(@RequestParam String username,
@RequestParam String email) {
// Primera query usa prepared statement (segura)
String insertQuery = "INSERT INTO Users (Username, Email) VALUES (?, ?)";
jdbcTemplate.update(insertQuery, username, email);
return "User registered successfully";
}
@GetMapping("/profile")
public String getUserProfile(@RequestParam String username) {
// Segunda query concatena (VULNERABLE!)
String query = "SELECT * FROM Users WHERE Username = '" + username + "'";
List<Map<String, Object>> result = jdbcTemplate.queryForList(query);
return result.toString();
}Ataque:
1. Registro: username = admin'-- , email = test@test.com
2. Acceso perfil: /profile?username=admin'--
Query resultante en segunda llamada:
SELECT * FROM Users WHERE Username = 'admin'--'💥 Puede bypassear autenticación o acceder a otros perfiles!
@PostMapping("/register")
public String registerUser_Secure(@RequestParam String username,
@RequestParam String email) {
// Validación estricta
if (!username.matches("^[a-zA-Z0-9_]{3,20}$")) {
return "Invalid username format";
}
if (!email.matches("^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$")) {
return "Invalid email format";
}
String insertQuery = "INSERT INTO Users (Username, Email) VALUES (?, ?)";
jdbcTemplate.update(insertQuery, username, email);
return "User registered successfully";
}
@GetMapping("/profile")
public String getUserProfile_Secure(@RequestParam String username) {
// Siempre usar prepared statements
String query = "SELECT * FROM Users WHERE Username = ?";
List<Map<String, Object>> result = jdbcTemplate.queryForList(query, username);
return result.toString();
}-- Stored Procedure vulnerable
CREATE PROCEDURE sp_SearchProducts_Vulnerable
@SearchTerm NVARCHAR(100)
AS
BEGIN
DECLARE @SQL NVARCHAR(500);
SET @SQL = 'SELECT * FROM Products WHERE ProductName LIKE ''%' + @SearchTerm + '%''';
EXEC sp_executesql @SQL;
END
GO// Llamada en C#
public List<Product> SearchProducts(string searchTerm)
{
SqlCommand cmd = new SqlCommand("sp_SearchProducts_Vulnerable", connection);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddWithValue("@SearchTerm", searchTerm);
// Ejecuta el procedimiento
SqlDataReader reader = cmd.ExecuteReader();
// ... procesar resultados
}Ataque:
searchTerm: laptop' UNION SELECT CreditCardNumber, SSN, NULL, NULL, SecretNote FROM SensitiveData--
-- Stored Procedure seguro
CREATE PROCEDURE sp_SearchProducts_Secure
@SearchTerm NVARCHAR(100)
AS
BEGIN
-- No usar SQL dinámico, usar query parametrizada directamente
SELECT * FROM Products
WHERE ProductName LIKE '%' + @SearchTerm + '%'
OR Category LIKE '%' + @SearchTerm + '%';
END
GO
-- O si necesitas SQL dinámico, usar sp_executesql con parámetros
CREATE PROCEDURE sp_SearchProducts_SecureDynamic
@SearchTerm NVARCHAR(100),
@Category NVARCHAR(50) = NULL
AS
BEGIN
DECLARE @SQL NVARCHAR(500);
SET @SQL = 'SELECT * FROM Products WHERE ProductName LIKE @Search';
IF @Category IS NOT NULL
SET @SQL = @SQL + ' AND Category = @Cat';
-- Usar sp_executesql con parámetros
EXEC sp_executesql @SQL,
N'@Search NVARCHAR(100), @Cat NVARCHAR(50)',
@Search = @SearchTerm,
@Cat = @Category;
END
GO| Tipo de Ataque | Vulnerable | Mitigado |
|---|---|---|
| Authentication Bypass | Concatenación de strings | Prepared Statements / Stored Procedures |
| Union-Based | Query sin validación | Parametrización + Validación de entrada |
| Error-Based | Errores expuestos al usuario | Manejo de errores genéricos + logging |
| Blind Injection | Respuestas reveladoras | Rate limiting + Respuestas constantes |
| Second-Order | Mix de técnicas seguras/inseguras | Validación consistente en todas las capas |
| Stored Procedure | SQL dinámico sin parámetros | sp_executesql con parámetros |
-- NUNCA:
"SELECT * FROM Users WHERE id = " + userId
-- SIEMPRE:
"SELECT * FROM Users WHERE id = @userId"// Whitelist de caracteres permitidos
if (!Regex.IsMatch(input, "^[a-zA-Z0-9_]{3,20}$"))
{
throw new ArgumentException("Invalid input");
}-- Usuario de aplicación NO debe tener permisos de:
-- DROP, ALTER, CREATE, GRANT
-- Solo: SELECT, INSERT, UPDATE, DELETE en tablas específicas# Entity Framework, Hibernate, SQLAlchemy
user = session.query(User).filter_by(username=username).first()// Como última línea de defensa (no única)
const escaped = mysql.escape(userInput);- ModSecurity
- AWS WAF
- Cloudflare WAF
# Test vulnerable
curl "http://localhost/login?user=admin'%20OR%20'1'='1&pass=x"
# Test mitigado
curl "http://localhost/login-secure?user=admin'%20OR%20'1'='1&pass=x"# Test vulnerable
curl "http://localhost/product?id=1%20UNION%20SELECT%20username,password%20FROM%20users--"
# Test mitigado
curl "http://localhost/product-secure?id=1%20UNION%20SELECT%20username,password%20FROM%20users--"- OWASP SQL Injection Guide: https://owasp.org/www-community/attacks/SQL_Injection
- OWASP Top 10 - A03:2021 Injection: https://owasp.org/Top10/A03_2021-Injection/
- SQLMap Tool: Herramienta automatizada para detectar y explotar SQL Injection
- DVWA: Damn Vulnerable Web Application (para práctica)
IMPORTANTE: Estos ejemplos son únicamente para fines educativos. Nunca uses estas técnicas en sistemas sin autorización explícita. El uso no autorizado es ilegal.
Fecha de creación: Diciembre 2025
Propósito: Demostración académica - Seguridad de Aplicaciones