Skip to Content
Technical Articles

Inbound HTTPS with CSRF Protection in CPI Integration Flows

Intro

The entire concept of protection against Cross-Site Request Forgery (CSRF) attacks is relatively commonly faced when being put in context of discussions of securing exposed HTTP resources.

From technical standpoint, the flow prescribes a caller to firstly obtain a CSRF token from the resource provider by sending HEAD or GET request with the header X-CSRF-Token = Fetch and looking for a value of the header X-CSRF-Token contained in the response from the resource provider which is a value of the CSRF token, and then pass the obtained CSRF token value in the header X-CSRF-Token of subsequently issued modifying requests, such as those sent using HTTP methods POST, PUT, PATCH and DELETE. Usage of a CSRF token is normally not required when issuing non-modifying requests, such as those sent using HTTP methods GET, HEAD and OPTIONS.

CPI natively supports enablement of CSRF protection for inbound HTTPS connections in integration processes – this is one of out of the box standard features of the HTTPS adapter. Though, it is worth bearing in mind few notes and remarks that might become useful when coming across configuration and usage of this feature particularly in CPI.

 

Overview of baseline scenario

As a starting point and a baseline scenario, let’s introduce an iFlow that exposes a CSRF-protected HTTPS endpoint and produces a fixed response message when being called. Note that in HTTPS connection, a corresponding feature (CSRF Protection) has been activated:

In order to collect required details about processed requests (to be more precise, to be able to see content of messages that will arrive to the iFlow), I temporarily increase log level of the iFlow to Trace after the iFlow is deployed.

 

I’m going to use Postman to send requests to CPI and invoke this iFlow. Following general principles and requests flow that is required when consuming CSRF-protected HTTP resources, we are going to need two requests in Postman:

  1. Send a request to fetch a CSRF token. Note that the request to fetch a CSRF token is sent to the iFlow endpoint – in CPI, CSRF tokens are obtained from interface-specific endpoints of iFlows and not from a common interface-agnostic endpoint of the CPI tenant.
  2. Send a test message to the iFlow endpoint with the obtained CSRF token. I’m going to use a request with method POST to emulate a modifying request.

 

In real life scenarios, it is convenient to configure environment and use variables in Postman so that the CSRF token that is contained in a response provided for the first request, can be automatically retrieved by the script and inserted to a corresponding header of the second request using the variable, as well as to use variables to hold some environment and scenario specific information such as the iFlow endpoint and credentials used for authentication. For example, CSRF token can be read from a response for the first call and put to the variable in a one-line script in Postman:

pm.environment.set('csrf_token', pm.response.headers.get('X-CSRF-Token'));

followed by using the variable in the second call when populating the header X-CSRF-Token with the token value. Just a day before, Jerry Wang published a blog post “Just a single click to test SAP OData Service which needs CSRF token validation” that contains detailed step by step explanation on how to use this technique to make usage of Postman more efficient.

Here however we need two simple requests and most of time we are going to use only one of them during tests, so I will deviate from these best practices and will not use variables in sake of more simplified and compacted illustration of requests sent by Postman, this will also help me to avoid usage of Postman Console to access information about effectively generated requests and actual values sent in variables’ place.

 

Iteration 1: Baseline scenario

Let’s put a baseline scenario under test.

At the beginning, we send a modifying request – POST request – without CSRF token in it to check that the iFlow will return authorization error:

 

We can also check that the iFlow will run successfully in case of sending a non-modifying request – let’s use GET request to illustrative that:

 

Next, we follow the flow mentioned above and firstly fetch a CSRF token and then send a POST request with the obtained CSRF token.

Send a request to fetch a CSRF token:

 

Send a test message with the obtained CSRF token:

Received responses for issued requests look as expected in Postman.

Please note that, as it has been highlighted by Yuri Ziryukin, after the caller fetches a CSRF token, it is strongly encouraged to retain cookies that have been received from CPI, and present them in subsequent calls within the same session – a valid CSRF token alone will not be sufficient to complete subsequent calls successfully. In particular, CPI tends to set several secure cookies that are used in session management – such as BIGipServer*, JSESSIONID and JTENANTSESSIONID* cookies. In the example above, default settings for cookies management in Postman will allow those cookies to be implicitly used and added to subsequent requests, and we don’t delete neither of those cookies manually. If you happen to miss sending those cookies in subsequent requests, you will receive a response with HTTP status code 403 (Forbidden) from CPI, even if the valid CSRF token is present in the request.

 

Now let’s have a look in CPI Message Monitor – interestingly, there are two messages there for the last test:

 

More detailed inspection of observed messages drives us to the following conclusion:

  • The first message corresponds to a HEAD request, which is the request to fetch a CSRF token:

  • The second message corresponds to a POST request, which is a test message.

Both above messages followed the same processing steps in the iFlow:

 

