Misschien denk je dat jou Linux server veilig is. Grote kans dat dit niet het geval is. De beveiliging van veel servers is gebaseerd op principes uit de jaren 70 van de vorige eeuw. Om een server echt veilig te maken heb je SELinux nodig. In dit artikel leer je hoe het werkt.

Laten we beginnen met een verhaal uit de praktijk, waar gebeurd op mijn eigen server. Ik had een server draaien met daarop een aantal websites. Zo ongeveer elke web site heeft zijn eigen ontwikkelaar en omdat ik me totaal niet interesseer in web development, liet ik deze ontwikkelaars hun gang gaan – een realistisch scenario voor een doorsnee Linux beheerder waarvan je niet mag verwachten dat hij weet wat er in alle toepassingen omgaat.

 Op een goede dag was de server in kwestie gehackt. Nadere analyse maakte duidelijk dat een indringer binnen gekomen was via een gammel PHP script en op die manier shell toegang had verkregen. Vervolgens had de indringen met het gebruikersaccount waaronder de web server draaide een groot aantal scripts neergezet in de directory /var/tmp/-, en deze scripts deden hun kwaadaardige werk om de rest van de wereld lastig te vallen.

De belangrijke vraag in een dergelijk geval is natuurlijk hoe je een dergelijk scenario kunt voorkomen. In theorie zijn er een aantal mogelijkheden:

*             Installeer een firewall

*             Zorg dat je server altijd up to date is

*             Zorg ervoor dat het web server account niet zomaar scripts kan aanmaken en lanceren.

*             Zet alle scripts uit

Geen van deze opties biedt echter een oplossing voor het geschetste probleem. Om te beginnen de firewall: de webserver moet bereikbaar zijn en de scripts op de webserver staan er ook met een reden. De indringer was gewoon binnen gekomen via http, TCP poort 80 en had een script uitgevoerd. Poort 80 blokkeren is hoe dan ook geen optie, want dan hoef je geen web server te draaien.

 Het up-to-date houden van de server in kwestie is nog wel de beste optie. Door consequent te zijn in het installeren van updates minimaliseer je de kans dat een indringer binnen komt door een bekend beveiligingsprobleem in de software die op de server draait. Maar met updates maak je een slecht geprogrammeerd script niet beter. In dit geval ook geen optie dus.

Dan de mogelijkheid om het web server account niet zomaar scripts aan te laten maken en lanceren. De standaardoptie die hiervoor bestaat, is Linux permissies en die zijn gebaseerd op het principe dat iedereen (the “others”  entiteit) bestanden aan mag maken in de /tmp en /var/tmp directory. Dat is functionaliteit die je niet zomaar uit kunt zetten.

Tot slot dan de optie om alle scripts uit te zetten, dat is natuurlijk een bijzonder slecht idee. Vrijwel geen enkele website kan zijn werk nog doen zonder scripts, dus ook hiermee zul je niet verder komen. Er is echter een oplossing die wel werkt: SELinux.

 

SELinux of AppArmor?

In de Linux kernel wordt gebruikgemaakt van het Linux security framework. Dit is een kernel-level framework op basis waarvan SELinux gebaseerd is. Naast SELinux maakt ook AppArmor gebruik van dit framework en biedt dus beveiliging op basis van dezelfde principes.

Traditioneel werd AppArmor door Novell/SUSE ontwikkeld en SELinux door Fedora/Red Hat. De afgelopen jaren is de aandacht voor AppArmor afgenomen en wordt er niet veel meer ontwikkeld op AppArmor. Daarbij komt dat ook SUSE, de belangrijkste ontwikkelaar van AppArmor sinds recent SELinux ondersteunt, en wijst alles er op dat SUSE zelfs binnenkort een volledige switch zal gaan maken naar SELinux. Feitelijk gezien mag je dus stellen dat er nog maar één techniek is die er toe doet en dat is SELinux

De werking van SELinux

Het basisprincipe van SELinux is eenvoudig uit te leggen: alle syscalls worden door SELinux geblokkeerd. Dat betekent dat als je op een server SELinux aanzet en verder helemaal niets doet, er dus helemaal niets meer mogelijk is en de server er al tijdens het opstarten heel snel mee op zal houden met een kernel panic.

Om ervoor te zorgen dat een systeem dat met SELinux beschermd is nog wel wat mag, is er de policy. In de policy bepaalt de beheerder heel nauwkeurig wat er allemaal precies is toegestaan. Hiervoor wordt gebruikgemaakt van labels.

