Rzeczywiste znaczenie „shell = True” w podprocesie

260

Z subprocessmodułem wywołuję różne procesy . Mam jednak pytanie.

W następujących kodach:

callProcess = subprocess.Popen(['ls', '-l'], shell=True)

i

callProcess = subprocess.Popen(['ls', '-l']) # without shell

Oba działają. Po przeczytaniu dokumentacji dowiedziałem się, że shell=Trueoznacza to wykonanie kodu przez powłokę. Oznacza to, że w przypadku nieobecności proces rozpoczyna się bezpośrednio.

Więc co powinienem preferować w moim przypadku - muszę uruchomić proces i uzyskać jego wynik. Jakie korzyści czerpię z wywoływania go z poziomu powłoki lub poza nią.

użytkownik225312
źródło
21
pierwsze polecenie jest niepoprawne: -ljest przekazywane do /bin/sh(powłoki) zamiast lsprogramu w Uniksie, jeślishell=True . shell=TrueW większości przypadków należy użyć argumentu ciągu zamiast listy.
jfs
1
ponownie „proces jest bezpośrednio uruchamiany”: Wut?
allyourcode
9
Stwierdzenie „Oba działają”. o tych 2 połączeniach jest niepoprawna i myląca. Połączenia działają inaczej. Tylko przejście od shell=Truecelu Falsei vice versa jest błąd. Z dokumentacji : „W systemie POSIX z powłoką = prawda (...) Jeśli argument jest sekwencją, pierwszy element określa ciąg polecenia, a wszelkie dodatkowe elementy będą traktowane jako dodatkowe argumenty do samej powłoki.”. W systemie Windows istnieje automatyczna konwersja , która może być niepożądana.
mbdevpl,
Zobacz także stackoverflow.com/q/59641747/874188
tripleee

Odpowiedzi:

183

Zaletą nie wywoływania przez powłokę jest to, że nie wywołujesz „tajemniczego programu”. W POSIX zmienna środowiskowa SHELLkontroluje, który plik binarny jest wywoływany jako „powłoka”. W systemie Windows nie ma potomka powłoki Bourne'a, tylko cmd.exe.

Wywołanie powłoki wywołuje więc program wybrany przez użytkownika i zależy od platformy. Ogólnie rzecz biorąc, unikaj wywołań za pośrednictwem powłoki.

Wywoływanie za pośrednictwem powłoki umożliwia rozszerzanie zmiennych środowiskowych i globów plików zgodnie ze zwykłym mechanizmem powłoki. W systemach POSIX powłoka rozszerza globusy plików do listy plików. W systemie Windows glob pliku (np. „*. *”) I tak nie jest rozszerzany przez powłokę (ale zmienne środowiskowe w wierszu poleceń rozwijane przez cmd.exe).

Jeśli uważasz, że chcesz rozszerzeń zmiennych środowiskowych i globów plików, sprawdź ILSataki z 1992 roku na usługi sieciowe, które wykonały wywołania podprogramów za pośrednictwem powłoki. Przykłady obejmują różne sendmailzaangażowane backdoory ILS.

Podsumowując, użyj shell=False.

Heath Hunnicutt
źródło
2
Dziękuję za odpowiedź. Chociaż tak naprawdę nie jestem na etapie, w którym powinienem się martwić o exploity, ale rozumiem, do czego zmierzasz.
user225312
55
Jeśli na początku jesteś nieostrożny, żadne zmartwienie nie pomoże ci później nadrobić zaległości. ;)
Heath Hunnicutt
Co jeśli chcesz ograniczyć maksymalną pamięć podprocesu? stackoverflow.com/questions/3172470/…
Pramod
8
stwierdzenie o $SHELLjest niepoprawne. Cytując subprocess.html: „W Uniksie z shell=Truedomyślną powłoką jest /bin/sh.” (nie $SHELL)
marcin
1
@ user2428107: Tak, jeśli używasz wywołania wstecznego na Perlu, używasz wywoływania powłoki i otwierasz te same problemy. Użyj argumentu 3+, openjeśli chcesz w bezpieczny sposób wywołać program i przechwycić dane wyjściowe.
ShadowRanger
137
>>> import subprocess
>>> subprocess.call('echo $HOME')
Traceback (most recent call last):
...
OSError: [Errno 2] No such file or directory
>>>
>>> subprocess.call('echo $HOME', shell=True)
/user/khong
0