This is not what we would expect to see – the request to fetch a CSRF token shall only be handled by runtime to generate the CSRF token and send it back to a caller in case security checks are passed successfully, but shall not invoke the iFlow, as this would mean an unexpected message gets processed by the iFlow, which might potentially cause errors in the iFlow or in systems to which subsequent requests are sent if there are no relevant validations within the iFlow.

 

Iteration 2: Add special handling of request to fetch CSRF token

The most straightforward preventive measure that can be applied here, is to filter out CSRF token fetch requests right at the beginning of iFlow execution. One of technical options how this can be achieved, is to add a router step at the beginning of the integration process and introduce a dedicated “dead end” route for CSRF token fetch requests that doesn’t produce any response message, while other requests remain routed to a “main” route and lead to invocation of integration process steps.

The introduced route for capturing CSRF token fetch requests shall be defined with the relevant condition – the condition shall at least check the header X-CSRF-Token to have value Fetch, and preferably check an HTTP method that is used by the request. When fetching a CSRF token, some systems generate requests with an HTTP method HEAD (as the CSRF token is contained in the header and response body doesn’t bring value here, a caller might want to emphasize that and request callee not to produce body, but to only send headers), whereas some other systems generate requests with an HTTP method GET:

 

After this is done, a request to fetch a CSRF token is re-sent and messages generated in CPI are checked. We can still see that the message still got routed to the “main” route, which is not what we want to happen:

A closer look at this message suggests that not all conditions for the “dead end” route were met: the request was sent with an HTTP method HEAD, but the header X-CSRF-Token was missing:

 

Iteration 3: Allow header X-CSRF-Token

To ensure that the header in question is not removed by CPI runtime, that the message arrives to a router step with that header and the header value is evaluated, we need to explicitly allow the header X-CSRF-Token in runtime configuration of the iFlow:

 

After this is done, a request to fetch a CSRF token is re-sent once again and messages generated in CPI are checked. This time, we can see that the message got routed to the “dead end” route, and not to the “main” route:

 

Curiously, the displayed value of the header X-CSRF-Token looks cryptic, although the message met all conditions (including the one checking that the header X-CSRF-Token is passed with the value Fetch) – otherwise, it would have been routed to a default “main” route:

 

To make sense of it, let’s add a step before a router step to the iFlow and retrieve all HTTP headers of the received message. I will use the following Groovy script that retrieves HTTP headers and saves them as a message attachment:

 

import com.sap.gateway.ip.core.customdev.util.Message

Message processData(Message message) {
    
    StringBuilder builder = new StringBuilder()
    def headers = message.getHeaders()
    def messageLog = messageLogFactory.getMessageLog(message)
    
    headers.each { key, value -> builder << "${key}=${value}\n" }
    messageLog.addAttachmentAsString("Incoming message headers", builder.toString(), "text/plain")
    
    return message

}

 

After re-sending a request to fetch a CSRF token, we can now see values of HTTP headers of the received message in plain text – note that the header X-CSRF-Token has value Fetch, as expected:

 

There is no mystery in the observed behaviour: the header X-CSRF-Token contains sensitive information, and its content is secured in Message Monitor by generating an SHA-256 hash from the original value and displaying hash value instead of the original value. This can be verified by generating SHA-256 hash for the value Fetch and ascertaining that the obtained value and the earlier observed value are identical:

 

Summary on final scenario and conclusion

Based on iterative analysis done above, here we go with a summary of adjustments that need to be introduced to the iFlow:

  • In runtime configuration of the iFlow, add header X-CSRF-Token to allowed headers,
  • In the integration process of the iFlow, add a router step and ensure that requests to fetch a CSRF token are routed to the dedicated route and do not get routed to the “main” process flow.

 

It shall be noted that this approach works well for requests that are classified as modifying requests – POST, PUT, PATCH and DELETE are most commonly used amongst them. The described approach will not work for non-modifying requests – GET, HEAD and OPTIONS – as it is not common to enable CSRF protection for resources that are accessed with these types of HTTP methods, and as a consequence, HTTPS adapter in CPI will not issue HTTP status code 403 for such requests sent with no CSRF token even if the iFlow configuration would imply enabled CSRF protection.

If the requirement is to ensure that the iFlow processes requests with only specific HTTP methods, it might be a good idea to add another route that will be used for requests with all HTTP methods except those explicitly specified in conditions of the “main” route, and if the request was sent with inappropriate HTTP method, a corresponding message with an HTTP status code 405 (Method Not Allowed) and empty body can be issued back to a caller:

 

Given that the in case of responses with HTTP status code 405, generally speaking, a server is not mandated to provide additional information in the response message, we can also make a bit of housekeeping in regards to headers that are contained in the produced response message and ensure that we clear all of them or at least those that we don’t want to communicate back to a caller. For details about background on this step, further reading is the blog post “The Curious Case of the Unexpected Headers” written by Morten Wittrock.

 

