Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
vadimklimov
Active Contributor

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 7a519509aed84a2c9e6f627841825b5a.

 

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
23 Comments
0 Kudos
Such an excellent piece of information..! Thanks for sharing.
vadimklimov
Active Contributor
0 Kudos
You are welcome, Thosif! It is nice if information here turns to be helpful.
engswee
Active Contributor
0 Kudos
Great stuff again, Vadim. Definitely useful reference as we start to get more interesting integration scenarios in the cloudy world of CPI! 😉
vadimklimov
Active Contributor
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.
sumanth171
Active Participant
0 Kudos
Very well explained. Thanks for sharing.
vadimklimov
Active Contributor
0 Kudos
Thank you, Anil!
former_member290364
Discoverer
0 Kudos

Hi Vadim,

Thanks for sharing 🙂 

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

 

Thanks

vadimklimov
Active Contributor
0 Kudos
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.
sanjaybadhai
Explorer
0 Kudos
Any clue what is the validity period (e.g in milliseconds/seconds etc.) for this token for a single call?
vadimklimov
Active Contributor
0 Kudos
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.
0 Kudos
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
vadimklimov
Active Contributor
0 Kudos
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
KeshavDesai
Explorer
0 Kudos
Thanks Vadim, this is exactly the information [in your reply] I was looking for..

Regards

Keshav
yuri_ziryukin
Employee
Employee
0 Kudos
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
vadimklimov
Active Contributor
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
yuri_ziryukin
Employee
Employee
0 Kudos
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.
bbauwens
Discoverer
0 Kudos
Hi Yuri,

This remark attracks my attention. We do have a 2-node setup, and we randomly get a 403 Forbidden on our CSRF protected endpoints.

Do I interprete your comment correctly: if we do fetch a new CSRF token first (assume from node 1), and next we do a POST call using this token and returned cookies (assume this POST request lands on node 2), we will get a 403 back?

Many thanks for the feedback.

Kind regards,

Brecht
yuri_ziryukin
Employee
Employee
0 Kudos
Hello Brecht,

CSRF token is node-dependent. If you fetched it from node 1, but the second request lands on node 2, the 403 will be generated.

However there is a special cookie that should be part of the first reply - BIGipServer*. This cookie will tell the load balancer to which node the second request should go. If you make sure that this cookie is properly provided, there should be no issues.

All 3 cookies (BIGipServer, JSESSIONID and JTENANTSESSIONID) are a MUST for the CSRF token to work properly.

Yuri
bbauwens
Discoverer
0 Kudos
Hi Yuri,

Thanks a lot for your prompt feedback! We will further check if all cookies are correctly passed during the subsequent calls as well.

Regards,

Brecht
former_member37947
Participant
0 Kudos
Hi Vadim,

I´m facing a different security issue and I would really appreciate your suggestions here.

A UI Interface is sending a request to our CPI tenant:

GET --> https://CPI_TENNANT/http/accounts?query=SELECT * FROM Accounts where CompanyId = X

We have noticed that this request can be debuged in Chrome, modified and sent back (reusing the session) with no where clause, resulting in a security breach where attackers could get data they are not supposed to have access to.

How could I ensure that the original request has not been modified?

I have been reading about message digest but I´m not really sure if that would be the better approach.

Any hints on the best practice here?

Thanks in advance.

 

 
vadimklimov
Active Contributor
Hi,

To address the requirement you described, I can think of two directions:

  • Ensure the authenticity of the message that is received by CPI. This is to avoid vulnerabilities related to unauthorized modification of the message after the authoritative party had produced it. Message digest/hashing is a good option here.

  • Apply access segregation and authorization check on the backend, with corresponding principal propagation flow through CPI. With this setup, a user context of the user who calls the client side of the flow (e.g. UI of the caller application) will be propagated to the backend, and the backend can run authorization checks and decide if the user is authorized to fetch records that they want to get. This part will rely on the capabilities of the backend in part of authorization management,


Regards,

Vadim
former_member37947
Participant
0 Kudos
Thanks for the hints!
tapas_cg
Discoverer
0 Kudos

Hi @vadimklimov,  

We implemented the same solution mentioned in the detailed blog and it is working perfectly fine in our CPI DEV to S/4 HANA Cloud DEV setup. However we have noticed the first call b/w CPI and S/4 HANA Cloud in PROD instances to fail regularly with the CSRF Token error. We have split the incoming records and are making calls to S/4 with each records individually. 

The issue seems to take place while we enable the parallel processing as well in our splitter during the 1st call being made b/w CPI and S/4 HANA Cloud. Kindly suggest for any workaround to fix this from happening.

 

Thanks

Tarun

 

Labels in this area