Ustawienie prawdziwej wartości argumentu powłoki powoduje, że podproces spawnuje pośredni proces powłoki i każe mu uruchomić polecenie. Innymi słowy, użycie powłoki pośredniej oznacza, że ​​zmienne, wzorce globów i inne specjalne funkcje powłoki w ciągu poleceń są przetwarzane przed uruchomieniem polecenia. W tym przykładzie $ HOME zostało przetworzone przed poleceniem echo. W rzeczywistości jest to przypadek polecenia z rozszerzaniem powłoki, podczas gdy polecenie ls -l uważane jest za proste polecenie.

źródło: Moduł podprocesu

Mina Gabriel
źródło
16
Nie wiem, dlaczego nie jest to wybrana odpowiedź. Zdecydowanie ten, który faktycznie pasuje do pytania
Rodrigo Lopez Guerra,
1
Zgodzić się. to dobry przykład dla mnie, aby zrozumieć, co oznacza shell = True.
user389955
2
Ustawienie prawdziwej wartości argumentu powłoki powoduje, że podproces odradza pośredni proces powłoki i każ mu uruchomić polecenie O Boże, to wszystko mówi. Dlaczego ta odpowiedź nie jest akceptowana? czemu?
pouya
Myślę, że pierwszym argumentem, który należy wywołać, jest lista, a nie ciąg znaków, ale to daje błąd, jeśli powłoka jest fałszywa. Zmiana polecenia na listę sprawi, że zadziała
Lincoln Randall McFarland
Przepraszam, mój poprzedni komentarz poszedł przed zakończeniem. Żeby było jasne: często widzę użycie podprocesu z powłoką = prawda, a polecenie jest łańcuchem, np. „Ls -l” (oczekuję, że uniknę tego błędu), ale podproces pobiera listę (i ciąg jako listę jednego elementu) . Aby uruchomić bez wywoływania powłoki (i związane z tym problemy bezpieczeństwa ) użyj listy subprocess.call (['ls', '-l'])
Lincoln Randall McFarland
42

Tutaj pokazano przykład, w którym coś może pójść nie tak z Shell = True

>>> from subprocess import call
>>> filename = input("What file would you like to display?\n")
What file would you like to display?
non_existent; rm -rf / # THIS WILL DELETE EVERYTHING IN ROOT PARTITION!!!
>>> call("cat " + filename, shell=True) # Uh-oh. This will end badly...

Sprawdź dokument tutaj: subprocess.call ()

Richeek
źródło
6
Link jest bardzo przydatny. Jak podano w linku: Wykonywanie poleceń powłoki zawierających niezarządzane dane wejściowe z niezaufanego źródła powoduje, że program jest podatny na wstrzykiwanie powłoki, co stanowi poważną lukę w zabezpieczeniach, która może spowodować wykonanie dowolnego polecenia. Z tego powodu zdecydowanie nie zaleca się używania shell = True w przypadkach, gdy ciąg poleceń jest zbudowany z danych zewnętrznych.
jtuki
39

Wykonywanie programów przez powłokę oznacza, że ​​wszystkie dane wejściowe użytkownika przekazywane do programu są interpretowane zgodnie ze składnią i regułami semantycznymi wywoływanej powłoki. W najlepszym wypadku powoduje to tylko niedogodności dla użytkownika, ponieważ użytkownik musi przestrzegać tych zasad. Na przykład ścieżki zawierające specjalne znaki powłoki, takie jak cudzysłów lub spacje, muszą być poprzedzone znakami ucieczki. W najgorszym przypadku powoduje wyciek bezpieczeństwa, ponieważ użytkownik może wykonywać dowolne programy.

shell=Trueczasem jest wygodne korzystanie z określonych funkcji powłoki, takich jak dzielenie słów lub rozwijanie parametrów. Jeśli jednak taka funkcja jest wymagana, skorzystaj z innych modułów, które otrzymałeś (np. W os.path.expandvars()celu rozszerzenia parametrów lub shlexpodziału słów). Oznacza to więcej pracy, ale pozwala uniknąć innych problemów.

W skrócie: Unikaj shell=True za wszelką cenę.

księżycowy
źródło
16

