The Responsibilities:
You can read more about XSS on the OWASP site here, but in short, XSS vulnerability involves our application storing potentially malicious HTML or JavaScript data in a storage location, such as a database or blob storage, and potentially exposing other endpoints beyond our own if we return this data unaltered.
I've encountered the counterargument that most modern browsers, infrastructures, or front-end code have built-in mechanisms to combat XSS attacks. Frankly, I view this as, at best, incorrect and, at worst, downright complacent. Even as a backend component, we want to be a responsible participant in the entire application stack because security starts with you, and we should not delegate that responsibility to another part of the ecosystem..
The Protection:
In general, one should follow closely the following steps to ensure the safety of your data against XSS vulnerabilities found through data input & output.
Input Data:
Input Validation
Input Sanitization
Output Data:
Content Security Policy Header
Output Encoding
For input data, you should either halt the workflow if any malicious data is detected or "clean" the data before allowing the workflow to proceed.
In securing output data, you can either "clean" any potentially dangerous data before passing it on to the next part of the workflow, or, in the case of response/request, you can use the header to reduce or eliminate the avenues through which XSS can occur.
The Confusion:
The implementation of this protection varies across .NET project types. This became even more apparent when I started working with Azure Functions, which, in addition to HTTP Triggers, also include non-request/response triggers such as EventGrid or Queue Triggers.
Part of my confusion arose because traditionally Microsoft offers a native library, RequestValidator, for .NET WebForm and .NET MVC projects, where validation takes place natively with each request. These are prominently featured as recommendations on the OWASP page.
However, no such option exists for server-side applications such as WebAPI and Azure Functions, and Microsoft provides no explanation for this.
I believe they want to place the responsibility on developers because we understand the application best and can tailor the conditions to validate and sanitize our own data.
The Sanitization:
I discovered that .NET Core offers some degree of out-of-the-box data sanitization. However, for specific use cases, it is advisable to implement your own sanitization logic and create your own whitelisting.
The last option - AddWebEncoders() conveniently package HTML, JavaScript and URL encoders through dependency injection for you to use where you see fits.
The Validation:
From an Azure Function perspective, you can either perform "manual" validation or utilize "automatic" validation through .NET Core custom middleware.
In this context, "manual" means you must explicitly call the validation within each Azure Function, while "automatic" indicates that the pipeline can invoke the validation for all inbound data, which is what middleware is designed for.
As to the details of how to implement this, there are lots of avenue which I will go into details in my Part 2 of this blog.
The Middleware:
Middleware can be used to validate and sanitize request input before it reaches the main logic in your Azure Function. Additionally, middleware can be employed to make last-minute adjustments to your output data before sending it back as a response or passing it to the next stage in your invocation pipeline (e.g., durable functions).
This is where my confusion arose with Azure Functions, as middleware cannot be used in all scenarios. Middleware is not available for in-process Azure Functions because you don't have control over the application's startup, making it impossible to add middleware. Starting with .NET 5, Azure Functions can run in isolated mode, where you can control the startup and the dependencies it consumes, thereby supporting middleware.
In summary, we can use the manual approach for any scenario, but if we want to use the middleware automatic approach, it must be an Azure Function running in isolated mode, which means using .NET 5 or later.
The Header:
When working with Azure Function, it makes sense to utilize Azure Front Door or Azure API Management to configure the request and response policies to implement security headers. This is an entire topic in itself, and I'll save it for a separate blog.
Conclusion:
In this blog, I demonstrated the importance of addressing XSS vulnerabilities from a .NET Core Azure Function perspective. The key takeaway is the variety of implementation methods. The options available to you vary from one program type to another, and understanding what is accessible and its limitations will help alleviate initial confusion.