Zezwalaj na setuid w skryptach powłoki

184

setuidNieco pozwolenie mówi Linux do uruchomienia programu z efektywnym id użytkownika właściciela zamiast wykonawcy:

> cat setuid-test.c

#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    printf("%d", geteuid());
    return 0;
}

> gcc -o setuid-test setuid-test.c
> ./setuid-test

1000

> sudo chown nobody ./setuid-test; sudo chmod +s ./setuid-test
> ./setuid-test

65534

Dotyczy to jednak tylko plików wykonywalnych; skrypty powłoki ignorują bit setuid:

> cat setuid-test2

#!/bin/bash
id -u

> ./setuid-test2

1000

> sudo chown nobody ./setuid-test2; sudo chmod +s ./setuid-test2
> ./setuid-test2

1000

Wikipedia mówi :

Ze względu na zwiększone prawdopodobieństwo błędów w zabezpieczeniach wiele systemów operacyjnych ignoruje atrybut setuid, gdy jest stosowany do wykonywalnych skryptów powłoki.

Zakładając, że jestem gotów zaakceptować to ryzyko, czy jest jakiś sposób, aby powiedzieć Linuksowi, aby traktował setuid tak samo w skryptach powłoki, jak w plikach wykonywalnych?

Jeśli nie, to czy istnieje wspólne obejście tego problemu? Moje obecne rozwiązanie polega na dodaniu sudoerswpisu umożliwiającego ALLuruchomienie danego skryptu jako użytkownik, którego chcę, aby był uruchamiany, NOPASSWDaby uniknąć pytania o hasło. Główną wadą tego jest potrzeba sudoerswpisu za każdym razem, gdy chcę to zrobić, i potrzeba dzwoniącego sudo some-scriptzamiast po prostusome-script

Michał Mrożek
źródło

Odpowiedzi:

202

