Khi nào JavaScript của eval () không xấu?


263

Tôi đang viết một số mã JavaScript để phân tích các hàm do người dùng nhập (cho chức năng giống như bảng tính). Sau khi phân tích công thức, tôi có thể chuyển đổi nó thành JavaScript và chạy eval()trên nó để mang lại kết quả.

Tuy nhiên, tôi luôn tránh sử dụng eval()nếu tôi có thể tránh được vì nó xấu (và, đúng hay sai, tôi luôn nghĩ rằng nó thậm chí còn xấu hơn trong JavaScript, bởi vì mã được đánh giá có thể được thay đổi bởi người dùng ).

Vì vậy, khi sử dụng nó là OK?


5
Trên thực tế, hầu hết các thư viện JSON không sử dụng eval dưới mui xe, để bảo vệ chống lại các rủi ro bảo mật.
Sean McMillan

11
@Sean - Cả JQuery và Prototype đều sử dụng eval (JQuery sử dụng nó thông qua Hàm mới)
plodder

5
@plodder - Bạn lấy thông tin từ đâu? jQuery đã sử dụng JSON.parse () gốc từ 1.4 (trở lại vào 1/2010)! Xem cho chính mình: code.jquery.com/jquery-1.4.js
ken

3
"Rõ ràng người ta phải sử dụng eval () để phân tích JSON" - điều này không đúng, ngược lại - người ta không nên sử dụng eval để phân tích JSON! Sử dụng tập lệnh json2.js của Douglas Crockfords (người tạo JSON) từ json.org !
TMS

11
@Tomas thật trớ trêu khi json2.js sử dụng eval để phân tích JSON
tobyodavies

Câu trả lời:


262

Tôi muốn dành một chút thời gian để giải quyết tiền đề cho câu hỏi của bạn - rằng eval () là " ác ". Từ " ác ", được sử dụng bởi những người sử dụng ngôn ngữ lập trình, thường có nghĩa là "nguy hiểm", hay chính xác hơn là "có thể gây ra nhiều tác hại với một lệnh đơn giản". Vì vậy, khi nào thì sử dụng một cái gì đó nguy hiểm? Khi bạn biết nguy hiểm là gì và khi bạn thực hiện các biện pháp phòng ngừa thích hợp.

Đến thời điểm này, chúng ta hãy nhìn vào những nguy hiểm trong việc sử dụng eval (). Có thể có nhiều mối nguy hiểm tiềm ẩn nhỏ giống như mọi thứ khác, nhưng hai rủi ro lớn - lý do tại sao eval () bị coi là xấu xa - là hiệu suất và tiêm mã.

  • Hiệu suất - eval () chạy trình thông dịch / trình biên dịch. Nếu mã của bạn được biên dịch, thì đây là một cú hích lớn, bởi vì bạn cần phải gọi một trình biên dịch có thể nặng vào giữa thời gian chạy. Tuy nhiên, JavaScript vẫn chủ yếu là một ngôn ngữ được diễn giải, điều đó có nghĩa là việc gọi eval () không phải là một hiệu suất lớn trong trường hợp chung (nhưng hãy xem các nhận xét cụ thể của tôi bên dưới).
  • Mã tiêm - eval () có khả năng chạy một chuỗi mã theo các đặc quyền nâng cao. Ví dụ: một chương trình chạy với tư cách quản trị viên / root sẽ không bao giờ muốn eval () đầu vào của người dùng, bởi vì đầu vào đó có khả năng là "rm -rf / etc / Quan trọng-tập tin" hoặc tệ hơn. Một lần nữa, JavaScript trong trình duyệt không gặp phải vấn đề đó, vì dù sao chương trình cũng đang chạy trong tài khoản của người dùng. JavaScript phía máy chủ có thể có vấn đề đó.

Về trường hợp cụ thể của bạn. Theo những gì tôi hiểu, bạn đang tự tạo các chuỗi, vì vậy, giả sử bạn cẩn thận không cho phép một chuỗi như "rm -rf Something-quan trọng" được tạo ra, không có rủi ro tiêm mã (nhưng xin hãy nhớ, nó rất rất khó để đảm bảo điều này trong trường hợp chung). Ngoài ra, nếu bạn đang chạy trong trình duyệt thì việc tiêm mã là một rủi ro khá nhỏ, tôi tin thế.

Về hiệu suất, bạn sẽ phải cân nhắc việc chống lại mã hóa. Theo ý kiến ​​của tôi, nếu bạn phân tích công thức, bạn cũng có thể tính kết quả trong quá trình phân tích thay vì chạy một trình phân tích cú pháp khác (công cụ bên trong eval ()). Nhưng có thể mã hóa dễ dàng hơn bằng cách sử dụng eval () và hiệu năng có thể sẽ không được chú ý. Có vẻ như eval () trong trường hợp này không tệ hơn bất kỳ chức năng nào khác có thể giúp bạn tiết kiệm thời gian.


78
Bạn không giải quyết vấn đề về mã sử dụng eval rất khó gỡ lỗi
bobobobo

48
Code Injection là một vấn đề rất nghiêm trọng đối với javascript nếu bạn hoàn toàn lo lắng về dữ liệu người dùng của mình. Mã được tiêm sẽ chạy (trong trình duyệt) như thể nó đến từ trang web của bạn, cho phép nó thực hiện bất kỳ loại shenanigan nào mà người dùng có thể làm thủ công. Nếu bạn cho phép mã (bên thứ ba) vào trang của bạn, nó có thể đặt hàng thay mặt cho khách hàng của bạn hoặc thay đổi gravatar của họ hoặc bất cứ điều gì họ có thể làm thông qua trang web của bạn. Hãy thật cẩn thận. Để tin tặc sở hữu khách hàng của bạn cũng tệ như để họ sở hữu máy chủ của bạn.
Sean McMillan

71
Nếu dữ liệu đến từ máy chủ của bạn và thứ gì đó mà bạn, nhà phát triển đã tạo, sẽ không có hại khi sử dụng eval (). Tác hại thực sự là tin vào tất cả những gì bạn đọc. Bạn thấy rất nhiều người nói eval () là xấu xa và họ không biết tại sao ngoại trừ việc họ đọc nó ở đâu đó.
Vince Panuccio

