stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
To zwraca następujący błąd:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'
Wiem, że eval
można to obejść, ale czy nie ma lepszej i - co ważniejsze - bezpieczniejszej metody oceny wyrażenia matematycznego przechowywanego w ciągu?
Odpowiedzi:
Pyparsing może służyć do analizowania wyrażeń matematycznych. W szczególności fourFn.py pokazuje, jak analizować podstawowe wyrażenia arytmetyczne. Poniżej przepisałem fourFn na numeryczną klasę parsera, aby ułatwić ponowne użycie.
Możesz tego używać w ten sposób
źródło
eval
jest złyUwaga: nawet jeśli używasz zestawu
__builtins__
doNone
niego nadal może być możliwe, aby wyrwać się za pomocą introspekcji:Oblicz wyrażenie arytmetyczne za pomocą
ast
Możesz łatwo ograniczyć dozwolony zakres dla każdej operacji lub dowolnego wyniku pośredniego, np. Aby ograniczyć argumenty wejściowe dla
a**b
:Lub w celu ograniczenia zakresu wyników pośrednich:
Przykład
źródło
import math
?ast.parse
nie jest to bezpieczne. Na przykładast.parse('()' * 1000000, '<string>', 'single')
zawiesza tłumacza.if len(expr) > 10000: raise ValueError
. Przy użyciu .len(expr)
czeku? A może chodzi o to, że w implementacji Pythona są błędy i dlatego generalnie nie można pisać bezpiecznego kodu?Niektóre bezpieczniejsze alternatywy dla
eval()
i * :sympy.sympify().evalf()
* SymPy
sympify
jest również niebezpieczny, zgodnie z następującym ostrzeżeniem zawartym w dokumentacji.źródło
Okay, więc problem z eval polega na tym, że zbyt łatwo może uciec ze swojej piaskownicy, nawet jeśli się go pozbędziesz
__builtins__
. Wszystkie metody ucieczki z piaskownicy sprowadzają się do użyciagetattr
lubobject.__getattribute__
(przez.
operatora) w celu uzyskania odniesienia do jakiegoś niebezpiecznego obiektu za pośrednictwem dozwolonego obiektu (''.__class__.__bases__[0].__subclasses__
lub podobnego).getattr
jest eliminowana przez ustawienie__builtins__
naNone
.object.__getattribute__
jest trudna, ponieważ nie można jej tak po prostu usunąć, zarówno dlatego, żeobject
jest niezmienna, jak i dlatego, że jej usunięcie zepsułoby wszystko. Jednak__getattribute__
jest dostępny tylko przez.
operatora, więc wyczyszczenie tego z danych wejściowych jest wystarczające, aby upewnić się, że eval nie wydostanie się z piaskownicy.W formułach przetwarzania liczb dziesiętnych można używać tylko wtedy, gdy są poprzedzone lub zakończone
[0-9]
, więc po prostu usuwamy wszystkie inne wystąpienia.
.Zauważ, że podczas gdy Python normalnie traktuje
1 + 1.
jako1 + 1.0
, to usunie to końcowe.
i pozostawi cię z1 + 1
. Mógłbyś dodać)
,i
EOF
do listy rzeczy, które można śledzić.
, ale po co?źródło
.
jest w tej chwili poprawny, pozostawia to potencjalne luki w zabezpieczeniach, jeśli przyszłe wersje Pythona wprowadzą nową składnię, umożliwiającą dostęp do niebezpiecznych obiektów lub funkcji w inny sposób. To rozwiązanie jest już niebezpieczny ponieważ Python 3,6 F-łańcuchów, które pozwalają na następujące atak:f"{eval('()' + chr(46) + '__class__')}"
. Rozwiązanie oparte na białej liście, a nie czarnej liście, będzie bezpieczniejsze, ale naprawdę lepiej jest rozwiązać ten problemeval
w ogóle.Możesz użyć modułu ast i napisać NodeVisitor, który sprawdza, czy typ każdego węzła jest częścią białej listy.
Ponieważ działa za pośrednictwem białej listy, a nie czarnej listy, jest bezpieczny. Jedyne funkcje i zmienne, do których ma dostęp, to te, do których jawnie dajesz dostęp. Dyktowałem funkcjami związanymi z matematyką, więc jeśli chcesz, możesz łatwo zapewnić dostęp do nich, ale musisz jawnie ich użyć.
Jeśli ciąg próbuje wywołać funkcje, które nie zostały dostarczone, lub wywołać jakiekolwiek metody, zostanie zgłoszony wyjątek i nie zostanie wykonany.
Ponieważ używa to wbudowanego analizatora i analizatora Pythona, dziedziczy również pierwszeństwo i reguły promocji Pythona.
Powyższy kod został przetestowany tylko w Pythonie 3.
Jeśli chcesz, możesz dodać dekorator limitu czasu do tej funkcji.
źródło
Powodem
eval
iexec
są tak niebezpieczne jest to, żecompile
funkcja domyślna wygeneruje kod bajtowy dla dowolnego prawidłowego wyrażenia Pythona, a domyślnyeval
lubexec
wykona dowolny prawidłowy kod bajtowy Pythona. Wszystkie dotychczasowe odpowiedzi skupiały się na ograniczaniu kodu bajtowego, który może być generowany (poprzez odkażanie danych wejściowych) lub budowaniu własnego języka specyficznego dla domeny za pomocą AST.Zamiast tego możesz łatwo utworzyć prostą
eval
funkcję, która nie jest w stanie zrobić niczego nikczemnego i może łatwo sprawdzić w czasie wykonywania pamięć lub wykorzystany czas. Oczywiście, jeśli jest to prosta matematyka, istnieje skrót.Sposób, w jaki to działa, jest prosty, każde stałe wyrażenie matematyczne jest bezpiecznie oceniane podczas kompilacji i przechowywane jako stała. Obiekt kodu zwrócony przez kompilację składa się z
d
kodu bajtowego dlaLOAD_CONST
, po którym następuje numer stałej do załadowania (zwykle ostatnia na liście), poS
którym następuje kod bajtowy dlaRETURN_VALUE
. Jeśli ten skrót nie działa, oznacza to, że dane wejściowe użytkownika nie są wyrażeniem stałym (zawierają zmienną, wywołanie funkcji lub podobne).Otwiera to również drzwi do bardziej wyrafinowanych formatów wejściowych. Na przykład:
Wymaga to rzeczywistej oceny kodu bajtowego, co nadal jest dość proste. Kod bajtowy Pythona jest językiem zorientowanym na stos, więc wszystko jest proste
TOS=stack.pop(); op(TOS); stack.put(TOS)
lub podobne. Kluczem jest implementacja tylko tych opkodów, które są bezpieczne (ładowanie / przechowywanie wartości, operacje matematyczne, zwracanie wartości), a nie niebezpiecznych (wyszukiwanie atrybutów). Jeśli chcesz, aby użytkownik mógł wywoływać funkcje (cały powód, aby nie używać powyższego skrótu), w prosty sposób wprowadź w życieCALL_FUNCTION
tylko zezwalanie na funkcje na liście „bezpiecznych”.Oczywiście rzeczywista wersja byłaby nieco dłuższa (jest 119 rozkazów, z których 24 są związane z matematyką). Dodanie
STORE_FAST
i kilka innych pozwoliłoby na wprowadzenie podobnych'x=5;return x+x
lub podobnych, trywialnie łatwo. Może być nawet używany do wykonywania funkcji utworzonych przez użytkownika, o ile funkcje utworzone przez użytkownika są wykonywane przez VMeval (nie należy ich wywoływać !!! w przeciwnym razie mogą zostać użyte jako wywołanie zwrotne). Obsługa pętli wymaga obsługigoto
kodów bajtowych, co oznacza zmianę zfor
iteratorawhile
na bieżącą instrukcję i utrzymywanie wskaźnika do bieżącej instrukcji, ale nie jest to zbyt trudne. Aby uzyskać odporność na DOS, główna pętla powinna sprawdzać, ile czasu minęło od rozpoczęcia obliczeń, a niektórzy operatorzy powinni odmawiać wprowadzania danych powyżej rozsądnego limitu (BINARY_POWER
jest najbardziej oczywiste).Chociaż to podejście jest nieco dłuższe niż prosty parser gramatyczny dla prostych wyrażeń (patrz wyżej o zwykłym przechwytywaniu skompilowanej stałej), rozciąga się łatwo na bardziej skomplikowane dane wejściowe i nie wymaga zajmowania się gramatyką (
compile
weź wszystko, co jest dowolnie skomplikowane i redukuje je do sekwencja prostych instrukcji).źródło
Myślę, że użyłbym
eval()
, ale najpierw sprawdziłbym, czy ciąg jest prawidłowym wyrażeniem matematycznym, w przeciwieństwie do czegoś złośliwego. Możesz użyć wyrażenia regularnego do walidacji.eval()
pobiera również dodatkowe argumenty, których możesz użyć do ograniczenia przestrzeni nazw, w której działa, dla większego bezpieczeństwa.źródło
+
,-
,*
,/
,**
,(
,)
lub coś bardziej skomplikowaneeval()
jeśli nie kontrolujesz danych wejściowych, nawet jeśli ograniczasz przestrzeń nazw, np.eval("9**9**9**9**9**9**9**9", {'__builtins__': None})
Zużywa procesor, pamięć.To bardzo późna odpowiedź, ale myślę, że będzie przydatna w przyszłości. Zamiast pisać swój własny parser matematyczny (chociaż powyższy przykład pyparsingu jest świetny), możesz użyć SymPy. Nie mam z tym dużego doświadczenia, ale zawiera o wiele potężniejszy silnik matematyczny, niż ktokolwiek mógłby napisać dla konkretnej aplikacji, a podstawowa ocena wyrażenia jest bardzo łatwa:
Naprawdę super! A
from sympy import *
zapewnia o wiele więcej obsługi funkcji, takich jak funkcje trygonometryczne, funkcje specjalne itp., Ale unikałem tego tutaj, aby pokazać, co przychodzi skąd.źródło
evalf
nie bierze NumPy ndarrays.sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")
tych połączeń,subprocess.Popen()
które przeszedłemls
zamiastrm -rf /
. Indeks będzie prawdopodobnie inny na innych komputerach. To jest wariant exploita Neda Batcheldera[Wiem, że to stare pytanie, ale warto wskazać nowe przydatne rozwiązania, gdy się pojawią]
Od pythona 3.6 ta funkcja jest teraz wbudowana w język , tworząc „f-strings” .
Zobacz: PEP 498 - Interpolacja dosłownych ciągów znaków
Na przykład (zwróć uwagę na
f
przedrostek):źródło
str(eval(...))
, więc z pewnością nie jest to bezpieczniejsze niżeval
.Użyj
eval
w czystej przestrzeni nazw:Czysta przestrzeń nazw powinna uniemożliwić wstrzyknięcie. Na przykład:
W przeciwnym razie otrzymasz:
Możesz dać dostęp do modułu matematycznego:
źródło
eval("""[i for i in (1).__class__.__bases__[0].__subclasses__() if i.__name__.endswith('BuiltinImporter')][0]().load_module('sys').modules['sys'].modules['os'].system('/bin/sh')""", {'__builtins__': None})
wykonuje powłokę Bourne'a ...This is not safe
- cóż, myślę, że jest tak samo bezpieczny, jak ogólnie używanie basha. BTW:eval('math.sqrt(2.0)')
<- "matematyka". jest wymagane, jak opisano powyżej.Oto moje rozwiązanie problemu bez użycia eval. Działa z Python2 i Python3. Nie działa z liczbami ujemnymi.
test.py
solution.py
źródło