Linux ignoruje bit setuid¹ na wszystkich interpretowanych plikach wykonywalnych (tj. Plikach rozpoczynających się od #!linii). Comp.unix.questions FAQ wyjaśnia problemy bezpieczeństwa z setuid skryptów powłoki. Problemy te są dwojakiego rodzaju: związane z shebangiem i związane z powłoką; Więcej szczegółów poniżej.

Jeśli nie dbasz o bezpieczeństwo i chcesz zezwolić na skrypty setuid, pod Linuksem musisz załatać jądro. Jeśli chodzi o jądra 3.x, myślę, że musisz dodać wywołanie install_exec_credsw load_scriptfunkcji przed wywołaniem do open_exec, ale nie testowałem.


Setuid shebang

Występuje warunek wyścigu nieodłącznie związany ze sposobem, w jaki shebang ( #!) jest zwykle implementowany:

  1. Jądro otwiera plik wykonywalny i stwierdza, że ​​zaczyna się od #!.
  2. Jądro zamyka plik wykonywalny i zamiast tego otwiera interpreter.
  3. Jądro wstawia ścieżkę do skryptu na listę argumentów (as argv[1]) i wykonuje interpreter.

Jeśli w tej implementacji dozwolone są skrypty setuid, osoba atakująca może wywołać dowolny skrypt, tworząc dowiązanie symboliczne do istniejącego skryptu setuid, wykonując go i organizując zmianę łącza po wykonaniu kroku 1 przez jądro i zanim interpreter przejdzie do otwierając swój pierwszy argument. Z tego powodu większość jednorożców ignoruje bit setuid, gdy wykrywa shebang.

Jednym ze sposobów zabezpieczenia tej implementacji byłoby zablokowanie pliku skryptu przez jądro, dopóki interpreter go nie otworzy (pamiętaj, że musi to zapobiegać nie tylko odłączeniu lub zastąpieniu pliku, ale także zmianie nazwy dowolnego katalogu na ścieżce). Ale systemy uniksowe zwykle unikają obowiązkowych blokad, a dowiązania symboliczne sprawiłyby, że poprawna funkcja blokady byłaby szczególnie trudna i inwazyjna. Nie sądzę, żeby ktokolwiek to robił w ten sposób.

Kilka systemów uniksowych (głównie OpenBSD, NetBSD i Mac OS X, z których wszystkie wymagają włączenia ustawienia jądra) implementuje bezpieczny shebang setuid za pomocą dodatkowej funkcji: ścieżka odnosi się do pliku już otwartego w deskryptorze pliku N (więc otwieranie jest z grubsza odpowiada ). Wiele systemów uniksowych (w tym Linux) ma skrypty setuid./dev/fd/N/dev/fd/Ndup(N)/dev/fd

  1. Jądro otwiera plik wykonywalny i stwierdza, że ​​zaczyna się od #!. Powiedzmy, że deskryptorem pliku dla pliku wykonywalnego jest 3.
  2. Jądro otwiera interpreter.
  3. Jądro wstawia /dev/fd/3listę argumentów (as argv[1]) i wykonuje interpreter.

Strona shebang Svena Maschecka zawiera wiele informacji na temat shebang u jednorożców, w tym wsparcie setuid .


Tłumacze Setuid

Załóżmy, że udało Ci się uruchomić program jako root, albo dlatego, że twój system obsługuje setuid shebang, albo dlatego, że użyłeś natywnego binarnego opakowania (takiego jak sudo). Czy otworzyłeś lukę bezpieczeństwa? Może . Tutaj nie chodzi o programy interpretowane a skompilowane. Problem polega na tym, czy system wykonawczy zachowuje się bezpiecznie, jeśli jest wykonywany z uprawnieniami.

  • Każdy dynamicznie połączony natywny binarny plik wykonywalny jest w sposób interpretowany przez program ładujący dynamiczny (np. /lib/ld.so), Który ładuje biblioteki dynamiczne wymagane przez program. W wielu jednostkach unikalnych można skonfigurować ścieżkę wyszukiwania bibliotek dynamicznych za pośrednictwem środowiska ( LD_LIBRARY_PATHjest to wspólna nazwa zmiennej środowiskowej), a nawet załadować dodatkowe biblioteki do wszystkich wykonywanych plików binarnych ( LD_PRELOAD). Wywołującego programu można wykonać dowolny kod w kontekście tego programu poprzez umieszczenie specjalnie spreparowane libc.sow $LD_LIBRARY_PATH(między innymi taktyki). Wszystkie zdrowe systemy ignorują LD_*zmienne w plikach wykonywalnych setuid.

  • W powłokach, takich jak sh, csh i pochodne, zmienne środowiskowe automatycznie stają się parametrami powłoki. Poprzez parametrów takich jak PATH, IFSi wiele więcej, wywołującego skrypt ma wiele szans na wykonanie dowolnego kodu w kontekście skryptów powłoki. Niektóre powłoki ustawiają te zmienne na rozsądne wartości domyślne, jeśli wykryją, że skrypt został wywołany z uprawnieniami, ale nie wiem, czy istnieje jakaś konkretna implementacja, której mógłbym zaufać.

  • Większość środowisk wykonawczych (macierzystych, kodu bajtowego lub interpretowanych) ma podobne funkcje. Niewielu zachowuje szczególne środki ostrożności w plikach wykonywalnych setuid, chociaż te, które uruchamiają natywny kod, często nie robią nic bardziej wymyślnego niż dynamiczne łączenie (które podejmuje środki ostrożności).

  • Perl jest godnym uwagi wyjątkiem. To wyraźnie obsługuje skryptów setuid w bezpieczny sposób. W rzeczywistości skrypt może uruchamiać setuid, nawet jeśli system operacyjny zignorował bit setuid w skryptach. Wynika to z faktu, że Perl jest dostarczany z pomocnikiem setuid root, który wykonuje niezbędne kontrole i ponownie wywołuje interpreter na pożądanych skryptach z pożądanymi uprawnieniami. Jest to wyjaśnione w podręczniku perlsec . Kiedyś potrzebne były #!/usr/bin/suidperl -wTzamiast tego skrypty pertu z setuid #!/usr/bin/perl -wT, ale w większości nowoczesnych systemów #!/usr/bin/perl -wTsą wystarczające.

Zauważ, że użycie natywnego opakowania binarnego samo w sobie nie zapobiega tym problemom . W rzeczywistości może to pogorszyć sytuację , ponieważ może uniemożliwić środowisku wykonawczemu wykrycie, że jest wywoływane z uprawnieniami i pominięcie jego konfigurowalności.

Natywne opakowanie binarne może uczynić skrypt powłoki bezpiecznym, jeśli opakowanie dezynfekuje środowisko . Skrypt musi uważać, aby nie przyjmować zbyt wielu założeń (np. Dotyczących bieżącego katalogu), ale tak jest. Możesz użyć do tego sudo, pod warunkiem, że jest skonfigurowany do odkażania środowiska. Zmienne na czarnej liście są podatne na błędy, dlatego zawsze należy do białej listy. W sudo upewnij się, że env_resetopcja jest włączona, że setenvjest wyłączona env_filei env_keepzawiera tylko nieszkodliwe zmienne.


TL, DR:

  • Setuid shebang jest niepewny, ale zwykle jest ignorowany.
  • Jeśli uruchamiasz program z uprawnieniami (przez sudo lub setuid), napisz kod natywny lub perl lub uruchom program z opakowania, które dezynfekuje środowisko (takie jak sudo z env_resetopcją).

¹ Dyskusja ta ma zastosowanie również w przypadku zastąpienia słowa „setgid” słowem „setuid”; oba są ignorowane przez jądro Linuksa w skryptach

Gilles
źródło
2
@Josh: Bezpieczne skrypty powłoki setuid są możliwe, ale tylko wtedy, gdy zarówno implementator powłoki, jak i autor skryptów są bardzo ostrożni. Zamiast kodu natywnego polecam Perla, w którym realizatorzy zadbali o to, aby skrypty setuid były bezpieczne przy niewielkim wysiłku ze strony autora skryptów.
Gilles
3
najwidoczniej suidperlrzeczy były przestarzałe i oznaczone do usunięcia przez lata (ale nadal są dostępne)
jmtd
7
W rzeczywistości suidperlzostał usunięty od wersji Perla 5.11 (stabilny 5.12): perl5110delta:> „suidperl” został usunięty. Kiedyś zapewniał mechanizm emulacji bitów uprawnień setuid w systemach, które nie obsługują go poprawnie. perl5120delta:> „suidperl” nie jest już częścią Perla. Kiedyś zapewniał mechanizm emulacji bitów uprawnień setuid w systemach, które nie obsługują go poprawnie.
Randy Stauner,
5
Zwróć także uwagę na ten wiersz z dokumentacji Perla 5.6.1 (prawie dekadę temu) ... perl561delta:> Zauważ, że suidperl nie jest ani wbudowany, ani domyślnie instalowany w żadnej najnowszej wersji perla. Stosowanie suidperl jest wysoce odradzane . Jeśli uważasz, że potrzebujesz, najpierw wypróbuj alternatywy, takie jak sudo. Zobacz courtesan.com/sudo .
Randy Stauner
3
Nie rozumiem: wydaje się to wyjaśnieniem przyczyn problemów, ale czy istnieje tutaj rzeczywista odpowiedź na pytanie PO? Czy istnieje sposób, aby powiedzieć mojemu systemowi operacyjnemu, aby uruchamiał skrypty powłoki setuid?
Tom
55

Jednym ze sposobów rozwiązania tego problemu jest wywołanie skryptu powłoki z programu, który może używać bitu setuid.
to coś w stylu sudo. Na przykład oto, jak można to osiągnąć w programie C:

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    setuid( 0 );   // you can set it at run time also
    system( "/home/pubuntu/setuid-test2.sh" );
    return 0;
 }

Zapisz go jako setuid-test2.c.
kompiluj
Teraz wykonaj setuid na tym programie binarnym:

su - nobody   
[enter password]  
chown nobody:nobody a.out  
chmod 4755 a.out  

Teraz powinieneś być w stanie go uruchomić, a zobaczysz, że twój skrypt jest wykonywany bez żadnych uprawnień.
Ale tutaj musisz albo zakodować ścieżkę skryptu, albo przekazać ją jako argument linii poleceń powyżej exe.

Hemant
źródło
25
Odradzałbym sugestię, aby zezwolić na przekazywanie skryptu jako argumentu wiersza poleceń, ponieważ zasadniczo daje to każdemu, kto może uruchomić program, możliwość uruchomienia dowolnego skryptu jako zdefiniowany użytkownik.
dsp
39
Pamiętaj, że TO NIE JEST BEZPIECZNE, nawet jeśli pełna ścieżka do skryptu jest zakodowana na stałe. Powłoka odziedziczy zmienne ze środowiska, a wiele z nich umożliwia inwokatowi wstrzyknięcie dowolnego kodu. PATHi LD_LIBRARY_PATHsą oczywistymi wektorami. Niektóre muszle wykonać $ENVlub $BASHENVlub ~/.zshenvjeszcze przed rozpoczęciem właściwego wykonywania skryptu, więc nie może chronić od nich w ogóle od wewnątrz skryptu. Jedynym bezpiecznym sposobem na wywołanie skryptu powłoki z uprawnieniami jest oczyszczenie środowiska . Sudo wie, jak to zrobić bezpiecznie. Więc nie pisz własnego opakowania, użyj sudo .
Gilles
28
Źle się czuję, że nagle został za to zlekceważony - powiedziałem konkretnie, że chcę też usłyszeć niepewne wersje, i wyobrażałem sobie plik wykonywalny, który wziął argument za skryptem powłoki, kiedy to powiedziałem. Oczywiście jest to bardzo niepewne, ale chciałem wiedzieć, jakie są możliwości
Michael Mrozek
8
@Gilles: FYI, Linux resetuje LD_LIBRARY_PATHmiędzy innymi, gdy napotka bit setuid.
grawitacja
3
Zamiast systemkorzystać z jednej z execrodziny może być łatwiej (i wydajniej) - najprawdopodobniej execve. W ten sposób nie tworzysz nowego procesu ani nie uruchamiasz powłoki i możesz przekazywać argumenty (zakładając, że twój uprzywilejowany skrypt może bezpiecznie obsługiwać argumenty).
Toby Speight
23

Prefiksuję kilka skryptów znajdujących się w tej łodzi w ten sposób:

#!/bin/sh
[ "root" != "$USER" ] && exec sudo $0 "$@"

Zauważ, że to nie używa, setuidale po prostu wykonuje bieżący plik za pomocą sudo.

rcrowley
źródło
4
Nie używa setuid, a jedynie wyświetla sudomonit. (Dla mnie sedno setuida polega na tym, że pozwala, aby rzeczy działały jako root bez potrzeby sudo.)
Luc
12

Jeśli chcesz uniknąć dzwonienia sudo some_script, możesz po prostu:

  #!/ust/bin/env sh

  sudo /usr/local/scripts/your_script

Programy SETUID należy projektować z najwyższą starannością, ponieważ działają z uprawnieniami roota, a użytkownicy mają nad nimi dużą kontrolę. Muszą wszystko sprawdzić zdrowo. Nie możesz tego zrobić za pomocą skryptów, ponieważ:

  • Powłoki są dużymi programami, które silnie współdziałają z użytkownikiem. Prawidłowe sprawdzenie wszystkiego jest prawie niemożliwe - zwłaszcza, że ​​większość kodu nie jest przeznaczona do działania w takim trybie.
  • Skrypty są przeważnie szybkim i brudnym rozwiązaniem i zwykle nie są przygotowywane z taką starannością, by pozwalały na setuid. Mają wiele potencjalnie niebezpiecznych funkcji.
  • Zależą one w dużym stopniu od innych programów. Nie wystarczy sprawdzenie powłoki. sed, awkitp. również musiałyby zostać sprawdzone

Pamiętaj, że sudozapewnia pewne sprawdzanie poprawności, ale nie jest wystarczające - sprawdź każdą linię w swoim własnym kodzie.

Ostatnia uwaga: rozważ użycie możliwości. Pozwalają one nadać procesowi działającemu jako użytkownik specjalne uprawnienia, które normalnie wymagałyby uprawnień administratora. Jednak na przykład, chociaż pingtrzeba manipulować siecią, nie musi ona mieć dostępu do plików. Nie jestem jednak pewien, czy są one dziedziczone.

Maciej Piechotka
źródło
2
Przypuszczam, że /ust/bin/envpowinno być /usr/bin/env.
Bob
4

Możesz utworzyć alias dla sudo + nazwę skryptu. Oczywiście, jest to jeszcze więcej pracy, aby skonfigurować, ponieważ musisz także ustawić alias, ale to oszczędza ci konieczności wpisywania sudo.

Ale jeśli nie masz nic przeciwko strasznym zagrożeniom bezpieczeństwa, użyj powłoki setuid jako interpretera skryptu powłoki. Nie wiem, czy to zadziała dla ciebie, ale myślę, że może.

Chciałbym jednak powiedzieć, że odradzam to robić. Wspominam o tym w celach edukacyjnych ;-)