42
@Sean McMillan: Tôi muốn tin bạn, nhưng nếu ai đó sẽ chặn và thay đổi javascript eval()từ máy chủ của bạn, họ cũng có thể thay đổi nguồn của trang ngay từ đầu và cũng kiểm soát thông tin của người dùng. . . Tôi không thấy sự khác biệt.
Walt W

20
Re "Mã tiêm - ... Một lần nữa, JavaScript trong trình duyệt không gặp phải vấn đề đó" & "Ngoài ra, nếu bạn đang chạy trên trình duyệt thì việc tiêm mã là một rủi ro khá nhỏ, tôi tin thế." Bạn có gợi ý rằng việc tiêm mã trong trình duyệt không phải là vấn đề? XSS đã nằm trong top 3 âm hộ trong danh sách top 10 của OWASP trong vài năm hoạt động.
Mike Samuel

72

eval()không phải là xấu xa. Hoặc, nếu nó là, nó xấu giống như cách phản chiếu, I / O của tệp / mạng, luồng và IPC là "ác" trong các ngôn ngữ khác.

Nếu, với mục đích của bạn , eval()nhanh hơn giải thích thủ công hoặc làm cho mã của bạn đơn giản hơn hoặc rõ ràng hơn ... thì bạn nên sử dụng nó. Nếu không, thì bạn không nên. Đơn giản như vậy.


5
Một mục đích như vậy có thể là tạo mã được tối ưu hóa quá dài hoặc quá lặp đi lặp lại để viết bằng tay. Các loại công cụ, trong LISP, sẽ gọi cho một macro.
dâu

5
Đây là lời khuyên chung đến mức nó có thể được áp dụng cho bất kỳ khối mã nào tồn tại. Nó thực sự không thêm bất cứ điều gì vào câu hỏi này; đặc biệt, nó không giúp bất cứ ai đến đây xác định liệu việc sử dụng cụ thể của họ có vấn đề hay không.
jpmc26

2
Nhanh hơn, đơn giản hơn, rõ ràng hơn ... Câu trả lời này không bao hàm ý nghĩa bảo mật đủ tốt.
Ruud Helderman

55

Khi bạn tin tưởng nguồn.

Trong trường hợp JSON, ít nhiều khó khăn để giả mạo nguồn, bởi vì nó đến từ một máy chủ web mà bạn kiểm soát. Miễn là bản thân JSON không chứa dữ liệu mà người dùng đã tải lên, không có nhược điểm lớn nào khi sử dụng eval.

Trong tất cả các trường hợp khác, tôi sẽ cố gắng hết sức để đảm bảo dữ liệu do người dùng cung cấp tuân thủ quy tắc của tôi trước khi đưa dữ liệu vào eval ().


13
Một chuỗi json phải luôn được kiểm tra đối với ngữ pháp json trước khi sử dụng nó trong eval (). Vì vậy, chuỗi json "{foo: alert ('XSS')}" sẽ không vượt qua vì cảnh báo ('XSS') không phải là một giá trị phù hợp.
Gumbo

3
Vâng, sử dụng HTTPS, sau đó. OTOH: man-in-the-middle không phải là kịch bản tấn công điển hình cho ứng dụng web dành cho khu vườn, trong khi đó, kịch bản chéo trang là.
Tomalak

7
evalcũng sẽ không phân tích chính xác tất cả các chuỗi JSON hợp lệ. Ví dụ: JSON.parse(' "\u2028" ') === "\u2028"nhưng eval(' "\u2028" ')đưa ra một ngoại lệ vì U + 2028 là một dòng mới trong JavaScript nhưng nó không phải là một dòng mới theo như JSON có liên quan.
Mike Samuel

1
@Justin - nếu giao thức bị xâm phạm, thông thường, tải trang ban đầu sẽ được gửi qua cùng giao thức đó, và sau đó là điểm tranh luận vì máy khách đã bị xâm phạm như có thể.
antinome

1
@Tomalak nói rất hay, tôi đã đề cập đến điều này trong câu trả lời của tôi ngay bây giờ! Tuyệt vời!
NiCk Newman

25

Hãy lấy những người thực sự:

  1. Mọi trình duyệt chính hiện nay đều có bảng điều khiển tích hợp mà hacker của bạn có thể sử dụng với số lượng lớn để gọi bất kỳ chức năng nào với bất kỳ giá trị nào - tại sao họ lại bận tâm sử dụng một tuyên bố tệ hại - ngay cả khi họ có thể?

  2. Nếu phải mất 0,2 giây để biên dịch 2000 dòng JavaScript, thì sự suy giảm hiệu năng của tôi là gì nếu tôi đánh giá bốn dòng JSON?

Ngay cả lời giải thích của Crockford về 'eval is evil' cũng yếu.

eval là Evil, Hàm eval là tính năng bị lạm dụng nhiều nhất của JavaScript. Tránh nó đi

Như chính Crockford có thể nói "Loại tuyên bố này có xu hướng tạo ra chứng loạn thần kinh phi lý. Đừng mua nó."

Hiểu eval và biết khi nào nó có thể hữu ích là cách quan trọng hơn. Ví dụ, eval là một công cụ hợp lý để đánh giá phản hồi của máy chủ được tạo bởi phần mềm của bạn.

BTW: Prototype.js gọi eval trực tiếp năm lần (bao gồm trong evalJSON () và evalResponse ()). jQuery sử dụng nó trong parseJSON (thông qua hàm tạo).


10
JQuery sử dụng hàm JSON.parse dựng sẵn của trình duyệt nếu có (nhanh hơn & an toàn hơn nhiều), chỉ sử dụng eval làm cơ chế dự phòng. Câu nói "eval is evil" là một hướng dẫn hợp lý tốt.
jjmontes

30
Re "Mỗi trình duyệt chính hiện có một bảng điều khiển tích hợp ...". Mã tiêm là một vấn đề khi một người dùng có thể nhập mã sau đó chạy trong trình duyệt của người dùng khác. Các bảng điều khiển trình duyệt không tự cho phép một người dùng chạy mã trong trình duyệt của người dùng khác vì vậy họ không liên quan khi quyết định liệu nó có đáng để bảo vệ chống lại việc tiêm mã hay không.
Mike Samuel

