ffmpeg/ffprobe argument injection via untrusted input filename in mediainfo()/mediainfo_json() (CWE-88)
Summary
pydub shells out to ffmpeg/ffprobe with an argument list (no shell, so classic shell injection is not possible). But in the media-probe helpers -- pydub.utils.mediainfo() and pydub.utils.mediainfo_json() -- the caller-supplied filepath is appended as a bare argument to the ffprobe/ffmpeg command line, with no guard against it starting with -. An attacker who can influence the input filename (an upload filename, a path built from a query parameter) can inject arbitrary ffprobe/ffmpeg options into that probe invocation. AudioSegment.from_file() calls mediainfo_json() internally, so a --prefixed input filename flows into the vulnerable probe.
Vulnerable code
pydub/utils.py, mediainfo():
command_args = [
"-v", "quiet",
"-show_format",
"-show_streams",
filepath # caller-controlled, appended as a BARE argument
]
command = [prober, '-of', 'old'] + command_args
res = Popen(command, stdout=PIPE)pydub/utils.py, mediainfo_json() (used by from_file):
...
command_args += [fsdecode(filepath)] # caller-controlled, bare argument
...
command = [prober, '-of', 'json'] + command_args
res = Popen(command, stdin=stdin_parameter, stdout=PIPE, stderr=PIPE)prober = get_prober_name() -> ffprobe (or ffmpeg fallback). Neither function checks that filepath does not begin with -, nor is it passed after an explicit input option, so a leading - is interpreted by ffprobe/ffmpeg as an option.
Proof of concept (deterministic, captured command, no ffmpeg required)
Intercepting subprocess.Popen to capture the exact command list pydub builds:
mediainfo('song.mp3') -> ['ffprobe','-of','old','-v','quiet','-show_format','-show_streams','song.mp3']
mediainfo('-show_entries')-> ['ffprobe','-of','old','-v','quiet','-show_format','-show_streams','-show_entries']
mediainfo('-v') -> ['ffprobe','-of','old','-v','quiet','-show_format','-show_streams','-v']
mediainfo_json('song.mp3') -> ['ffprobe','-of','json','-v','info','-show_format','-show_streams','song.mp3']
mediainfo_json('-show_entries')-> ['ffprobe','-of','json','-v','info','-show_format','-show_streams','-show_entries']In the 2nd/3rd lines the injected value lands where ffprobe/ffmpeg parses it as an option (e.g. -show_entries, -v), not as the input file -- changing what tokens the probe consumes and how it behaves, driven entirely by an untrusted string the caller believed only named a file.
# minimal trigger (needs ffmpeg/ffprobe on PATH):
from pydub.utils import mediainfo
mediainfo("-show_entries") # '-show_entries' parsed as an ffprobe option
from pydub import AudioSegment
AudioSegment.from_file("-af", format="mp3") # -> mediainfo_json('-af') probe injectionImpact
Argument injection (CWE-88) into the ffprobe/ffmpeg probe invocation with caller-controlled content:
- ffprobe: force a different output section/format (
-show_entries,-of,-v), or make it consume/expect the following token as an option value. - ffmpeg-fallback prober: inject an option + value pair (e.g. extra
-i/-af), changing what the process reads/processes.
Reachable from attacker-influenceable input filenames in applications passing user-supplied names into pydub.utils.mediainfo/mediainfo_json/AudioSegment.from_file. Not shell injection (argument list, no shell=True), so this is argument/option injection rather than arbitrary command execution -- Low-Medium severity.
Suggested fix
- Reject/validate a leading
-on the input path before it reaches the subprocess argv, or pass it after an explicit input option, or (where the tool supports it) terminate options with--before the path. - Centralize the "build the ffprobe/ffmpeg argv" logic and assert the input-file token is positioned after
-i/ a fixed input slot. - Note:
export(..., parameters=...)andfrom_file(..., parameters=...)splice a caller-provided list directly into argv (documented as "additional ffmpeg parameters") -- that's a by-design, higher-trust surface, distinct from this untrusted-filename path.
Disclosure note
github.com/jiaaro/pydub/security/advisories says "There aren't any published security advisories" and there's no SECURITY.md, so filing as a public issue.
This report was produced with AI assistance (Claude, Anthropic). The PoC intercepts subprocess.Popen to capture the exact argv pydub builds (deterministic, no ffmpeg required); the minimal trigger snippet requires ffmpeg/ffprobe on PATH and was not separately re-run in this pass -- the captured-argv evidence above is what was actually executed and verified.
Source: jiaaro/pydub