In this blog post, I deliberately don’t cover alternative ways of addressing requirements described above and limit suggestions with those based on capabilities of CPI. If the organization runs an API management solution (SAP API Management or its equivalents) and builds a layer of managed APIs on top of APIs available to the organization, another option could have been to introduce corresponding policies to verify an HTTP method of the incoming request on the managed API level, and ensure that only well-formed requests with allowed HTTP methods are sent to the endpoint of the iFlow running in CPI

16 Comments
You must be Logged on to comment or reply to a post.
  • Great stuff again, Vadim. Definitely useful reference as we start to get more interesting integration scenarios in the cloudy world of CPI! 😉

    • Thank you, Eng Swee. It looks to me that the more we progress with various cloud integration scenarios and cloud services and applications, the more we have interesting findings and creative ways to address those requirements and also to deal with the entire integration design and development cycle.

  • Hi Vadim,

    Thanks for sharing 🙂 

    Can you please post one HTTPS configuration steps to establish connectivity between CPI and SAP as well.

     

    Thanks

    • I didn’t really use a SAP system as a caller in this integration and there was also no dedicated receiver there (in sake of simplification of the flow) – I emulated a caller system (which could be SAP or non-SAP) with Postman. If you meant HTTPS configuration for that (configuration of a connection from a sender to the integration process), then you can find corresponding screenshots at the beginning of the blog (in the section that describes a baseline scenario), where the relevant part that the blog draws attention to, is a configuration option to enable / disable CSRF protection.

    • The CSRF token that is generated by CPI, is valid in scope of the session where it was obtained. I’m not aware of the specific value of the token lifetime / expiry time for the acquired token.

  • Vadim Klimov

    machine2machine communication (API consumption) without a UI imho disqualifies the neccessity of a CSRF Token – it rather makes authentifaction harder if one token is presented to different apis which terminat with different users in the backend system.

     

    Unfortunately we cannot deactivate CSRF token for certain apis on NW Gateway – there only is a global on/off switch.

     

    I personally think that an api should be protected through API key and maybe an additional basic auth. Therefore we would like to hide the CSRF mechanism from the API consumer.

     

    Do you have a working version for SAP API management to do the CSRF lookup also?

    This would still be a workaround solution but one that makes developer onBoarding easier by not sacrifizing the complete NW Security.

     

    Regards

    Holger

    • Hi Holger,

      You raised very valid points – thank you for bringing them up.

      My thinking on CSRF token based protection applicability to application-to-application integrations resonates to your thoughts – I agree that CSRF protection makes greater sense in UI integrations, but is commonly of lesser use in application-to-application integrations. CSRF protection is a good example where an API Management platform can bring value – as this is where API policies can be fine-tuned (CSRF protection can be enabled for some APIs, and disabled for some others), and we can shift CSRF protection mechanisms from CPI to API Management platform in that case (where required, API Management platform enforces usage of CSRF tokens for consumers, and makes calls to CPI iFlows’ endpoints that are not CSRF protected).

      As for SAP API Management – no, I’m afraid I don’t have the working setup for that to demonstrate how API Management can fetch a CSRF token from CPI (for CSRF protected endpoints of iFlows), but this is where a service callout step in API Management can be used for.

      Regards,

      Vadim

  • Hello Vadim,

    I would like to ask you to enhance your article a little bit. Unfortunately you completely ignore the topic of cookies in your article, however it is crucial for the proper working solution with CSRF protection. Please add that in the second HTTP call the client has to pass all cookies generated by CPI during the first call: JSESSIONID, JTENANTSESSIONID* and BIGipServer* cookies.

    Thanks,
    Yuri

    • Hi Yuri,

      That is a very valid point, thank you for bringing it here and drawing attention to it! The blog post was focused on the server side of the process, on the CPI iFlow, so I only used an HTTP client (Postman) to trigger the iFlow and to demo end-to-end execution. Indeed, as you mentioned, a valid CSRF token on its own will not be sufficient – we also need to ensure that cookies are passed in requests together with the CSRF token. During my tests, I noticed that out of the three cookies (BIGipServer*, JSESSIONID and JTENANTSESSIONID*), if at least JSESSIONID or JTENANTSESSIONID* cookie is not present, then the request fails with HTTP status code 403 / Forbidden, but if the client doesn’t pass a BIGipServer* cookie, then the request succeeds (the test was done with a CPI tenant that uses a single runtime node). Though, given a BIGipServer* cookie is used by BIG-IP load balancer, I guess its absence might have a breaking implication in an environment with multi-node setup, in such cases when subsequent requests will happen to get load-balanced to a runtime node that didn’t issue other cookies (for example, JSESSIONID). So the safest and the most consistent approach would be, as you highlighted it, to keep all three cookies and set them in subsequent requests.

      I updated the blog post with this note and a message of warning about it, it definitely makes sense to keep this in mind.

      Regards,

      Vadim

      • Many thanks, Vadim. Yes, your understanding is correct. In the setup with more than 1 worker node if the subsequent request lands on the wrong worker node it will also result in HTTP 403.