Pastikan kode yang tidak aman tidak digunakan secara tidak sengaja

10

Fungsi f()menggunakan eval()(atau sesuatu yang berbahaya) dengan data yang saya buat dan simpan di local_filedalam mesin yang menjalankan program saya:

import local_file

def f(str_to_eval):
    # code....
    # .... 
    eval(str_to_eval)    
    # ....
    # ....    
    return None


a = f(local_file.some_str)

f() aman untuk dijalankan karena string yang saya berikan padanya adalah milik saya.

Namun, jika saya memutuskan untuk menggunakannya untuk sesuatu yang tidak aman (mis. Input pengguna) hal-hal bisa jadi sangat salah . Juga, jika local_fileberhenti menjadi lokal maka itu akan membuat kerentanan karena saya perlu mempercayai mesin yang menyediakan file itu juga.

Bagaimana saya harus memastikan bahwa saya tidak pernah "lupa" bahwa fungsi ini tidak aman untuk digunakan (kecuali kriteria tertentu terpenuhi)?

Catatan: eval()berbahaya dan biasanya bisa diganti dengan sesuatu yang aman.

Fermi paradox
sumber
1
Saya biasanya suka mengikuti pertanyaan yang diajukan daripada mengatakan "Anda tidak boleh melakukan ini", tetapi saya akan membuat pengecualian dalam kasus ini. Saya cukup yakin bahwa tidak ada alasan mengapa Anda harus menggunakan eval. Fungsi-fungsi ini disertakan dengan bahasa seperti PHP dan Python sebagai semacam bukti konsep seberapa banyak yang dapat dilakukan dengan bahasa yang ditafsirkan. Pada dasarnya tidak dapat dibayangkan bahwa Anda dapat menulis kode yang membuat kode, tetapi Anda tidak dapat membuat kode logika yang sama itu menjadi suatu fungsi. Python kemungkinan memiliki callback dan fungsi binding yang akan memungkinkan Anda untuk melakukan apa yang Anda butuhkan
user1122069
1
" Saya perlu mempercayai mesin yang menyediakan file itu juga " - Anda selalu perlu mempercayai mesin tempat Anda mengeksekusi kode Anda.
Bergi
Beri komentar di file yang mengatakan "String ini akan dievaluasi; tolong jangan menaruh kode jahat di sini"?
user253751
@immibis Komentar tidak akan cukup. Saya tentu saja akan memiliki "PERINGATAN: Fungsi ini menggunakan eval ()" yang sangat besar dalam dokumen fungsi, tetapi saya lebih suka mengandalkan sesuatu yang lebih aman dari itu. Misalnya (karena waktu yang terbatas) saya tidak selalu membaca ulang dokumen fungsi. Saya pikir saya juga tidak. Ada cara yang lebih cepat (yaitu, lebih efisien) untuk mengetahui sesuatu tidak aman, seperti metode yang ditautkan oleh Murphy dalam jawabannya.
Fermi paradox
@Fermiparadox ketika Anda mengambil eval dari pertanyaan itu menjadi tanpa contoh yang berguna. Apakah Anda berbicara sekarang tentang fungsi yang tidak aman karena pengawasan yang disengaja, seperti tidak melarikan diri parameter SQL. Apakah maksud Anda kode uji tidak aman untuk lingkungan produksi? Bagaimanapun, sekarang saya membaca komentar Anda pada jawaban Amon - pada dasarnya Anda mencari cara untuk mengamankan fungsi Anda.
user1122069

Jawaban:

26

Tentukan jenis untuk menghias input "aman". Dalam fungsi Anda f(), nyatakan bahwa argumen memiliki tipe ini atau menimbulkan kesalahan. Cukup pintar untuk tidak pernah menentukan pintasan def g(x): return f(SafeInput(x)).

Contoh:

class SafeInput(object):
  def __init__(self, value):
    self._value = value

  def value(self):
    return self._value

def f(safe_string_to_eval):
  assert type(safe_string_to_eval) is SafeInput, "safe_string_to_eval must have type SafeInput, but was {}".format(type(safe_string_to_eval))
  ...
  eval(safe_string_to_eval.value())
  ...