wzzrd
źródło
5
To będzie działać. Okropnie, jak powiedziałeś. Bit SETUID umożliwia wykonanie z prawem właściciela. Powłoka Setuid (chyba że została zaprojektowana do pracy z setuid) będzie działać jako root dla każdego użytkownika. Oznacza to, że każdy może uruchomić rm -rf /(i inne polecenia z serii DON'T DO IT AT HOME ).
Maciej Piechotka,
9
@MaciejPiechotka od DON'T DO IT AT HOME masz na myśli Zapraszam do robienia tego w pracy? :)
peterph
4

komenda super [-r reqpath] [args]

Super pozwala określonym użytkownikom na wykonywanie skryptów (lub innych poleceń) tak, jakby byli rootami; lub może ustawić UID, GID i / lub dodatkowe grupy dla poszczególnych poleceń przed wykonaniem polecenia. Ma to stanowić bezpieczną alternatywę dla ustawiania skryptów jako root. Super umożliwia także zwykłym użytkownikom dostarczanie poleceń do wykonania przez innych; są one wykonywane z identyfikatorami UID, GID i grupami użytkowników oferujących polecenie.

Super sprawdza plik `` super.tab '', aby sprawdzić, czy użytkownik może wykonać żądane polecenie. Jeśli zezwolenie zostanie udzielone, super wykona pgm [args], gdzie pgm jest programem powiązanym z tym poleceniem. (Rootowanie jest domyślnie dozwolone, ale nadal można odmówić, jeśli reguła wyklucza rootowanie. Zwykłym użytkownikom domyślnie nie zezwala się na wykonywanie.)

Jeśli polecenie jest dowiązaniem symbolicznym (lub też linkiem twardym) do super programu, wówczas wpisanie% polecenie args jest równoważne z wpisaniem% super polecenia args (polecenie nie może być super, lub super nie rozpozna, że ​​jest wywoływane przez połączyć.)

http://www.ucolick.org/~will/RUE/super/README

http://manpages.ubuntu.com/manpages/utopic/en/man1/super.1.html

Nizam Mohamed
źródło
Witam na stronie! Oczekujemy, że odpowiedzi będą bardziej szczegółowe tutaj. Czy mógłbyś edytować swoją odpowiedź i wyjaśnić, czym jest ten program i jak może pomóc rozwiązać problem PO?
terdon
Dziękuję Nizam, za godziny próbujące znaleźć coś super, aby konta mogły być wykonywane przez innego użytkownika jako użytkownika pliku.
Czad
2

Jeśli z jakiegoś powodu sudonie jest dostępny, możesz napisać cienki skrypt opakowania w C:

#include <unistd.h>
int main() {
    setuid(0);
    execle("/bin/bash","bash","/full/path/to/script",(char*) NULL,(char*) NULL);
}

I po kompilacji to ustawić go jako setuidz chmod 4511 wrapper_script.

Jest to podobne do innej opublikowanej odpowiedzi, ale uruchamia skrypt w czystym środowisku i jawnie używa /bin/bashzamiast powłoki wywoływanej przez system(), a zatem zamyka niektóre potencjalne luki w zabezpieczeniach.

Pamiętaj, że całkowicie usuwa środowisko. Jeśli chcesz użyć niektórych zmiennych środowiskowych bez ujawniania luk, naprawdę musisz po prostu użyć sudo.

Oczywiście chcesz się upewnić, że sam skrypt jest zapisywalny tylko przez root.

Chris
źródło
-3

Po raz pierwszy znalazłem to pytanie, nieprzekonujące wszystkimi odpowiedziami, tutaj jest o wiele lepsze, które pozwala również całkowicie zaciemnić skrypt bash, jeśli jesteś tak skłonny!

Powinno to być oczywiste.

#include <string>
#include <unistd.h>

template <typename T, typename U>
T &replace (
          T &str, 
    const U &from, 
    const U &to)
{
    size_t pos;
    size_t offset = 0;
    const size_t increment = to.size();

    while ((pos = str.find(from, offset)) != T::npos)
    {
        str.replace(pos, from.size(), to);
        offset = pos + increment;
    }

    return str;
}

int main(int argc, char* argv[])
{
    // Set UUID to root
    setuid(0);

    std::string script = 
R"(#!/bin/bash
whoami
echo $1
)";

    // Escape single quotes.
    replace(script, std::string("'"), std::string("'\"'\"'"));

    std::string command;
    command = command + "bash -c '" + script + "'"; 

    // Append the command line arguments.
    for (int a = 0; a < argc; ++a)
    {
        command = command + " " + argv[a];
    }

    return system(command.c_str());
}

Potem biegniesz

g++ embedded.cpp -o embedded
sudo chown root embedded
sudo chmod u+s embedded
Theodore R. Smith
źródło
Dlaczego opinie negatywne ???
Theodore R. Smith
2
Prawdopodobnie dlatego, że jest to zbyt skomplikowane rozwiązanie, które manipuluje ciągami znaków i ma osadzony kod powłoki. Albo tworzysz prosty program uruchamiający, albo używasz sudo. To najgorsza ze wszystkich opcji.
siride