सैंडबॉक्स किए गए iframe में eval() का इस्तेमाल करें

Chrome का एक्सटेंशन सिस्टम, डिफ़ॉल्ट रूप से काफ़ी सख्त कॉन्टेंट की सुरक्षा के बारे में नीति (सीएसपी) लागू करता है. नीति से जुड़ी पाबंदियां आसान हैं: स्क्रिप्ट को अलग-अलग JavaScript फ़ाइलों में, लाइन के बाहर ले जाना चाहिए. इनलाइन इवेंट हैंडलर को addEventListener का इस्तेमाल करने के लिए बदलना चाहिए. साथ ही, eval() को बंद किया जाना चाहिए.

हालांकि, हम जानते हैं कि परफ़ॉर्मेंस को ऑप्टिमाइज़ करने और आसानी से जानकारी देने के लिए, कई लाइब्रेरी eval() और eval जैसे कंस्ट्रक्ट का इस्तेमाल करती हैं. जैसे, new Function(). टेंप्लेट बनाने वाली लाइब्रेरी ख़ास तौर पर इस स्टाइल के लागू होने की संभावना ज़्यादा होती है. कुछ फ़्रेमवर्क (जैसे, Angular.js) में सीएसपी पहले से काम करता है. हालांकि, कई लोकप्रिय फ़्रेमवर्क को अब तक ऐसे तरीके के साथ अपडेट नहीं किया गया है जो एक्सटेंशन के eval-लेस वर्ल्ड के साथ काम करता हो. इसलिए, इस सुविधा के लिए सहायता हटाने से, डेवलपर के लिए ज़्यादा मुश्किल हो गई है.

इस दस्तावेज़ में, सैंडबॉक्सिंग को सुरक्षित तरीके के तौर पर पेश किया गया है. इससे, सुरक्षा से समझौता किए बिना, इन लाइब्रेरी को अपने प्रोजेक्ट में शामिल किया जा सकता है.

सैंडबॉक्स का इस्तेमाल क्यों करें?

eval एक्सटेंशन में खतरनाक है, क्योंकि इसके ज़रिए चलाए जाने वाले कोड के पास, एक्सटेंशन के ज़्यादा अनुमति वाले एनवायरमेंट में मौजूद हर चीज़ का ऐक्सेस होता है. ऐसे कई दमदार chrome.* API उपलब्ध हैं जो उपयोगकर्ता की सुरक्षा और निजता पर गंभीर असर डाल सकता है; डेटा को आसानी से बाहर निकाला जाना हमारी सबसे कम चिंता है. हमारा समाधान एक सैंडबॉक्स की तरह है. इसकी मदद से, eval एक्सटेंशन का डेटा या एक्सटेंशन के ज़्यादा वैल्यू वाले एपीआई का इस्तेमाल करते हैं. कोई डेटा नहीं, कोई एपीआई नहीं, कोई समस्या नहीं.

हम ऐसा करने के लिए, एक्सटेंशन पैकेज में मौजूद कुछ एचटीएमएल फ़ाइलों को सैंडबॉक्स में डालते हैं. जब भी कोई सैंडबॉक्स किया गया पेज लोड होता है, तो उसे यूनीक ऑरिजिन में ले जाया जाएगा और इसे अस्वीकार कर दिया जाएगा chrome.* एपीआई का ऐक्सेस. अगर हम इस सैंडबॉक्स किए गए पेज को किसी iframe के ज़रिए अपने एक्सटेंशन में लोड करते हैं, तो उसे संदेश भेज देता है, उसे उन संदेशों पर किसी तरह कार्रवाई करने देता है, और जब तक वह हमें नतीजा. इस आसान मैसेज सेवा के ज़रिए हमें वह सब कुछ मिलता है जो हमें eval-ड्रिवन को सुरक्षित तरीके से शामिल करने के लिए चाहिए हमारे एक्सटेंशन के वर्कफ़्लो में इस्तेमाल किया जा सकता है.

सैंडबॉक्स बनाना और इस्तेमाल करना

