Postinstallation script snippet found in the `shrugging-logging` package
May 23, 2018 ยท View on GitHub
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()