Het werken met labels is kernfunctionaliteit voor SELinux. Op een SELinux systeem is alles voorzien van labels: poorten, bestanden, processen, users en meer. Deze labels definiëren heel nauwkeurig specifieke functionaliteit. Deze labels worden bijeengebracht in de zogenaamde context van een object. Deze context bestaat uit drie delen: user (te herkennen aan _u), role (te herkennen aan _r) en type (te herkennen aan _t). Elk object heeft een context.

Als nu een proces toegang wil tot een bestand, spelen deze contexten een rol. Het proces in kwestie heeft een source context en het bestand waar het proces naartoe wil heeft een target context en in de SELinux policy moeten beiden samengebracht worden in regels. Als er niet een regel is die toestaat dat een bepaalde source context een target context benadert, wordt toegang gewijzigd. De SELinux policy is de verzameling van alle regels die op een systeem bestaan.

Laten we eens kijken naar een voorbeeld dat rechtstreeks ontleent is aan een CentOS systeem. Doel is te begrijpen hoe het komt dat op een systeem dat met SELinux beveiligd is Apache toegang krijgt tot zijn DocumentRoot.

Het eerste dat een rol speelt is de source context van het Apache proces. Deze kan opgevraagd worden door de optie Z te gebruiken met het commando ps. In listing 1 zie je hoe de opdracht ps Zaux de SELinux context laat zien.

Listing 1: De opdracht ps Zaux laat de source context van Apache zien

[root@tls ~]# ps Zaux | grep http

system_u:system_r:httpd_t:s0    root      2002  0.0  0.2 186300  4344 ?        Ss   Oct22   0:07 /usr/sbin/httpd

system_u:system_r:httpd_t:s0    apache   13450  0.0  0.1 186300  2592 ?        S    Oct22   0:00 /usr/sbin/httpd

system_u:system_r:httpd_t:s0    apache   13451  0.0  0.1 186300  2592 ?        S    Oct22   0:00 /usr/sbin/httpd

system_u:system_r:httpd_t:s0    apache   13452  0.0  0.1 186300  2592 ?        S    Oct22   0:00 /usr/sbin/httpd

system_u:system_r:httpd_t:s0    apache   13453  0.0  0.1 186300  2592 ?        S    Oct22   0:00 /usr/sbin/httpd

system_u:system_r:httpd_t:s0    apache   13454  0.0  0.1 186300  2592 ?        S    Oct22   0:00 /usr/sbin/httpd

system_u:system_r:httpd_t:s0    apache   13455  0.0  0.1 186300  2592 ?        S    Oct22   0:00 /usr/sbin/httpd

system_u:system_r:httpd_t:s0    apache   13456  0.0  0.1 186300  2592 ?        S    Oct22   0:00 /usr/sbin/httpd

system_u:system_r:httpd_t:s0    apache   13459  0.0  0.1 186300  2592 ?        S    Oct22   0:00 /usr/sbin/httpd

In bovenstaande listing bestaat de context uit 3 delen: system_u, system_r en httpd_t. De eerste twee spelen alleen bij geavanceerde configuraties een rol, dus waar het hier om gaat is het label httpd_t waarmee Apache gemarkeerd wordt als van het type httpd_t.

 Nu wil Apache normaliter documenten aanbieden vanuit zijn document root, de directory /var/www in dit geval. Ook de items in deze directory zijn voorzien van labels zoals je kunt zien in listing 2:

Listing 2: context labels op het bestandssysteem

[root@tls www]# ls -Z

drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 bandwidthd

drwxr-xr-x. root root system_u:object_r:httpd_sys_script_exec_t:s0 cgi-bin

drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 error

drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 html

drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 icons

Zoals je kunt zien, is duidelijk te herkennen dat de directories voorzien zijn van een label dat het voor Apache eenvoudig moet maken de inhoud ervan aan te bieden. In de SELinux policy zijn regels die er voor zorgen dat een proces dat voorzien is van het label httpd_t toegang krijgt tot onder andere bestanden die gelabels zijn met httpd_sys_content_t.

De werking van SELinux op een server staat of valt met de aanwezigheid van een goede policy die rekening houdt met wat er op die server gebeurt. Nu is de policy op CentOS prima in orde en deze staat het toe dat Apache gewoon zijn ding mag doen in de standaard omgeving.

SELinux foutanalyse

Om nu echt te begrijpen wat er gebeurt, is het nuttig om ook eens te kijken naar wat er gebeurt als het een keer niet goed gaat.  Dit wordt duidelijk als we de documentroot van de Apache webserver veranderen naar een niet-standaard directory. Om dit voor elkaar te krijgen hebben we in /etc/httpd/conf/httpd.conf de regel DocumentRoot aangepast en laten verwijzen naar de directory /web. Deze directory /web hebben we net aangemaakt en in deze directory hebben we een bestand index.html geplaatst. In listing 3 zie je dat de SELinux context labels op het eerste gezicht niets te maken hebben met het functioneren van een Apache web server.