यदि आप सीधे कोड में जाना चाहते हैं, तो सैंडबॉक्सिंग नमूना एक्सटेंशन पकड़ें और बंद है. यह हैंडलबार के ऊपर बने छोटे से मैसेजिंग एपीआई का उदाहरण है आपको लाइब्रेरी बनानी होगी. इसमें आपको काम शुरू करने से जुड़ी हर चीज़ मिल जाएगी. अगर आपको इस बारे में थोड़ी और जानकारी चाहिए, तो यहां दिए गए सैंपल को देखें.

मेनिफ़ेस्ट में फ़ाइलों की सूची

जिस फ़ाइल को सैंडबॉक्स में चलाना है उसे एक्सटेंशन मेनिफ़ेस्ट में एक sandbox प्रॉपर्टी. यह एक अहम चरण है और इसे आसानी से भुलाया जा सकता है. इसलिए, दोबारा जांच लें कि आपकी सैंडबॉक्स की गई फ़ाइल, मेनिफ़ेस्ट में शामिल है या नहीं. इस सैंपल में, हम सही तरीके से फ़ाइल को सैंडबॉक्स कर रहे हैं "sandbox.html" नाम दिया गया है. मेनिफ़ेस्ट एंट्री इस तरह दिखती है:

{
  ...,
  "sandbox": {
     "pages": ["sandbox.html"]
  },
  ...
}

सैंडबॉक्स की गई फ़ाइल लोड करें

सैंडबॉक्स की गई फ़ाइल के साथ कुछ दिलचस्प करने के लिए, हमें उसे ऐसे कॉन्टेक्स्ट में लोड करना होगा जहां तो उसे एक्सटेंशन के कोड से ठीक किया जा सकता है. यहां, sandbox.html लोड किया गया है iframe से मिला एक्सटेंशन पेज. पेज की JavaScript फ़ाइल में एक कोड होता है. यह कोड, ब्राउज़र ऐक्शन पर क्लिक करने पर सैंडबॉक्स में एक मैसेज भेजता है. इसके लिए, यह पेज पर iframe को ढूंढता है और उसके contentWindow पर postMessage() को कॉल करता है. मैसेज एक ऑब्जेक्ट है, जिसमें तीन प्रॉपर्टी होती हैं: context, templateName, और command. हम जल्द ही context और command के बारे में बात करेंगे.

service-worker.js:

chrome.action.onClicked.addListener(() => {
  chrome.tabs.create({
    url: 'mainpage.html'
  });
  console.log('Opened a tab with a sandboxed page!');
});

extension-page.js:

