1
0
mirror of https://github.com/yt-dlp/yt-dlp.git synced 2026-01-22 23:02:08 +00:00

[core] Prevent RCE when using --exec with %q (CVE-2023-40581)

The shell escape function is now using `""` instead of `\"`. `utils.Popen` has been patched to properly quote commands.

Prior to this fix using `--exec` together with `%q` when on Windows could cause remote code to execute. See https://github.com/yt-dlp/yt-dlp/security/advisories/GHSA-42h4-v29r-42qg for reference.

Authored by: Grub4K
This commit is contained in:
Simon Sawicki
2023-09-24 02:29:01 +02:00
parent 61bdf15fc7
commit de015e9307
6 changed files with 46 additions and 13 deletions

View File

@@ -30,7 +30,7 @@ compat_os_name = os._name if os.name == 'java' else os.name
if compat_os_name == 'nt':
def compat_shlex_quote(s):
import re
return s if re.match(r'^[-_\w./]+$', s) else '"%s"' % s.replace('"', '\\"')
return s if re.match(r'^[-_\w./]+$', s) else s.replace('"', '""').join('""')
else:
from shlex import quote as compat_shlex_quote # noqa: F401

View File

@@ -1,8 +1,6 @@
import subprocess
from .common import PostProcessor
from ..compat import compat_shlex_quote
from ..utils import PostProcessingError, encodeArgument, variadic
from ..utils import Popen, PostProcessingError, variadic
class ExecPP(PostProcessor):
@@ -27,10 +25,10 @@ class ExecPP(PostProcessor):
def run(self, info):
for tmpl in self.exec_cmd:
cmd = self.parse_cmd(tmpl, info)
self.to_screen('Executing command: %s' % cmd)
retCode = subprocess.call(encodeArgument(cmd), shell=True)
if retCode != 0:
raise PostProcessingError('Command returned error code %d' % retCode)
self.to_screen(f'Executing command: {cmd}')
_, _, return_code = Popen.run(cmd, shell=True)
if return_code != 0:
raise PostProcessingError(f'Command returned error code {return_code}')
return [], info

View File

@@ -825,7 +825,7 @@ class Popen(subprocess.Popen):
_fix('LD_LIBRARY_PATH') # Linux
_fix('DYLD_LIBRARY_PATH') # macOS
def __init__(self, *args, env=None, text=False, **kwargs):
def __init__(self, args, *remaining, env=None, text=False, shell=False, **kwargs):
if env is None:
env = os.environ.copy()
self._fix_pyinstaller_ld_path(env)
@@ -835,7 +835,21 @@ class Popen(subprocess.Popen):
kwargs['universal_newlines'] = True # For 3.6 compatibility
kwargs.setdefault('encoding', 'utf-8')
kwargs.setdefault('errors', 'replace')
super().__init__(*args, env=env, **kwargs, startupinfo=self._startupinfo)
if shell and compat_os_name == 'nt' and kwargs.get('executable') is None:
if not isinstance(args, str):
args = ' '.join(compat_shlex_quote(a) for a in args)
shell = False
args = f'{self.__comspec()} /Q /S /D /V:OFF /C "{args}"'
super().__init__(args, *remaining, env=env, shell=shell, **kwargs, startupinfo=self._startupinfo)
def __comspec(self):
comspec = os.environ.get('ComSpec') or os.path.join(
os.environ.get('SystemRoot', ''), 'System32', 'cmd.exe')
if os.path.isabs(comspec):
return comspec
raise FileNotFoundError('shell not found: neither %ComSpec% nor %SystemRoot% is set')
def communicate_or_kill(self, *args, **kwargs):
try: