DIZMANA
← Tüm yazılar
Thick Client Security

CVE-2026-83681: Mouse Without Borders SettingsSync

PowerToys Mouse Without Borders 0.100.2’de, farklı bir Windows hesabından SettingsSync pipe’ına bağlanarak eşleştirme anahtarını nasıl değiştirebildiğimi anlattım.

YAYIMLANDI
OKUMA SÜRESİ
8 dakika
DİL
Türkçe
Okumaya başla ↓

PowerToys’un Mouse Without Borders bileşenini incelerken, başka bir Windows kullanıcısının eşleştirme anahtarını değiştirebildiğimi gördüm. Bunun için ayarlar uygulamasının kullandığı SettingsSync named pipe’ına bağlanıp ConnectToMachine metodunu çağırmam yetiyordu. Gönderdiğim anahtar ve bilgisayar adı, diğer kullanıcının MWB ayarlarında görünüyordu.

Daha önce Thick Client Pipeline Güvenliği yazısında named pipe izinlerini ve istemci kontrollerini anlatmıştım. Bu yazıda da o konunun PowerToys’taki örneğine bakacağız. Pipe’a nasıl bağlandığımı, gönderdiğim isteği ve hedefte neyin değiştiğini kod üzerinden anlatacağım.

Açığın CVE numarası CVE-2026-83681. Buradaki kodlar PowerToys 0.100.2 sürümünden, ekran görüntüleri de o sürümle yaptığım testten.

Mouse Without Borders nedir?

PowerToys, Windows için geliştirilmiş açık kaynaklı bir araç paketi. İçindeki Mouse Without Borders (MWB), aynı klavye ve fareyle en fazla dört bilgisayarı kullanmanızı sağlıyor. Bilgisayarlar arasında fareyi geçirebiliyor, kopyaladığınız içeriği paylaşabiliyor ve dosya aktarabiliyorsunuz.

Bilgisayarları eşleştirirken önce birinde güvenlik anahtarı oluşturuyorsunuz. Sonra diğer bilgisayara bu anahtarı ve ilk bilgisayarın adını giriyorsunuz. Yani birazdan değiştireceğimiz anahtar, bilgisayarların birbirine bağlanırken kullandığı anahtar. Kurulum adımları Microsoft’un Mouse Without Borders belgesinde var.

MWB’de iki ayrı iletişim var:

  • Eşleşen bilgisayarlar, klavye, fare ve pano verilerini ağ üzerinden paylaşıyor.
  • PowerToys Ayarları, aynı bilgisayardaki MWB sürecine eşleştirme komutlarını bir IPC kanalı üzerinden gönderiyor.

Ben ikinci yoldan ilerledim. Aynı bilgisayarda çalışan MWB sürecine, eşleştirme anahtarını değiştiren bir istek gönderdim. Eski anahtarı bilmem gerekmiyordu.

SettingsSync nasıl çalışıyor?

0.100.2 sürümünde ayarlar arayüzü ve MWB ayrı süreçlerde çalışıyor. Arayüzdeki MouseWithoutBordersViewModel sınıfı, eşleştirme isteğini SettingsSync kanalından MWB’ye gönderiyor. Ayarları değiştiren taraf MWB süreci.

Kaynak koddaki kanal adı:

1
MouseWithoutBorders/SettingsSync

Test çıktısında görünen Win32 pipe yolu:

1
\\.\pipe\MouseWithoutBorders\SettingsSync

Kodda /, test çıktısında \ kullanıldığını görebilirsiniz. İkisi de aynı pipe’ı gösteriyor.

Ayarlar uygulamasının ve farklı kullanıcıyla çalışan PoC’nin SettingsSync bağlantıları Şekil 1: Ayarlar uygulaması ve farklı kullanıcıyla çalıştırdığım PoC aynı pipe’a bağlanabiliyor.

Arayüz tarafındaki çağrı zinciri şöyle:

1
2
3
4
5
MouseWithoutBordersViewModel.SubmitConnectionRequestAsync
  → GetSettingsSyncHelperAsync
  → NamedPipeClientStream.ConnectAsync
  → JsonRpc.Attach<ISettingsSyncHelper>
  → Endpoint.ConnectToMachine(pcName, securityKey)

Sunucuda Program.StartSettingSyncThread, IpcChannel<SettingsSyncHelper> üzerinden pipe’ı oluşturuyor. Gelen JSON-RPC isteğinin method alanı, SettingsSyncHelper içindeki hangi metodun çağrılacağını belirliyor. PoC’de kullandığım ConnectToMachine da uygulamanın kendi eşleştirme metodu.

