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