With HTML and scripting people would write scripts and get you to execute them, and so access your personal information, and steal your money. Over time security has been improved to make this harder, and now you have to explicitly say which web sites can run scripts which use the mqweb interface to access MQ to put and get messages.
One way of protecting the access is using Cross Origin Resource Sharing, or CORS. I explained the basics of CORS here. I struggled getting it to work with a web browser.
- browsers have been improved and what worked last year may no longer work now, and the documentation does not reflect the newer browsers.
- Chrome carefully changes your hand crafted HTTP headers, so what is sent up may not what you expected.
I’ll go through three examples, and then show how it gets more difficult, and what you can do to identify problems.
Note: If you use a web page from a file:// then the origin is ‘null’, and this will fail any checks in the backend, as the checks compare the passed origin to the list of acceptable urls.
I used Dev Tools in Chrome (Alt+Ctrl+i) to display information including the headers flowing.
Simple HTML
With the following within your web page,
<a href=”https://localhost:9445/ibmmq/rest/v1/messaging/qmgr/…/message ”
> direct link ></a>
It issues the REST request, returns the data from the queue, and displays it.
From the headers we see
Simple HTML: Request headers
The important request headers were
- Host: localhost:9445
- Referer: http://localhost:8889/
And no origin header.
Simple HTML: Response headers
The response headers have
- HTTP/1.1 200 OK
- ibm-mq-md-expiry: unlimited
- ibm-mq-md-messageId: 414d5120514d412020202020202020204c27165d04a98a25
- ibm-mq-md-persistence: persistent
- Content-Length: 1024
- Set-Cookie: LtpaToken2_1 ….
There are no Allow…. headers, so this indicates the response is not a valid cross origin response. The request came from one page, so there was no cross origin request.
You can see the MQ attributes returned, ibm-mq*.
Invoking request from java script.
My HTML page had
<html
<head>
<script src=”http://localhost:8884/src.js ” text=”text/javascript”></script >
</head>
<body>
<button onclick=”src()””>Get message</button>
</body>
I had the src() script downloaded from http://localhost:8884/src.js, and inline within the html file. Both worked.
function src() { fetch("https://localhost:9445/ibmmq/rest/v1/messaging/qmgr/QMA/queue/DEEPQ/message", { method :'DELETE', headers: { 'Origin': 'https://localhost:888499' ,'Access-Control-Request-Headers' : 'Content-Type' ,'ibm-mq-rest-csrf-token' : '99' } } ) // end of fetch .then((response) => response.text() ) .then(x => {document.write("OUTPUT:"+x); } ) .catch(error => {console.log("Error from src:" + error);}); }
When the button was pressed, the script was executed, there was an OPTIONS request (and response), and a DELETE request (and response). It returned a message and displayed it.
In more detail, the flows were:
JavaScript OPTIONS request
The OPTIONS request had headers
- Access-Control-Request-Method: DELETE
- Origin: http://localhost:8889
This is doing a preflight check, saying it intends to issue a DELETE request from Origin http://localhost:8889, the url of the web page.
JavaScript OPTIONS response headers
The OPTIONS response headers were the same as before but with additional ones
- Access-Control-Allow-Credentials: true
- Access-Control-Allow-Origin: http://localhost:8889
- Access-Control-Allow-Max-Age: 91
- Access-Control-Allow-Methods: GET, POST, PATCH, PUT, DELETE
- Access-Control-Allow-Headers: Accept-Charset, Accept-Language, Authorization, Content-Type, ibm-mq-rest-csrf-token, ibm-mq-md-correlationId, ibm-mq-md-expiry, ibm-mq-md-persistence, ibm-mq-md-replyTo, ibm-mq-rest-mft-total-transfers
This means, the script is allowed to issue a request from http://localhost:8889 using a request method in the list {GET, POST, PATCH, PUT, DELETE } and the valid headers that can be used are in the list of headers.
The Access-Control-Allow-Max-Age: 91 came from my mqwebuser.xml file, <variable name=”mqRestCorsMaxAgeInSeconds” value=”91″/>.
After this there was a DELETE request to get the message.
JavaScript DELETE request headers
- Host: localhost:9445
- ibm-mq-rest-csrf-token: 99
- Origin: http://localhost:8889
JavaScript DELETE response headers
The response included the CORS headers, and included the headers from the non CORS situation
- HTTP/1.1 200 OK
- Access-Control-Allow-Credentials: true
- Access-Control-Allow-Origin: http://localhost:8889
- Access-Control-Expose-Headers: Content-Language, Content-Length, Content-Type, Location, ibm-mq-qmgrs, ibm-mq-md-messageId, ibm-mq-md-correlationId, ibm-mq-md-expiry, ibm-mq-md-persistence, ibm-mq-md-replyTo, ibm-mq-rest-mft-total-transfers
- Content-Type: text/plain; charset=utf-8
- Content-Language: en-GB
- ibm-mq-md-expiry: unlimited
- ibm-mq-md-messageId: 414d5120514d412020202020202020204c27165d04a98a25
- ibm-mq-md-persistence: persistent
- Content-Length: 1024
- Set-Cookie: LtpaToken2_….
Because the Access-* headers are present, this is a CORS response.
The message content was displayed in a browser window.
Link to another page
I set up a link <a href=”http://localhost:8884/page.html”/>localhost 8884</a> to execute a page on another web server. When this executed, it issued the java script request as before. The Origin was Origin: http://localhost:8884 – so the page where the script executed.
What happens if CORS is not set up?
If the http://localhost:8889 is not in the list in the mqwebuser.xml file,
No data was displayed. The Chrome browser Developer tools ( Alt+Ctrl+i) displays a message
Access to fetch at ‘ https://localhost:9445/ibmmq/rest/v1/messaging/qmgr/QMA/queue/DEEPQ/message ‘ from origin ‘http://localhost:8889 ‘ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
The OPTIONS Request header has, as before
- HTTP/1.1 200 OK
- Host: localhost:9445
- Access-Control-Request-Method: DELETE
- Origin: http://localhost:8889
- Access-Control-Request-Headers: ibm-mq-rest-csrf-token
but the OPTIONS Response header has no Access-* headers.
No DELETE request was issued.
The HTTP/1.1 200 OK is the same for all cases – it means the request was successful.
Trying to be clever
I read the documentation on the web, and much of it was very helpful, but some of it is no longer true. It was hard to get it working, because every things has to be right for it to work.
Unsupported header.
I added an extra header, which is a valid CORS thing to do – but the back end has to support it. With hindsight it makes no sense to add headers that will be ignored by the server.
headers: { 'Origin': 'https://localhost:888499' , 'Access-Control-Request-Headers' : 'Content-Type' , 'colin':'TRUE' ,'ibm-mq-rest-csrf-token' : '99' }
This sent up a header with
Access-Control-Request-Headers: colin,ibm-mq-rest-csrf-token
Header “colin” is not in the list of accepted headers, header ibm-mq-rest-csrf-token is in the list:
Access-Control-Allow-Headers: Accept-Charset, Accept-Language, Authorization, Content-Type, ibm-mq-rest-csrf-token, ibm-mq-md-correlationId, ibm-mq-md-expiry, ibm-mq-md-persistence, ibm-mq-md-replyTo, ibm-mq-rest-mft-total-transfers
The Developer tools message was the same as before,
Access to fetch at ‘https://localhost:9445/ibmmq/rest/v1/messaging/qmgr/QMA/queue/DEEPQ/message ‘ from origin ‘http://localhost:8889 ‘ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
I removed the unsupported header and it worked.
Other headers do not work
The request header ‘Access-Control-Allow-Method’ : ‘DELETE’ is a valid header.
When this was used, the request headers included
- Access-Control-Request-Headers: access-control-allow-method,ibm-mq-rest-csrf-token
- Access-Control-Request-Method: DELETE
As before Access-Control-Request-Method is not in the list of Access-Control-Allow-Headers, so the request fails.
The Access-Control-Request-Method: DELETE is not needed, as the method: DELETE defines what will be used.
Using Access-Control-Request-Headers to add more headers does not work, if the header is not in the list of valid parameters.
The true origin is used
I tried using the header ‘Origin’: ‘https://localhost:888499 ‘ – as it did with my curl – and this was ignored. The true Origin from the web page was used (I am glad to say, otherwise it would make the whole protection scheme worthless)
Some options ignored
I found the “fetch” options credentials:, redirect: , and cache:, were all ignored by Chrome.
Some cookies were used.
The LtpaToken2_… was sent up and down
Problems in mqwebuser.xml file
I misconfigured the configuration file with <variable name=”mqRestCorsMaxAgeInSeconds” value=”AA91″/>
This gave me the familair message
Access to fetch at ‘https://localhost:9445/ibmmq/rest/v1/messaging/qmgr/QMA/queue/DEEPQ/message ‘ from origin ‘http://localhost:8889 ‘ has been blocked by CORS policy: Response to preflight request doesn’t pass access control check: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. If an opaque response serves your needs, set the request’s mode to ‘no-cors’ to fetch the resource with CORS disabled.
So if you get this message it is worth checking the configuration file for problems.
Can I trace it?
I used <variable name=”traceSpec” value=”*=audit:CorsService=finest”/> in the mqwebuser.xml file to provide information about the CORS processing, and what was coming in, and what was being checked.
One thought on “Can evil websites get to your mqweb – using java script to get to the backend server”