Skip to content

Repository files navigation

SQL Injection - Demostración Completa

Ejemplos Vulnerables vs Mitigados


📋 Índice

  1. Authentication Bypass
  2. Union-Based SQL Injection
  3. Error-Based SQL Injection
  4. Blind SQL Injection
  5. Second-Order SQL Injection
  6. Stored Procedure Injection

1. Authentication Bypass

🔴 Código Vulnerable

// 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!

🟢 Código Mitigado

// 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;
    }
}

2. Union-Based SQL Injection

🔴 Código Vulnerable

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

🟢 Código Mitigado

# 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

3. Error-Based SQL Injection

🔴 Código Vulnerable

// 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!

🟢 Código Mitigado

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

4. Blind SQL Injection

🔴 Código Vulnerable

// 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!

🟢 Código Mitigado

// 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");
    }
});

5. Second-Order SQL Injection

🔴 Código Vulnerable

// 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!

🟢 Código Mitigado

@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();
}

6. Stored Procedure Injection

🔴 Código Vulnerable

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

🟢 Código Mitigado

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

📊 Tabla Comparativa Resumen

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

🛡️ Principios de Mitigación

1. Prepared Statements / Parametrized Queries

-- NUNCA:
"SELECT * FROM Users WHERE id = " + userId

-- SIEMPRE:
"SELECT * FROM Users WHERE id = @userId"

2. Validación de Entrada (Whitelist)

// Whitelist de caracteres permitidos
if (!Regex.IsMatch(input, "^[a-zA-Z0-9_]{3,20}$"))
{
    throw new ArgumentException("Invalid input");
}

3. Principio de Menor Privilegio

-- Usuario de aplicación NO debe tener permisos de:
-- DROP, ALTER, CREATE, GRANT
-- Solo: SELECT, INSERT, UPDATE, DELETE en tablas específicas

4. ORM (Object-Relational Mapping)

# Entity Framework, Hibernate, SQLAlchemy
user = session.query(User).filter_by(username=username).first()

5. Sanitización y Escapado

// Como última línea de defensa (no única)
const escaped = mysql.escape(userInput);

6. Web Application Firewall (WAF)

  • ModSecurity
  • AWS WAF
  • Cloudflare WAF

🧪 Pruebas de Demostración

Caso 1: Authentication Bypass

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

Caso 2: Union Attack

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

📚 Recursos Adicionales


⚠️ Disclaimer

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

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages