Podsumowanie ucieczek z sudo: GTFOBins (skaner)
Napisał: Patryk Krawaczyński
14/03/2021 w Bezpieczeństwo Brak komentarzy. (artykuł nr 776, ilość słów: 1106)
Jest to ostatni wpis odnośnie ucieczek (1, 2, 3, 4) z sudo. Aktywnie utrzymywana, wyselekcjonowanych plików binarnych oraz skryptów Linuksa, których można użyć do ominięcia lokalnych ograniczeń bezpieczeństwa w źle skonfigurowanych systemach jest zawarta w projekcie GTFOBins (alternatywa dla systemu Windows to LOLBAS). Projekt gromadzi wykorzystanie zaimplementowanych funkcjonalności *niksowych plików binarnych, które mogą być wykorzystane do wyłamywania się z ograniczeń powłok, poleceń sudo, eskalacji uprawnień, czytania zastrzeżonych plików, czy uruchamiania powłok zwrotnych. Należy mieć na uwadze, że nie jest to lista eksploitów, a programy wymienione w GTFOBins same w sobie nie są podatne na ataki. Raczej projekt ten jest kompendium wiedzy o tym, jak wykorzystać je jeśli, któreś z nich uruchamiane jest z prawami administratora lub w ograniczonych środowiskach / powłokach.
Przedstawioną listę możemy również wykorzystać w „dobrym” celu. Na przykład napisać prosty „skaner”, który będzie przeszukiwał naszą infrastrukturę w celu weryfikacji, czy inni administratorzy przez przypadek nie dodali jednego z wymienionych plików do programu sudo lub nadali im prawa setuid. Pierwszym komponentem skanera będzie skrypt w powłoce bash, który stworzy nam dwie listy. Pierwsza posłuży do przeszukiwania pliku /etc/sudoers, a druga do plików SUID:
#!/usr/bin/env bash
set -Eeuo pipefail
GTFOBINS="GTFOBins.github.io"
if ! command -v git $> /dev/null
then
echo "git command not found."
exit 1
else
if [ -d GTFOBins.github.io ]
then
echo "GTFOBins repository already exists."
else
git clone https://github.com/GTFOBins/$GTFOBINS
fi
> gtfobins_sudo.txt
> gtfobins_suid.txt
for f in `ls -1 $GTFOBINS/_gtfobins`
do
if grep -i sudo $GTFOBINS/_gtfobins/$f > /dev/null 2>&1
then
echo -e "${f%.md}" >> gtfobins_sudo.txt
fi
if grep -i suid $GTFOBINS/_gtfobins/$f > /dev/null 2>&1
then
echo -e "${f%.md}" >> gtfobins_suid.txt
fi
done
fi
Ponieważ ściągamy repozytorium z serwisu github, powinniśmy mieć w systemie zainstalowany pakiet git. Po uruchomieniu skryptu, który pozwoliłem sobie nazwać: create_list.sh – w bieżącym katalogu powinny powstać dwa pliki: gtfobins_sudo.txt oraz gtfobins_suid.txt. Po ich zawartości możemy sobie iterować sprawdzając odpowiednie ścieżki w systemie. Za taki iterator możemy wykorzystać ansible, który bardzo fajnie sprawdza się do pisania różnych scenariuszy wykonujących zadania na serwerach:
---
- name: "This playbook scans system for GTFOBins presence."
hosts: "{{ servers }}"
gather_facts: no
serial: 1
tasks:
- name: "Check GTFOBins in /etc/sudoers entries."
lineinfile:
path: /etc/sudoers
state: absent
regexp: '^[^#].*{{ item }}.*'
check_mode: yes
register: sudoers
loop: "{{ lookup('file', 'gtfobins_sudo.txt').splitlines() }}"
- name: "Find SUIDs files in system."
shell: find / -perm /4000 -print 2>/dev/null > /tmp/suids; chmod 640 /tmp/suids
register: findsuids
- name: "Check GTFOBins in SUID entries."
lineinfile:
path: /tmp/suids
state: absent
regexp: '.*\/{{ item }}$'
check_mode: yes
register: suids
loop: "{{ lookup('file', 'gtfobins_suid.txt').splitlines() }}"
- name: "Remove SUIDs file list from system."
file:
path: /tmp/suids
state: absent
Tak zapisany playbook pod nazwą: scan_gtfobins.yml możemy uruchomić na wybranej grupie serwerów, którą chcemy sprawdzić:
agresor@darkstar:~$ ansible-playbook scan_gtfobins.yml --extra-vars "servers=web" \ --diff -u secops --become --ask-become-pas --ask-pass
W przypadku trafienia powinniśmy zobaczyć je na ekranie, co daje nam możliwość jego weryfikacji:
TASK [Check GTFOBins in SUID entries.] *** --- before: /tmp/suids (content) +++ after: /tmp/suids (content) @@ -1,6 +1,5 @@ /bin/ping /bin/su -/bin/cat /bin/fusermount /bin/umount /usr/lib/openssh/ssh-keysign changed: [web-farm1.dc66.lan] => (item=cat)
Niestety ze względu na fakt, że próbujemy dość uniwersalnie łapać dopasowania do wpisów w pliku /etc/sudoers (które mogą mieć różne formy zapisu) na nim może zostać wygenerowanych najwięcej trafień typu false positive:
TASK [Check GTFOBins in /etc/sudoers entries.] *** --- before: /etc/sudoers (content) +++ after: /etc/sudoers (content) @@ -7,7 +7,6 @@ # See the man page for details on how to write a sudoers file. # Defaults env_reset -Defaults mail_badpass # Host alias specification changed: [web-farm1.dc66.lan] => (item=ss)
Na szczęście z kolejnymi uruchomieniami możemy poddawać ewolucji przyjęte warunki w scenariuszach, co finalnie powinno przybliżyć nas do bardzo małej liczby pomyłek:
- regexp: '^[^#].*{{ item }}.*'
+ regexp: '^[^(#|Defaults)].*{{ item }}.*'
Dla pełni podstawowego szkieletu skanera pozostaje jeszcze obsługa dodatkowych plików programu sudo skonfigurowanych za pomocą dyrektywy: #includedir (standardowa wartość to: /etc/sudoers.d):
- name: "Get #includedir value from /etc/sudoers."
command: grep -oP '(?<=#includedir ).*' /etc/sudoers
register: checksudoers
changed_when: False
- name: "Get sudoers files from {{ checksudoers.stdout }}"
find:
paths: "{{ checksudoers.stdout }}"
file_type: file
register: sudoers_files
changed_when: False
when: checksudoers.rc == 0
- name: "Check GTFOBins in {{ checksudoers.stdout }}"
lineinfile:
path: "{{ item[0].path }}"
state: absent
regexp: '^[^(#|Defaults)].*{{ item[1] }}.*'
check_mode: yes
register: multisudoers
with_nested:
- "{{ sudoers_files.files }}"
- "{{ lookup('file', 'gtfobins_sudo.txt').splitlines() }}"
when:
- checksudoers.rc == 0
- sudoers_files.files | length < 0
Jeśli używamy innego rozwiązania automatyzującego zadania to nic nie szkodzi na przeszkodzie byśmy do tego celu wykorzystali np. fabric, puppet, chef, czy SaltStack.
Więcej informacji: man sudoers