Listing 3: Context labels op een nieuwe directory

[root@tls /]# ls -Z web

-rw-r–r–. root root unconfined_u:object_r:default_t:s0 index.html

[root@tls /]# ls -Zd web

drwxr-xr-x. root root unconfined_u:object_r:default_t:s0 web

Als je nu probeert de inhoud van de nieuwe documentroot weer te geven in een browser, zul je merken dat je de standaard CentOS web pagina te zien krijgt, en niet de inhoud van het bestand index.html. Dit komt omdat er standaard geen SELinux regel bestaat in de policy die het een proces dat gelabeld is met httpd_t toestaat om bestanden te benaderen met het label default_t. Dit is overigens ook precies wat je wilt, en wat een hack tegen kan houden zoals beschreven is in de introductie van dit artikel.

Het is ook inzichtelijk te maken dat SELinux toegang tot deze directory blokkeert voor de web server. SELinux foutmeldingen worden weggeschreven naar de audit log (/var/log/audit/audit.log op een CentOS systeem). De foutmeldingen zijn te herkennen aan het label AVC, dus met een opdracht als tail -n 100 /var/log/audit/audit.log | grep AVC kun je alle SELinux foutmeldingen zien die in de laatste 100 audit log regels zijn weggeschreven (zie listing 4).

Listing 4: in audit.log zie je wat SELinux geblokkeerd heeft

type=AVC msg=audit(1382474065.474:9406): avc:  denied  { search } for  pid=15034 comm=”cgrulesengd” scontext=unconfined_u:system_r:cgred_t:s0 tcontext=system_u:object_r:sysctl_kernel_t:s0 tclass=dir

type=AVC msg=audit(1382474073.249:9407): avc:  denied  { search } for  pid=15034 comm=”cgrulesengd” scontext=unconfined_u:system_r:cgred_t:s0 tcontext=system_u:object_r:sysctl_kernel_t:s0 tclass=dir

type=AVC msg=audit(1382514573.557:14418): avc:  denied  { getattr } for  pid=13450 comm=”httpd” path=”/web/index.html” dev=dm-0 ino=533696 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file

type=AVC msg=audit(1382514573.558:14419): avc:  denied  { getattr } for  pid=13450 comm=”httpd” path=”/web/index.html” dev=dm-0 ino=533696 scontext=system_u:system_r:httpd_t:s0 tcontext=unconfined_u:object_r:default_t:s0 tclass=file

Laten we eens kijken naar de laatste regel die gelogd is. Het eerste belangrijke deel in deze regel is avc: denied { getattr } for pid=13450. Hier lezen we dat het proces met PID 13450 geprobeerd heeft attributen op te vragen voor iets en dat dat niet is toegestaan. Vervolgens lezen we dat het commando in kwestie httpd was, en dat het pad dat benaderd werd /web/index.html was. Hier is dus duidelijk te zien dat het gaat om de webserver die probeerde /web/index.html te benaderen en dat mag dus niet. In het laatste deel van de log regel lezen we waarom het niet mag: de scontext (source context) is httpd_t en de target context )tcontext) is default_t. En in de SELinux policy staat nu eenmaal niet een regel die toestaat dat httpd_t processen bestanden mogen benaderen die daar niet specifiek voor gelabeld zijn.

Context typen aanpassen

Op basis van het bovenstaande voorbeeld weten we nu wat er aan de hand is: het context type label op het bestand index.html staat niet goed. De oplossing is niet moeilijk te begrijpen: er moet voor gezorgd worden dat dit label wel goed staat. Hiervoor wordt gebruikgemaakt van het commando semanage fcontext. Dit commando zorgt ervoor dat de te gebruiken context in de policy wordt weggeschreven. Vervolgens moet de context ook naar het bestandssysteem geschreven worden en dat gebeurt met de opdracht restorecon.

Het uitgangspunt hierbij is dat alle context regels in de policy staan en van daaruit beheerd worden. Deze werkwijze is sterk aan te bevelen, want als er dan eens bestanden gekopieerd worden vanaf een medium waarop geen SELinux contexten staan, dan kan het bestandssysteem vanuit de policy eenvoudig opnieuw gelabeld worden. SELinux zal dat ook automatisch doen als er wijzigingen opgetreden zijn in het bestandssysteem die niet door SELinux gevolgd zijn.