28
"Mọi trình duyệt chính hiện nay đều có bảng điều khiển tích hợp ... tại sao họ lại bận tâm sử dụng một tuyên bố tệ hại?" - Bạn đang lạc lối. Tôi đề nghị bạn chỉnh sửa câu trả lời. Khả năng một người dùng tiêm mã có thể chạy trong trình duyệt của người khác là một vấn đề lớn. Và đây là nơi bạn cần để có được thực sự thực sự.
akkishore

5
@akkishore, tôi sẽ đánh giá cao nếu bạn đưa ra một ví dụ thực tế hỗ trợ cho các tuyên bố đã nêu của bạn.
Akash Kava

7
@AkashKava Điều bạn không nhận ra là, nếu tôi gửi javascript trong hộp bình luận của mình và javascript sẽ đưa nó vào cơ sở dữ liệu. Khi một người dùng khác xem bình luận đó (mà tôi đã đặt javascript vào), eval sẽ lấy javascript đó khi nó được hiển thị và đánh giá nó bằng trình thông dịch, khiến javascript nhúng của tôi thực thi trên trình duyệt của người dùng khác. Bằng cách này, tôi có thể thu thập tất cả các loại thông tin. Tên người dùng của họ, id người dùng của họ trong cơ sở dữ liệu, địa chỉ e-mail của họ, v.v ... Đây không phải là một câu trả lời khó, nếu bạn đã sử dụng XSS, bạn sẽ thấy trong khoảng 10 giây tại sao lại có vấn đề.
Kyle Richter

18

Tôi có xu hướng làm theo lời khuyên của Crockford cho eval(), và tránh nó hoàn toàn. Ngay cả những cách xuất hiện để yêu cầu nó không. Ví dụ, setTimeout()cho phép bạn vượt qua một chức năng chứ không phải eval.

setTimeout(function() {
  alert('hi');
}, 1000);

Ngay cả khi đó là một nguồn đáng tin cậy , tôi không sử dụng nó, bởi vì mã được trả về bởi JSON có thể bị cắt xén, tốt nhất có thể làm một cái gì đó mạnh mẽ, tệ nhất, phơi bày một cái gì đó xấu.


2
Tôi nghĩ rằng các lỗi trong trình định dạng JSON ở phía máy chủ chắc chắn là một vấn đề. Có phản hồi từ máy chủ phụ thuộc vào bất kỳ loại văn bản người dùng gửi? Sau đó, bạn phải xem XSS.
swilliams

3
Nếu máy chủ web của bạn không được xác thực qua HTTPS, thì bạn có thể phải chịu một cuộc tấn công trung gian nào đó, nơi một máy chủ khác chặn yêu cầu và gửi dữ liệu của chính nó.
Ben Combee

11
Nếu ai đó có thể thực hiện tấn công trung gian, anh ta có thể dễ dàng tiêm bất cứ thứ gì vào kịch bản của bạn.
el.pescado

10
Bạn hoàn toàn không nên dựa vào mã javascript của mình ... Bạn không dựa vào bất cứ thứ gì chạy ở phía máy khách ... Nếu ai đó tấn công trung gian tại sao anh ta lại gây rối với các đối tượng json của bạn? Anh ấy có thể phục vụ một trang web khác cho bạn và các tệp js khác nhau ...
Calmarius

5
Cá nhân tôi không thích lập luận "luôn có những cách khác để làm điều đó". Ví dụ, bạn cũng có thể nói luôn có cách để tránh lập trình hướng đối tượng. Điều đó không có nghĩa là nó không phải là một lựa chọn tuyệt vời. Nếu bạn hiểu eval và nó là mối nguy hiểm, nó có thể là một công cụ tuyệt vời để sử dụng trong các tình huống phù hợp.
dallin

4

Tôi thấy mọi người ủng hộ không sử dụng eval, vì là xấu xa , nhưng tôi thấy cùng một người sử dụng chức năng và setTimeout một cách linh hoạt, vì vậy họ sử dụng eval dưới mũ trùm : D

BTW, nếu hộp cát của bạn không đủ chắc chắn (ví dụ: nếu bạn đang làm việc trên một trang web cho phép tiêm mã) thì đây là vấn đề cuối cùng của bạn. Quy tắc bảo mật cơ bản là tất cả các đầu vào đều xấu, nhưng trong trường hợp JavaScript, ngay cả JavaScript cũng có thể là xấu, bởi vì trong JavaScript, bạn có thể ghi đè bất kỳ chức năng nào và bạn không thể chắc chắn rằng mình đang sử dụng một công cụ thực, vì vậy, nếu mã độc bắt đầu trước bạn, bạn không thể tin tưởng bất kỳ chức năng tích hợp JavaScript nào: D

Bây giờ phần kết của bài này là:

Nếu bạn THỰC SỰ cần nó (80% thời gian không cần thiết) và bạn chắc chắn về những gì bạn đang làm, chỉ cần sử dụng eval (hoặc Chức năng tốt hơn;)), đóng và OOP chiếm 80/90% trong trường hợp eval có thể được thay thế bằng cách sử dụng loại logic khác, phần còn lại là mã được tạo động (ví dụ: nếu bạn đang viết trình thông dịch) và như bạn đã nói đánh giá JSON (ở đây bạn có thể sử dụng đánh giá an toàn Crockford;))


như chính Crockford đã chỉ ra , các trình duyệt web hiện tại có hàm JSON.parse tích hợp sẵn .
Ruud Helderman

4

Eval là bổ sung cho việc biên dịch được sử dụng trong việc tạo khuôn mã. Bằng cách tạo khuôn mẫu, tôi có nghĩa là bạn viết một trình tạo mẫu đơn giản hóa để tạo mã mẫu hữu ích giúp tăng tốc độ phát triển.

Tôi đã viết một khung, trong đó các nhà phát triển không sử dụng EVAL, nhưng họ sử dụng khung của chúng tôi và đến lượt khung đó phải sử dụng EVAL để tạo mẫu.

Hiệu suất của EVAL có thể được tăng lên bằng cách sử dụng phương pháp sau; thay vì thực thi tập lệnh, bạn phải trả về một hàm.

var a = eval("3 + 5");

Nó nên được tổ chức như

var f = eval("(function(a,b) { return a + b; })");

var a = f(3,5);

Bộ nhớ đệm f chắc chắn sẽ cải thiện tốc độ.

