2011-07-23 16 views
15

Czy poniższy fragment kodu z aplikacji Python WSGI jest bezpieczny z katalogu traversal? Odczytuje nazwę pliku przekazaną jako parametr i zwraca wskazany plik.Czy mój kod zapobiega przechodzeniu katalogu?

file_name = request.path_params["file"] 
file = open(file_name, "rb") 
mime_type = mimetypes.guess_type(file_name)[0] 
start_response(status.OK, [('Content-Type', mime_type)]) 
return file 

I zamontowany aplikację pod http://localhost:8000/file/{file} i wysłane zapytania z adresów URL http://localhost:8000/file/../alarm.gif i http://localhost:8000/file/%2e%2e%2falarm.gif. Ale żadna z moich prób nie dostarczyła (istniejącego) pliku. Czy mój kod jest już bezpieczny z katalogu?

Nowe podejście

Wydaje się następującym kodem uniemożliwia przechodzenie katalogu:

file_name = request.path_params["file"] 
absolute_path = os.path.join(self.base_directory, file_name) 
normalized_path = os.path.normpath(absolute_path) 

# security check to prevent directory traversal 
if not normalized_path.startswith(self.base_directory): 
    raise IOError() 

file = open(normalized_path, "rb") 
mime_type = mimetypes.guess_type(normalized_path)[0] 
start_response(status.OK, [('Content-Type', mime_type)]) 
return file 
+1

Co stanie się, jeśli nadasz mu absolutną ścieżkę? – katrielalex

+0

Dobry pomysł! Twoja sugestia doprowadziła mnie do odpowiedzi: jest niepewna! Przemieszczenie katalogów zostało "przypadkowo" zablokowane przez inną część używanego środowiska. – deamon

Odpowiedz

18

Twój kod nie uniemożliwia przechodzenie katalogów. Możesz tego uniknąć poprzez moduł os.path.

>>> import os.path 
>>> os.curdir 
'.' 
>>> startdir = os.path.abspath(os.curdir) 
>>> startdir 
'/home/jterrace' 

startdir jest ścieżka bezwzględna, gdzie nie chcesz, aby ścieżka iść poza. Teraz załóżmy, że otrzymujemy nazwę pliku od użytkownika i podają nam złośliwą /etc/passwd.

>>> filename = "/etc/passwd" 
>>> requested_path = os.path.relpath(filename, startdir) 
>>> requested_path 
'../../etc/passwd' 
>>> requested_path = os.path.abspath(requested_path) 
>>> requested_path 
'/etc/passwd' 

Przekształciliśmy teraz ich ścieżkę w bezwzględną ścieżkę względem naszej ścieżki początkowej. Ponieważ nie było to na ścieżce początkowej, nie ma przedrostka naszej ścieżki początkowej.

>>> os.path.commonprefix([requested_path, startdir]) 
'/' 

Możesz to sprawdzić w swoim kodzie. Jeśli funkcja commonprefix zwraca ścieżkę, która nie rozpoczyna się od startdir, ścieżka jest nieprawidłowa i nie powinieneś zwracać zawartości.


Powyższe może być owinięta w sposób statyczny w taki sposób:

import os 

def is_directory_traversal(file_name): 
    current_directory = os.path.abspath(os.curdir) 
    requested_path = os.path.relpath(file_name, start=current_directory) 
    requested_path = os.path.abspath(requested_path) 
    common_prefix = os.path.commonprefix([requested_path, current_directory]) 
    return common_prefix != current_directory 
+2

Po prostu nie polegaj na robieniu rzeczy w stosunku do bieżącego katalogu roboczego, ponieważ może to być wszystko w aplikacji internetowej. Powinien zawsze opierać go na bezwzględnej ścieżce jako punkcie początkowym, czy jest to połączenie przewodowe czy obliczone z \ _ \ _ pliku \ _ \ _. –

+0

@ graham-dumpleton Nie ma to nic wspólnego z względną ani absolutną. Ścieżka zawsze może wybuchnąć, chyba że wykonasz tego rodzaju kontrolę poprawności. – jterrace

+0

Nie wydaje się, aby uzyskać to, co mam na myśli.Podałeś w swoim przykładzie "startdir = os.path.abspath (os.curdir)". To ustawi "startdir" na bieżący katalog roboczy. W aplikacji internetowej Python nie ma gwarancji co do bieżącego katalogu roboczego. Jest to więc zły przykład, ponieważ ludzie będą ślepo wycinać i wklejać kod bez zrozumienia, że ​​powinni zakotwiczyć go na absolutnej ścieżce, która ma znaczenie dla ich zastosowania, a nie polegać na tym, co powróci os.getcwd() po wywołaniu os.path.abspath (os.curdir). –

1

Zastosowanie wyłącznie nazwę podstawy użytkownika przypisana pliku:

file_name = request.path_params["file"] 
file_name = os.path.basename(file_name) 
file = open(os.path.join("/path", file_name), "rb") 

os.path.basename pasków ../ od ścieżki :

>>> os.path.basename('../../filename') 
'filename' 
+1

To nie przeszkadza w przechodzeniu do katalogu, ponieważ 'nazwa_pliku' może zawierać' ../ '! Ale twój kod i tak był pomocny. – deamon

+1

Niestety, twoje rozwiązanie również działa. Ale byłby ograniczony do jednego poziomu katalogu (bez podkatalogów). Poprawię mój głos, jeśli nieznacznie zmodyfikujesz swoją odpowiedź, aby móc głosować ponownie). – deamon

+0

@daemon 'os.path.basename' usuwa' ../ 'ze ścieżki. Sprawdź odpowiedź aktualizacji. –

2

Jest to znacznie prostsze rozwiązanie tutaj:

relative_path = os.path.relpath(path, start=self.test_directory) 
has_dir_traversal = relative_path.startswith(os.pardir) 

relpath dba normalizacji ścieżkę do nas. A jeśli ścieżka względna zaczyna się od .., oznacza to, że nie pozwalasz na to.

Powiązane problemy