İstemci kodu MouseWithoutBordersViewModel.cs, sunucunun başlatıldığı bölüm de Program.cs içinde.

Pipe’a kimler bağlanabiliyor?

IpcChannel<T>.StartIpcServer içinde pipe’ın erişim izinlerini belirleyen bir DACL oluşturuluyor:

1
2
3
4
5
6
7
SecurityIdentifier securityIdentifier = new SecurityIdentifier(
    WellKnownSidType.AuthenticatedUserSid, null);

pipeSecurity.AddAccessRule(new PipeAccessRule(
    securityIdentifier,
    PipeAccessRights.ReadWrite | PipeAccessRights.CreateNewInstance,
    AccessControlType.Allow));

Kod, Authenticated Users grubuna izin veriyor. Grubun SID’i S-1-5-11. Bu izin başka Windows kullanıcılarının da pipe’a bağlanabilmesini sağlıyor. Oysa MWB ayarları belirli bir kullanıcıya ait.

Windows, bağlanmak isteyen iş parçacığının token’ını pipe’ın izinleriyle karşılaştırıyor. Buradaki kural erişime izin verdiği için bağlantı kuruluyor. Bağlanan kişinin bu kullanıcının ayarlarını değiştirmeye yetkili olup olmadığını ise uygulamanın kontrol etmesi gerekiyor.

Burada NULL DACL kullanılmıyor. Sorun, açıkça eklenen iznin fazla geniş olması. İlgili kod IClipboardHelper.cs dosyasında. Windows’un pipe izinlerini nasıl kontrol ettiğini de Named Pipe Security and Access Rights belgesinden okuyabilirsiniz.

Pipe’ın yerel olması, yalnızca onu oluşturan kullanıcının bağlanabileceği anlamına gelmiyor. Bunu erişim izinleriyle sınırlamak gerekiyor. Önceki pipeline yazısındaki örnekte Everyone / FullControl ve role=admin kontrolü vardı. Burada izin Authenticated Users grubuna verilmiş. PoC’nin bir rol bilgisi göndermesi de gerekmiyor, doğrudan ConnectToMachine çağrısını yapabiliyor.

Bağlantıdan sonra istemci kontrol ediliyor mu?

Sunucu, bağlantıyı kabul ettikten sonra şu kodu çalıştırıyor:

1
2
3
await serverChannel.WaitForConnectionAsync();
var taskRpc = JsonRpc.Attach(serverChannel, new T());
await taskRpc.Completion;

WaitForConnectionAsync tamamlanır tamamlanmaz JsonRpc.Attach çağrılıyor. Arada bağlanan sürecin PowerToys Ayarları olup olmadığını kontrol eden bir adım yok. Kullanıcı SID’i, Windows oturumu ve süreç kimliği de kontrol edilmiyor.

JsonRpc.Attach, gelen istekleri sunucudaki metotlara bağlıyor. İstemci mesajı doğru biçimde gönderdiğinde ve parametreler uyduğunda metot çağrılabiliyor. Bu işlem sırasında istemcinin yetkisini ayrıca kontrol eden bir kod gerekiyor.

Kodda SettingsSyncHelper sınıfının private olması da pipe’tan gelen isteği engellemiyor. Sunucu bu sınıftan bir nesne oluşturup RPC’ye bağlıyor. İstemcinin sınıfa doğrudan erişmesi gerekmiyor, metodun adını JSON içinde göndermesi yeterli.

ConnectToMachine çağrısı neyi değiştiriyor?

Metot, 0.100.2 sürümündeki Program.cs dosyasında. Aşağıda ayarları değiştiren satırları bıraktım:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Setting.Values.MyKey = securityKey;
Encryption.MyKey = securityKey;
Encryption.MagicNumber = Encryption.Get24BitHash(Encryption.MyKey);

MachineStuff.MachineMatrix = new string[MachineStuff.MAX_MACHINE]
{
    pcName.Trim().ToUpper(CultureInfo.CurrentCulture),
    Common.MachineName.Trim(),
    string.Empty,
    string.Empty
};

Common.ReopenSockets(true);
Setting.Values.SaveSettings();

Gönderdiğimiz securityKey ve pcName değerleri burada birkaç yerde kullanılıyor:

Değer veya çağrı Ne yapıyor?
securityKey Ayarlardaki Setting.Values.MyKey ve çalışan sürecin kullandığı Encryption.MyKey değerlerini değiştiriyor
Get24BitHash Yeni anahtara göre MagicNumber değerini yeniden hesaplıyor
pcName Bilgisayar adını yeni makine matrisinin ilk sırasına yazıyor
MachinePool.Initialize Makine havuzunu yeni matrisle yeniden kuruyor
ReopenSockets(true) Soketlerin yeniden açılmasını istiyor
SaveSettings() Ayarları kaydetme işlemini başlatıyor