Ngoài ra Chrome cho phép gỡ lỗi các chức năng như vậy rất dễ dàng.

Về bảo mật, sử dụng eval hay không sẽ khó tạo ra sự khác biệt nào,

  1. Trước hết, trình duyệt gọi toàn bộ tập lệnh trong hộp cát.
  2. Bất kỳ mã nào là xấu trong EVAL, là xấu trong chính trình duyệt. Kẻ tấn công hoặc bất kỳ ai cũng có thể dễ dàng chèn một nút script trong DOM và làm bất cứ điều gì nếu anh ta / cô ta có thể đánh bại bất cứ điều gì. Không sử dụng EVAL sẽ không làm cho bất kỳ sự khác biệt.
  3. Nó chủ yếu là bảo mật phía máy chủ kém có hại. Xác thực cookie kém hoặc triển khai ACL kém trên máy chủ gây ra hầu hết các cuộc tấn công.
  4. Một lỗ hổng Java gần đây, v.v. đã có trong mã gốc của Java. JavaScript đã và được thiết kế để chạy trong hộp cát, trong khi các applet được thiết kế để chạy bên ngoài hộp cát có chứng chỉ, v.v ... dẫn đến lỗ hổng và nhiều thứ khác.
  5. Viết mã để bắt chước một trình duyệt không khó. Tất cả bạn phải làm là thực hiện một yêu cầu HTTP đến máy chủ với chuỗi tác nhân người dùng yêu thích của bạn. Tất cả các công cụ kiểm tra giả lập trình duyệt nào; nếu kẻ tấn công muốn làm hại bạn, EVAL là biện pháp cuối cùng của họ. Họ có nhiều cách khác để đối phó với bảo mật phía máy chủ của bạn.
  6. DOM trình duyệt không có quyền truy cập vào tệp và không có tên người dùng. Trong thực tế không có gì trên máy mà eval có thể cung cấp quyền truy cập.

Nếu bảo mật phía máy chủ của bạn đủ vững chắc để bất cứ ai tấn công từ bất cứ đâu, bạn không nên lo lắng về EVAL. Như tôi đã đề cập, nếu EVAL không tồn tại, kẻ tấn công có nhiều công cụ để xâm nhập vào máy chủ của bạn bất kể khả năng EVAL của trình duyệt của bạn.

Eval chỉ tốt khi tạo một số mẫu để xử lý chuỗi phức tạp dựa trên thứ gì đó không được sử dụng trước. Ví dụ, tôi sẽ thích

"FirstName + ' ' + LastName"

Như trái ngược với

"LastName + ' ' + FirstName"

Như tên hiển thị của tôi, có thể đến từ cơ sở dữ liệu và không được mã hóa cứng.


Bạn có thể sử dụng chức năng thay vì eval - function (first, last) { return last + ' ' + first }.
Konrad Borowski

Tên của các cột đến từ cơ sở dữ liệu.
Akash Kava

3
Mối đe dọa của evalhầu hết là người dùng khác . Giả sử bạn có một trang cài đặt và nó cho phép bạn đặt tên của bạn xuất hiện với người khác như thế nào. Chúng ta cũng nói rằng bạn không suy nghĩ rõ ràng khi bạn viết nó, vì vậy hộp chọn của bạn có các tùy chọn như thế nào <option value="LastName + ' ' + FirstName">Last First</option>. Tôi mở các công cụ dev của mình, thay đổi valuetùy chọn thành alert('PWNED!'), chọn tùy chọn đã thay đổi và gửi biểu mẫu. Bây giờ, bất cứ khi nào một số người khác có thể thấy tên hiển thị của tôi, mã đó sẽ chạy.
cHao

@cHao, Người bạn đang nói đến là ví dụ về bảo mật phía máy chủ kém, máy chủ không bao giờ nên chấp nhận dữ liệu có thể được thực thi dưới dạng mã trong trình duyệt của bất kỳ ai. Một lần nữa, bạn đã không hiểu khái niệm về bảo mật phía máy chủ kém.
Akash Kava

1
Bạn có thể than vãn về bảo mật phía máy chủ nếu bạn muốn, nhưng toàn bộ vấn đề evallà thực thi mã không phải là một phần của tập lệnh bạn đã viết. Nếu bạn không cần sức mạnh để làm điều đó (và bạn gần như không bao giờ làm thế), việc tránh evalgiúp giải quyết toàn bộ một loại vấn đề. Đó là một điều tốt nếu mã phía máy chủ của bạn không hoàn hảo.
cHao

4

Khi gỡ lỗi trong Chrome (v28.0.1500.72), tôi thấy rằng các biến không bị ràng buộc với các bao đóng nếu chúng không được sử dụng trong một hàm lồng nhau tạo ra bao đóng. Tôi đoán, đó là một sự tối ưu hóa của công cụ JavaScript.

NHƯNG : khi eval()được sử dụng bên trong một hàm gây ra sự đóng, TẤT CẢ các biến của các hàm bên ngoài bị ràng buộc với bao đóng, ngay cả khi chúng không được sử dụng. Nếu ai đó có thời gian để kiểm tra xem có thể tạo ra rò rỉ bộ nhớ hay không, vui lòng để lại cho tôi một bình luận bên dưới.

Đây là mã kiểm tra của tôi:

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is visible in debugger
            eval("1");
        })();
    }

    evalTest();
})();

(function () {
    var eval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();   // Variable "unused" is NOT visible in debugger
            var noval = eval;
            noval("1");
        })();
    }

    evalTest();
})();

(function () {
    var noval = function (arg) {
    };

    function evalTest() {
        var used = "used";
        var unused = "not used";

        (function () {
            used.toString();    // Variable "unused" is NOT visible in debugger
            noval("1");
        })();
    }

    evalTest();
})();

Điều tôi muốn chỉ ra ở đây là, eval () không nhất thiết phải đề cập đến eval()hàm gốc . Tất cả phụ thuộc vào tên của hàm . Vì vậy, khi gọi người bản địa eval()bằng một tên bí danh (nói var noval = eval;và sau đó là một hàm bên trong noval(expression);) thì việc đánh giá expressioncó thể thất bại khi nó đề cập đến các biến nên là một phần của bao đóng, nhưng thực tế thì không.



