Skip to content

Instantly share code, notes, and snippets.

@scovetta
Last active April 4, 2022 14:56
Show Gist options
  • Save scovetta/08311824fbc63b82fe89177817e835a9 to your computer and use it in GitHub Desktop.
Save scovetta/08311824fbc63b82fe89177817e835a9 to your computer and use it in GitHub Desktop.
This script can be used to automate reporting of malware to the npm team, as a shortcut to npmjs.com/support.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This script will automate reporting malware to the npm team.
It automates reporting via https://npmjs.com/support.
Before using it, either ensure you have the Requests module installed, or install it with
`pip install requests`.
To use it, run the script with the following arguments:
python npm-report-malware.py
-c "Add a comment to the report"
-e "[email protected]"
-r "Your name"
package-name
another-package-name@version
...
If you'd like you can add a `--dry-run` flag to the script to see what it would do without actually
sending the report.
This script is not officially supported. Your mileage may vary.
Author: Michael Scovetta <[email protected]>
License: MIT
Copyright: Microsoft Corporation
Last Updated: 4/3/2022
"""
import json
import requests
import argparse
import re
import logging
logging.basicConfig(level=logging.ERROR)
class NpmReportMalware:
packages = []
comment = ""
"""
Initializes the malware reporter.
"""
def __init__(self):
self.logger = logging.getLogger(__name__)
self.session = requests.Session()
self.csrf_token = self.get_csrf_token()
self.logger.debug("csrf_token: %s", self.csrf_token)
if not self.csrf_token:
raise Exception("Unable to get CSRF token, cannot continue.")
"""
Processes input and reports the packages.
"""
def main(self):
parser = argparse.ArgumentParser(description="Report malware to npm")
parser.add_argument(
"--dry-run", help="Do not report, just print what would be sent.", action="store_true"
)
parser.add_argument(
"-c", "--comment", help="Include comments with the report", required=False
)
parser.add_argument("-r", "--reporter-name", help="Name of the reporter", required=True)
parser.add_argument("-e", "--reporter-email", help="Email of the reporter", required=True)
parser.add_argument(
"package", type=str, nargs="+", help="Package(s) to report (@version optional)"
)
args = parser.parse_args()
self.reporter_name = args.reporter_name
self.reporter_email = args.reporter_email
for package in args.package:
parts = package.split("@")
if len(parts) == 2:
self.packages.append({"name": parts[0], "version": parts[1]})
else:
self.packages.append({"name": package, "version": None})
self.comment = args.comment
self.report_malware(args.dry_run)
def get_csrf_token(self):
try:
response = self.session.get("https://www.npmjs.com/support?inquire=security")
response.raise_for_status()
match = re.search(
'<input type="hidden" name="csrftoken" value="([^"]+)"/>',
response.text,
re.IGNORECASE,
)
if match:
return match.group(1)
except:
self.logger.error("Failed to get csrf token", exc_info=True)
return None
def report_malware(self, dry_run=False):
payload = {
"inquire": "security",
"security-inquire": "malware",
"name": self.reporter_name,
"email": self.reporter_email,
"csrftoken": self.csrf_token,
}
if len(self.packages) == 1:
if self.packages[0]["version"]:
payload[
"subject"
] = f"Malware report in {self.packages[0]['name']}@{self.packages[0]['version']}"
else:
payload["subject"] = f"Malware report in {self.packages[0]['name']}"
payload["package"] = self.packages[0]["name"]
payload["version"] = self.packages[0]["version"] or "Unspecified"
else:
payload["subject"] = "Malware report in {} package(s)".format(len(self.packages))
payload["package"] = "Multiple packages"
payload["version"] = "Unspecified"
# Create the message / comments
_comment = ""
if self.comment:
_comment = self.comment.replace("\\n", "\n") + "\n\n"
_comment += "Packages:\n"
for package in self.packages:
if package["version"]:
_comment += f" - {package['name']}@{package['version']}\n"
else:
_comment += f" - {package['name']}\n"
payload["message"] = _comment
if dry_run:
print("Payload:")
print(json.dumps(payload, indent=2))
else:
try:
response = self.session.post("https://www.npmjs.com/support", data=payload)
response.raise_for_status()
print(f"Reported {len(self.packages)} package(s)")
self.logger.info("Reported %d package(s)", len(self.packages))
except Exception as msg:
print(f"Failed to send report: {msg}")
self.logger.error("Failed to send report", exc_info=True)
if __name__ == "__main__":
reporter = NpmReportMalware()
reporter.main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment