143 findings from static reverse engineering of Axis ACAP applications across ARTPEC-5/6/7/8 and CV25 platforms
Overview • Tooling • Method • Findings • Attack Classes • Coverage • Modules • Query • Report
AXIS Camera Application Platform (ACAP) is the SDK and runtime environment for third-party applications installed on Axis IP cameras and video encoders. ACAPs ship as .eap packages: gzip'd tar archives containing ELF binaries, Lua scripts, manifests, and CGI configuration. They run inside an application sandbox on the camera's embedded Linux OS, with access to camera firmware services via D-Bus, axparameter, libaxhttp, and libaxevent APIs.
Platforms covered: ARTPEC-5 (MIPS32/Lua), ARTPEC-6/7 (ARM32 armhf/armv7hf), ARTPEC-8 (aarch64), CV25 (Ambarella aarch64). Most analytics ACAPs ship multi-arch EAP packages covering all four.
Binary analysis was done with Ablation - a custom reverse engineering tool built around semantic BERT embeddings. Ablation reads stripped ELF binaries with no symbols, no source, and no debug info. It encodes what each function does using BinFuse opcode normalization (11 architecture-agnostic categories: DATA_TRANSFER_OP, ARITH_OP, BRANCH_OP, etc.) combined with Markov transitions, then runs cosine similarity queries against a vulnerability pattern corpus using sentence-transformers/all-MiniLM-L6-v2 to surface candidates. Cross-version and cross-arch homolog tracking uses BERT-encoded seed functions swept against multiple binary corpora - which matters here because most ACAP binaries are fully stripped and ship no debug information.
The RE modules in this repo are Ablation's output format: structured Python dicts with full technical detail, binary evidence, and prerequisite conditions attached to each finding.
Static analysis only. No live camera access.
- Obtain
.eappackages from Axis public FTP mirror (ftp.axis.com/pub/axis/software/ACAP/applications/) and the Axis SDK FTP archive. - Extract package contents:
tar xzf <app>.eapyields the ELF binary, Lua scripts,manifest.json,param.conf,cgi.conf,package.conf, and shell scripts. - Run Ablation semantic sweep against stripped ELF binaries. Query profiles targeting:
popen/format string injection,tarpath traversal, uncheckeddlopen, hardcoded credential strings,dlsym+LD_PRELOADdetection, unauthenticated socket listeners. - Cross-reference CGI access levels from
cgi.confandmanifest.jsonagainst HTTP handler code. - Extract shell scripts (
post_install.sh,param.conf,privacy.sh) for plaintext credential and injection surface analysis. - Examine
manifest.jsonD-BusrequiredMethodsfor wildcard permission grants. - Confirm each candidate: locate the responsible code path, identify preconditions, determine blast radius (APPUSR=root vs sdk sandbox).
The finding modules are Ablation's structured output - each finding records the binary evidence (string constant, dlopen path, CGI handler name, D-Bus method) alongside the exploitation prerequisite and severity.
| ID | Application | Title |
|---|---|---|
| AXIS-SBP-01 | sbplayer | UpdateURL checksum-optional library load: server omits checksum header → dlopen attacker-supplied .so |
| AXIS-PC-01 | People Counter 4.6.110 | Shell injection via curl format strings in popen (runs as sdk) |
| AXIS-PC-07 | People Counter 4.6.110 | TrueviewVAPIX legacy account: default credential VAPIX admin access |
| AXIS-PC40-01 | People Counter 4.0.0 | TrueviewVAPIX account not removed in postinst - persists on all 4.0.0 installs |
| AXIS-BR-01 | Barcode Reader | Physical access bypass: NBIX IndicateRemoteActivities accepts any barcode token with no server-side crypto validation |
| AXIS-OE-01 | Occupancy Estimator | Tarslip via .restore_backup as root (APPUSR=root) |
| AXIS-COG-01 | Cognimatics tvpc base | Tarslip as root - all tvpc variants (People Counter, OE, DD, QM) |
| AXIS-DD-03 | Direction Detector | Dual .restore_backup tarslip entry points, both as root |
| AXIS-DI-01 | Demographic Indicator | Tarslip as root |
| AXIS-SDM-01 | StoreDataManager | Hardcoded backdoor: support@cognimatics.com:pass123 (admin-equivalent, every installation) |
| AXIS-SDM-02 | StoreDataManager | Static salt qvUReYOddaOWw7qkb6QfHAd3kgUAo6 - all password hashes pre-crackable |
| ID | Application | Title |
|---|---|---|
| AXIS-SMD-01 | Sensor Metrics Dashboard | Modbus TCP SSRF: camera pivots to internal OT/ICS network; chains with CVE-2019-14463 |
| AXIS-MDP-01 | Metadata Provider | MQTT bridge SSRF + /etc/hosts injection via echo $1 $2 >> /etc/hosts |
| AXIS-LSS-07/08 | Body Worn LSS 2.0.x | GraphQL standalone config mutations at operator; CA path injection via ENV var |
| AXIS-UCS-03 | UCS SIP | liblicensekey.so stub bypass via dlopen RUNPATH (LD_PRELOAD blocked; RUNPATH is not) |
| AXIS-IC-01 | InformaCast | ServerAddress SSRF: audio alert stream routed to attacker-controlled InformaCast server |
| AXIS-PC5-01 | People Counter 5.x Rust | popen shell injection via allparams generation survives Rust rewrite |
| AXIS-QM3-01 | Queue Monitor 3.x Rust | WebReportUpload SSRF: full URL from operator axparam with TLS bypass flag |
| AXIS-LPS-03 | Live Privacy Shield | Privacy masking DoS: process exit disables all video masking (GDPR implication) |
| AXIS-LSS-11 | Body Worn LSS | LD_LIBRARY_PATH injection → coturn/lib code execution |
Multiple ACAP backup/restore endpoints invoke tar xzf without sanitizing archive entry paths. Malicious .tar.gz with ../../ traversal entries writes to arbitrary camera filesystem paths.
Seven instances run as APPUSR=root (Cognimatics tvpc family): full camera filesystem write with no sandbox boundary - /etc/init.d/, /usr/sbin/, cron entries.
Affected: people_counter, occupancy_estimator, direction_detector, queue_monitor, demographic, lpv_remaining, people_counter_400, cognimatics_tvpc, queue_monitor_v3, people_counter_v5.
- TCP 23456: passage event injection port present in all tvpc variants including the Rust 5.x rewrite. Any LAN host sends synthetic count events without credentials.
- TCP 4066: master/slave synchronization with no TLS. SlavePass in cleartext (2.x) or libsodium-encrypted with unknown key derivation (5.x/3.x).
popen() is used for backup generation across both C and Rust codebases. Operator-writable axparameter values are interpolated into shell commands without quoting. The Rust 5.x rewrite of People Counter and Queue Monitor preserved popen() in the parambackup path.
Camera-to-internal-network pivoting via configurable connection targets:
- Modbus TCP (sensor_metrics): camera connects as Modbus client to admin-configured IP:502. Chains with CVE-2019-14463 (libmodbus 3.1.11 heap corruption) if attacker controls the Modbus server response.
- MQTT bridge (metadata_provider):
addhost.shwrites unsanitized args to/etc/hosts; bridge connects to attacker-supplied MQTT broker URL. - WebReportUpload (queue_monitor, queue_monitor_v3): curl with
AllowInsecureflag and operator-controlled URL. - SocketCameraContainer (loitering_guard, fence_guard, motion_guard): socket connections to admin-configured remote camera hosts.
- ServerAddress (informacast): audio alerts and metadata to attacker-controlled Singlewire InformaCast server on port 8081.
Axis license verification loads liblicensekey.so via dlopen. Three bypass paths:
- LD_PRELOAD stub - viable on InformaCast (no detection code). Blocked by explicit
test_ld_preload/dir_contains_overriding_libchecks in UCS/SipThirdPartyIntegration. - dlopen RUNPATH - place stub at
/usr/local/packages/<app>/liblicensekey.so; not blocked by the LD_PRELOAD detection. - SBP-01 variant - server omits checksum header → binary logs "checksum missing, ignoring download" and calls
dlopen()on the downloaded library regardless.
UCS TOCTOU: anti-tamper checks run only at STARTMODE=once install time. Library placed after install check completes evades all detection.
axptzoversnmp (appId 47267) accepts SNMP SET with community string public for NTCIP 1205 PTZ operations (OIDs: pan, tilt, zoom, preset load/store). No authentication required. Writable OIDs include absolute PTZ positioning and preset poisoning.
| Credential | Location | Impact |
|---|---|---|
support@cognimatics.com:pass123 |
StoreDataManager postinst | Admin backdoor on every installation |
ftp://root:pass@192.168.0.90:21 |
Occupancy Estimator binary | Dev FTP credential in production EAP |
root:pass |
Cognimatics tvpc debugar.cgi.org |
Dev endpoint credential in production binary |
admin:1password23 |
3D People Counter Python pyc | Developer credential in pyc string constants |
STATIC_SALT = "qvUReYOddaOWw7qkb6QfHAd3kgUAo6" hardcoded with comment "DON'T CHANGE". Login SQL: MD5(CONCAT(STATIC_SALT, ?, password_salt)). Known salt enables precomputed rainbow tables against all installed user password hashes. Migration path (MigrateCompany.php:423) produces MD5(STATIC_SALT + "" + dynamicSalt) = empty password for migrated users.
Facedetector 2.x manifests declare com.axis.Param.* (wildcard) as required D-Bus methods - grants read/write access to any camera parameter including stored credentials and network config. CV25 variant additionally declares com.axis.VideoObjectDetection1.* wildcard, enabling suppression or spoofing of object detections in co-resident analytics ACAPs (Object Analytics, Loitering Guard, Fence Guard).
Live Privacy Shield (appId 346005): single point of failure. "Failed to start PrivacyShield service. Exiting" - crash disables all video privacy masking until restart. On streams configured for privacy protection of natural persons: unmasked exposure is a reportable GDPR incident.
- Demographic Indicator: estimated age range and gender (GDPR Art. 9 special category biometric data) accessible at viewer level without operator/admin auth.
- Occupancy Estimator: live counting API at viewer level.
- Radar Data Visualizer:
/consume.cgiviewer-accessible, exposes real-time radar target positions, velocities, zone assignments.
| Severity | Count |
|---|---|
| CRITICAL | 11 |
| HIGH | 44 |
| MEDIUM | 62 |
| LOW | 25 |
| INFO | 1 |
| Total | 143 |
| Module | Application | appId | Arch |
|---|---|---|---|
lpv/axis_lpv_sqli.py |
AXIS License Plate Verifier (fflprapp) | 333330 | aarch64 |
lpv/axis_lpv_shell_inject.py |
AXIS License Plate Verifier (fflprapp) | 333330 | aarch64 |
lpv/axis_lpv_remaining.py |
AXIS License Plate Verifier - LPV-03 to LPV-07 | 333330 | aarch64 |
media/axis_sbplayer.py |
AXIS Player for Soundtrack Business | 413325 | aarch64 + ARM32 |
media/axis_audio_spectrum_viz.py |
AXIS Audio Spectrum Visualizer 2.3.0 | 413132 | aarch64 |
tvpc/axis_people_counter.py |
AXIS People Counter 4.6.110 | 211490 | aarch64 |
tvpc/axis_people_counter_400.py |
AXIS People Counter 4.0.0 / S5L | 211490 | aarch64 |
tvpc/axis_people_counter_v5.py |
AXIS People Counter 5.0.5 (Rust) | 211490 | aarch64 |
tvpc/axis_occupancy_estimator.py |
AXIS Occupancy Estimator | 413742 | ARM32 |
tvpc/axis_direction_detector.py |
AXIS Direction Detector 3.16.3 | 220302 | armv7hf |
tvpc/axis_cognimatics_tvpc.py |
Cognimatics tvpc base (shared binary) | multiple | ARM32 |
tvpc/axis_queue_monitor.py |
AXIS Queue Monitor 2.x (C) | 211492 | armv7hf |
tvpc/axis_queue_monitor_v3.py |
AXIS Queue Monitor 3.0.20 (Rust) | 211492 | aarch64 |
tvpc/axis_demographic.py |
AXIS Demographic Indicator (tvgd) | - | ARM32 |
radar/axis_ptz_snmp.py |
AXIS PTZ over SNMP | 47267 | MIPS32 |
radar/axis_ptz_remote.py |
AXIS p-ptz Remote Connection | 413658 | aarch64 |
radar/axis_radar_microbus.py |
AXIS Radar Integration for Microbus | 414271 | ARM32 |
radar/axis_radar_visualizer.py |
AXIS Radar Data Visualizer 3.3.2 | 414283 | aarch64 |
radar/axis_speed_monitor.py |
AXIS Speed Monitor 1.1.7 | 413872 | ARM32 |
access-control/axis_barcode_reader.py |
AXIS Barcode Reader | 413766 | ARM32 + aarch64 |
access-control/axis_door_controller.py |
AXIS Door Controller Extension | 414114 | ARM32 |
communication/axis_bodyworn_lss.py |
AXIS Body Worn Live Self-hosted Server | 414710 | aarch64 |
communication/axis_informacast.py |
AXIS Speaker for Singlewire InformaCast | 414050 | ARM32 |
communication/axis_ucs_sip.py |
AXIS Client for Unified Communication Systems | 414930 | aarch64 + ARM32 |
analytics/axis_facedetector.py |
AXIS Face Detector | 412581 | aarch64 + ARM32 |
analytics/axis_digital_autotrack.py |
AXIS Digital Auto Tracking | 6789 | Lua/ARTPEC-5 |
analytics/axis_sensor_metrics.py |
AXIS Sensor Metrics Dashboard | 413965 | aarch64 |
analytics/axis_metadata_provider.py |
AXIS Metadata Provider | 413493 | ARM32 |
analytics/axis_3dpc.py |
AXIS 3D People Counter | 211491 | MIPS32 |
infrastructure/axis_storedatamanager.py |
AXIS StoreDataManager (server Debian pkg) | - | PHP5/MySQL |
video-analytics/axis_vmd.py |
AXIS Video Motion Detection 4.4.4 | 143440 | ARM32 |
video-analytics/axis_vmd_458.py |
AXIS Video Motion Detection 4.5.8 | 143440 | aarch64 |
video-analytics/axis_vmd3_lua.py |
AXIS Video Motion Detection 3 (Lua) | 46396 | Lua/ARTPEC-5 |
video-analytics/axis_loitering_guard.py |
AXIS Loitering Guard 2.3.8 | 46775 | ARM32 |
video-analytics/axis_fence_guard.py |
AXIS Fence Guard 2.3.8 | 47775 | aarch64 |
video-analytics/axis_motion_guard.py |
AXIS Motion Guard 2.3.8 | 48170 | aarch64 |
video-analytics/axis_cross_line.py |
AXIS Cross Line Detection 1.1.5 (Lua) | 3051 | Lua/ARTPEC-5 |
video-analytics/axis_live_privacy_shield.py |
AXIS Live Privacy Shield 2.8.8 | 346005 | ARM32 |
Each module exports a FINDINGS list. Common fields:
FINDING = "AXIS-XX"
LABEL = "short description"
FINDINGS = [
{
"id": "AXIS-XX-01",
"severity": "CRITICAL", # CRITICAL / HIGH / MEDIUM / LOW / INFO
"title": "...",
"detail": "Full technical detail with binary evidence ...",
"prerequisite": "Admin/Operator/Viewer/network access ...",
"status": "UNPATCHED",
"cve": None, # CVE string where applicable
},
]Some modules also export supporting data structures: WRITABLE_OIDS (ptz_snmp), MOSQUITTO_CONFIG (metadata_provider), LIBMODBUS_CVES (sensor_metrics), NBIX_DBUS_CALLS (barcode_reader), HARDCODED_ARTIFACTS (3dpc), FLASK_ROUTES (3dpc), LD_PRELOAD_DETECTION_FUNCS (ucs_sip).
import glob
critical = []
for path in glob.glob('**/*.py', recursive=True):
ns = {}
exec(open(path).read(), ns)
for f in ns.get('FINDINGS', []):
if f.get('severity') == 'CRITICAL':
critical.append((path, f['id'], f['title']))
for path, fid, title in critical:
print(f"[{fid}] {title} ({path})")Findings have been or are in the process of being disclosed to Axis Communications through their responsible disclosure program (security@axis.com).
Authorization: Analysis performed on EAP packages obtained from Axis public software distribution (ftp.axis.com/pub/axis/software/). No production systems were accessed. Static analysis only.