„Named pipes“ (pavadinamosios eilutės) yra įprastas pasirinkimas bendrauti tarp tos pačios „Windows“ kompiuteryje veikiančių programų. Jos yra greitos, tiesiogiai palaikomos operacinės sistemos ir gerai veikia bendraujant tarp „Windows“ paslaugų, darbalaukio programų, dėklo procesų, komandinės eilutės priemonių ir foninių agentų.
Tipiškas dizainas gali apimti privilegijuotą „Windows“ paslaugą, veikiančią kaip „named pipe“ serveris, o vartotojo sąsajos programa jungiasi kaip klientas. Kadangi abu procesai veikia tame pačiame kompiuteryje, kūrėjai dažnai laiko šį ryšį vidiniu ir todėl patikimu.
Praktiškai prie eilutės galima prieiti iš aplinkos, kurioje veikia daugybė nesusijusių procesų, veikiančių pagal skirtingus vartotojus, sesijas ir saugos kontekstus.
Vietinis nereiškia patikimas
„Named pipes“ dažnai laikomos privačiomis, nes jos naudojamos bendrauti tarp programų tame pačiame kompiuteryje. Ši prielaida yra nesaugi.
„Windows“ darbo stotyje gali veikti procesai pagal LocalSystem, administratoriai, standartiniai vartotojai, paslaugų paskyros ir atskiros interaktyviosios ar nuotolinės sesijos. Joje taip pat gali būti trečiųjų šalių programinė įranga, scenarijai, diagnostikos įrankiai ir kenkėjiška programinė įranga, veikianti pagal pažeistą paskyrą.
Bet kuris procesas, žinantis eilutės pavadinimą ir turintis pakankamas prieigos teises, gali bandyti prisijungti. „Windows“ iš esmės nežino, kurią vykdomąją programą kūrėjas ketino naudoti eilutę.
Dėl šios priežasties „named pipe“ turėtų būti laikoma atviru vietiniu sąsaja. Prieš apdorojant užklausą, programa turi nustatyti, kas prisijungė, ką tas tapatumas gali daryti ir ar pateikti duomenys yra saugūs.
Tapatumas, prieigos kontrolė ir privilegijų ribos
Didžiausia rizika kyla, kai privilegijuota „Windows“ paslauga bendrauja su mažiau privilegijuota darbalaukio programa.
Kaip LocalSystem veikianti paslauga gali modifikuoti apsaugotus failus ir registro raktus, paleisti procesus, keisti sistemos konfigūraciją, pasiekti kitų vartotojų duomenis arba bendrauti su branduolio tvarkyklėmis. Kai šios operacijos yra atviros per „named pipe“, eilutė tampa API prie privilegijuotos funkcionalumo.
Sėkmingas prisijungimas įrodo tik tai, kad klientui buvo leista atidaryti eilutę. Tai neįrodo, kad:
- klientas yra laukiamoji programa;
- prijungtas vartotojas yra įgaliotas;
- leistina užklausiama operacija;
- pateiktas komandinis failas yra saugus.
Todėl eilutės leidimai turėtų būti apibrėžti aiškiai ir apriboti mažiausiam tinkamam tapatumų rinkiniui. Plati leidimų suteikimas visiems (Everyone), visiems autentifikuotiems vartotojams (Authenticated Users) arba visiems interaktyviems vartotojams gali leisti nesusijusiems procesams pasiekti eilutę.
Autentifikavimas ir autorizacija taip pat turi likti atskirai. Vartotojui gali būti leista užklausti paslaugos būseną, bet ne sustabdyti paslaugą, keisti apsaugotus nustatymus, paleisti procesus ar pasiekti bet kokius failus. Jautrios komandos turėtų būti autorizuojamos individualiai.
Imitacija gali padėti atliekant operacijas kliento saugos kontekste, tačiau ją reikia tvarkyti atsargiai. Serveris turėtų patvirtinti, kad imitacija pavyko, apriboti atliekamą darbą imitacijos metu ir visada atkurti savo pradinį tapatumą.
Nepatikimi serveriai, komandos ir duomenys
Klientas turi patikrinti serverį taip pat, kaip serveris tikrina klientą.
Numatomas eilutės pavadinimas yra tik identifikatorius. Tai nėra paslaptis ir neįrodo, kuris procesas sukūrė eilutę. Atakuotojas gali sukurti eilutę su numatomu pavadinimu prieš paleidžiant teisėtą serverį, priverčiant klientą prisijungti prie atakuotojo valdomo proceso.
first-pipe-instance parinktis gali padėti aptikti, kad pavadinimas jau buvo užimtas, tačiau ji nepakeičia tinkamos prieigos kontrolės ar serverio tapatybės patvirtinimo.
Per eilutę gauti pranešimai taip pat turi būti laikomi netikėtais įvesties duomenimis. Net ir autentifikuotas klientas gali siųsti:
- sugadintus arba per didelius duomenų paketus;
- netinkamus failų ar registro kelius;
- nepalaikomus komandų derinius;
- sugadintus serializuotus objektus;
- vertybes, skirtas sukelti klaidas.
Privilegijuota paslauga, kuri tokius įvesties duomenis tiesiogiai paverčia failų, registro, procesų ar komandinės eilutės operacijomis, gali tapti sumišusiu pavaduotoju: atakuotojas pateikia instrukciją, o paslauga – privilegijas.
Užklausos turėtų naudoti griežtą pranešimų karkasavimą, ribotus dydžius, leidžiamų komandų sąrašus, schemos patvirtinimą, kelio normalizavimą, operacijos specifinę autorizaciją ir saugų klaidų tvarkymą.
Pasiekiamumas ir nuotolinė prieiga
„Named pipe“ saugumas neapsiriboja tik privilegijų didinimu ir neleistinomis komandomis.
Kenkėjiškas arba netinkamai veikiantis procesas gali daug kartų jungtis, laikyti jungtis atidarytas, siųsti nepilnus pranešimus arba teikti užklausas, kurios naudoja per daug CPU, atminties ar branduolio išteklių.
Serveris turėtų naudoti jungčių limitus, laiko limitus, atšaukimą, ribotus pranešimų dydžius, kontroliuojamą vienalaikiškumą ir, jei tinkama, greičio apribojimus.
Taip pat nesaugu manyti, kad kiekviena „named pipe“ pasiekiama tik iš vietinio kompiuterio. „Windows“ „named pipes“ kai kuriose konfigūracijose gali palaikyti nuotolinę prieigą.
Vietiniam IPC skirtos eilutės turėtų aiškiai blokuoti tinklo tapatumus, tokius kaip NT AUTHORITY\NETWORK, arba naudoti mechanizmą, garantuojantį tik vietinį ryšį.
Tinkamas grėsmių modelis yra paprastas: kiekvienas „named pipe“ ryšys turėtų būti laikomas potencialiai priešišku, kol nebus patvirtintas kliento ar serverio tapatumas, leidimai, užklausta operacija ir pranešimo turinys.
Kada „Named Pipe“ tampa saugumo riba
„Named pipe“ tampa saugumo riba, kai jos abiejuose galuose veikiantys procesai turi skirtingas privilegijas arba veikia skirtinguose pasitikėjimo lygiuose.
Dažnas pavyzdys yra „Windows“ paslauga, veikianti kaip LocalSystem, ir darbalaukio programa, veikianti kaip standartinis vartotojas. Paslauga gali modifikuoti apsaugotus failus ir registro raktus, paleisti procesus, keisti visos sistemos konfigūraciją, pasiekti kitų vartotojų duomenis arba bendrauti su branduolio tvarkykle. Darbalaukio programa paprastai negali atlikti tokių operacijų tiesiogiai.
Kai paslauga priima komandas per „named pipe“, eilutė tampa sąsaja į tas privilegijuotas galimybes. Bet koks eilutės leidimų, tapatybės patikrinimų, komandų patvirtinimo ar autorizacijos logikos trūkumas gali leisti nepatikimam vietiniam procesui netinkamai pasinaudoti paslaugos privilegijomis.
Sėkmingas prisijungimas neįrodo, kad klientas yra laukiamoji programa. Tai tik įrodo, kad besijungiantis procesas turėjo pakankamus leidimus atidaryti eilutę. Kitas to paties vartotojo paskyros procesas gali turėti tokius pačius leidimus. Todėl serveris turi patvirtinti saugos tapatybę už prisijungimo, o ne remtis proceso pavadinimu, vykdomosios programos keliu ar eilutės pavadinimo paslaptimi.
Serveris taip pat turi autorizuoti kiekvieną operaciją atskirai. Klientas, kuriam leidžiama užklausti paslaugos būseną, neturėtų automatiškai turėti leidimo sustabdyti paslaugą, keisti apsaugotą konfigūraciją, paleisti procesą ar pasiekti bet kokį failą.
Autentifikavimas nustato, kas prisijungė; autorizacija nustato, ką tas tapatumas gali daryti.
Šis skirtumas ypač svarbus, kai serveris apdoroja klientų valdomus kelius, komandinės eilutės argumentus, registro vietas, vykdomųjų failų pavadinimus ar serializuotas komandas. Be griežto patvirtinimo, serveris gali tapti „sumišusiu pavaduotoju“: klientas pasirenka veiksmą, bet privilegijuotas serveris jį atlieka.
Pavyzdžiui, nekenksmingai atrodanti užklausa, pvz.:
Skaityti failą: C:\ProgramData\Product\status.json
gali tapti pavojinga, jei klientas gali pakeisti kelią į:
Skaityti failą: C:\Windows\System32\config\SAM
Tas pats problema taikoma užklausoms, kurios paleidžia procesus, ištrina failus, atnaujina registro reikšmes, įdiegia komponentus ar bendrauja su tvarkykle. Serveris turi ne tik patvirtinti, kad komanda yra sintaksiškai teisinga. Jis turi patvirtinti, kad prijungtas tapatumas turi leidimą atlikti tą tikslią operaciją su tuo tiksliu ištekliumi.
Saugi „named pipe“ serveris turėtų atlikti kelis patikrinimus prieš vykdydamas privilegijuotą užklausą:
- patvirtinti prijungto kliento „Windows“ tapatybę;
- apriboti prieigą per aiškų eilutės saugos aprašą;
- autorizuoti kiekvieną komandą nepriklausomai;
- patvirtinti visus kelius, argumentus, identifikatorius ir duomenų paketų dydžius;
- atmesti nepalaikomas ar neaiškias operacijas;
- vengti atvirumo bendrosios paskirties privilegijuotoms funkcijoms.
Pastarasis punktas yra kritinis. Komanda, pvz., „rašykite šią reikšmę į bet kokį registro raktą“, sukuria daug didesnę atakos paviršių nei siaurai apibrėžta komanda, pvz., „atnaujinkite šį konkretų programos nustatymą“. Kuo bendresnis eilutės protokolas, tuo labiau jis panašus į privilegijuotą vietinę API – ir kuo atidžiau jis turi būti apsaugotas.
Tinkamas dizaino principas yra paprastas: eilutės serveris niekada neturi atlikti operacijos vien dėl to, kad ją paprašė prijungtas klientas. Jis turėtų atlikti operaciją tik po to, kai patvirtins, kas ją paprašė, ar tas tapatumas yra įgaliotas, ir ar užklausa neviršija siaurai apibrėžtų saugumo ribų.
Prieigos kontrolė ir kliento autorizacija
„Named pipe“ serveris turėtų nuspręsti, kas gali prisijungti, prieš pradėdamas apdoroti pranešimus. Tai prasideda nuo aiškaus saugos aprašo, kuris leidžia prieigą tik reikiamiems „Windows“ tapatumams, pvz., konkrečiam vartotojo SID, paslaugos paskyrai, administratorių grupei ar prisijungimo sesijai.
Eilutės DACL kontroliuoja prieigą prie abiejų „named pipe“ galų. Kai klientas bando prisijungti, „Windows“ palygina kliento prieigos žetoną ir prašomus teises su tuo DACL. Remtis numatytuoju aprašu yra rizikinga, nes jo leidimai gali būti platesni nei programai reikia.
Prieiga prie eilutės automatiškai neautorizuoja kiekvienos turimos komandos. Klientui gali būti leista gauti būsenos informaciją, tačiau gali būti neleista modifikuoti konfigūracijos, paleisti procesų ar pasiekti apsaugotus failus. Todėl autorizacija turėtų būti atliekama kiekvienai jautriai operacijai, o ne tik vieną kartą, kai užmezgamas ryšys.
Vietiniam programų tarpusavio bendravimui programos taip pat gali patikrinti procesą, susietą su priešingu eilutės galu:
- serveris gali iškviesti
GetNamedPipeClientProcessId; - klientas gali iškviesti
GetNamedPipeServerProcessId.
Šios „Windows“ API grąžina procesų identifikatorių, susietą su prijungtu klientu ar serveriu. Jos turėtų būti iškviečiamos tik užmezgus eilutės ryšį.
Šis C# pagalbininkas gauna kaimyninio PID naudodamas gimtąsias „Windows“ API:
(DllImport("kernel32.dll", SetLastError = true))
(return: MarshalAs(UnmanagedType.Bool))
private static extern bool GetNamedPipeClientProcessId(SafePipeHandle pipe, out uint clientProcessId);
(DllImport("kernel32.dll", SetLastError = true))
(return: MarshalAs(UnmanagedType.Bool))
private static extern bool GetNamedPipeServerProcessId(SafePipeHandle pipe, out uint serverProcessId);
Taip pat galime iškviesti kitą funkciją iš kernel32.dll, QueryFullProcessImageName, kad gautume vykdomosios programos kelią iš proceso rankenos, atidarytos su PROCESS_QUERY_INFORMATION arba PROCESS_QUERY_LIMITED_INFORMATION. Grąžintas kelias tada gali būti palygintas su laukiamu vykdomosios programos vieta kaip papildomas patvirtinimo žingsnis.
Serverio pusėje patvirtinimas turėtų vykti iškart po ryšio priėmimo ir prieš skaitant ar vykdant komandas:
Laukiama vykdomoji programa turėtų būti įkurdinta kataloge, kurio standartiniai vartotojai negali modifikuoti. Priešingu atveju atakuotojas gali pakeisti failą, išsaugodamas laukiamą kelią.
Norint stipresnio patvirtinimo, programa gali papildomai patvirtinti vykdomosios programos „Authenticode“ parašą arba palyginti ją su patvirtintu kriptografiniu maišeliu. „Windows“ naudoja WinVerifyTrust pasirašytų vykdomųjų failų patvirtinimui.
Tačiau PID ir vykdomosios programos kelio patikrinimas turėtų likti antrine kontrole, o ne pagrindiniu autorizacijos mechanizmu. Saugumo tyrimai parodė būdus, kaip suklastoti PID, pranešamą „named pipe“ klientui, ir būdus, kaip perduoti prijungtą eilutės rankeną kitam procesui. Grąžintas PID gali identifikuoti procesą, kuris atidarė ryšį, bet neįrodo, kuris procesas šiuo metu siunčia kiekvieną pranešimą.
Todėl saugi implementacija turėtų derinti kelis kontrolės mechanizmus:
- aiškus ir ribotas eilutės DACL;
- kliento „Windows“ tapatybės arba SID patvirtinimas;
- autorizacija kiekvienai privilegijuotai komandai;
- griežtas pranešimo turinio patvirtinimas;
- neprivalomas PID, vykdomosios programos kelio, parašo ar maišelio patvirtinimas kaip papildoma gynyba.
Ryšys turėtų būti atmestas, kai tapatybės patvirtinimas nepavyksta arba negali būti baigtas. Privilegijuota paslauga niekada neturėtų atmesti užklausos vien todėl, kad eilutės ryšys pavyko.
Imitacija ir privilegijuotos operacijos
„Named pipe“ serveris dažnai veikia su didesnėmis privilegijomis nei prie jo prijungtas klientas. Pavyzdžiui, „Windows“ paslauga gali veikti kaip LocalSystem, o kliento programa veikia kaip standartinė vartotojo paskyra. Jei paslauga atlieka kiekvieną užklausiamą operaciją savo tapatybe, klientas gali netiesiogiai gauti prieigą prie failų, registro raktų, procesų ir sistemos išteklių, kurių negalėjo pasiekti tiesiogiai.
„Named pipe“ imitacija leidžia serveriui laikinai vykdyti kodą kliento saugos kontekste. Tada „Windows“ vertina išteklių prieigą naudodama kliento žetoną, o ne paslaugos paskyros žetoną.
.NET aplinkoje NamedPipeServerStream.RunAsClient suteikia kontroliuojamą būdą imituoti prijungtą klientą:
server.WaitForConnection();
server.RunAsClient(() =>
{
string path = @"C:\ProgramData\MyApplication\settings.json";
// Prieiga tikrinama naudojant prijungto kliento tapatybę.
string content = File.ReadAllText(path);
ProcessClientData(content);
});
Šis metodas yra naudingas, kai klientui turėtų būti leista atlikti operaciją tik tuo atveju, jei jo nuosava „Windows“ paskyra jau turi leidimą. Pavyzdžiui, imitacija gali būti naudojama skaitant vartotojo valdomą failą, pasiekiant vartotojo specifinį registro raktą arba patvirtinant, ar klientas turi prieigą prie apsaugotų išteklių.
Tačiau imitacija nepakeičia autorizacijos. Serveris vis tiek turėtų patvirtinti, kad klientui leidžiama užklausti operaciją. Imitacija tik pakeičia saugos kontekstą, kuriame „Windows“ atlieka prieigos patikrinimus; ji nenustato, ar pati komanda yra tinkama.
Privilegijuota paslauga taip pat turėtų vengti nereikalingai keisti kliento ir paslaugos tapatybes. Apsvarstykite užklausą, kuria prašoma paslaugos perskaityti failą, o tada jo turinį įdiegti kaip konfigūraciją.
Failas gali būti skaitomas imituojant klientą, tačiau diegimas gali įvykti vėliau pagal LocalSystem. Tokiu atveju klientas vis tiek gali paveikti privilegijuotą operaciją, net jei dalis užklausos buvo apdorota imituojant.
Saugesnis dizainas yra atskirti operaciją į aiškiai apibrėžtus etapus:
- Autentifikuoti ir autorizuoti klientą.
- Patvirtinti visus kliento valdomus kelius, argumentus ir duomenis.
- Imituoti tik operacijoms, kurios turėtų naudoti kliento leidimus.
- Grįžti prie paslaugos tapatybės prieš atliekant siaurai apibrėžtą privilegijuotą darbą.
- Pakartotinai patvirtinti bet kokius duomenis, pereinančius iš imituojamo etapo į privilegijuotą etapą.
Imitacijos apimtis turėtų būti kuo mažesnė. Ilgai trunkančios užduotys, grįžtamieji ryšiai, asinchroninės operacijos ir nesusijusi paslaugos logika neturėtų būti vykdomos kliento tapatybe.
Kai naudojamos gimtosios „Windows“ API, taikomas tas pats modelis:
(DllImport("advapi32.dll", SetLastError = true))
(return: MarshalAs(UnmanagedType.Bool))
private static extern bool ImpersonateNamedPipeClient(SafePipeHandle pipe);
(DllImport("advapi32.dll", SetLastError = true))
(return: MarshalAs(UnmanagedType.Bool))
private static extern bool RevertToSelf();
Serveris turi patikrinti, ar ImpersonateNamedPipeClient pavyko, ir visada turi iškviesti RevertToSelf finally bloke:
if (!ImpersonateNamedPipeClient(server.SafePipeHandle))
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
try
{
// Veikia prijungto kliento saugos kontekste.
PerformClientScopedOperation();
}
finally
{
if (!RevertToSelf())
{
throw new Win32Exception(Marshal.GetLastWin32Error());
}
}
Klaidų tvarkymas yra kritinis. Jei imitacija nepavyksta, o paslauga toliau apdoroja, operacija gali būti vykdoma pagal pradinę serverio privilegijuotą tapatybę. Todėl nepavykus imitacijos bandymui užklausa turi būti atmesta, o ne tyliai grįžta prie serverio paskyros.
Tas pats principas taikomas ir po imitacijos. Programa turi patikimai atkurti savo pradinę tapatybę prieš apdorodama kitą klientą ar atlikdama nesusijusį darbą. Priešingu atveju vėlesnės operacijos gali atsitiktinai vykti ankstesnio kliento kontekste.
Privilegijuotos eilutės komandos taip pat turėtų būti siauros ir specifinės paskirties. Komanda, pvz.:
Rašykite bet kokią reikšmę į bet kokį registro raktą
sukuriamas daug didesnis atakos paviršius nei:
Atnaujinti programos patvirtintą politikos nustatymą
Serveris neturėtų atskleisti bendrosios paskirties failų prieigos, registro modifikavimo, procesų kūrimo ar komandų vykdymo vien todėl, kad gali atlikti tas operacijas. Kiekviena privilegijuota komanda turėtų tiksliai apibrėžti, kokie ištekliai gali būti pasiekiami, kokios reikšmės priimamos ir kokie kliento tapatumai gali ją iškviesti.
Imitacija yra efektyviausia, kai naudojama kaip viena iš platesnio saugos dizaino sluoksnių. Serveris vis tiek turėtų taikyti ribojančius eilutės leidimus, tikrinti prijungtą klientą, autorizuoti kiekvieną komandą, patvirtinti kiekvieną užklausą ir išlaikyti privilegijuotas operacijas siaurai apibrėžtas.
Pranešimų eilutės laikykite netikėtais įvesties duomenimis
Patikrinus procesą, prijungtą prie „named pipe“, jo pranešimai netampa saugūs. Legali programa gali būti pažeista, turėti pažeidžiamumą arba perduoti vartotojo valdomus duomenis į eilutę. Kenkėjiškas procesas taip pat gali gauti ar paveldėti galiojančią eilutės rankeną.
Dėl šios priežasties kiekvienas per „named pipe“ gautas pranešimas turėtų būti laikomas netikėtu įvesties duomenimis. Serveris turėtų patvirtinti tiek pranešimo struktūrą, tiek jo prašomą operaciją prieš atlikdamas bet kokį privilegijuotą veiksmą.
Pavojinga implementacija gali tiesiogiai deserializuoti užklausą ir ją vykdyti:
PipeRequest request = Deserialize(data);
File.WriteAllText(request.Path, request.Content);
Net jei request turi laukiamą struktūrą, tokios reikšmės kaip Path ir Content lieka kontroliuojamos kliento. Todėl privilegijuotas serveris gali būti instruktuotas perrašyti failus už programos katalogo ribų, modifikuoti apsaugotą konfigūraciją ar išeikvoti per daug disko vietos.
Saugesnis metodas yra atskleisti siaurai apibrėžtas komandas ir patvirtinti kiekvieną lauką:
private static void ProcessRequest(PipeRequest request)
{
if (request == null)
throw new InvalidDataException("The request is missing.");
switch (request.Command)
{
case PipeCommand.UpdateConfiguration:
ValidateConfiguration(request.Configuration);
UpdateApprovedConfiguration(request.Configuration);
break;
case PipeCommand.GetStatus:
ReturnApplicationStatus();
break;
default:
throw new InvalidDataException("Unsupported command.");
}
}
Protokolas turėtų vengti bendrosios paskirties operacijų, tokių kaip:
WriteFile(path, content)
StartProcess(path, arguments)
SetRegistryValue(key, name, value)
ExecuteCommand(command)
Šios komandos leidžia klientui pasirinkti tiek privilegijuotą operaciją, tiek jos tikslą. Pirmenybę teikite programos specifinėms užklausoms, kurių leidžiamą elgesį kontroliuoja serveris:
UpdateApplicationConfiguration(configuration)
RequestApplicationRepair()
InstallApprovedUpdate(updateId)
GetServiceStatus()
Patvirtinkite pranešimo struktūrą ir dydį
„Named pipe“ ryšys yra baitų srautas, nebent programa sąmoningai naudoja pranešimų perdavimo režimą. Vienas Read kvietimas negarantuoja visos programos žinutės gavimo, ir serveris neturėtų manyti, kad skaitymo ribos atitinka užklausos ribas.
Protokolas turėtų apibrėžti aiškų pranešimų karkasavimą, pvz., fiksuoto dydžio antraštę, po kurios seka ilgio prefiksu apdorojamas duomenų paketas:
(Version)(Command)(Payload Length)(Payload)
Deklaruotas ilgis turi būti patvirtintas prieš priskiriant atmintį ar skaitant duomenų paketą:
private const int MaxMessageSize = 1024 * 1024;
private static async Task<byte[]> ReadPayloadAsync(
Stream pipe,
int payloadLength,
CancellationToken cancellationToken)
{
if (payloadLength > MaxMessageSize || payloadLength < 0)
throw new InvalidDataException("Invalid payload length.");
byte[] payload = new byte[payloadLength];
int offset = 0;
while (offset < payloadLength)
{
int bytesRead = await pipe.ReadAsync(payload, offset, payloadLength - offset, cancellationToken);
if (bytesRead == 0)
throw new EndOfStreamException("Unexpected end of stream.");
offset += bytesRead;
}
return payload;
}
Be maksimalaus dydžio, atakuotojas gali deklaruoti labai didelį duomenų paketą ir priversti serverį priskirti per daug atminties. Programa taip pat turėtų apriboti kolekcijos dydžius, eilutės ilgius, įterpimo gylį ir objektų, kuriuos priima deserializatorius, skaičių.
Patvirtinkite vertes, ne tik tipus
Sėkmingas deseralizavimas įrodo tik tai, kad duomenų paketas galėjo būti paverstas laukiamu objekto tipu. Tai neįrodo, kad reikšmės yra priimtinos.
Pavyzdžiui, failo kelias turėtų būti normalizuotas ir patikrintas pagal patvirtintą katalogą:
private static string ValidatePath(
string suppliedPath,
string allowedDirectory)
{
string fullPath = Path.GetFullPath(suppliedPath);
string fullDirectory = Path.GetFullPath(allowedDirectory)
.TrimEnd(Path.DirectorySeparatorChar)
+ Path.DirectorySeparatorChar;
if (!fullPath.StartsWith(
fullDirectory,
StringComparison.OrdinalIgnoreCase))
{
throw new UnauthorizedAccessException(
"The requested path is outside the allowed directory.");
}
return fullPath;
}
Tas pats principas taikomas registro keliams, proceso argumentams, URL, identifikatoriams, atnaujinimo paketams ir konfigūracijos reikšmėms. Serveris turėtų patvirtinti kiekvieną reikšmę pagal leidžiamų reikšmių sąrašą arba siaurai apibrėžtą intervalą, o ne bandyti blokuoti žinomai pavojingas reikšmes.
Kelių patikrinimai taip pat reikalauja atidumo dėl simbolinių nuorodų, jungčių, peradresavimo taškų ir „laiko-patikrinimo-laiko-naudojimo“ lenktynių. Jautrioms failų operacijoms nepakanka patvirtinti tik eilutės kelią.
Saugiai atmesti netinkamas užklausas
Sugadinti arba neleistini pranešimai turėtų būti atmesti ne tęsiant dalinį apdorojimą. Serveris turėtų vengti grąžinti klientui kodo krūvas, vidinius kelius, saugos žetonus ar išsamias išimties informaciją.
Per eilutę siunčiamose klaidose turėtų būti naudojamas mažas, kontroliuojamas atsakymų kodų rinkinys:
public enum PipeResult
{
Success,
InvalidRequest,
Unauthorized,
UnsupportedCommand,
InternalError
}
Išsamūs diagnostikos duomenys gali būti įrašyti į apsaugotus serverio žurnalus, o klientas gauna tik informaciją, reikalingą klaidai ištaisyti.
Kiekviena užklausa turėtų praeiti per numatomą seką:
- Skaityti riboto dydžio pranešimą.
- Patvirtinti protokolo versiją ir pranešimo struktūrą.
- Autentifikuoti ir autorizuoti prijungtą klientą.
- Patvirtinti kiekvieną kliento valdomą reikšmę.
- Vykdyti tik siaurai apibrėžtą operaciją.
- Grąžinti kontroliuojamą atsakymą.
„Named pipe“ yra tik transportavimo mechanizmas. Tai nepadaro duomenų patikimų, negarantuoja tinkamo pranešimo karkasavimo ar neleidžia prijungtam procesui siųsti kenkėjiškų užklausų. Priimanti programa lieka atsakinga už protokolo vykdymą ir kiekvienos per jį atskleistos operacijos apsaugą.
Atmetimo tarnyba ir nuotolinės prieigos rizikos
„Named pipe“ galas gali būti apsaugotas nuo neleistinų komandų ir vis tiek likti pažeidžiamas atmetimo tarnybos atakoms. Atakuotojui ne visada reikia leidimo atlikti privilegijuotą operaciją; pakanka sutrikdyti produktą, neleisti legalioms programoms bendrauti su tarnyba.
Kenkėjiškas arba netinkamai veikiantis procesas gali daug kartų jungtis prie eilutės, užimti visas turimas instances, laikyti jungtis atidarytas nesiųsdamas pilnų pranešimų arba nuolat persijungti po atjungimo. Kai visi serverio instances bus užimti, legaliems klientams gali nepavykti užmegzti ryšio.
Ta pati rizika išlieka ir priėmus ryšį. Klientas gali siųsti duomenis labai lėtai, deklaruoti per didelį duomenų paketą, sustoti pusiaukelėje pranešimo ar užplūsti serverį galiojančiomis, bet brangiomis užklausomis. Be apribojimų, šie elgesio modeliai gali išeikvoti gijas, užduotis, atmintį, CPU laiką, rankenas ir vidinius užklausų eilinius.
„Named pipe“ buferiai taip pat naudoja branduolio nepuslapio atmintį. Todėl eilutės instances skaičius ir buferinių duomenų kiekis yra ribojamas sistemos išteklių. Sukūrus neribotą skaičių instances arba pasirinkus nereikalingai didelius buferius, galima prisidėti prie išteklių išeikvojimo.
Gynybinis serveris turėtų nustatyti aiškius limitus:
- vienalaikiai ryšiai ir eilutės instances;
- pranešimų ir laukų dydžiai;
- laikas, skirtas ryšiui užmegzti ir užklausai užbaigti;
- laukiančios užklausos vienam klientui;
- vienalaikės brangios operacijos;
- užklausų dažnis;
- vidinio eilės pajėgumo.
Blokavimo operacijos turėtų palaikyti atšaukimą ir neturėtų laukti neribotai, kol klientas atsiųs daugiau duomenų. Kai klientas viršija laiko, dydžio ar užklausos limitą, serveris turėtų nutraukti tą ryšį ir nedelsdamas atlaisvinti jo išteklius.
Limitai turėtų būti taikomi prieš pradedant brangų darbą. Pavyzdžiui, serveris turėtų atmesti per didelį deklaruotą duomenų paketo dydį prieš priskiriant atitinkamą buferį. Panašiai, autorizacija ir pagrindinis užklausos patvirtinimas turėtų įvykti prieš failų prieigą, procesų kūrimą, kriptografinį darbą, duomenų bazės užklausas ar bendravimą su branduolio tvarkykle.
Programa taip pat turėtų vengti kurti po vieną neribotą darbo giją kiekvienam ryšiui. Riboto vienalaikiškumo modelis neleidžia didelei prijungtų klientų skaičiui išeikvoti proceso gijų pulko ar sukurti nekontroliuojamą eilę. Greičio limitai gali būti taikomi kiekvienam ryšiui, procesui, vartotojo tapatybei ar prisijungimo sesijai, priklausomai nuo programos architektūros.
Tačiau prieinamumo kontrolė negali priklausyti tik nuo kliento PID. Procesas gali daug kartų paleisti iš naujo, naudoti kelis procesus ar užmegzti ryšius su ta pačia vartotojo paskyra. Keli signalai gali būti vertinami kartu, o serveris turi išlaikyti pasaulinį limitą, net jei taikomi individualūs kliento limitai.
Kita dažnai pamirštama rizika yra nuotolinis pasiekiamumas. „Windows“ „named pipes“ nebūtinai yra ribojamos tik vietinio kompiuterio bendravimu. Jos taip pat gali palaikyti bendravimą tarp kompiuterių per tinklą, o „Microsoft“ teigia, kad „named pipes“ gali būti pasiekiamos nuotoliniu būdu, kai veikia „Windows Server“ paslauga.
Tai reiškia, kad naudojant vietinį eilutės pavadinimą savaime negarantuojama, kad bendravimas bus tik vietinis. Eilutė, skirta bendrauti tarp vietinės paslaugos ir vietinės darbalaukio programos, turėtų tai aiškiai užtikrinti.
Naminės eilutės serveriai gali nurodyti PIPE_REJECT_REMOTE_CLIENTS, todėl „Windows“ automatiškai atmeta nuotolinius ryšius. Be šios parinkties nuotoliniai klientai gali būti priimami ir vertinami pagal eilutės saugos aprašą.
Eilutės prieigos kontrolės sąrašas taip pat gali neigti prieigą prie NT AUTHORITY\NETWORK tapatybės. Kai prieiga turi būti ribojama iki vienos interaktyviosios sesijos, serveris gali suteikti prieigą prie atitinkamo prisijungimo SID, o ne prie plačių grupių, kuriomis dalijasi vietiniai ir nuotoliniai vartotojai.
Šios apsaugos priemonės turėtų būti derinamos, o ne laikomos alternatyvomis:
- atmesti nuotolinius klientus eilutės kūrimo metu, kai API tai palaiko;
- neigti tinklo tapatumus eilutės saugos apraše;
- suteikti prieigą tik reikiamiems vartotojams ar prisijungimo sesijoms;
- patvirtinti prijungto proceso tapatybę;
- taikyti ryšio, laiko limito, dydžio ir vienalaikiškumo limitus.
Atmetimo tarnybos apsauga ir nuotolinės prieigos apribojimai yra dalis eilutės saugos modelio. „Named pipe“ serveris nėra saugus vien todėl, kad atmesti neleistinos komandos. Jis taip pat turi išlikti pasiekiamas legalioms programoms ir užtikrinti, ar ryšiai gali kilti ne iš vietinio kompiuterio.
Saugaus „Named Pipe“ architektūros projektavimas
Saugi „named pipe“ projekcija turėtų sumažinti tiek atskleidžiamų operacijų skaičių, tiek privilegijuoto kodo, tiesiogiai apdorojančio kliento valdomus duomenis, kiekį. Eilutė turėtų veikti kaip siaura ryšio riba, o ne kaip bendrosios paskirties sąsaja su operacine sistema.
Praktiška architektūra atskiria ryšio apdorojimą, patvirtinimą, autorizaciją ir privilegijuotą vykdymą:
Klientas niekada neturėtų tiesiogiai bendrauti su bendrosios paskirties privilegijuota funkcija. Vietoj to, jis turėtų pateikti siaurai apibrėžtą užklausą eilutės šliuzui. Šliuzas patvirtina pranešimo formatą ir perduoda tik struktūrizuotą užklausą autorizacijos sluoksniui. Privilegijuotas darbas prasideda tik po visų saugos patikrinimų.
Laikykite eilutės protokolą siaurą
Eilutės protokolas turėtų atskleisti verslo operacijas, o ne operacinės sistemos pirminius elementus.
Pavyzdžiui, programai gali būti teisėtai reikalinga politika atnaujinti, įdiegti patvirtintą atnaujinimą, gauti paslaugos būseną arba atnaujinti konkretų konfigūracijos reikšmę. Paprastai jai nereikia neribotų komandų rašyti bet kokius failus, modifikuoti bet kokius registro raktus, paleisti bet kokius vykdomuosius failus ar vykdyti komandinės eilutės instrukcijas.
Siauros operacijos daro autorizaciją ir patvirtinimą praktiškais. Serveris žino, kokius išteklius kiekviena komanda gali pasiekti, kokie laukai laukiami ir kokie kliento tapatumai gali ją iškviesti.
Geras protokolas turėtų apimti:
- aiškią protokolo versiją;
- fiksuotą užklausų tipų rinkinį;
- unikalius užklausų identifikatorius;
- ribotus duomenų paketų dydžius;
- numatomus atsakymų ir klaidų formatus;
- aiškias taisykles nepalaikomoms ar netinkamai suformuotoms žinutėms.
Serveris turėtų atmesti nežinomas versijas, komandas, laukus ir būsenas, o ne bandyti jas interpretuoti švelniai.
Atskirkite prieigą prie ryšio nuo komandos leidimo
Leidimas prisijungti prie eilutės neturėtų reikšti leidimo naudoti visas per ją atskleistas funkcijas.
Eilutės saugos aprašas turėtų riboti, kurie „Windows“ tapatumai gali užmegzti ryšį. Po ryšio serveris turėtų identifikuoti klientą ir autorizuoti kiekvieną komandą nepriklausomai.
Tai leidžia palaikyti skirtingus pasitikėjimo lygius per tą pačią paslaugą. Pavyzdžiui, paprastiems vartotojams gali būti leidžiama užklausti būseną, o tik administratoriams ar patikimam valdymo procesui gali būti leidžiama modifikuoti apsaugotus nustatymus.
Ypač jautrioms operacijoms gali būti pageidautina naudoti atskiras „named pipes“:
Product.Status Read-only information
Product.UserActions Limited user operations
Product.Admin Administrative operations
Product.Internal Trusted component communication
Kiekviena eilutė tada gali turėti savo prieigos kontrolės taisykles, pranešimų limitus ir palaikomą komandų rinkinį. Tai paprastai saugiau nei visas operacijas perkelti už vieno didelio protokolo ir visiškai pasikliauti vidiniais komandų patikrinimais.
Tačiau papildomų eilučių kūrimas automatiškai nepagerina saugumo. Kiekvienas naujas galas padidina atakos paviršių ir turi būti nepriklausomai saugomas. Eilutės turėtų būti atskiriamos tik tada, kai jos atstovauja iš tikrųjų skirtingas pasitikėjimo ribas.
Naudokite kelis tapatybės patvirtinimo sluoksnius
Nvienas atskiras tapatybės patikrinimas neturėtų būti laikomas galutiniu.
Architektūra gali derinti:
- ribojantis eilutės DACL;
- prijungto vartotojo SID;
- kliento prisijungimo sesija;
- kaimyninio proceso ID;
- vykdomosios programos kelias;
- vykdomosios programos skaitmeninis parašas;
- taikomojo lygio iššūkis ir atsakymas;
- operacijos specifinė autorizacija.
Proceso ID ir vykdomosios programos kelio patikrinimai gali padėti aptikti netikėtas programas, tačiau jie turėtų likti papildomos gynybos kontrolėmis. Procesai gali keistis, rankenos gali būti paveldėtos ar perduotos, o patikimas procesas pats gali būti pažeistas.
Stipriausi sprendimai turėtų būti pagrįsti „Windows“ saugos tapatybėmis ir siaurai apibrėžtais leidimais, o ne tik akivaizdžiu vykdomosios programos pavadinimu.
Izoliuokite privilegijuotą vykdymą
Komponentas, atsakingas už eilutės pranešimų skaitymą, turėtų atlikti kuo mažiau privilegijuoto darbo.
Ryšio apdorojimas, deseralizavimas, karkasavimas ir pagrindinis patvirtinimas yra veikiami atakuotojo valdomų įvesties duomenų. Šios logikos atskyrimas nuo privilegijuotų operacijų sumažina parserio ar protokolo pažeidžiamumo poveikį.
Privilegijuotos operacijos sluoksnis turėtų gauti tik patvirtintas, griežtai tipizuotas instrukcijas. Jis neturėtų gauti tiesioginių pranešimų buferių, bet kokių nors kelių, komandų eilučių ar serializuotų objektų tiesiogiai iš kliento.
Labai jautrioms programoms dizainas gali eiti toliau, atskiriant eilutės šliuzą ir privilegijuotą darbininką į skirtingus procesus. Šliuzas gali veikti su sumažintomis privilegijomis, patvirtinti gaunamas užklausas ir perduoti tik patvirtintas operacijas mažesniam privilegijuotam komponentui per antrą ribotą kanalą.
Ši papildoma proceso riba padidina sudėtingumą, tačiau gali žymiai sumažinti atakos paviršiaus kodų, veikiančių kaip LocalSystem ar kitos galingos paskyros, kiekį.
Kontroliuokite kiekvieno ryšio gyvavimo trukmę
Kiekvienas priimtas ryšys turėtų turėti aiškų ir ribotą gyvavimo ciklą:
- Priimti ryšį.
- Identifikuoti ir patvirtinti kaimyną.
- Taikyti ryšio lygio apribojimus.
- Skaityti ribotą užklausą.
- Autorizuoti ir patvirtinti užklaustą operaciją.
- Vykdyti patvirtintą veiksmą.
- Grąžinti kontroliuojamą atsakymą.
- Atsijungti arba laukti kitos ribotos užklausos.
Serveris neturėtų leisti neautentifikuotiems klientams laikyti ryšių neribotą laiką. Tingėjimo laiko limitai, užklausų terminai, ryšių limitai, atšaukimas ir ribotos eilės turėtų būti architektūros dalis nuo pat pradžių.
Ilgai trunkančios operacijos neturėtų blokuoti eilutės skaitytuvo. Paslauga gali priimti užklausą, priskirti operacijos identifikatorių ir leisti klientui tikrinti eigą per atskirą būsenos užklausą. Tai neleidžia vienam ryšiui monopolizuoti serverio išteklių.
Padarykite serverį autoritetingu
Klientas turėtų prašyti rezultato, o serveris turėtų nustatyti, kaip tas rezultatas bus pasiektas.
Pavyzdžiui, klientas gali prašyti įdiegti patvirtintą atnaujinimą pagal identifikatorių. Serveris turėtų rasti paketą, patvirtinti jo parašą, nustatyti diegimo komandą ir taikyti leidžiamą paskirties vietą. Klientas neturėtų pateikti vykdomosios programos kelio, atsisiuntimo URL, komandinės eilutės argumentų ir tikslo katalogo.
Tai leidžia saugos sprendimus laikyti patikimame komponente ir sumažina kliento valdomų reikšmių, kertančių privilegijų ribą, skaičių.
Serveris taip pat turėtų vengti pasitikėti anksčiau kliento priimtais saugos sprendimais. Teiginiai, pvz., „vartotojas yra administratorius“, „šis failas pasirašytas“ arba „šis kelias yra saugus“, turi būti nepriklausomai patvirtinti serverio.
Audituokite saugos atžvilgiu svarbią veiklą
Saugi architektūra turėtų įrašyti pakankamai informacijos, kad būtų galima ištirti įtartiną elgesį, neatskleidžiant jautrių duomenų.
Naudingi audito įvykiai apima:
- atmesti ryšiai;
- nesėkmingi tapatybės patikrinimai;
- neautorizuotos komandos;
- sugadinti ar per dideli pranešimai;
- pasikartojantys laiko limitai;
- netikėti proceso tapatumai;
- privilegijuotos operacijos ir jų rezultatai;
- nenormalus ryšių ar užklausų dažnis.
Žurnalai turėtų nurodyti „Windows“ vartotoją, sesiją, kaimyninio proceso PID, komandos tipą ir, jei tinkama, rezultatą. Žurnaluose neturėtų būti įrašomi žali slaptažodžiai, autentifikavimo žetonai ir visi jautrūs duomenų paketai.
Pasikartojantys gedimai gali rodyti ataką, bet taip pat gali atskleisti defektinį kliento versiją ar diegimo problemą. Todėl audito duomenys turėtų palaikyti tiek saugos tyrimą, tiek operacijų trikčių šalinimą.
Rekomenduojama architektūra
Daugumai privilegijuotų „Windows“ paslaugų scenarijų, tinkama projekcija apima:
- tik vietinė „named pipe“ su aiškiu saugos aprašu;
- atskiri galai esmingai skirtingiems pasitikėjimo lygiams;
- tiek „Windows“ tapatybės, tiek kaimyninio proceso patvirtinimas;
- versijuotas, ilgio apribotas, programos specifinis protokolas;
- autorizacija kiekvienai komandai;
- griežtas kiekvienos kliento valdomos reikšmės patvirtinimas;
- trumpos ir atidžiai kontroliuojamos imitacijos apimtys;
- mažas privilegijuotos vykdymo sluoksnis;
- ribotos jungtys, eilės ir vykdymo laikas;
- saugos orientuotas audito žurnalas.
Pagrindinis principas yra tas, kad „named pipe“ turėtų atskleisti kuo mažesnę sąsają tarp pasitikėjimo lygių. Saugi architektūra nesistengia padaryti bet kokias privilegijuotas operacijas saugias. Ji iš viso vengia atskleisti bet kokias privilegijuotas operacijas.
Praktinis „Named Pipe“ saugos patikrinimų sąrašas
Prieš atskleidžiant programos funkcionalumą per „named pipe“, patikrinkite, ar dizainas apima kiekvieną iš šių sričių:
- Apibrėžkite pasitikėjimo ribą. Laikykite eilutę atviru vietiniu interfeisu, ypač kai viena pusė veikia su padidintomis privilegijomis.
- Aiškiai apribokite prieigą prie eilutės. Naudokite siaurą saugos aprašą, o ne pasikliaukite numatytaisiais leidimais ar plačiomis grupėmis, tokiomis kaip
Everyone. - Atmesti nuotolinius klientus. Konfigūruokite eilutę tik vietiniam bendravimui ir neleiskite tinklo tapatumams, kai nuotolinė prieiga nereikalinga.
- Patikrinkite abu galus. Patikrinkite prijungtą „Windows“ tapatybę ir, jei tinkama, patvirtinkite kaimyninio proceso PID, vykdomosios programos kelią ir skaitmeninį parašą.
- Nepasitikėkite eilutės pavadinimu. Numatomas pavadinimas identifikuoja galą, bet neautentifikuoja jo sukūrusio proceso.
- Autorizuokite kiekvieną komandą. Leidimas prisijungti neturėtų suteikti prieigos prie visų serverio teikiamų operacijų.
- Laikykite protokolą siaurą. Atskleiskite programos specifinius veiksmus, o ne bet kokias failų, registro, procesų ar komandų vykdymo galimybes.
- Visus pranešimus laikykite netikėtais. Patvirtinkite karkasavimą, protokolo versiją, komandos tipą, duomenų paketo dydį, laukų reikšmes, kelius ir objektų skaičius.
- Taikykite limitus anksti. Atmesti netinkamus dydžius ir nepalaikomas užklausas prieš priskiriant atmintį ar pradedant brangų darbą.
- Naudokite imitaciją atsargiai. Imituokite tik tada, kai operacija turėtų naudoti kliento leidimus, išlaikykite apimtį mažą ir uždarykite, jei imitacija nepavyksta.
- Išlaikykite privilegijuotą vykdymą izoliuotą. Atskirkite analizę ir patvirtinimą nuo kodo, atliekančio privilegijuotas operacijas.
- Kontroliuokite išteklių naudojimą. Ribokite vienalaikius ryšius, laukiančias užklausas, laisvą laiką, vykdymo laiką, eilės gylį ir užklausų dažnį.
- Grąžinkite kontroliuojamas klaidas. Venkite atskleisti kodo krūvas, vidinius kelius, žetonus ar kitas jautrias implementacijos detales.
- Audituokite saugos atžvilgiu svarbius įvykius. Įrašykite atmestus ryšius, nesėkmingus tapatybės patikrinimus, netinkamai suformuotas užklausas, neleistinas komandas ir privilegijuotas operacijas.
- Uždaryti klaidos atveju. Jei tapatybės, autorizacijos, patvirtinimo ar imitacijos negali būti patikimai užbaigtos, atmesti užklausą.
Saugi „named pipe“ implementacija neturėtų priklausyti nuo vienos apsaugos. Stipriausias dizainas derina ribojamą prieigos kontrolę, galų patvirtinimą, operacijos lygio autorizaciją, griežtą įvesties patvirtinimą, ribotą išteklių naudojimą ir siaurai apibrėžtą privilegijuotą funkcionalumą.


