Fix uncontrolled command line in V4L2
https://github.com/motioneye-project/motioneye/blob/5158ddc8fb3d9925f11d078ae55654864a28edd0/motioneye/controls/v4l2ctl.py#L98-L98
https://github.com/motioneye-project/motioneye/blob/5158ddc8fb3d9925f11d078ae55654864a28edd0/motioneye/utils/init.py#L664-L664
Fix the issue need to ensure that user-provided input is sanitized or validated before being passed to subprocess.run. The best approach is to implement an allowlist of acceptable commands or arguments and reject any input that does not match the allowlist. Additionally, we should avoid using shell=True wherever possible, as it significantly increases the risk of command injection.
Steps to fix:
- Introduce an allowlist of valid commands or arguments in
motioneye/controls/v4l2ctl.pyfor thelist_resolutionsfunction. - Validate the
deviceparameter against this allowlist before constructing thecmdstring. - Modify
call_subprocessinmotioneye/utils/__init__.pyto reject unsafe inputs or enforce stricter validation. - Ensure that all paths leading to
call_subprocesssanitize or validate user input.
Code that passes user input directly to exec, eval, or some other library routine that executes a command, allows the user to execute malicious code. The following shows two functions. The first is unsafe as it takes a shell script that can be changed by a user, and passes it straight to subprocess.call() without examining it first. The second is safe as it selects the command from a predefined allowlist.
urlpatterns = [
# Route to command_execution
url(r'^command-ex1$', command_execution_unsafe, name='command-execution-unsafe'),
url(r'^command-ex2$', command_execution_safe, name='command-execution-safe')
]
COMMANDS = {
"list" :"ls",
"stat" : "stat"
}
def command_execution_unsafe(request):
if request.method == 'POST':
action = request.POST.get('action', '')
#BAD -- No sanitizing of input
subprocess.call(["application", action])
def command_execution_safe(request):
if request.method == 'POST':
action = request.POST.get('action', '')
#GOOD -- Use an allowlist
subprocess.call(["application", COMMANDS[action]])
IMO there is no point to sanitize call_subprocess. It really is just a wrapper for subprocess.run, changing some defaults. Any harmful code could just call subprocess.run directly, bypassing the sanity check.
But it generally makes sense to sanitize any kind of user input, including output of external commands. I am not 100% sure, but AFAIK v4l2-ctl --list-devices is supposed to list /dev/video[0-9]+ device paths only, so we could do a sanity check with regex, instead of a hardcoded allow list. Regarding security, at least, thanks to shlex.quote(), no matter what the device name is, any unintended expansion or code execution is ruled out, and the whole input is assured to be taken literally as device name argument for the command. Worst that can happen is that v4l2-ctl complains about the input not being a valid video device. Quite possible that this error is even better than what we could achieve with a sanity check.
I'll turn this into a draft, since with hardcoded example allow lists, it is not ready to be tested, but a discussion basis.
Regarding shell=True: yes, we should ban it entirely. Where used, we should convert any logic done in the shell into logic done in Python. We are happily accepting any PR, whether it replaces all or just a single shell=True.