Security Research Notebook
A structured record of independent security research: the how matters as much as the what.
A structured record of independent security research: the how matters as much as the what.
This notebook collects my vulnerability research: writeups that went through coordinated disclosure to the affected vendor before appearing here. Everything is organized by target class: web application and cloud platform, Aiven managed services (PostgreSQL, Valkey, Dragonfly, Kafka), blockchain consensus nodes, embedded camera firmware (AXIS OS).
The organizing principle is methodology-first. The writeup leads with how the bug class was identified (the recon pass, the code path, the wrong assumption in the codebase), not just the end result. The 'how' generalizes to future targets; the specific instance usually doesn't.
Managed database services (Aiven). The most technically deep cluster. PostgreSQL's privilege boundary is complex when a managed provider adds its own gatekeeper extension and SECURITY DEFINER chains on top. The interesting cases are where those layers interact unexpectedly. Notable entries: an autovacuum-triggered code execution path (autovacuum evaluates expression index functions under session_user=postgres, which reaches a SECDEF dblink chain not subject to SECURITY_RESTRICTED_OPERATION); a Valkey RESTORE path that plants a corrupt listpack key surviving RDB persistence and producing an infinite crash loop on restart; a Kafka Karapace gzip-bomb that decompresses without bounds.
Embedded firmware (AXIS OS). Five findings covering SSRF via test endpoints that bypass the camera's own validateaddr helper, an IPv6-mapped IPv4 loopback filter bypass on httptest.cgi, and unauthenticated RTSP-over-WebSocket access via ONVIF.
Blockchain consensus (Electroneum). Validation of CVE-2024-32972 against an unpatched etn-sc node: a single unauthenticated TCP packet with Amount=0 causes an integer underflow in serviceContiguousBlockHeaderQuery, triggering a 7.8 GB allocation that OOM-kills the node. Targeting all IBFT validators halts the chain.
def crc64(data):
"""Exact reimplementation of Valkey's _crc64() from src/crc64.c.
Polynomial: 0xad93d23594c935a9 (Jones)
Initial value: 0
Algorithm: bit-by-bit MSB-first with final reflect
Verified against live Valkey DUMP output: matches byte-for-byte.
"""
POLY = 0xad93d23594c935a9
crc = 0
for byte in data:
c = byte
i = 0x01
while i & 0xFF:
bit = 1 if (crc & 0x8000000000000000) else 0
if c & i:
bit = 0 if bit else 1
crc = (crc << 1) & 0xFFFFFFFFFFFFFFFF
if bit:
crc ^= POLY
i <<= 1
return _crc_reflect64(crc)
def make_corrupt_listpack(entry_count=200):
"""Build a listpack whose 'total bytes' header lies about its real size.
The validation check passes (declared size fits); the reader walks off the end.
"""
entries = [struct.pack('<BB', 0x81, 0x80) for _ in range(entry_count)]
payload = b''.join(entries)
# Declare a total-bytes header smaller than the real payload
false_total = len(payload) // 2
return struct.pack('<I', false_total) + struct.pack('<H', entry_count) + payload
The methodology entry is a root-cause walk-through of CVE-2025-47934 in OpenPGP.js: a signature-verification bypass caused by msg.packets mutation during streaming verification, and its relationship to the v6.2.0 compression refactor. It analyses a public CVE and is not presented as a new discovery.
Disclosure policy: every report in this notebook was disclosed to the affected vendor or program before it was published here. Proof-of-concept material is sanitized: hosts, credentials and identifiers are replaced with placeholders. No customer data was accessed or retained during any of this research.
Each report below was disclosed to the affected vendor before it appeared here. Click any title for the full writeup: summary, impact, root cause, a sanitized proof of concept, and the fix.