Security PoC β synalinks Program.load() unsafe deserialization
This repository hosts proof-of-concept malicious model files for a responsible-disclosure report submitted to huntr (Model File Vulnerability program). It is gated; access is granted only to the huntr triage bot (protectai-bot).
What this is
synalinks.Program.load() loads a .json "program" file (the framework's model file) through a Keras-serialization fork that lacks Keras' post-CVE safe-mode allowlist. Loading a crafted file gives:
Tier A β no victim opt-in (default
safe_mode=True): SSRF (server-sidehttpxGET,follow_redirects=True) and arbitrary local file read (file://), via theImage/AudioDataModelvalidator that resolves aurl/pathat construction. Files:ssrf_program.json,poc_nooptin.py,poc_nooptin_output.txt(10/10).Tier B β with
enable_unsafe_deserialization()(documented toggle for custom-Lambda models): arbitrary code execution via asynalinks.Lambda's__lambda__marshalled bytecode. Files:evil_program.json,e2e_build.py,e2e_victim.py,poc_e2e_output.txt(10/10 + negative control).Affected package:
synalinks(PyPI), version 0.9.008 (latest) and earlier.Class: insecure deserialization (CWE-502) + SSRF (CWE-918) + file read (CWE-22). Keras-fork; cf. Keras CVE-2024-3660 / CVE-2025-1550.
Payloads are BENIGN
Tier B prints SYNALINKS_MFV_RCE_PROOF user=... host=... py=... and writes a sentinel in the OS temp dir. Tier A fetches an attacker URL / reads a non-sensitive local file. No destructive action, no data exfiltration, no persistence.
Reproduce
Tier A (default safe_mode, no opt-in):
python poc_nooptin.py # SSRF (direct + via Program.load) + file read; in_safe_mode()=None
Tier B (RCE):
import synalinks, asyncio
synalinks.config.enable_unsafe_deserialization()
class In(synalinks.DataModel):
x: str
program = synalinks.Program.load("evil_program.json")
asyncio.run(program(In(x="hello"))) # attacker bytecode executes
Contact: responsible disclosure via huntr.