Saturday, August 4, 2018

My First Chrome Extension: Airgap Scan

Today I wrote my first Chrome Extension, and it was fun. I want to share what the experience was like. The code for this post may be found here: https://github.com/davidpallmann/AirgapScan

Chrome has become a favorite browser to many, and if you do web development at all you have no doubt seen how important Chrome Extensions have become. Some of the ones I use frequently are WhatFont (tells me what font I am looking at when I hover over text in a page) and ng-inspector (AngularJS variable inspector), among many others.

It's always best to learn something new when you have a firm project idea in mind, something that needs to be created. Fortunately, I had a project in mind.

AirgapScan: An Extension That Scans Pages for Disallowed Internet URLs

Today I decided I was in need of a Chrome Extension to help verify whether my web site pages were air-gapped. What is air-gapping? Air-gapping is when your software has to be able to run in a location that allows no Internet access; for various reasons, there are security-minded customers with that requirement. Honoring this requirement is harder than you might think: as a modern developer, we tend to take Internet availability for granted. And, we frequently rely on open source libraries, many of which also take Internet availability for granted.

And so, having made changes to support air-gapping, it's important to test that we haven't missed an Internet reference somewhere. That's why I wanted this Chrome extension: when our testers visit one of our solution's web pages, I want the extension to report if there are airgap violations (that is, Internet access outside of the approved network).

The way I'd like this to work is as follows: you browse to a page in your application. If you want to check your air-gapping, you right-click the AirgapScan icon and select Scan Page. You'll either get a happy green message box telling you all is well, or a red alert box listing the HREF(s) found in the page that are disallowed.

Hello, World

But you have to walk before you can run, so next up was to take a basic tutorial and create a simple "Hello, World!" extension in order to get familiar with the basics. I stumbled across an excellent getting started tutorial by Jake Prins, How to Create and Publish a Chrome Extension in 20 Minutes which walked me through the basics.

I was surprised and pleased to learn just how easy it is to write a Chrome Extension. In a nutshell, here are the basics:

  1. Web Technologies. Your extension is written in familiar HTML, CSS, and JavaScript.
  2. Developer Mode. While developing, you can easily load and re-load your extension in Chrome as you make changes, making for a great interactive experience as you work. This is done by visiting chrome://extensions and switching on Developer Mode. When you want to load your extension, click LOAD UNPACKED and browse to the folder where your files are. It's a very simple process.
  3. Manifest. Your extension starts with a manifest.json file, in which you declare a number of things about your extension--including name, version, icon, permissions needed, and which css / script files it uses.
  4. Scripts. You'll write some JavaScript code to do your magic. Depending on what you do, you may have to create more than one based on Google's rules. Again, tutorials and documentation are your friend.
  5. You Can Use Your Favorite Libraries. Used to using jQuery? Or one of the many other popular libraries out there? It's fine to include those in your extension--just copy the .js/.css files to your extension folder and declare them in your manifest.
  6. Your Extension Can Do A Lot Of Things. In the tutorial I took, I learned I could control the page that is created when a new browser tab is opened. Later on, I learned how to scan the current page's DOM. You can also do things like add context menus to a selection on the page. It's a really powerful platform.
Creating the AirgapScan Extension

1. The Manifest

The first element of any Chrome Extension is the manifest, manifest.json.

{
  "manifest_version": 2,
  "name": "Airgap Scan",
  "author": "David Pallmann",
  "version": "1.0",
  "description": "Scans the page for Internet references. Useful for testing software meant for air-gapped environments (without public Internet access).",
  "background": {
"scripts": [ "background.js" ]
  },
  "icons": {
"128": "icon-128.png"
  },
  "browser_action": {
   "default_icon": "tab-icon.png",
   "default_title": "Airgap Scanner"
 },
  "content_scripts": [
    {
      "matches": [
        ""
      ],
  "css": ["jquery-confirm.min.css"],
      "js": ["jquery-2.2.4.min.js", "jquery-confirm.min.js", "content.js"]
    }
  ],
  "permissions" : [
    "contextMenus"
    ]
}

Key things to note about the manifest:

  • It lists required permissions, similar to what you do in a phone app. When the user installs, they'll be asked to confirm they are okay with the extension's required permissions. In my case, I had to list adding context menus as a a permission, since I want to use a context menu item to perform scans on-demand.
  • The background property, scripts property specifies one of my script files, background.js.
  • The content_scripts object declares some important things. 
    • The matches property indicates which URLs the extension is active for (all URLs, in my case). 
    • The css property lists CSS files we're including (jquery-confirm.min.css). 
    • The js property lists JavaScript files we're including: my own source file content.js, plus libraries jquery.js and jquery-confirm.js.

Why is my JavaScript code in two places (background.js and content.js)? Well, with Chrome extensions there are content scripts which run in the context of page (content.js). But you may also need a global script or page that is running for the lifetime of your extension (background.js).

2. Content.js