3

Dòng dưới cùng

Nếu bạn tạo hoặc vệ sinh mã bạn eval, nó không bao giờ là xấu .

Chi tiết hơn một chút

evalxấu nếu chạy trên máy chủ bằng cách sử dụng đầu vào được gửi bởi khách hàng không được nhà phát triển tạo ra hoặc không được nhà phát triển vệ sinh .

evalkhông phảixấu nếu chạy trên máy khách, ngay cả khi sử dụng đầu vào không được xác thực được tạo bởi máy khách .

Rõ ràng là bạn phải luôn vệ sinh đầu vào, để có một số quyền kiểm soát đối với những gì mã của bạn tiêu thụ.

Lý luận

Máy khách có thể chạy bất kỳ mã tùy ý nào mà chúng muốn, ngay cả khi nhà phát triển không mã hóa nó; Điều này đúng không chỉ với những gì bị đánh giá, mà còn gọi cho evalchính nó .


2

Ví dụ duy nhất khi bạn nên sử dụng eval () là khi bạn cần chạy JS động khi đang di chuyển. Tôi đang nói về JS mà bạn tải xuống không đồng bộ từ máy chủ ...

... Và 9 lần 10 bạn có thể dễ dàng tránh làm điều đó bằng cách tái cấu trúc.


Ngày nay, có nhiều cách khác (và tốt hơn) để tải JavaScript không đồng bộ từ máy chủ: w3bits.com/async-javascript
Ruud Helderman

1

evalhiếm khi là lựa chọn đúng đắn. Mặc dù có thể có nhiều trường hợp bạn có thể thực hiện những gì bạn cần thực hiện bằng cách ghép các tập lệnh lại với nhau và chạy nó một cách nhanh chóng, bạn thường có các kỹ thuật mạnh hơn và có thể bảo trì theo ý của bạn: ký hiệu mảng kết hợp ( obj["prop"]giống như obj.prop) , đóng cửa, kỹ thuật hướng đối tượng, kỹ thuật chức năng - sử dụng chúng thay thế.


1

Theo như kịch bản của khách hàng, tôi nghĩ vấn đề bảo mật là một điểm cần thiết. Mọi thứ được tải vào trình duyệt đều phải chịu sự thao túng và nên được xử lý như vậy. Không có rủi ro trong việc sử dụng câu lệnh eval () khi có nhiều cách dễ dàng hơn để thực thi mã JavaScript và / hoặc thao tác với các đối tượng trong DOM, chẳng hạn như thanh URL trong trình duyệt của bạn.

javascript:alert("hello");

Nếu ai đó muốn thao túng DOM của họ, tôi nói xoay đi. Bảo mật để ngăn chặn mọi loại tấn công phải luôn là trách nhiệm của ứng dụng máy chủ, định kỳ.

Từ quan điểm thực dụng, không có lợi ích gì khi sử dụng eval () trong tình huống mà mọi thứ có thể được thực hiện theo cách khác. Tuy nhiên, có những trường hợp cụ thể mà một eval NÊN được sử dụng. Khi đó, nó chắc chắn có thể được thực hiện mà không có bất kỳ nguy cơ làm nổ tung trang.

<html>
    <body>
        <textarea id="output"></textarea><br/>
        <input type="text" id="input" />
        <button id="button" onclick="execute()">eval</button>

        <script type="text/javascript">
            var execute = function(){
                var inputEl = document.getElementById('input');
                var toEval = inputEl.value;
                var outputEl = document.getElementById('output');
                var output = "";

                try {
                    output = eval(toEval);
                }
                catch(err){
                    for(var key in err){
                        output += key + ": " + err[key] + "\r\n";
                    }
                }
                outputEl.value = output;
            }
        </script>
    <body>
</html>

6
Re "Không có rủi ro trong việc sử dụng câu lệnh eval () khi có nhiều cách dễ dàng hơn để thực thi javascript và / hoặc thao tác với các đối tượng trong DOM". Mã tiêm là một vấn đề khi một người dùng có thể nhập mã sau đó chạy trong trình duyệt của người dùng khác. Các bảng điều khiển trình duyệt không tự cho phép một người dùng chạy mã trong trình duyệt của người dùng khác vì vậy họ không liên quan khi quyết định liệu nó có đáng để bảo vệ chống lại việc tiêm mã hay không.
Mike Samuel

Không <head></head>cần thiết, ngay cả khi trống?
Peter Mortensen

2
Câu trả lời này hoàn toàn bỏ qua các rủi ro của XSS .
Ruud Helderman

1

Về phía máy chủ, eval rất hữu ích khi xử lý các tập lệnh bên ngoài như sql hoặc Influxdb hoặc mongo. Nơi xác thực tùy chỉnh trong thời gian chạy có thể được thực hiện mà không cần triển khai lại dịch vụ của bạn.

Ví dụ: dịch vụ thành tích có siêu dữ liệu sau

{
  "568ff113-abcd-f123-84c5-871fe2007cf0": {
    "msg_enum": "quest/registration",
    "timely": "all_times",
    "scope": [
      "quest/daily-active"
    ],
    "query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" LIMIT 1`",
    "validator": "valid > 0",
    "reward_external": "ewallet",
    "reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/registration:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/registration\"}`"
  },
  "efdfb506-1234-abcd-9d4a-7d624c564332": {
    "msg_enum": "quest/daily-active",
    "timely": "daily",
    "scope": [
      "quest/daily-active"
    ],
    "query": "`SELECT COUNT(point) AS valid from \"${userId}/dump/quest/daily-active\" WHERE time >= '${today}' ${ENV.DAILY_OFFSET} LIMIT 1`",
    "validator": "valid > 0",
    "reward_external": "ewallet",
    "reward_external_payload": "`{\"token\": \"${token}\", \"userId\": \"${userId}\", \"amountIn\": 1, \"conversionType\": \"quest/daily-active:silver\", \"exchangeProvider\":\"provider/achievement\",\"exchangeType\":\"payment/quest/daily-active\"}`"
  }
}

Mà sau đó cho phép,

  • Tiêm trực tiếp đối tượng / giá trị thông qua chuỗi ký tự trong json, hữu ích cho việc tạo khuôn mẫu văn bản

  • Có thể được sử dụng như một công cụ so sánh, giả sử chúng tôi đưa ra các quy tắc làm thế nào để xác thực nhiệm vụ hoặc sự kiện trong CMS