Er is een andere manier om context labels aan te maken en dat is het commando chcon. Dit commando echter schrijft wijzigingen weg in het bestandssysteem en niet in de policy en zou je om die reden alleen in specifieke gevallen moeten gebruiken. Je riskeert namelijk dat wijzigingen die aangebracht zijn met chcon na een relabeling van het bestandssysteem met restorecon weer allemaal verdwijnen!

Om de context aan te passen, moet je eerst weten welke context dat moet zijn. Er zijn een aantal manieren om hier achter te komen:

*             Kijk of er een standaarddirectory is die de goede context labels al heeft

*             Kijk of er een specifieke SELinux man pagina is voor de betreffende service.

 De meest eenvoudige manier is om in een standaard directory te kijken naar het context label dat daar gebruikt is.  In dit geval is dat de directory /var/www/html, waarop je kunt zien dat de contextinstelling staat op system_u:object_r:httpd_sys_content_t. Dat is de context die uiteindelijk nodig is.

De alternatieve methode is te kijken in de SELinux specifieke man-pagina voor de service die je moet aanpassen.  Type man -k _selinux voor een overzicht van alle SELinux man pagina’s die er zijn, en kijk dan of er een is voor de service die je aan moet passen, bijvoorbeeld door er met grep een filter op los te laten. Deze aanpak leert dat er een httpd_selinux man pagina is waarin alle SELinux opties genoemd worden die voor de Apache web server relevant zijn. Op CentOS en Red Hat is een groot aantal van deze man pagina’s beschikbaar, op andere distributies zijn het er een stuk minder dus werkt deze aanpak niet altijd goed.  Wat ook niet handig is, is dat de hoeveelheid informatie in de man pagina’s bijzonder groot is waardoor het lastig kan zijn die informatie te vinden die je ook echt nodig hebt.

Nu we weten welke context ingesteld moet worden, rest nog deze context toe te passen. Hiervoor gebruiken we de volgende opdracht:

semanage fcontext -a -t httpd_sys_content_t “/web(/.*)?”

Met deze opdracht wordt de nieuwe context weggeschreven in de policy. De opdracht op zich is prima te begrijpen, maar qua syntaxis kan deze lastig zijn te onthouden, het goede nieuws is dat in de man pagina van semanage een heel goed voorbeeld staat dat je gewoon over kunt typen.  Let wel even op het onderdeel “/web(.*)?”. Dit is een vrij ingewikkelde reguliere expressie waarmee je aangeeft dat je de context toe wilt passen op de directory /web en alles wat daar eventueel onder bestaat. Als je contexten toepast op directories, zorg er dan altijd voor dat het onderdeel (/.*)? achter de directory staat zodat je ook de inhoud meeneemt.

Nadat de nieuwe context met semanage naar de policy is weggeschreven, rest nog hem toe te passen op het bestandssysteem. Dit doe je met de opdracht restorecon -R -v /web. De context is nu aangepast waardoor Apache toestemming zal hebben files uit de genoemde directory aan te bieden. Het is niet nodig hiervoor iets opnieuw te starten, de functionaliteit wordt geboden via de kernel en is daarom direct beschikbaar.

In dit artikel heb je kunnen lezen waarom je SELinux zou moeten gebruiken om je servers te beveiligen en hoe je dat dan moet doen. Een aantal onderwerpen is vanwege de beschikbare ruimte in dit artikel niet aan bod gekomen. Als eerste zijn dat de booleans (zie de commando’s getsebool en sesebool voor een indruk). Dit zijn makkelijk toe te passen aan/uit switches waarmee je eenvoudig onderdelen van de policy aan of uit zet.

Ook is niet aan bod gekomen hoe je SELinux totaal uit kunt zetten. Eigenlijk zou dat na het lezen van dit artikel niet meer nodig hoeven te zijn, maar soms vereisen leveranciers van producten het. Als het ooit nodig is, kun je SELinux uit zetten door in het bestand /etc/sysconfig/selinux de regel SELINUX=disabled op te nemen. Na een herstart van je server zal SELinux totaal uit staan en geen bescherming meer bieden.

Een veel verstandiger alternatief voor totaal uitzetten van SELinux, is om selinux in permissive mode te zetten met de opdracht setenforce permissive. Hierbij zal SELinux niets blokkeren, maar wel alles loggen in /var/log/audit/audit.log zodat je kunt zien wat SELinux geblokkeerd zou hebben als het in enforcing mode had gestaan. Dat maakt de SELinux permissive mode een ideale manier om problemen op te lossen en nukkige applicaties toch aan het werk te krijgen op een systeem dat door SELinux beschermd wordt.