Novel Arbitrary Write in SQLite
8 hours ago
- SQLite can be exploited for arbitrary code execution by abusing the sqlite_dbpage virtual table, which provides low-level read/write access to raw database pages and is often enabled by default.
- Using ATTACH DATABASE plus sqlite_dbpage, an attacker can overwrite database pages; however, the first 100-byte SQLite header (magic bytes, counters) cannot be directly controlled, preventing fully arbitrary file writes.
- The technique works by writing a shared object (.so) file, then relocating the ELF Program Header Table (e_phoff) to the end of the file using LIEF to avoid the immutable header bytes in page 1.
- A complete SQL script can be generated automatically: allocate pages, chunk the payload, pad to page size, and issue reverse-order UPDATE statements starting from the last page to avoid corrupting the database header prematurely.
- In Python, the attack targets import shadowing: a .so file takes precedence over a .py module, allowing code execution when the application restarts and re-imports the module.
- Restart can be forced by crashing the process—either via Gunicorn's worker timeout (default 30s) or by memory exhaustion using a recursive common table expression (e.g., CREATE TEMP TABLE ... SELECT x+1 FROM c) when memory limits are hit.
- The technique extends to Ruby by shadowing C extensions like nio4r_ext.so (a Puma dependency), which is loaded when a Puma worker restarts; Ruby's requirer uses .so files in the load path.
- For Ruby, a .rb file can also be abused using the __END__ marker: everything after__END__ is treated as the DATA constant, so the SQLite header can be hidden with comments and a short payload.
- Node.js is more restrictive: native addons use .node files (same ELF format) and require CommonJS, but sqlite_dbpage is rarely enabled in mainstream SQLite builds, limiting practical exploitation.
- Python's .pth files can also achieve code execution via 'import' lines in site-packages, allowing a malicious .pth file to circumvent the null-byte issue by placing SQLite header bytes after '#' comments.
- The attack requires stacked queries on the SQL injection sink, and sqlite_dbpage must be compiled in (ENABLE_DBPAGE_VTAB) and enabled at runtime; compatibility varies by OS and language runtime (e.g., python:bookworm disables it, while python:slim and Ruby enable it).