let counter = 0;
document.addEventListener('DOMContentLoaded', () => {
  document.getElementById('reset').addEventListener('click', function () {
    counter = 0;
    document.querySelector('#result').innerHTML = '';
  });

  document.getElementById('sendMessage').addEventListener('click', function () {
    counter++;
    let message = {
      command: 'render',
      templateName: 'sample-template-' + counter,
      context: { counter: counter }
    };
    document.getElementById('theFrame').contentWindow.postMessage(message, '*');
  });

कुछ खतरनाक करना

sandbox.html के लोड होने पर, यह हैंडलबार लाइब्रेरी को लोड करता है और इनलाइन बनाता है और उसे कंपाइल करता है टेंप्लेट को हैंडलबार के सुझावों के तौर पर देखें:

extension-page.html:

<!DOCTYPE html>
<html>
  <head>
    <script src="mainpage.js"></script>
    <link href="styles/main.css" rel="stylesheet" />
  </head>
  <body>
    <div id="buttons">
      <button id="sendMessage">Click me</button>
      <button id="reset">Reset counter</button>
    </div>

    <div id="result"></div>

    <iframe id="theFrame" src="sandbox.html" style="display: none"></iframe>
  </body>
</html>

sandbox.html:

   <script id="sample-template-1" type="text/x-handlebars-template">
      <div class='entry'>
        <h1>Hello</h1>
        <p>This is a Handlebar template compiled inside a hidden sandboxed
          iframe.</p>
        <p>The counter parameter from postMessage() (outer frame) is:
          </p>
      </div>
    </script>

    <script id="sample-template-2" type="text/x-handlebars-template">
      <div class='entry'>
        <h1>Welcome back</h1>
        <p>This is another Handlebar template compiled inside a hidden sandboxed
          iframe.</p>
        <p>The counter parameter from postMessage() (outer frame) is:
          </p>
      </div>
    </script>

ऐसा नहीं होता! भले ही, Handlebars.compile new Function का इस्तेमाल करता है, लेकिन सब कुछ ठीक से काम करता है. आखिर में, हमें templates['hello'] में कंपाइल किया गया टेंप्लेट मिलता है.

नतीजे को वापस पास करें

हम निर्देशों को स्वीकार करने वाला मैसेज लिसनर सेट अप करके, इस टेंप्लेट को इस्तेमाल के लिए उपलब्ध कराएंगे एक्सटेंशन पेज से. हम पास किए गए command का इस्तेमाल करके यह तय करेंगे कि क्या किया जाना चाहिए (आप सिर्फ़ रेंडरिंग से ज़्यादा काम करने के बारे में सोचें; शायद टेंप्लेट बना रहे हैं? शायद उन्हें कुछ जगहों पर मैनेज किया जा रहा है तरीके से मेल खाना चाहिए?) और रेंडरिंग के लिए context को सीधे टेंप्लेट में पास कर दिया जाएगा. रेंडर किया गया एचटीएमएल को एक्सटेंशन पेज पर वापस पास कर दिया जाएगा, ताकि एक्सटेंशन बाद में इसके साथ कुछ उपयोगी कर सके:

 <script>
      const templatesElements = document.querySelectorAll(
        "script[type='text/x-handlebars-template']"
      );
      let templates = {},
        source,
        name;

      // precompile all templates in this page
      for (let i = 0; i < templatesElements.length; i++) {
        source = templatesElements[i].innerHTML;
        name = templatesElements[i].id;
        templates[name] = Handlebars.compile(source);
      }

      // Set up message event handler:
      window.addEventListener('message', function (event) {
        const command = event.data.command;
        const template = templates[event.data.templateName];
        let result = 'invalid request';

       // if we don't know the templateName requested, return an error message
        if (template) {
          switch (command) {
            case 'render':
              result = template(event.data.context);
              break;
            // you could even do dynamic compilation, by accepting a command
            // to compile a new template instead of using static ones, for example:
            // case 'new':
            //   template = Handlebars.compile(event.data.templateSource);
            //   result = template(event.data.context);
            //   break;
              }
        } else {
            result = 'Unknown template: ' + event.data.templateName;
        }
        event.source.postMessage({ result: result }, event.origin);
      });
    </script>

एक्सटेंशन पेज पर वापस आने पर, हमें यह मैसेज मिलेगा. साथ ही, हमें html पास किए गए डेटा का इस्तेमाल करके कुछ दिलचस्प काम करने का मौका मिलेगा. इस मामले में, हम सिर्फ़ एक सूचना के ज़रिए इसकी जानकारी देंगे, लेकिन तो एक्सटेंशन के यूज़र इंटरफ़ेस (यूआई) के हिस्से के रूप में, इस एचटीएमएल का सुरक्षित तरीके से इस्तेमाल किया जा सकता है. इसे से शामिल किया जा रहा है हमें रेंडर किए गए कॉन्टेंट पर भरोसा है. इसलिए, innerHTML की सुरक्षा को लेकर कोई ज़्यादा जोखिम नहीं है से जुड़ी समस्याओं को हल करने में मदद मिलती है.

इस तरीके से टेंप्लेट बनाने का तरीका आसान है. हालांकि, इसमें टेंप्लेट के अलावा और भी चीज़ें शामिल हो सकती हैं. कॉन्टेंट की सुरक्षा से जुड़ी सख्त नीति के तहत, ऐसा कोई भी कोड सैंडबॉक्स किया जा सकता है जो सही तरीके से काम नहीं करता. असल में, अपने एक्सटेंशन के उन कॉम्पोनेंट को सैंडबॉक्स करना अक्सर फ़ायदेमंद होता है जो सही तरीके से चलेंगे. इससे आपके प्रोग्राम के हर हिस्से को, सही तरीके से लागू करने के लिए ज़रूरी अनुमतियों के सबसे छोटे सेट पर पाबंदी लगाई जा सकती है. Google I/O 2012 के सुरक्षित वेब ऐप्लिकेशन और Chrome एक्सटेंशन लिखना प्रज़ेंटेशन में, इन तकनीकों के इस्तेमाल के कुछ अच्छे उदाहरण दिए गए हैं. यह प्रज़ेंटेशन 56 मिनट का है.