Implementing a CSRF check on a Zimbra extension

Hello Zimbra Customers, Partners & Friends,

In this article you will learn how to implement a CSRF check in a Zimbra
extension. The Java project and source code can be found at
https://github.com/Zimbra/zm-extension-guide.

In many cases the Cross-Origin-Resource-Policy in the web browser and
the setting of the SameSite=Strict attribute on the authentication
cookie will provide enough security for your extension. However in some
cases you may want to implement an additional CSRF check in your
extension. For example you may want to allow the posting of HTTP forms
only from a specific domain in your organisation (and not allow
subdomains).

Here is an example implementation:

 /**
  * Processes HTTP OPTIONS requests.
  *
  * Here is an example CSRF implementation, you may not need it for your application, also be aware of the following setting:
  * zmlocalconfig -e zimbra_same_site_cookie="Strict"
  *
  * In this demonstration case the CSRF check is implemented on the HTTP Options request, in reality you would probably implement it on HTTP Post and Get.
  *
  * You will also need to set zimbraCsrfAllowedRefererHosts if you want to implement a referer check:
  * zmprov mcf +zimbraCsrfAllowedRefererHosts "zimbra.example.com"
  *
  * Example request to test this CSRF implementation:
  * curl 'https://zimbra.example.com/service/extension/mytest' -X OPTIONS -H 'X-Zimbra-Csrf-Token: 0_3278....030b' -H 'Referer: https://zimbra.example.com/modern/email/Inbox/conversation/266' -H 'Cookie: ZM_AUTH_TOKEN=0_78fe8e....a313b;'
  *
  * @param req  request message
  * @param resp response message
  * @throws java.io.IOException
  * @throws javax.servlet.ServletException
  */

 @Override
 public void doOptions(HttpServletRequest req, HttpServletResponse resp) throws IOException, ServletException {
     //all authentication is done by AuthUtil.getAuthTokenFromHttpReq, returns null if unauthorized
     final AuthToken authToken = AuthUtil.getAuthTokenFromHttpReq(req, resp, false, true);
     if (authToken == null) {
         resp.getOutputStream().print("No or invalid Auth token received.");
         return;
     }
     String csrfToken = req.getHeader(Constants.CSRF_TOKEN);
     if (!StringUtil.isNullOrEmpty(csrfToken)) {
         resp.getOutputStream().print("No CSRF token received.");
         return;
     }

     //check for valid CSRF token
     if (!CsrfUtil.isValidCsrfToken(csrfToken, authToken)) {
         resp.getOutputStream().print("CSRF check FAILED.");
         return;
     }

     //do a CSRF referrer check
     String[] allowedRefHosts = null;
     Provisioning prov = Provisioning.getInstance();
     try {
         allowedRefHosts = prov.getConfig().getCsrfAllowedRefererHosts();
     } catch (Exception e) {
         resp.getOutputStream().print("getCsrfAllowedRefererHosts failed.");
         return;
     }

     if (isValidCsrfReferrer(req, allowedRefHosts)) {
         resp.getOutputStream().print("All CSRF checks passed.");
         //Add your code here
     } else {
         resp.getOutputStream().print("CSRF referrer checks FAIL.");
     }
 }


 public static boolean isValidCsrfReferrer(final HttpServletRequest req, final String[] allowedRefHost) {
     List<String> allowedRefHostList = Arrays.asList(allowedRefHost);
     String referrer = req.getHeader(HttpHeaders.REFERER);
     String refHost = null;

     URL refURL = null;
     try {
         refURL = new URL(referrer);
     } catch (Exception e) {
         return false;
     }
     refHost = refURL.getHost().toLowerCase();

     return allowedRefHost != null && allowedRefHostList.contains(refHost);
 }

Testing the CSRF

 

To be able to test you will first need to obtain a valid ZM_AUTH_TOKEN
and a valid CSRF token. The easiest way to obtain these is to log-in to
your Zimbra test server, in the web browser open the Developer Console
(F12), navigate to the network tab and copy/paste them for use with the
curl command.

 

image

 

Example curl request:

 

curl 'https://zimbra.example.com/service/extension/mytest' \
     -X OPTIONS \
     -H 'X-Zimbra-Csrf-Token: 0_32786543c8a1da061a5294e011823dfe54e7030b' \
     -H 'Referer: https://zimbra.example.com/modern/email/Inbox/conversation/266' \
     -H 'Cookie: ZM_AUTH_TOKEN=0_78fe8ed6316247bbf0489b9443079e02462f36bf_69643d33363a373.....e302e305f47415f343137383b637372663d313a313b;'

 

Now you can change the Referer and X-Zimbra-Csrf-Token headers to
simulate a malicious request. You can also alter the ZM_AUTH_TOKEN
cookie to test the authentication implementation of your extension.

, ,

Comments are closed.

Copyright © 2022 Zimbra, Inc. All rights reserved.

All information contained in this blog is intended for informational purposes only. Synacor, Inc. is not responsible or liable in any manner for the use or misuse of any technical content provided herein. No specific or implied warranty is provided in association with the information or application of the information provided herein, including, but not limited to, use, misuse or distribution of such information by any user. The user assumes any and all risk pertaining to the use or distribution in any form of any subject matter contained in this blog.

Legal Information | Privacy Policy | Do Not Sell My Personal Information | CCPA Disclosures