Con này:

  • Có thể là lỗi trong mã và phá vỡ mọi thứ trong dịch vụ, nếu không được kiểm tra đầy đủ.

  • Nếu một hacker có thể viết tập lệnh trên hệ thống của bạn, thì bạn đã bị lừa khá nhiều.

  • Một cách để xác thực tập lệnh của bạn là giữ băm tập lệnh của bạn ở nơi an toàn, vì vậy bạn có thể kiểm tra chúng trước khi chạy.


Đẹp. Khi tôi hỏi câu hỏi, tôi thậm chí đã không nghĩ về JS phía máy chủ.
Richard Turner

1

Tôi nghĩ rằng bất kỳ trường hợp nào của eval được biện minh sẽ là hiếm. Bạn có nhiều khả năng sử dụng nó với suy nghĩ rằng nó hợp lý hơn bạn sử dụng nó khi nó thực sự hợp lý.

Các vấn đề bảo mật được biết đến nhiều nhất. Nhưng cũng cần lưu ý rằng JavaScript sử dụng trình biên dịch JIT và điều này hoạt động rất kém với eval. Eval giống như một hộp đen cho trình biên dịch và JavaScript cần có khả năng dự đoán mã trước thời hạn (ở một mức độ nào đó) để áp dụng tối ưu và chính xác các tối ưu hóa hiệu suất và phạm vi. Trong một số trường hợp, tác động hiệu suất thậm chí có thể ảnh hưởng đến mã khác bên ngoài eval.

Nếu bạn muốn biết thêm: https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20%26%20closures/ch2.md#eval


0

Bạn có thể sử dụng nó nếu bạn có toàn quyền kiểm soát mã được truyền cho evalhàm.


2
Nếu bạn có toàn quyền kiểm soát những gì bạn đang chuyển đến eval, thì câu hỏi lớn sẽ trở thành, khi nào thì nó có ý nghĩa cho đó là một chuỗi chứ không phải là JS thực sự?
cHao

@cHao Ví dụ: nếu bạn có Ứng dụng trò chơi lớn (5-10 MB Javascript), tốt hơn hết là xây dựng AJAX-Preloader tải nhanh đơn giản (1kb), tải Main-Script lớn, trong khi hiển thị Đang tải- Thanh hoặc một cái gì đó tương tự. Sau khi tải xuống, bạn có thể sử dụng "eval (nguồn)" hoặc tốt hơn là "Chức năng mới (nguồn)" để chạy Game-Application-Script đã tải. Bằng cách đó, người dùng có thể thấy trực quan rằng Ứng dụng cần có thời gian để tải xuống cho đến khi trò chơi có thể bắt đầu. Không có điều đó, người dùng phải chờ cho toàn bộ Ứng dụng tải mà không có bất kỳ phản hồi trực quan nào.
SammieFox

@SammieFox Có nhiều cách khác (và tốt hơn) để làm điều này, đáng chú ý nhất <script async="true" src="...">. Xem thêm: w3bits.com/async-javascript
Ruud Helderman 14/07/19

Câu trả lời là lời khuyên nguy hiểm; quá nhiều nhà phát triển có một cảm giác sai lầm trong việc kiểm soát. Lời khuyên này có ý nghĩa đối với phần mềm không còn được duy trì tích cực. Nhưng phần mềm như vậy nên được coi là đã chết.
Ruud Helderman

0

Chỉ trong quá trình thử nghiệm, nếu có thể. Cũng lưu ý rằng eval () chậm hơn nhiều so với các trình đánh giá JSON, v.v.


0

Không có lý do gì để không sử dụng eval () miễn là bạn có thể chắc chắn rằng nguồn mã đến từ bạn hoặc người dùng thực tế. Mặc dù anh ta có thể thao tác những gì được gửi vào hàm eval (), nhưng đó không phải là vấn đề bảo mật, vì anh ta có thể thao túng mã nguồn của trang web và do đó có thể tự thay đổi mã JavaScript.

Vậy ... khi nào không sử dụng eval ()? Eval () không nên được sử dụng khi có cơ hội bên thứ ba có thể thay đổi nó. Giống như chặn kết nối giữa máy khách và máy chủ của bạn (nhưng nếu đó là sự cố, hãy sử dụng HTTPS). Bạn không nên eval () để phân tích mã được viết bởi những người khác như trong một diễn đàn.


Re "Không có lý do gì để không sử dụng eval () miễn là bạn có thể chắc chắn rằng nguồn mã đến từ bạn hoặc người dùng thực tế." Điều này giả định rằng có một người dùng duy nhất. Tiền đề đó không được nêu trong OP. Khi có nhiều người dùng, việc bất cẩn evalmột chuỗi được tạo từ nội dung từ một người dùng có thể cho phép người dùng đó thực thi mã trong trình duyệt của người dùng khác.
Mike Samuel

@MikeSamuel, eval có thể thực thi mã trong trình duyệt của người dùng khác, tôi chưa nghe thấy điều này, bạn đã thử chưa? Điều này không bao giờ xảy ra trong lịch sử duyệt web, bạn có thể chỉ cho chúng tôi một ví dụ không?
Akash Kava

@AkashKava, Một chuỗi có thể bắt nguồn với một tác nhân người dùng, được lưu trữ trong cơ sở dữ liệu và sau đó được phục vụ cho một trình duyệt khác eval. Lúc nào chả vậy.
Mike Samuel

Cơ sở dữ liệu @MikeSamuel? Ở đâu? ai phục vụ sai chuỗi? Không phải là cơ sở dữ liệu về phía máy chủ để đổ lỗi? trước hết EVAL không được đổ lỗi cho mã phía máy chủ được viết kém. Sử dụng jsfiddle và cho thế giới thấy một ví dụ thế giới thực nơi nó có thể gây hại.
Akash Kava

2
@AkashKava, tôi không hiểu câu hỏi của bạn. Chúng tôi không nói về một ứng dụng cụ thể, nhưng lý do không sử dụng eval. Làm thế nào là hữu ích để đổ lỗi cho máy chủ? Nếu bất cứ ai nên bị đổ lỗi, nó nên là kẻ tấn công. Bất kể đổ lỗi, một khách hàng không dễ bị tổn thương với XSS mặc dù các lỗi trong máy chủ tốt hơn một khách hàng dễ bị tổn thương, tất cả những người khác đều bình đẳng.
Mike Samuel

