Skip to content

Instantly share code, notes, and snippets.

@faffyman
Forked from Neo23x0/wpwatcher.py
Last active May 12, 2019 20:39
Show Gist options
  • Save faffyman/3f2b8726e9c52c053a09abbebe6d4676 to your computer and use it in GitHub Desktop.
Save faffyman/3f2b8726e9c52c053a09abbebe6d4676 to your computer and use it in GitHub Desktop.
Wordpress Watcher - WPScan Vulnerabilty Scan on Wordpress Sites and Reporting
#!/usr/bin/env python
# -*- coding: iso-8859-1 -*-
# -*- coding: utf-8 -*-
#
# Wordpress Watcher
# Automating WPscan to scan and report vulnerable Wordpress sites
# Forked from https://gist.github.com/Neo23x0/2a97509f50c57a2e737e
# v0.1.1
# March 2017
#
# DISCLAIMER - USE AT YOUR OWN RISK.
import os
import re
import smtplib
import traceback
from subprocess import check_output, CalledProcessError, STDOUT
from email.mime.text import MIMEText
from subprocess import Popen, PIPE
from datetime import datetime
# General Options
wpscan_dir = r'/usr/local/bin/'
# list of websites to be scanned, with optional commands, e.g. the wordpress content dir, or enumerate users etc
wp_sites = [
'websiteone.com',
['website-two.com', ['--option_one', 'option-two']],
'website-three.com',
['website-four.com', ['--wp-content-dir', 'app']],
'website-five.com',
]
false_positive_strings = [ 'XML-RPC', 'GHOST vulnerability' ]
# Log file
log_file = r'./wpwatcher.log'
# Email Report
send_email_report = True
smtp_server = r'smtp.server.net:587' # 'server:port'
smtp_auth = True
smtp_user = 'username'
smtp_pass = 'password'
smtp_ssl = True
to_email = r'[email protected]'
from_email = r'[email protected]'
# Check if WPScan is installed
def is_wpscan_installed():
if not os.path.exists(wpscan_dir):
result = check_output(r'mkdir -p %s' % wpscan_dir, shell=True)
if result:
print "[ERROR] %s" % result.replace("\n", " ")
sys.exit(1)
return 0
else:
return 1
# Install WPScan from github
# This function is likely to fail as it strongly depends ton the OS platform
# and other settings to succeed
# I strongly recommend installing WPScan yourself and set the directory in the
# options above
def install_wpscan():
print "[INFO] Trying to install WPScan"
os.chdir(wpscan_dir)
try:
result = check_output(r'GIT_CURLOPT_SSLVERSION=3 && git clone https://github.com/wpscanteam/wpscan.git && gem install bundler && bundle install --without test', stderr=STDOUT, shell=True, universal_newlines=True)
except CalledProcessError as exc:
print "[ERROR]", exc.returncode, exc.output
print "If errors occur - try to install wpscan and all dependancies manually"
print "[INFO] %s" % result
# Update WPScan from github
def update_wpscan():
print "[INFO] Updating WPScan"
os.chdir(wpscan_dir)
try:
result = check_output(r'./wpscan.rb --batch --update', stderr=STDOUT, shell=True, universal_newlines=True)
except CalledProcessError as exc:
print "[ERROR]", exc.returncode, exc.output
print result
# Run WPScan on defined domains
def run_scan():
print "[INFO] Starting scans on configured sites"
os.chdir(wpscan_dir)
for wp_site in wp_sites:
result = ''
# Scan ----------------------------------------------------------------
try:
print "[INFO] Scanning '%s'" % wp_site
scan_options = '' # initalise scan_options as empty string
if isinstance(wp_site, list):
(wp_site, scan_options) = wp_site
# set out command and some default scan options
cmd = ['./wpscan.rb', '--batch','--url', wp_site, '--random-agent','--follow-redirection']
# if scan_options was passed as array, merge the two arrays
if isinstance(scan_options, list):
cmd = cmd + scan_options
# if scan_options is a string i.e. just a single option, just append it to the cmd array
if isinstance(scan_options, str):
cmd.append(scan_options)
# send the command and grab the output
p = Popen(cmd, stdout=PIPE, stderr=PIPE, stdin=PIPE )
result, err = p.communicate()
except CalledProcessError as exc:
print "[ERROR]", exc.returncode, exc.output
# Parse the results ---------------------------------------------------
(warnings, alerts) = parse_results(result)
# Report Options ------------------------------------------------------
# Email
if send_email_report:
send_report(wp_site, warnings, alerts, result)
# Logfile
try:
with open(log_file, 'a') as log:
for warning in warnings:
log.write("%s %s WARNING: %s\n" % (get_timestamp(), wp_site, warning))
for alert in alerts:
log.write("%s %s ALERT: %s\n" % (get_timestamp(), wp_site, alert))
except Exception, e:
traceback.print_exc()
print "[ERROR] Cannot write to log file"
# Parsing the results
def parse_results(results):
print "[INFO] parse results \n"
warnings = []
alerts = []
warning_on = False
alert_on = False
last_message = ""
# Parse the lines
for line in results.splitlines():
# Remove colorization
line = re.sub(r'(\x1b|\[[0-9][0-9]?m)','',line)
# Empty line = end of message
if line == "" or line.startswith("[+]"):
if warning_on:
if not is_false_positive(warning):
warnings.append(warning)
warning_on = False
if alert_on:
if not is_false_positive(alert):
alerts.append(alert)
alert_on = False
# Add to warning/alert
if warning_on:
warning += " / %s \n" % line.lstrip(" ")
if alert_on:
alert += " / %s \n" % line.lstrip(" ")
# Start Warning/Alert
if line.startswith("[i]"):
# Warning message
# warning = "%s / %s \n" % ( last_message, line )
warning = line + " \n"
warning_on = True
if line.startswith("[!]"):
# Warning message
alert = line + " \n"
alert_on = True
# Store lase message
last_message = line
return ( warnings, alerts )
# Send email report
def send_report(wp_site, warnings, alerts, raw):
print "[INFO] Sending email report stating items found on %s to %s" % (wp_site, to_email)
# Remove colorization in raw output
raw = re.sub(r'(\x1b|\[[0-9][0-9]?m)','',raw)
try:
message = "Issues have been detected by WPScan on one of your sites\n"
message += "%s \n" % wp_site
if warnings:
message += "\nWarnings\n"
message += "\n".join(warnings)
if alerts:
message += "\nAlerts\n"
message += "\n".join(alerts)
# ad d teh entire output of the scan, because sometimes it's easier than the summary
message += "\n\nRAW SCAN OUTPUT\n\n%s \n" % raw
mime_msg = MIMEText(message)
mime_msg['Subject'] = 'WPWatcher report on %s - %s' % (wp_site, get_timestamp())
mime_msg['From'] = from_email
mime_msg['To'] = to_email
# Uncomment this block to use an SMTP connection
# SMTP Connection
# s = smtplib.SMTP(smtp_server)
# s.ehlo()
# SSL
# if smtp_ssl:
# s.starttls()
# SMTP Auth
# if smtp_auth:
# s.login(smtp_user, smtp_pass)
# Send Email
# s.sendmail(from_email, to_email, mime_msg.as_string())
# s.quit()
# simple sendmail - SMTP preferred
p = Popen(["/usr/sbin/sendmail", "-t", "-oi"], stdin=PIPE)
p.communicate(mime_msg.as_string())
except Exception, e:
traceback.print_exc()
# Is the line defined as false positive
def is_false_positive(string):
# False Positive Detection
for fp_string in false_positive_strings:
if fp_string in string:
# print fp_string, string
return 1
return 0
def get_timestamp():
return datetime.now().strftime('%Y-%m-%d %H:%M:%S')
if __name__ == '__main__':
# Check if WP-Scan exists
if not is_wpscan_installed():
install_wpscan()
else:
update_wpscan()
# Run Scan
run_scan()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment