stringExp = "2^4"
intVal = int(stringExp) # Expected value: 16
Ini mengembalikan kesalahan berikut:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int()
with base 10: '2^4'
Saya tahu itu eval
bisa mengatasi ini, tetapi bukankah ada metode yang lebih baik dan - yang lebih penting - lebih aman untuk mengevaluasi ekspresi matematika yang disimpan dalam string?
Jawaban:
Pyparsing dapat digunakan untuk mengurai ekspresi matematika. Secara khusus, fourFn.py menunjukkan bagaimana mengurai ekspresi aritmatika dasar. Di bawah, saya telah membungkus ulang fourFn menjadi kelas parser numerik agar lebih mudah digunakan kembali.
Anda bisa menggunakannya seperti ini
sumber
eval
itu jahatCatatan: bahkan jika Anda menggunakan set
__builtins__
untukNone
itu masih mungkin untuk keluar menggunakan introspeksi:Evaluasi ekspresi aritmatika menggunakan
ast
Anda dapat dengan mudah membatasi rentang yang diizinkan untuk setiap operasi atau hasil antara, misalnya, untuk membatasi argumen input untuk
a**b
:Atau untuk membatasi besaran hasil antara:
Contoh
sumber
import math
?ast.parse
tidak aman. Misalnyaast.parse('()' * 1000000, '<string>', 'single')
crash interpreter.if len(expr) > 10000: raise ValueError
.len(expr)
cek? Atau maksud Anda adalah ada bug dalam implementasi Python dan oleh karena itu tidak mungkin untuk menulis kode aman secara umum?Beberapa alternatif yang lebih aman untuk
eval()
dan * :sympy.sympify().evalf()
* SymPy
sympify
juga tidak aman menurut peringatan berikut dari dokumentasi.sumber
Oke, jadi masalah dengan eval adalah ia dapat keluar dari kotak pasirnya terlalu mudah, bahkan jika Anda membuangnya
__builtins__
. Semua metode untuk melarikan diri dari kotak pasir turun menggunakangetattr
atauobject.__getattribute__
(melalui.
operator) untuk mendapatkan referensi ke beberapa objek berbahaya melalui beberapa objek yang diizinkan (''.__class__.__bases__[0].__subclasses__
atau serupa).getattr
dihilangkan dengan menyetel__builtins__
keNone
.object.__getattribute__
adalah yang sulit, karena tidak bisa begitu saja dihilangkan, baik karenaobject
tidak dapat diubah dan karena menghapusnya akan merusak segalanya. Namun,__getattribute__
hanya dapat diakses melalui.
operator, jadi membersihkannya dari masukan Anda sudah cukup untuk memastikan eval tidak dapat keluar dari kotak pasirnya.Dalam memproses rumus, satu-satunya penggunaan desimal yang valid adalah ketika diawali atau diikuti oleh
[0-9]
, jadi kami hanya menghapus semua contoh lainnya dari.
.Perhatikan bahwa sementara python biasanya diperlakukan
1 + 1.
sebagai1 + 1.0
, ini akan menghapus.
jejak dan meninggalkan Anda1 + 1
. Anda dapat menambahkan)
,,dan
EOF
ke daftar hal-hal yang boleh diikuti.
, tetapi mengapa repot-repot?sumber
.
benar atau tidak pada saat ini, ini meninggalkan potensi kerentanan keamanan jika versi Python di masa mendatang memperkenalkan sintaks baru yang memungkinkan objek atau fungsi yang tidak aman diakses dengan cara lain. Solusi ini sudah tidak aman di Python 3.6 karena f-string, yang memungkinkan serangan berikut:f"{eval('()' + chr(46) + '__class__')}"
. Solusi berdasarkan daftar putih daripada daftar hitam akan lebih aman, tetapi sebenarnya lebih baik menyelesaikan masalah ini tanpa masalaheval
sama sekali.Anda dapat menggunakan modul ast dan menulis NodeVisitor yang memverifikasi bahwa jenis setiap node adalah bagian dari daftar putih.
Karena berfungsi melalui daftar putih daripada daftar hitam, ini aman. Satu-satunya fungsi dan variabel yang dapat diaksesnya adalah yang Anda berikan akses secara eksplisit. Saya mengisi dikt dengan fungsi yang berhubungan dengan matematika sehingga Anda dapat dengan mudah memberikan akses ke fungsi tersebut jika Anda mau, tetapi Anda harus menggunakannya secara eksplisit.
Jika string mencoba memanggil fungsi yang belum disediakan, atau memanggil metode apa pun, pengecualian akan dimunculkan, dan tidak akan dijalankan.
Karena ini menggunakan parser dan evaluator bawaan Python, ini juga mewarisi aturan promosi dan prioritas Python juga.
Kode di atas hanya diuji pada Python 3.
Jika diinginkan, Anda dapat menambahkan dekorator batas waktu pada fungsi ini.
sumber
Alasan
eval
danexec
sangat berbahaya adalah bahwacompile
fungsi default akan menghasilkan bytecode untuk ekspresi python yang valid, dan defaulteval
atauexec
akan menjalankan bytecode python yang valid. Semua jawaban sampai saat ini berfokus pada pembatasan bytecode yang dapat dihasilkan (dengan membersihkan masukan) atau membangun bahasa khusus domain Anda sendiri menggunakan AST.Sebaliknya, Anda dapat dengan mudah membuat
eval
fungsi sederhana yang tidak mampu melakukan hal jahat dan dapat dengan mudah melakukan pemeriksaan waktu proses pada memori atau waktu yang digunakan. Tentunya jika matematika itu sederhana, maka ada jalan pintas.Cara kerjanya sederhana, ekspresi matematika konstan apa pun dievaluasi dengan aman selama kompilasi dan disimpan sebagai konstanta. Objek kode yang dikembalikan oleh kompilasi terdiri dari
d
, yang merupakan bytecode untukLOAD_CONST
, diikuti oleh jumlah konstanta yang akan dimuat (biasanya yang terakhir dalam daftar), diikuti olehS
, yang merupakan bytecode untukRETURN_VALUE
. Jika pintasan ini tidak berfungsi, berarti input pengguna bukanlah ekspresi konstan (berisi panggilan variabel atau fungsi atau serupa).Ini juga membuka pintu ke beberapa format input yang lebih canggih. Sebagai contoh:
Ini membutuhkan evaluasi bytecode, yang masih cukup sederhana. Bytecode Python adalah bahasa berorientasi tumpukan, jadi semuanya adalah masalah sederhana
TOS=stack.pop(); op(TOS); stack.put(TOS)
atau serupa. Kuncinya adalah hanya mengimplementasikan opcode yang aman (memuat / menyimpan nilai, operasi matematika, mengembalikan nilai) dan bukan yang tidak aman (pencarian atribut). Jika Anda ingin pengguna dapat memanggil fungsi (seluruh alasan untuk tidak menggunakan pintasan di atas), buat implementasi AndaCALL_FUNCTION
hanya mengizinkan fungsi dalam daftar 'aman'.Jelas, versi sebenarnya dari ini akan sedikit lebih lama (ada 119 opcode, 24 di antaranya terkait dengan matematika). Menambahkan
STORE_FAST
dan beberapa lainnya akan memungkinkan untuk memasukkan seperti'x=5;return x+x
atau serupa, dengan mudah. Ia bahkan dapat digunakan untuk menjalankan fungsi yang dibuat pengguna, selama fungsi yang dibuat oleh pengguna dijalankan sendiri melalui VMeval (jangan membuatnya dapat dipanggil !!! atau dapat digunakan sebagai panggilan balik di suatu tempat). Menangani loop membutuhkan dukungan untukgoto
bytecode, yang berarti mengubah darifor
iterator menjadi yang paling jelas).while
dan mempertahankan pointer ke instruksi saat ini, tetapi tidak terlalu sulit. Untuk resistansi terhadap DOS, loop utama harus memeriksa berapa lama waktu telah berlalu sejak dimulainya kalkulasi, dan operator tertentu harus menolak input melebihi batas yang wajar (BINARY_POWER
Meskipun pendekatan ini agak lebih panjang dari parser tata bahasa sederhana untuk ekspresi sederhana (lihat di atas tentang hanya mengambil konstanta yang dikompilasi), pendekatan ini meluas dengan mudah ke masukan yang lebih rumit, dan tidak memerlukan penanganan tata bahasa (
compile
ambil sesuatu yang rumit secara sewenang-wenang dan menguranginya menjadi urutan instruksi sederhana).sumber
Saya pikir saya akan menggunakan
eval()
, tetapi pertama-tama akan memeriksa untuk memastikan string tersebut adalah ekspresi matematika yang valid, sebagai lawan dari sesuatu yang berbahaya. Anda bisa menggunakan regex untuk validasi.eval()
juga membutuhkan argumen tambahan yang dapat Anda gunakan untuk membatasi namespace yang dioperasikannya untuk keamanan yang lebih baik.sumber
+
,-
,*
,/
,**
,(
,)
atau sesuatu yang lebih rumiteval()
jika Anda tidak mengontrol input bahkan jika Anda membatasi namespace misalnya,eval("9**9**9**9**9**9**9**9", {'__builtins__': None})
menggunakan CPU, memori.Ini adalah balasan yang sangat terlambat, tapi menurut saya berguna untuk referensi di masa mendatang. Daripada menulis parser matematika Anda sendiri (meskipun contoh penguraian di atas bagus), Anda dapat menggunakan SymPy. Saya tidak memiliki banyak pengalaman dengannya, tetapi ini berisi mesin matematika yang jauh lebih kuat daripada yang mungkin ditulis siapa pun untuk aplikasi tertentu dan evaluasi ekspresi dasar sangat mudah:
Sangat keren! A
from sympy import *
membawa lebih banyak dukungan fungsi, seperti fungsi trigonometri, fungsi khusus, dll., Tetapi saya menghindari itu di sini untuk menunjukkan apa yang datang dari mana.sumber
evalf
tidak mengambil ndarrays numpy.sympy.sympify("""[].__class__.__base__.__subclasses__()[158]('ls')""")
panggilan inisubprocess.Popen()
yang saya lulusls
sebagai gantinyarm -rf /
. Indeks mungkin akan berbeda di komputer lain. Ini adalah varian dari exploit Ned Batchelder[Saya tahu ini adalah pertanyaan lama, tetapi ada baiknya menunjukkan solusi baru yang berguna saat muncul]
Sejak python3.6, kemampuan ini sekarang dibangun ke dalam bahasa , yang disebut "f-string" .
Lihat: PEP 498 - Interpolasi String Literal
Misalnya (perhatikan
f
awalannya):sumber
str(eval(...))
, jadi tentu saja tidak lebih aman darieval
.Gunakan
eval
di namespace yang bersih:Namespace yang bersih harus mencegah injeksi. Misalnya:
Jika tidak, Anda akan mendapatkan:
Anda mungkin ingin memberikan akses ke modul matematika:
sumber
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})
mengeksekusi shell bourne ...This is not safe
- yah, saya rasa ini sama amannya dengan menggunakan bash secara keseluruhan. BTW:eval('math.sqrt(2.0)')
<- "matematika." diperlukan seperti yang tertulis di atas.Inilah solusi saya untuk masalah tanpa menggunakan eval. Bekerja dengan Python2 dan Python3. Ini tidak bekerja dengan angka negatif.
test.py
solution.py
sumber