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

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

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

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

सैंडबॉक्सिंग क्यों?

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

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

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

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

मेनिफ़ेस्ट में फ़ाइलें लिस्ट करना

सैंडबॉक्स में चलाई जाने वाली हर फ़ाइल को, एक्सटेंशन मेनिफ़ेस्ट में लिस्ट किया जाना चाहिए. इसके लिए, 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 लोड होने पर, यह Handlebars लाइब्रेरी लोड करता है. साथ ही, Handlebars के सुझाव के मुताबिक, एक इनलाइन टेंप्लेट बनाता और कंपाइल करता है:

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 मिनट लगते हैं.