Đảm bảo mã không an toàn không được sử dụng vô tình


10

Một hàm f()sử dụng eval()(hoặc một cái gì đó nguy hiểm) với dữ liệu mà tôi đã tạo và lưu trữ local_filetrên máy đang chạy chương trình của tôi:

import local_file

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


a = f(local_file.some_str)

f() chạy an toàn vì các chuỗi tôi cung cấp cho nó là của riêng tôi.

Tuy nhiên, nếu tôi từng quyết định sử dụng nó cho một cái gì đó không an toàn (ví dụ: đầu vào của người dùng) thì mọi thứ có thể trở nên cực kỳ sai lầm . Ngoài ra, nếu các local_fileđiểm dừng là cục bộ thì nó sẽ tạo ra một lỗ hổng vì tôi cũng cần phải tin vào máy cung cấp tệp đó.

Làm thế nào để tôi đảm bảo rằng tôi không bao giờ "quên" rằng chức năng này không an toàn để sử dụng (trừ khi các tiêu chí cụ thể được đáp ứng)?

Lưu ý: eval()là nguy hiểm và thường có thể được thay thế bằng một cái gì đó an toàn.


1
Tôi thường thích đi cùng với những câu hỏi được hỏi hơn là nói "bạn không nên làm điều này", nhưng tôi sẽ tạo ra một ngoại lệ trong trường hợp này. Tôi khá chắc chắn rằng không có lý do tại sao bạn nên sử dụng eval. Các hàm này được bao gồm với các ngôn ngữ như PHP và Python như một loại bằng chứng khái niệm có thể thực hiện được bao nhiêu với các ngôn ngữ được dịch. Về cơ bản không thể tưởng tượng được rằng bạn có thể viết mã tạo mã, nhưng bạn không thể mã cùng logic đó vào một hàm. Python có thể có các cuộc gọi lại và các ràng buộc chức năng sẽ cho phép bạn thực hiện những gì bạn cần với như vậy
user1122069

1
" Tôi cũng cần phải tin tưởng vào máy cung cấp tệp đó " - bạn luôn cần tin tưởng vào máy bạn đang thực thi mã của mình tại.
Bergi

Đặt một nhận xét trong tệp có nội dung "Chuỗi này được eval'd; vui lòng không đặt mã độc ở đây"?
dùng253751

@immibis Một bình luận sẽ không đủ. Tất nhiên tôi sẽ có một "CẢNH BÁO: Hàm này sử dụng eval ()" trong tài liệu của hàm, nhưng tôi muốn dựa vào thứ gì đó an toàn hơn thế nhiều. Ví dụ (do thời gian có hạn) Tôi không phải lúc nào cũng đọc lại tài liệu của hàm. Tôi cũng không nghĩ mình nên như vậy. Có nhiều cách nhanh hơn (nghĩa là hiệu quả hơn) để biết điều gì đó không an toàn, giống như các phương pháp được liên kết bởi Murphy trong câu trả lời của anh ấy.
Nghịch lý Fermi

@Fermiparadox khi bạn lấy eval ra khỏi câu hỏi nó trở thành không có ví dụ hữu ích. Bây giờ bạn đang nói về một chức năng không an toàn vì sự giám sát có chủ ý, chẳng hạn như không thoát các tham số SQL. Bạn có nghĩa là mã kiểm tra không an toàn cho một môi trường sản xuất? Dù sao, bây giờ tôi đã đọc bình luận của bạn về câu trả lời của Amon - về cơ bản bạn đang tìm cách bảo vệ chức năng của mình.
dùng1122069

Câu trả lời:


26

Xác định một loại để trang trí đầu vào an toàn trên mạng. Trong hàm của bạn f(), xác nhận rằng đối số có loại này hoặc đưa ra lỗi. Hãy đủ thông minh để không bao giờ xác định một phím tắt def g(x): return f(SafeInput(x)).

Thí dụ:

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))

Trong khi điều này có thể dễ dàng phá vỡ, nó làm cho nó khó khăn hơn để làm điều đó một cách tình cờ. Mọi người có thể chỉ trích một loại kiểm tra hà khắc như vậy, nhưng họ sẽ bỏ lỡ điểm. Vì SafeInputkiểu này chỉ là một kiểu dữ liệu và không phải là một lớp hướng đối tượng có thể được phân lớp, nên việc kiểm tra nhận dạng kiểu type(x) is SafeInputsẽ an toàn hơn so với việc kiểm tra tính tương thích của kiểu với isinstance(x, SafeInput). Vì chúng tôi muốn thực thi một loại cụ thể, bỏ qua loại rõ ràng và chỉ thực hiện gõ vịt ngầm cũng không thỏa đáng ở đây. Python có một hệ thống loại, vì vậy hãy sử dụng nó để nắm bắt những sai lầm có thể xảy ra!


5
Một thay đổi nhỏ có thể cần thiết mặc dù. assertđược xóa tự động vì tôi sẽ sử dụng mã python được tối ưu hóa. Một cái gì đó như if ... : raise NotSafeInputErrorsẽ cần thiết.
Nghịch lý Fermi ngày

4
Nếu bạn đang đi xuống con đường này dù sao, tôi sẽ mạnh mẽ đề nghị di chuyển eval()vào một phương pháp trên SafeInput, do đó bạn không cần phải có kiểm tra kiểu tường minh. Đặt cho phương thức một số tên không được sử dụng trong bất kỳ lớp nào khác của bạn. Bằng cách đó, bạn vẫn có thể chế nhạo toàn bộ mọi thứ cho mục đích thử nghiệm.
Kevin

@Kevin: Thấy evalcó quyền truy cập vào các biến cục bộ, có thể mã đó phụ thuộc vào cách nó biến đổi các biến. Bằng cách di chuyển eval sang một phương thức khác, nó sẽ không còn khả năng biến đổi các biến cục bộ.
Sjoerd Công việc Postmus

Một bước nữa có thể là để nhà SafeInputxây dựng lấy tên / xử lý tệp cục bộ và tự đọc, đảm bảo rằng dữ liệu thực sự đến từ một tệp cục bộ.
Owen

1
Tôi không có nhiều kinh nghiệm với trăn, nhưng tốt hơn hết là ném ngoại lệ? Trong hầu hết các ngôn ngữ, các xác nhận có thể bị tắt và (theo tôi hiểu chúng) nhiều hơn cho việc gỡ lỗi hơn là an toàn. Các ngoại lệ và thoát khỏi bàn điều khiển đảm bảo rằng mã tiếp theo sẽ không được chạy.
dùng1122069

11

Như Joel đã từng chỉ ra, làm cho mã sai nhìn sai (cuộn xuống phần "Giải pháp thực sự" hoặc tốt hơn là đọc nó hoàn toàn; nó đáng giá, ngay cả khi nó xử lý biến thay vì tên hàm). Vì vậy, tôi khiêm tốn đề nghị đổi tên chức năng của bạn thành một cái gì đó giống như f_unsafe_for_external_use()- rất có thể bạn sẽ tự mình nghĩ ra một số lược đồ viết tắt.

Chỉnh sửa: Tôi nghĩ đề xuất của amon là một biến thể dựa trên OO rất tốt trên cùng một chủ đề :-)


0

Bổ sung cho gợi ý của @amon, bạn cũng có thể suy nghĩ về việc kết hợp mẫu chiến lược ở đây, cũng như mẫu đối tượng phương thức.

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

    def run(self):
        # do some magic
        pass

Và sau đó thực hiện 'bình thường'

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

Và một triển khai 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)

(Lưu ý: với một ví dụ thực tế, tôi có thể đưa ra một ví dụ tốt hơn).

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.