0

Nếu nó thực sự cần thiết thì không phải là xấu xa. Nhưng 99,9% việc sử dụng eval mà tôi vấp phải là không cần thiết (không bao gồm công cụ setTimeout).

Đối với tôi, cái ác không phải là hiệu suất hay thậm chí là vấn đề bảo mật (tốt, gián tiếp là cả hai). Tất cả những sử dụng không cần thiết như vậy của eval thêm vào một địa ngục bảo trì. Công cụ tái cấu trúc được ném ra. Tìm kiếm mã là khó. Hiệu ứng không lường trước được của những kẻ xấu là quân đoàn.


5
eval không cần thiết cho setTimeout. Bạn có thể sử dụng một tham chiếu chức năng ở đó quá.
Matthew Crumley

0

Khi nào JavaScript của eval () không xấu?

Tôi luôn cố gắng ngăn cản sử dụng eval . Hầu như luôn luôn, một giải pháp sạch hơn và có thể bảo trì có sẵn. Eval không cần thiết ngay cả để phân tích cú pháp JSON . Eval thêm vào địa ngục bảo trì . Không phải không có lý do, nó được các bậc thầy như Douglas Crockford cau mày.

Nhưng tôi đã tìm thấy một ví dụ về nơi nó nên được sử dụng:

Khi bạn cần vượt qua biểu thức.

Ví dụ: tôi có một hàm xây dựng một google.maps.ImageMapTypeđối tượng chung cho tôi, nhưng tôi cần cho nó biết công thức, làm thế nào để xây dựng URL gạch từ zoomcoordcác tham số:

my_func({
    name: "OSM",
    tileURLexpr: '"http://tile.openstreetmap.org/"+b+"/"+a.x+"/"+a.y+".png"',
    ...
});

function my_func(opts)
{
    return new google.maps.ImageMapType({
        getTileUrl: function (coord, zoom) {
            var b = zoom;
            var a = coord;
            return eval(opts.tileURLexpr);
        },
        ....
    });
}

3
Điều này có vẻ như có thể được tái cấu trúc để eval () không cần thiết - brickURLexpr chỉ là một khuôn mẫu để một số sử dụng thay thế () hợp lý sẽ thực hiện công việc. Tuy nhiên, nó vẫn nhắc tôi về một ví dụ mà tôi đã nghĩ đến khi gửi câu hỏi, đó là việc cho phép người dùng chỉ định một công thức toán học được đánh giá, tương tự như chức năng bảng tính. Tất nhiên tôi đã không đề cập đến điều đó vào thời điểm đó bởi vì tôi không muốn ảnh hưởng đến câu trả lời!
Richard Turner

8
tileURL: function (zoom, coord) { return 'http://tile.openstreetmap.org/' + b + '/' + a.x + '/' + a.y + '.png'; },
Casey Chu

0

Ví dụ của tôi về việc sử dụng eval: nhập khẩu .

Làm thế nào nó thường được thực hiện.

var components = require('components');
var Button = components.Button;
var ComboBox = components.ComboBox;
var CheckBox = components.CheckBox;
...
// That quickly gets very boring

Nhưng với sự trợ giúp của evalvà một chức năng trợ giúp nhỏ, nó sẽ có được cái nhìn tốt hơn nhiều:

var components = require('components');
eval(importable('components', 'Button', 'ComboBox', 'CheckBox', ...));

importable có thể trông giống như (phiên bản này không hỗ trợ nhập thành viên cụ thể).

function importable(path) {
    var name;
    var pkg = eval(path);
    var result = '\n';

    for (name in pkg) {
        result += 'if (name !== undefined) throw "import error: name already exists";\n'.replace(/name/g, name);
    }

    for (name in pkg) {
        result += 'var name = path.name;\n'.replace(/name/g, name).replace('path', path);
    }
    return result;
}

2
+1 cho ý tưởng, nhưng bạn có một lỗi ở đây : .replace(/name/g, name).replace('path', path). Nếu namechứa chuỗi "path"thì bạn có thể nhận được bất ngờ.
cưới

1
Khai báo một biến cho mỗi thuộc tính componentslà mùi mã có thể có; tái cấu trúc mã của bạn có thể loại bỏ hoàn toàn 'vấn đề'. Giải pháp hiện tại của bạn chỉ là cú pháp đường. Nếu bạn khăng khăng làm điều đó, thì tôi khuyên bạn nên viết bộ tiền xử lý của riêng bạn, để được thực thi trước khi triển khai. Điều đó nên tránh evalxa mã sản xuất.
Ruud Helderman

0

Eval không xấu xa, chỉ bị lạm dụng.

Nếu bạn đã tạo mã đi vào nó hoặc có thể tin tưởng nó, nó sẽ ổn thôi. Mọi người cứ nói về việc đầu vào của người dùng không quan trọng với eval. Cũng sắp xếp rồi ~

Nếu có đầu vào của người dùng đi đến máy chủ, sau đó quay lại máy khách và mã đó đang được sử dụng trong eval mà không được khử trùng. Xin chúc mừng, bạn đã mở hộp pandora để dữ liệu người dùng được gửi đến bất cứ ai.

Tùy thuộc vào vị trí của eval, nhiều trang web sử dụng SPA và eval có thể giúp người dùng dễ dàng truy cập vào các ứng dụng bên trong mà nếu không thì sẽ không dễ dàng. Bây giờ họ có thể tạo một phần mở rộng trình duyệt không có thật có thể băng vào eval đó và đánh cắp dữ liệu một lần nữa.

Chỉ cần hiểu được quan điểm của bạn khi sử dụng eval. Tạo mã không thực sự lý tưởng khi bạn chỉ cần tạo các phương thức để thực hiện loại điều đó, sử dụng các đối tượng hoặc tương tự.

Bây giờ là một ví dụ tốt đẹp của việc sử dụng eval. Máy chủ của bạn đang đọc tệp swagger mà bạn đã tạo. Nhiều thông số URL được tạo theo định dạng {myParam}. Vì vậy, bạn muốn đọc URL và sau đó chuyển đổi chúng thành chuỗi mẫu mà không phải thực hiện thay thế phức tạp vì bạn có nhiều điểm cuối. Vì vậy, bạn có thể làm một cái gì đó như thế này. Lưu ý đây là một ví dụ rất đơn giản.