Content.js is my page-level JavaScript code. This includes the following important elements:

  • A message listener, whose purpose is to listen for the context menu's Scan Page command being clicked. When that happens, the listener invokes the airgapScan function.
  • The airgapScan function, which is the heart of the extension. It uses jQuery to find all the HREFs on the page. It discounts some of them, such as mailto: and javascript: links. The rest, it compares against the array of allowableNetworks. If the href partially matches any of the allowable networks, all is well. If not, an error is counted and the URL is added to a list of in-violation URLs. After scanning the HREFs on the page, a green message box (if no errors) or red alert box (displaying the problem URLs) is displayed.

// content.js

// This routes a message from background.js (context menu action selected) to the airgapScan function in this file.

chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
    sendResponse(airgapScan());
});


// airgapScan: scan the page, examine each href. Collect a list of non-allowable hrefs and display an alert.

function airgapScan() {

console.log('--- Airgap Scan ---');

var allowableNetworks = [ '://10.',       // allowed: [http|https]://10.x.x.x 
  '://www.mytestdomain.com'  // allowed: [http|https]://www.mytestdomain.com...
];
var urlCount = 0;
var errorCount = 0;
var url;
var urls = [];
var errList = '';
var listAll = false;

$("a").each(function() {
if (this.href != undefined) {
url = this.href;
if (url!=null && url!='' && url.indexOf('javascript:')==-1 && url.indexOf('mailto:')==-1) {
urlCount++;
urls.push(url);
var error = true;
for (var p = 0; p < allowableNetworks.length; p++) {
if (url.indexOf(allowableNetworks[p])!=-1) {
error = false;
break;
}
}
if (error) {
errorCount++;
console.error('URL outside of network detected: ' + url);
errList = errList + '
' + url;

}

}
}
})
if (listAll && urls.length > 0) {
for (var i = 0; i < urls.length; i++) {
console.log(i.toString() + ': ' + urls[i]);
}
}

console.log('--- end Airgap Scan - URLs: ' + urlCount.toString() + ', Errors: ' + errorCount.toString() + ' ---');

if (errorCount > 0) {
if (errorCount==1) {
$.alert({
//icon: 'fa fa-warning',
type: 'red',
title: 'Airgap Alert',
content: 'Warning: Airgrap scan found 1 url that violates airgap rules:
' + errList,

useBootstrap: false
});
}
else {
$.alert({
//icon: 'fa fa-warning',
type: 'red',
title: 'Airgap Alert',
content: 'Warning: Airgrap scan found ' + errorCount.toString() + ' urls that violate airgap rules:
' + errList,

useBootstrap: false
});
}
}
else {
$.alert({
title: 'Airgap OK',
type: 'green',
content: 'All good: No airgap errors found',
useBootstrap: false
});
}
}

// Default state is that the user initiates a scan from the context menu. Uncomment the line below if you want the scan to automatically run when a page loads. 
//airgapScan();

3. Background.js

Background.js is the lifetime-of-the-extension JavaScript file. It contains
  • A call to Chrome.contextMenus.create, which adds a context menu item to the extension, available to the user by right-clicking it's icon.
  • A listener to respond to the menu item being clicked. This in turns sends a message to content.js to please invoke the airgapScan method.

// Add "Scan page" action to extension context menu.


chrome.contextMenus.create({
"id": "AG_ScanPage",
    title: "Scan Page",
    contexts: ["browser_action"],
    onclick: function() {
    }
});

// When context menu item is selected, send a message to context.js to run an airgap Scan.

chrome.contextMenus.onClicked.addListener(function(info, tab) {
  if (tab && info.menuItemId=="AG_ScanPage")
    chrome.tabs.sendMessage(tab.id, {args: null }, function(response) {
    });
});

4. Library Files

As mentioned earlier, we are using a few libraries: jquery and jquery-confirm. The .js and .css files for them are included in the folder, and are referenced in the manifest.

5. Icons

Lastly, we have some icons in different sizes. The icon for AirgapScan is shown below.


And that's it. Time from first-time-hello-world-extension to AirgapScan was just a few hours on a Saturday. 

AirgapScan in Action

As I developed AirgapScan, I continually tested in chrome://extensions. When I had an update, I would REMOVE and then LOAD UNPACKED to get the latest changes applied, then visit a fresh page to test it out.


After visiting a page to be tested, the AG icon is visible. Hovering over it shows its name in a tooltip. Right-clicking it shows the Scan Page context menu that the extension code added.


Clicking Scan Page quickly comes back with a message box with the results of the scan. If one or more in-violation HREFs are found, a red alert box itemizes them.


If no violations are found, a green Airgap OK message appears.



You can download this extension here: https://github.com/davidpallmann/AirgapScan

This was a lot of fun, plus I created something that my team needs. Chrome Extensions are surprisingly easy to create and the platform is well thought through which makes it a pleasure to use. I am highly motivated now to create other extensions.











No comments: