Software engineers working on a product that highly values security, whether they’re working on frontend, backend, or security features in particular, need to ensure that their web applications aren’t vulnerable to attack.
Tetrate is a service mesh company, providing infrastructure for enterprise customers, including financial institutions and federal agencies handling highly sensitive data. We are sharing our recent efforts in securing the UI for our product, Tetrate Service Bridge, which was aided in part by some pointers from penetration testing conducted by a large financial institution. This article walks you through that experience that may help others who are building a secure UI.
Background
Just as we need security in our physical life, we also need security in the cyber world. Attackers, the term used to describe those who try to do malicious things towards websites or web applications, have proven that web security is a serious issue. But what is considered an attack?
According to MDN Web Docs, an attack is “unauthorized access, use, modification, destruction, or disruption.” Some groups of attackers hack for sport, while most do it for profit. The kind of attack itself ranges from Distributed Denial-of-Service (DDoS) to something as severe as Data Breach, where the attackers successfully obtain users’ data. As such, it is really important to implement proper security measures to prevent these incidents from happening. The target of the attack itself is mostly users — with their data and/or money at stake. Attackers can execute it only if the website or web application allows them to.
Scoping the Security Barrier
Tetrate takes care of components of its own product with security in mind, with Tetrate Service Bridge User Interface (later will be mentioned as “TSB UI”) being a part of the TSB deployment. We build it using React as Single-Page Application (SPA).
The figure below shows the scope of security issues that needed to be addressed in the UI. It describes the “interactions” between TSB UI components and other services in a TSB deployment, and annotates the components that needed to be secured.
As shown above, we needed to secure our static assets (SPA), application server, and web server.
Securing the SPA
In order to protect our site from XSS, we use React with JSX. As such, we pass event handlers as a function instead of a string. This makes it impossible to “tamper” the function. On top of that, React’s JSX renders a string as it is, meaning that malicious code or script won’t be executed.
However, React is not immune to XSS. In some cases, XSS can be executed in the form of base64 string or JavaScript string to an anchor tag’s href attribute or to an image tag’s src attribute. For example, the string below will execute a JavaScript code, which will pop up an alert:
<a href="javascript:alert('Hacked!');">Test</a>
To mitigate that, we need to prevent the “javascript:” substring from being in front of the href or src attribute. In our case, since we don’t persist external links, we could safely use relative links in the UI (instead of absolute). This ensures the JavaScript code does not execute when the link is clicked. For example, clicking the element like below will not trigger the JavaScript code:
<a href="/users/javascript:alert('Hacked!');">Test</a>
Lastly, we don’t use the React JSX prop dangerouslySetInnerHTML unless the HTML is properly sanitized.
Securing the Application Server
This is a component where we invested a lot of effort for security, because this component is an entry to all other services. If this component is not secure, then it will be really prone to be exploited by attackers.
Cookies
For authentication/authorization cookies, we use HTTPOnly mode to ensure that it can’t be extracted inside the browser using JavaScript.
We also keep the cookie age short. Shorter cookie age makes the time window for attacks shorter after a user closes their browser window. However, for User Experience (UX) purposes, we allow the user to extend the session at their own will before the session expires. This enables flexibility in the end-user perspective, as it prevents the user from getting logged out all of a sudden.
Lastly, it may be very tempting to store user information in the cookie, such as username, email address, or actual address. However, it’s not advised to put them there. It is safer to store them in memory (e.g. in the server’s session), where it is slightly out of reach from the attacker.
HTTP Body Validations
For PUT, POST, and PATCH requests, the UI needs to include a Request Body in order for the request to be deemed as “a valid request.” This is because it is important to reject an invalid request before we forward it to other services. Additionally, we enforce all requests that expect JSON responses to include Accept: application/json, because all of our other services’ APIs always return JSON as a response.
With regard to GraphQL queries, we also validate them in our application server, so that invalid requests won’t be forwarded to the GraphQL server. This includes disabling GraphQL introspection query, which enables its caller to get information regarding what queries the schema supports. This is to prevent attackers from exposing the data that should not be visible to users.
Cross-Site Request Forgery (CSRF)
CSRF is a kind of attack where the attacker baits a victim to hit an endpoint of an insecure website/web application with a GET or POST request.
To mitigate this, we ensure that we don’t implement GET endpoints for modifying resource(s). Additionally, we also reject POST requests coming from the HTML form tag. As such, every attempt to modify our backend resources will get the response 400 Bad Request.
Authentication
To prevent probing when authenticating, we do not show which field is incorrect when a user is logging in with an invalid username or password. Instead, we show a generic message: “Invalid username or password.” This is intended so the attacker is not given any further information with regard to user credentials.
When the user successfully logs in, the application server will fire a request to the Authentication Service, which will return a JSON Web Token (JWT) with a specified expiry time as a response. Upon logging out, this token is revoked, so that it can’t be used anymore by other parties.
Securing the Web Server
Our SPA is served using a web server. As such, requests and responses are being handled by it, too. This helps us to secure the delivered static bundles.
Preventing browser-caching when sending responses
When the backend is responsible for returning user sensitive information, it is important to prevent the browser from caching its responses. We append these headers to every response for this purpose: Cache-Control, Pragma, and Expires.
Preventing opening the SPA via iframe
iframe is a common HTML tag that can be used to open another URL inside a particular HTML page.
A website or web application can be opened inside an iframe if there is no X-Frame-Options response header specified from the web server. This is dangerous as the attacker can put our website or web application in an iframe, which allows them to “track” the user’s actions inside the iframe, such as clicking and typing.
Conclusion
One of the Tetrate’s aims is securing infrastructure deployments across platforms. By securing our product’s UI, we have taken a step forward towards that goal, as the UI is one of the “gateways” to access sensitive and confidential information.
Try Ajitiono is a Tetrate Engineer working on the Tetrate Service Bridge UI. Tetrate Service Bridge is a compute-agnostic, zero-trust service mesh fabric built to fit any enterprise architecture. This blog was edited by Tetrate Engineer Dhi Aurrahman and Tetrate Writer Tevah Platt.