Setting.Values.MyKey değerine atama yapıldığında setter, _properties.SecurityKey.Value alanını değiştiriyor. SaveSettings de uygun çalışma bağlamında ayarların JSON dosyasına kaydedilmesini başlatıyor. Bunlar hedef MWB sürecinde oluyor. İlgili kod Setting.cs içinde.

Metodun başındaki PauseInstantSaving = true, her ayar değişikliğinde ayrı ayrı kayıt yapılmasını durduruyor. Değişiklikler bitince bu bayrak kapatılıp ayarlar kaydediliyor.

Metot eski anahtarı sormuyor veya onu bildiğimizi doğrulayan bir challenge-response yapmıyor. İstekte gönderdiğimiz yeni anahtarı doğrudan kabul ediyor. Eski anahtarı bilmeden değiştirebilmemizin sebebi bu.

PoC isteği nasıl gönderiyor?

PoC, SettingsSync pipe’ına bağlanıp JSON-RPC mesajını kendisi gönderiyor. İstek gövdesi şöyle:

1
2
3
4
5
6
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "ConnectToMachine",
  "params": ["ATTACKERPC", "TEST_CHANGED_KEY"]
}

params içinde önce bilgisayar adı, sonra anahtar var. Sıra, metodun beklediği parametrelerle aynı. ATTACKERPC ve TEST_CHANGED_KEY, testte değişikliği kolayca görebilmek için kullandığım değerler.

Pipe üzerinden baytlar gönderiyoruz. Karşı tarafın JSON mesajının nerede bittiğini anlayabilmesi için başına bir uzunluk bilgisi ekliyoruz:

1
2
3
Content-Length: <UTF-8 gövdesinin bayt uzunluğu>\r\n
\r\n
<JSON-RPC gövdesi>

Mesaj biçimi yukarıdaki gibi. Content-Length hesabında UTF-8’e çevrilmiş gövdenin bayt uzunluğu kullanılıyor. Karakter sayısını almak burada hataya yol açabilir. PoC’deki PowerShell kodu:

1
2
3
4
5
6
7
$body = [System.Text.Encoding]::UTF8.GetBytes($json)
$hdr = [System.Text.Encoding]::ASCII.GetBytes(
    "Content-Length: $($body.Length)`r`n`r`n")

$stream.Write($hdr, 0, $hdr.Length)
$stream.Write($body, 0, $body.Length)
$stream.Flush()

Yanıtı okurken de önce \r\n\r\n satır sonlarına kadar başlık okunuyor. Ardından Content-Length kadar bayt alınıp JSON yanıtı elde ediliyor. Tek bir Read çağrısı mesajın tamamını döndürmeyebilir, okuma işlemini buna göre yapmak gerekiyor.

Bu biçim, PowerToys’un kullandığı StreamJsonRpc ile konuşmak için yeterli. PoC, uygulamanın zaten sunduğu metoda bilgisayar adını ve anahtarı gönderiyor.

Test

Testte MWB, erenc kullanıcısıyla çalışıyordu. PoC’yi çalıştıran PowerShell’i ise MSI\zeroday hesabıyla açmıştım. Test notlarında bu hesap standart kullanıcı olarak geçiyor. Ekrandaki whoami /priv, hesabın token ayrıcalıklarını gösteriyor. Grup üyeliklerini bu çıktıdan göremiyoruz.

Bileşen Testte kullanılan değer
İncelenen ürün PowerToys 0.100.2, Mouse Without Borders
Hedef kullanıcı erenc
PoC’yi çalıştıran kimlik MSI\zeroday
IPC hedefi MouseWithoutBorders/SettingsSync
Çağrılan metot ConnectToMachine
Test bilgisayar adı ATTACKERPC
Test anahtarı TEST_CHANGED_KEY

“Hizmet Kullan” seçeneği kapalıydı. MWB bu testte SYSTEM servisi olarak çalışmıyordu.

1. Bağlantı ve durum sorgusu

PoC önce NamedPipeClientStream oluşturup pipe’a bağlanıyor. Ardından RequestMachineSocketStateAsync çağrısını gönderiyor. Testte bu çağrı boş bir dizi döndürdü. Döndürecek makine soket durumu olmayabilir. Burada kontrol ettiğimiz şey, bağlantının kurulması ve JSON-RPC isteğine yanıt alabilmek.