a = f(SafeInput(local_file.some_str))

Meskipun ini dapat dengan mudah dielakkan, itu membuatnya lebih sulit untuk melakukannya secara tidak sengaja. Orang mungkin mengkritik semacam cek kejam, tetapi mereka akan melewatkan intinya. Karena SafeInputtipe ini hanya tipe data dan bukan kelas berorientasi objek yang dapat disubklasifikasikan, memeriksa identitas tipe dengan type(x) is SafeInputlebih aman daripada memeriksa kompatibilitas tipe isinstance(x, SafeInput). Karena kami ingin menegakkan jenis tertentu, mengabaikan jenis eksplisit dan hanya melakukan pengetikan bebek secara implisit juga tidak memuaskan di sini. Python memiliki sistem tipe, jadi mari kita gunakan untuk menangkap kemungkinan kesalahan!

amon
sumber
5
Namun, perubahan kecil mungkin diperlukan. assertdihapus secara otomatis karena saya akan menggunakan kode python yang dioptimalkan. Sesuatu seperti if ... : raise NotSafeInputErroritu akan dibutuhkan.
Fermi paradox
4
Jika Anda akan melalui jalan ini, saya akan sangat menyarankan untuk pindah eval()ke metode aktif SafeInput, sehingga Anda tidak memerlukan pemeriksaan tipe eksplisit. Berikan metode beberapa nama yang tidak digunakan di kelas Anda yang lain. Dengan begitu, Anda masih bisa mengejek semuanya untuk tujuan pengujian.
Kevin
@Kevin: Melihat evalmemiliki akses ke variabel lokal, mungkin kode tergantung pada bagaimana ia memutasi variabel. Dengan memindahkan eval ke metode lain, ia tidak lagi memiliki kemampuan untuk bermutasi variabel lokal.
Sjoerd Job Postmus
Langkah selanjutnya adalah meminta SafeInputkonstruktor mengambil nama / pegangan file lokal dan melakukan pembacaan sendiri, memastikan bahwa data memang berasal dari file lokal.
Owen
1
Saya tidak punya banyak pengalaman dengan python, tetapi bukankah lebih baik untuk melemparkan pengecualian? Dalam kebanyakan bahasa, penegasan dapat dimatikan dan (seperti yang saya mengerti) lebih untuk debugging daripada untuk keamanan. Pengecualian dan keluar ke konsol menjamin bahwa kode berikutnya tidak akan dijalankan.
user1122069
11

Seperti yang pernah ditunjukkan Joel, buat kode yang salah terlihat salah (gulir ke bawah ke bagian "Solusi Nyata", atau lebih baik baca sepenuhnya; itu sepadan, bahkan jika itu alamat variabel daripada nama fungsi). Jadi saya dengan rendah hati menyarankan untuk mengubah nama fungsi Anda menjadi sesuatu seperti f_unsafe_for_external_use()- Anda kemungkinan besar akan datang dengan beberapa skema singkatan sendiri.

Sunting: Saya pikir saran amon adalah variasi OO berdasarkan tema yang sangat bagus :-)

Murphy
sumber
0

Melengkapi saran oleh @amon, Anda juga dapat berpikir tentang menggabungkan pola strategi di sini, serta pola objek metode.

class AbstractFoobarizer(object):
    def handle_cond(self, foo, bar):
        """
        Handle the event or something.
        """
        raise NotImplementedError

    def run(self):
        # do some magic
        pass

Dan kemudian implementasi 'normal'

class PrintingFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        print("Something went very well regarding {} and {}".format(foo, bar))

Dan implementasi admin-input-eval

class AdminControllableFoobarizer(AbstractFoobarizer):
    def handle_cond(self, foo, bar):
        # Look up code in database/config file/...
        code = get_code_from_settings()
        eval(code)

(Catatan: dengan contoh dunia nyata, saya mungkin bisa memberikan contoh yang lebih baik).

Sjoerd Job Postmus
sumber