Hàm eval là một cách mạnh mẽ và dễ dàng để tạo mã động, vậy các cảnh báo là gì?
Hàm eval là một cách mạnh mẽ và dễ dàng để tạo mã động, vậy các cảnh báo là gì?
Câu trả lời:
Việc sử dụng eval không đúng cách sẽ mở ra mã của bạn cho các cuộc tấn công tiêm chích
Gỡ lỗi có thể khó khăn hơn (không có số dòng, v.v.)
mã eval'd thực thi chậm hơn (không có cơ hội biên dịch / bộ đệm mã eval'd)
Chỉnh sửa: Như @Jeff Walden đã chỉ ra trong các bình luận, # 3 ngày nay ít đúng hơn so với năm 2008. Tuy nhiên, trong khi một số bộ đệm của các tập lệnh được biên dịch có thể xảy ra, điều này sẽ chỉ giới hạn ở các tập lệnh được lặp đi lặp lại mà không sửa đổi. Một kịch bản có khả năng hơn là bạn đang đánh giá các tập lệnh đã trải qua sửa đổi nhỏ mỗi lần và như vậy không thể được lưu trữ. Chúng ta hãy nói rằng MỘT SỐ mã eval'd thực thi chậm hơn.
badHackerGuy'); doMaliciousThings();
và nếu bạn lấy tên người dùng của mình, hãy ghép nó thành một số tập lệnh và loại bỏ nó trong trình duyệt của người khác thì tôi có thể chạy bất kỳ javascript nào tôi muốn trên máy của họ (ví dụ: buộc họ phải +1 bài đăng của tôi, đăng dữ liệu của họ lên máy chủ của tôi, v.v.)
debugger;
câu lệnh vào mã nguồn của bạn. Điều này sẽ dừng việc thực hiện chương trình của bạn trên dòng đó. Sau đó, bạn có thể thêm các điểm dừng gỡ lỗi giống như nó chỉ là một tệp JS khác.
eval không phải lúc nào cũng xấu xa. Có những lúc nó hoàn toàn thích hợp.
Tuy nhiên, eval hiện đang được sử dụng ồ ạt bởi những người không biết họ đang làm gì. Điều đó bao gồm những người viết hướng dẫn JavaScript, thật không may, và trong một số trường hợp, điều này thực sự có thể có hậu quả bảo mật - hoặc, thường xuyên hơn, các lỗi đơn giản. Vì vậy, chúng ta càng có thể làm để ném một dấu hỏi lên eval, thì càng tốt. Bất cứ khi nào bạn sử dụng eval, bạn cần phải tỉnh táo - kiểm tra xem bạn đang làm gì, bởi vì rất có thể bạn có thể thực hiện nó một cách tốt hơn, an toàn hơn, sạch sẽ hơn.
Để đưa ra một ví dụ quá điển hình, để đặt màu của một phần tử với id được lưu trong biến 'khoai tây':
eval('document.' + potato + '.style.color = "red"');
Nếu các tác giả của loại mã ở trên có manh mối về những điều cơ bản về cách thức hoạt động của các đối tượng JavaScript, thì họ đã nhận ra rằng dấu ngoặc vuông có thể được sử dụng thay cho tên dấu chấm bằng chữ, tránh sự cần thiết của eval:
document[potato].style.color = 'red';
... dễ đọc hơn cũng như ít lỗi hơn.
(Nhưng sau đó, một người / thực sự / biết những gì họ đang làm sẽ nói:
document.getElementById(potato).style.color = 'red';
đáng tin cậy hơn thủ thuật cũ tinh ranh khi truy cập các phần tử DOM ngay từ đối tượng tài liệu.)
JSON.parse()
thay vì eval()
cho JSON?
Tôi tin rằng vì nó có thể thực thi bất kỳ hàm JavaScript nào từ một chuỗi. Sử dụng nó giúp mọi người dễ dàng tiêm mã lừa đảo vào ứng dụng.
Hai điểm đến trong tâm trí:
Bảo mật (nhưng miễn là bạn tạo chuỗi để tự đánh giá, đây có thể không phải là vấn đề)
Hiệu suất: cho đến khi mã được thực thi là không xác định, nó không thể được tối ưu hóa. (về javascript và hiệu suất, chắc chắn là bài thuyết trình của Steve Yegge )
eval
xử lý và sau đó, đoạn mã nhỏ đó chạy trên trình duyệt B của người dùng
Truyền đầu vào của người dùng vào eval () là một rủi ro bảo mật, nhưng mỗi lần gọi eval () tạo ra một phiên bản mới của trình thông dịch JavaScript. Đây có thể là một con heo tài nguyên.
Chủ yếu, nó khó hơn rất nhiều để duy trì và gỡ lỗi. Nó giống như một goto
. Bạn có thể sử dụng nó, nhưng nó khiến việc tìm kiếm vấn đề trở nên khó khăn hơn và khó hơn đối với những người có thể cần thực hiện thay đổi sau này.
Một lưu ý là bạn thường có thể sử dụng eval () để thực thi mã trong môi trường bị hạn chế khác - các trang web mạng xã hội chặn các hàm JavaScript cụ thể đôi khi có thể bị đánh lừa bằng cách phá vỡ chúng trong một khối eval -
eval('al' + 'er' + 't(\'' + 'hi there!' + '\')');
Vì vậy, nếu bạn đang tìm cách chạy một số mã JavaScript, nơi nó có thể không được phép ( Myspace , tôi đang nhìn bạn ...) thì eval () có thể là một mẹo hữu ích.
Tuy nhiên, vì tất cả các lý do đã đề cập ở trên, bạn không nên sử dụng nó cho mã của riêng mình, nơi bạn có toàn quyền kiểm soát - điều đó là không cần thiết và tốt hơn là nên chuyển sang kệ 'hack JavaScript lừa đảo'.
[]["con"+"struc"+"tor"]["con"+"struc"+"tor"]('al' + 'er' + 't(\'' + 'hi there!' + '\')')()
Trừ khi bạn để eval () một nội dung động (thông qua cgi hoặc đầu vào), nó sẽ an toàn và vững chắc như tất cả các JavaScript khác trong trang của bạn.
Cùng với phần còn lại của câu trả lời, tôi không nghĩ rằng các tuyên bố tệ hại có thể được giảm thiểu nâng cao.
Đó là một rủi ro bảo mật có thể xảy ra, nó có phạm vi thực thi khác và khá kém hiệu quả, vì nó tạo ra một môi trường tập lệnh hoàn toàn mới để thực thi mã. Xem ở đây để biết thêm thông tin: eval .
Tuy nhiên, nó khá hữu ích và được sử dụng có chừng mực có thể bổ sung rất nhiều chức năng tốt.
Trừ khi bạn chắc chắn 100% rằng mã được đánh giá là từ một nguồn đáng tin cậy (thường là ứng dụng của riêng bạn), thì đó là cách chắc chắn để đưa hệ thống của bạn vào một cuộc tấn công kịch bản chéo trang.
Tôi biết cuộc thảo luận này đã cũ, nhưng tôi thực sự thích cách tiếp cận này của Google và muốn chia sẻ cảm giác đó với những người khác;)
Một điều nữa là bạn càng hiểu rõ hơn Bạn càng cố gắng hiểu và cuối cùng Bạn không tin rằng điều gì đó tốt hay xấu chỉ vì ai đó nói vậy :) Đây là một video rất truyền cảm hứng giúp tôi suy nghĩ nhiều hơn :) THỰC HÀNH TỐT là tốt, nhưng đừng sử dụng chúng một cách thiếu suy nghĩ :)
Nó không hẳn là xấu với điều kiện bạn biết bạn đang sử dụng bối cảnh nào.
Nếu ứng dụng của bạn đang sử dụng eval()
để tạo một đối tượng từ một số JSON đã quay trở lại từ XMLHttpRequest đến trang web của riêng bạn, được tạo bởi mã phía máy chủ đáng tin cậy của bạn, thì đó có lẽ không phải là vấn đề.
Mã JavaScript phía máy khách không tin cậy dù sao cũng không thể làm được nhiều như vậy. Với điều kiện bạn đang thực hiện eval()
đã đến từ một nguồn hợp lý, bạn vẫn ổn.
Nó làm giảm đáng kể mức độ tự tin của bạn về bảo mật.
Nếu bạn muốn người dùng nhập một số hàm logic và đánh giá VÀ HOẶC thì hàm eval JavaScript là hoàn hảo. Tôi có thể chấp nhận hai chuỗi và eval(uate) string1 === string2
, v.v.
Nếu bạn phát hiện ra việc sử dụng eval () trong mã của mình, hãy nhớ câu thần chú là eval () là ác.
Hàm này lấy một chuỗi tùy ý và thực thi nó dưới dạng mã JavaScript. Khi mã được đề cập được biết trước (không được xác định khi chạy), không có lý do để sử dụng eval (). Nếu mã được tạo động khi chạy, thường có cách tốt hơn để đạt được mục tiêu mà không cần eval (). Ví dụ: chỉ cần sử dụng ký hiệu ngoặc vuông để truy cập các thuộc tính động là tốt hơn và đơn giản hơn:
// antipattern
var property = "name";
alert(eval("obj." + property));
// preferred
var property = "name";
alert(obj[property]);
Việc sử dụng eval()
cũng có ý nghĩa bảo mật, bởi vì bạn có thể đang thực thi mã (ví dụ: đến từ mạng) đã bị giả mạo. Đây là một phản mẫu phổ biến khi xử lý phản hồi JSON từ yêu cầu Ajax. Trong những trường hợp đó, tốt hơn là sử dụng các phương thức tích hợp sẵn của trình duyệt để phân tích phản hồi JSON để đảm bảo an toàn và hợp lệ. Đối với các trình duyệt không hỗ trợJSON.parse()
nguyên bản, bạn có thể sử dụng thư viện từ JSON.org.
Điều quan trọng cần nhớ là việc truyền các chuỗi tới setInterval()
, setTimeout()
và Function()
phần lớn hàm tạo, tương tự như việc sử dụng eval()
và do đó nên tránh.
Đằng sau hậu trường, JavaScript vẫn phải đánh giá và thực thi chuỗi bạn truyền dưới dạng mã lập trình:
// antipatterns
setTimeout("myFunc()", 1000);
setTimeout("myFunc(1, 2, 3)", 1000);
// preferred
setTimeout(myFunc, 1000);
setTimeout(function () {
myFunc(1, 2, 3);
}, 1000);
Sử dụng hàm tạo Hàm () mới tương tự như eval () và cần được tiếp cận cẩn thận. Nó có thể là một cấu trúc mạnh mẽ nhưng thường bị sử dụng sai. Nếu bạn hoàn toàn phải sử dụng eval()
, bạn có thể xem xét sử dụng Hàm () mới thay thế.
Có một lợi ích tiềm năng nhỏ vì mã được đánh giá trong Hàm mới () sẽ chạy trong phạm vi hàm cục bộ, do đó, bất kỳ biến nào được xác định bằng var trong mã được đánh giá sẽ không tự động trở thành toàn cục.
Một cách khác để ngăn chặn toàn cầu tự động là kết thúc
eval()
cuộc gọi thành một chức năng ngay lập tức.
Bên cạnh các vấn đề bảo mật có thể xảy ra nếu bạn đang thực thi mã do người dùng gửi, hầu hết thời gian có một cách tốt hơn không liên quan đến việc phân tích lại mã mỗi khi mã được thực thi. Các hàm hoặc thuộc tính đối tượng ẩn danh có thể thay thế hầu hết việc sử dụng eval và an toàn hơn và nhanh hơn nhiều.
Điều này có thể trở thành một vấn đề khi thế hệ trình duyệt tiếp theo xuất hiện với một số hương vị của trình biên dịch JavaScript. Mã được thực thi thông qua Eval có thể không hoạt động tốt như phần còn lại của JavaScript đối với các trình duyệt mới hơn này. Ai đó nên làm một số hồ sơ.
Đây là một trong những bài viết hay nói về eval và làm thế nào nó không phải là một tội ác: http://www.nczonline.net/blog/2013/06/25/eval-isnt-evil-just-misunder Hiểu /
Tôi không nói rằng bạn nên chạy ra ngoài và bắt đầu sử dụng eval () ở mọi nơi. Trong thực tế, có rất ít trường hợp sử dụng tốt để chạy eval () cả. Chắc chắn có những lo ngại về tính rõ ràng của mã, khả năng gỡ lỗi và hiệu năng chắc chắn không nên bỏ qua. Nhưng bạn không nên ngại sử dụng nó khi bạn gặp trường hợp eval () có ý nghĩa. Hãy thử không sử dụng nó trước, nhưng đừng để bất kỳ ai sợ bạn nghĩ rằng mã của bạn mỏng manh hơn hoặc kém an toàn hơn khi eval () được sử dụng một cách thích hợp.
eval () rất mạnh và có thể được sử dụng để thực thi câu lệnh JS hoặc đánh giá một biểu thức. Nhưng câu hỏi không phải là về việc sử dụng eval () mà chỉ nói một số cách chuỗi bạn chạy với eval () bị ảnh hưởng bởi một bên độc hại. Cuối cùng, bạn sẽ chạy mã độc. Với quyền lực đi kèm là trách nhiệm lớn. Vì vậy, sử dụng nó một cách khôn ngoan là bạn đang sử dụng nó. Điều này không liên quan nhiều đến hàm eval () nhưng bài viết này có thông tin khá tốt: http://bloss.popart.com/2009/07/javascript-injection-attacks/ Nếu bạn đang tìm kiếm những điều cơ bản về eval () xem tại đây: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
Công cụ JavaScript có một số tối ưu hóa hiệu suất mà nó thực hiện trong giai đoạn biên dịch. Một số trong số này có thể phân tích tĩnh về mã khi nó lexes và xác định trước vị trí của tất cả các khai báo biến và hàm, do đó, sẽ mất ít nỗ lực hơn để giải quyết các định danh trong khi thực thi.
Nhưng nếu Engine tìm thấy một eval (..) trong mã, thì về cơ bản, nó phải thừa nhận rằng tất cả nhận thức về vị trí định danh của nó có thể không hợp lệ, bởi vì nó không thể biết chính xác thời gian mà bạn có thể chuyển cho eval (..) để sửa đổi phạm vi từ vựng hoặc nội dung của đối tượng bạn có thể chuyển qua để tạo phạm vi từ vựng mới cần tham khảo.
Nói cách khác, theo nghĩa bi quan, hầu hết những tối ưu hóa đó sẽ trở nên vô nghĩa nếu eval (..) có mặt, vì vậy đơn giản là nó không thực hiện tối ưu hóa.
Điều này giải thích tất cả.
Tài liệu tham khảo :
https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#eval
https://github.com/getify/You-Dont-Know-JS/blob/master/scope%20&%20closures/ch2.md#performance
Nó không phải luôn luôn là một ý tưởng tồi. Lấy ví dụ, 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-ảo và tay 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 mà sau đó được sử dụng bởi ảo-dom. 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ư thế này bởi 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 .
Tôi sẽ đi xa hơn để nói rằng nó không thực sự quan trọng nếu bạn sử dụng eval()
trong javascript được chạy trong trình duyệt. * (Hãy cẩn thận)
Tất cả các trình duyệt hiện đại đều có bảng điều khiển dành cho nhà phát triển, nơi bạn có thể thực thi javascript tùy ý và bất kỳ nhà phát triển bán thông minh nào cũng có thể nhìn vào nguồn JS của bạn và đặt bất kỳ bit nào của nó vào bảng điều khiển dev để làm những gì họ muốn.
* Miễn là các điểm cuối máy chủ của bạn có xác thực & vệ sinh chính xác các giá trị do người dùng cung cấp, không có vấn đề gì được phân tích cú pháp và đánh giá trong javascript phía máy khách của bạn.
eval()
Tuy nhiên, nếu bạn hỏi liệu nó có phù hợp để sử dụng trong PHP hay không, câu trả lời là KHÔNG , trừ khi bạn đưa vào danh sách trắng bất kỳ giá trị nào có thể được chuyển cho tuyên bố eval của bạn.
Tôi sẽ không cố gắng bác bỏ bất cứ điều gì đã nói ở đây, nhưng tôi sẽ đề nghị sử dụng eval () mà theo như tôi biết) không thể được thực hiện bằng bất kỳ cách nào khác. Có lẽ có nhiều cách khác để mã hóa điều này, và có thể là cách để tối ưu hóa nó, nhưng điều này được thực hiện từ lâu và không có bất kỳ tiếng chuông và tiếng huýt sáo nào để minh họa cho việc sử dụng eval mà thực sự không có bất kỳ sự thay thế nào khác. Đó là: tên đối tượng động (hoặc chính xác hơn) được tạo theo chương trình (trái ngược với giá trị).
//Place this in a common/global JS lib:
var NS = function(namespace){
var namespaceParts = String(namespace).split(".");
var namespaceToTest = "";
for(var i = 0; i < namespaceParts.length; i++){
if(i === 0){
namespaceToTest = namespaceParts[i];
}
else{
namespaceToTest = namespaceToTest + "." + namespaceParts[i];
}
if(eval('typeof ' + namespaceToTest) === "undefined"){
eval(namespaceToTest + ' = {}');
}
}
return eval(namespace);
}
//Then, use this in your class definition libs:
NS('Root.Namespace').Class = function(settings){
//Class constructor code here
}
//some generic method:
Root.Namespace.Class.prototype.Method = function(args){
//Code goes here
//this.MyOtherMethod("foo")); // => "foo"
return true;
}
//Then, in your applications, use this to instantiate an instance of your class:
var anInstanceOfClass = new Root.Namespace.Class(settings);
EDIT: nhân tiện, tôi sẽ không đề xuất (vì tất cả các lý do bảo mật đã chỉ ra ở đây) rằng bạn căn cứ vào tên đối tượng của bạn trên đầu vào của người dùng. Tôi không thể tưởng tượng bất kỳ lý do tốt mà bạn muốn làm điều đó mặc dù. Tuy nhiên, tôi nghĩ rằng tôi sẽ chỉ ra rằng nó sẽ không phải là một ý tưởng tốt :)
namespaceToTest[namespaceParts[i]]
, không cần eval ở đây, vì vậy sự if(typeof namespaceToTest[namespaceParts[i]] === 'undefined') { namespaceToTest[namespaceParts[i]] = {};
khác biệt duy nhất choelse namespaceToTest = namespaceToTest[namespaceParts[i]];
Thu gom rác thải
Bộ sưu tập rác của trình duyệt không biết liệu mã đó có thể bị xóa khỏi bộ nhớ hay không để nó được lưu trữ cho đến khi trang được tải lại. Không quá tệ nếu người dùng của bạn chỉ ở trên trang của bạn trong thời gian ngắn, nhưng nó có thể là một vấn đề đối với webapp.
Đây là một kịch bản để demo vấn đề
https://jsfiddle.net/CynderRnAsh/qux1osnw/
document.getElementById("evalLeak").onclick = (e) => {
for(let x = 0; x < 100; x++) {
eval(x.toString());
}
};
Một cái gì đó đơn giản như đoạn mã trên khiến một lượng nhỏ bộ nhớ được lưu trữ cho đến khi ứng dụng chết. Điều này tồi tệ hơn khi tập lệnh bị loại bỏ là một hàm khổng lồ và được gọi là khoảng thời gian.