2. Aynı bağlantıda eşleştirme çağrısı

Testte kullandığım komut:

1
.\poc_crossuser_hijack.ps1 ATTACKERPC TEST_CHANGED_KEY

Betik, çalıştığı kullanıcı adını ekrana yazdıktan sonra aynı pipe üzerinden ConnectToMachine çağrısını gönderiyor. Terminalde bağlantıyı, parametreleri ve yanıtı görebilirsiniz:

MSI zeroday kimliğiyle SettingsSync bağlantısı ve ConnectToMachine çağrısının yanıtı Şekil 2: MSI\zeroday hesabıyla pipe’a bağlantı ve ConnectToMachine yanıtı.

Gelen yanıt:

1
{"jsonrpc":"2.0","id":1,"result":null}

ConnectToMachine bir void metot, bu yüzden yanıtta result: null var. Çağrı RPC hatasıyla dönmemiş. Ayarların gerçekten değişip değişmediğini görmek için hedefteki MWB arayüzüne baktım.

3. Hedef kullanıcının ayarındaki değişiklik

MWB’nin güvenlik anahtarı alanında TEST_CHANGED_KEY, cihaz düzeninde de ATTACKERPC görünüyordu:

Mouse Without Borders ayarlarında test anahtarı ve ATTACKERPC cihaz kaydı Şekil 3: Gönderdiğim anahtar ve bilgisayar adı, hedef kullanıcının MWB ayarlarında. Sarı cihaz kaydı, uzak bilgisayara bağlantı kurulduğunu göstermiyor.

İsteği MSI\zeroday hesabından gönderdim, değişen ayarlar ise erenc hesabıyla çalışan MWB’ye aitti. PoC, hedefin settings.json dosyasını açmıyor. Değişikliği, pipe üzerinden çağırdığımız MWB süreci yapıyor.

Kodda ayarları kaydetme çağrısı da var. Elimdeki görüntü ise arayüzdeki değişikliği gösteriyor. Yeniden başlatma sonrasında ayarın korunup korunmadığına dair bu kayıtta bir kontrol yok.

Bu neden güvenlik açığı?

Sorun, MSI\zeroday hesabının erenc kullanıcısına ait MWB ayarlarını değiştirebilmesi. Kullanıcı kendi ayarlarını değiştirebilir ama başka hesabın eşleştirme anahtarına bu yoldan müdahale edememeli.

İsteğin izlediği yol:

1
2
3
4
5
Farklı Windows kullanıcısındaki yerel süreç
  → Authenticated Users izniyle pipe’a ulaşma
  → Çağıran doğrulanmadan JSON-RPC hedefinin açılması
  → ConnectToMachine parametrelerinin işlenmesi
  → Hedef MWB sürecinin anahtar ve cihaz durumunu değiştirmesi

Windows, tanımlanan izin nedeniyle bağlantıyı kabul ediyor. MWB de bağlanan istemciyi kontrol etmeden isteği çalıştırıyor. Bu iki durum birleşince başka kullanıcının eşleştirme ayarlarını değiştirebiliyoruz.

Microsoft açığı CWE-1390 / Weak Authentication olarak sınıflandırmış. CVSS puanı 5.5, vektörü AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:H/A:N. Etki türü Tampering olarak geçiyor.

Bu değerlendirmede saldırganın bilgisayarda düşük yetkili bir hesabı olması gerekiyor. Hedef kullanıcının bir işlem yapmasına gerek yok. Bütünlük etkisi yüksek olarak puanlanmış, gizlilik ve erişilebilirlik etkileri ise sıfır.

Sonuç

Bu testte farklı bir Windows hesabından SettingsSync pipe’ına bağlanıp hedef kullanıcının eşleştirme anahtarını ve bilgisayar adını değiştirebildim. RCE, SYSTEM yetkisi veya uzak bilgisayarın kontrolünü ele geçirme bu testte gösterilmiş değil.

Benim bu örnekte üzerinde durmak istediğim nokta, pipe’a bağlanan sürece ne kadar yetki verildiği. Authenticated Users izniyle içeri giren bir istemci, ek bir kontrol olmadan MWB’nin ayar değiştiren metoduna ulaşabiliyor. Named pipe kullanan bir uygulamayı incelerken erişim izinlerinin ardından, bağlantıyı kabul eden kodun ne yaptığına da bakmak gerekiyor.

Kod parçalarını Microsoft PowerToys’un MIT lisanslı kaynaklarından aldım. Bazılarını kısalttım, ilgili sürümdeki tam kodlara bağlantı verdim.

Tüm yazılar