const params = { id: 5 };

const route = '/api/user/{id}';
route.replace(/{/g, '${params.');

// use eval(route); to do something

-1

Tạo mã. Gần đây tôi đã viết một thư viện có tên Hyperbars để thu hẹp khoảng cách giữa dom-ảotay lái . Nó làm điều này bằng cách phân tích một tay lái mẫu và chuyển đổi nó để hyperscript . Siêu ký tự được tạo dưới dạng một chuỗi trước tiên và trước khi trả lại, eval()nó sẽ biến nó thành mã thực thi. tôi đã tìm thấyeval() trong tình huống đặc biệt này trái ngược hoàn toàn với cái ác.

Cơ bản từ

<div>
    {{#each names}}
        <span>{{this}}</span>
    {{/each}}
</div>

Để điều này

(function (state) {
    var Runtime = Hyperbars.Runtime;
    var context = state;
    return h('div', {}, [Runtime.each(context['names'], context, function (context, parent, options) {
        return [h('span', {}, [options['@index'], context])]
    })])
}.bind({}))

Hiệu suất của eval() không phải là một vấn đề trong tình huống như vậy vì bạn chỉ cần diễn giải chuỗi được tạo một lần và sau đó sử dụng lại đầu ra thực thi nhiều lần.

Bạn có thể thấy cách tạo mã đã đạt được nếu bạn tò mò ở đây .


"Siêu ký tự được tạo dưới dạng một chuỗi đầu tiên (...)" Có ý nghĩa hơn để thực hiện tất cả việc tạo mã trong giai đoạn xây dựng, viết mã siêu ký tự kết quả vào một tệp thực thi (.js) riêng biệt, sau đó triển khai tệp đó để kiểm tra và sản xuất. Tôi thích cách bạn sử dụng tạo mã. Đó chỉ evallà một gợi ý rằng một số trách nhiệm thuộc về thời gian biên dịch, đã chuyển sang thời gian chạy.
Ruud Helderman

-1

Tôi tin rằng eval là một chức năng rất mạnh mẽ cho các ứng dụng web phía máy khách và an toàn ... An toàn như JavaScript, không có. :-) Các vấn đề bảo mật về cơ bản là vấn đề phía máy chủ bởi vì, bây giờ, với công cụ như Fireorms, bạn có thể tấn công bất kỳ ứng dụng JavaScript nào.


1
Việc sử dụng các evalnhu cầu cần được bảo đảm chống lại các cuộc tấn công XSS, điều này không phải lúc nào cũng dễ dàng để có được quyền.
Benjamin

-1

Eval rất hữu ích cho việc tạo mã khi bạn không có macro.

Ví dụ (ngu ngốc), nếu bạn đang viết trình biên dịch Brainfuck , có lẽ bạn sẽ muốn xây dựng một hàm thực hiện chuỗi lệnh dưới dạng chuỗi và đánh giá nó để trả về hàm.


Hoặc bạn viết một trình biên dịch (giúp lưu chứ không phải thực thi mã được tạo) hoặc bạn viết một trình thông dịch (trong đó mỗi lệnh có một triển khai được biên dịch trước). Không phải là một trường hợp sử dụng cho eval.
Ruud Helderman

Nếu bạn đã tạo mã javascript và muốn thực thi ngay lập tức (giả sử lợi ích hiệu suất so với giải thích trực tiếp), đó sẽ là trường hợp sử dụng cho eval.
Erik Haliewicz

Điểm tốt; Tôi đã thấy một ví dụ trong bài viết này về Blockly . Tôi bị sốc khi Google khuyến nghị eval, khi giải pháp thay thế ( Chức năng ) nhanh hơn ( như được giải thích trong MDN ) và đáng tin cậy hơn (ngăn ngừa các lỗi không thể đoán trước bằng cách cách ly tốt hơn giữa mã được tạo và mã 'hỗ trợ' khác trên cùng một trang web).
Ruud Helderman 17/07/19

-5

Khi bạn phân tích cấu trúc JSON bằng hàm phân tích cú pháp (ví dụ: jQuery.parseJSON), nó sẽ mong đợi một cấu trúc hoàn hảo của tệp JSON (mỗi tên thuộc tính nằm trong dấu ngoặc kép). Tuy nhiên, JavaScript linh hoạt hơn. Do đó, bạn có thể sử dụng eval () để tránh nó.


4
Đừng mù quáng sử dụng eval, đặc biệt. khi nhận dữ liệu JSON từ nguồn của bên thứ ba. Xem JSON.Stringify mà không có dấu ngoặc kép về thuộc tính? cho cách tiếp cận đúng để phân tích "JSON mà không trích dẫn tên khóa".
Cướp W

2
Nếu nó không sử dụng dấu ngoặc kép xung quanh tên thuộc tính, thì đó có thể là biểu diễn chuỗi của một đối tượng theo nghĩa đen, nhưng nó không phải là JSON . JSON xác định tên thuộc tính như một stringvà định nghĩa một stringnhư một chuỗi các số không hay nhiều ký tự Unicode, bọc trong dấu ngoặc kép, sử dụng thoát xuyệc ngược.
Mã vô dụng

Xem bài viết của Nikolas Zakas - "eval () không phải là xấu, chỉ là hiểu lầm" nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunderstood
vitmalina

@vitmalina Từ bài viết của Zakas: "Điều này có thể nguy hiểm nếu bạn lấy đầu vào của người dùng và chạy nó thông qua eval (). Tuy nhiên, nếu đầu vào của bạn không phải từ người dùng, có nguy hiểm thực sự không?" Đó chính xác là vấn đề. Khi mã của bạn phát triển vượt quá tỷ lệ 'xin chào thế giới', sẽ nhanh chóng chứng minh bạn không bị rò rỉ thông tin người dùng eval. Trong bất kỳ ứng dụng web nhiều người thuê nghiêm túc nào, với hàng tá nhà phát triển làm việc trên cùng một cơ sở mã, điều này là không thể chấp nhận được.
Ruud Helderman
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.