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()