Pozostałe odpowiedzi w odpowiedni sposób wyjaśniają zastrzeżenia bezpieczeństwa, które są również wymienione w subprocessdokumentacji. Ale oprócz tego narzut związany z uruchomieniem powłoki w celu uruchomienia programu, który chcesz uruchomić, jest często niepotrzebny i zdecydowanie głupi w sytuacjach, w których nie używasz żadnej z funkcji powłoki. Co więcej, dodatkowa ukryta złożoność powinna cię wystraszyć, szczególnie jeśli nie znasz dobrze powłoki lub usług, które ona zapewnia.

Tam, gdzie interakcje z powłoką są nietrywialne, musisz teraz czytać i utrzymywać skrypt Pythona (który może, ale nie musi być twoim przyszłym ja), aby zrozumieć zarówno Python, jak i skrypt powłoki. Pamiętaj motto Pythona „wyraźne jest lepsze niż niejawne”; nawet jeśli kod Pythona będzie nieco bardziej skomplikowany niż równoważny (i często bardzo zwięzły) skrypt powłoki, lepiej jest usunąć powłokę i zastąpić funkcjonalność natywnymi konstrukcjami Pythona. Minimalizowanie pracy wykonanej w procesie zewnętrznym i utrzymywanie kontroli nad własnym kodem w miarę możliwości jest często dobrym pomysłem, ponieważ poprawia widoczność i zmniejsza ryzyko pożądanych lub niepożądanych efektów ubocznych.

Rozszerzanie symboli wieloznacznych, interpolacja zmiennych i przekierowanie można łatwo zastąpić natywnymi konstrukcjami języka Python. Złożony potok powłoki, w którym części lub wszystkich nie można w rozsądny sposób przepisać w Pythonie, byłby jedyną sytuacją, w której można rozważyć użycie powłoki. Nadal upewnij się, że rozumiesz wpływ na wydajność i bezpieczeństwo.

W trywialnym przypadku, aby tego uniknąć shell=True, wystarczy wymienić

subprocess.Popen("command -with -options 'like this' and\\ an\\ argument", shell=True)

z

subprocess.Popen(['command', '-with','-options', 'like this', 'and an argument'])

Zauważ, że pierwszym argumentem jest lista ciągów, które należy przekazać execvp(), i w jaki sposób cytowanie ciągów i metaznaków powłoki odwracających ukośnik nie jest generalnie konieczne (ani przydatne, ani poprawne). Może zobacz także Kiedy zawijać cudzysłowy wokół zmiennej powłoki?

Nawiasem mówiąc, bardzo często chcesz uniknąć, Popenjeśli jeden z prostszych opakowań w subprocesspakiecie robi to, co chcesz. Jeśli masz dość najnowszego Pythona, prawdopodobnie powinieneś go użyć subprocess.run.

  • Dzięki check=Truenie powiedzie się, jeśli uruchomione polecenie nie powiedzie się.
  • Dzięki stdout=subprocess.PIPEniemu przechwyci wynik polecenia.
  • Nieco niejasno, dzięki universal_newlines=Trueniemu dekoduje dane wyjściowe do odpowiedniego łańcucha Unicode (inaczej jest to tylko bytessystemowe kodowanie w Pythonie 3).

Jeśli nie, w przypadku wielu zadań chcesz check_outputuzyskać dane wyjściowe z polecenia, sprawdzając, czy się powiodło, lub check_callczy nie ma danych wyjściowych do zebrania.

Zakończę cytatem Davida Korna: „Łatwiej jest napisać przenośną powłokę niż przenośny skrypt powłoki”. Nawet subprocess.run('echo "$HOME"', shell=True)nie jest przenośny na Windows.

potrójny
źródło
Myślałem, że cytat pochodzi od Larry'ego Walla, ale Google mówi mi inaczej.
tripleee
To duża rozmowa - ale nie ma technicznych sugestii dotyczących wymiany: oto jestem, na OS-X, próbuję uzyskać pid aplikacji Mac uruchomionej przez „open”: process = subprocess.Popen ('/ usr / bin / pgrep - n '+ nazwa_aplikacji, shell = False, stdout = podproces.PIPE, stderr = podproces.PIPE) app_pid, err = process.communicate () --- ale to nie działa, chyba że użyję shell = True. Co teraz?
Motti Shneor
Jest mnóstwo pytań na temat tego, jak tego uniknąć shell=True, wiele z doskonałymi odpowiedziami. Zdarzyło ci się wybrać ten, który dotyczy tego zamiast tego.
tripleee
@MottiShneor Dzięki za opinie; dodano prosty przykład
potrójny