Virtual Patching with the NGINX JavaScript Module

Original: https://www.nginx.com/blog/virtual-patching-with-nginx-javascript-module/

Virtual patching refers to fixing a problem with application code by making a change to related infrastructure rather than the code itself. In the security realm, it’s common to use ModSecurity to virtually patch a vulnerability, for example. But virtual patching can be applied to other types of bugs as well, such as the bugs in backend applications we often encounter in production environments. For various reasons it can be challenging to fix these bugs directly (for example, if the original developer has left the company) and virtual patching is a practical alternative.

An NGINX Plus customer recently experienced an unusual issue: a client‑side app was making GET and POST requests in lowercase (as get and post). The backend application was expecting uppercase, as specified by RFC 7231 section 4.1, and so could not process the requests. The customer had no way to modify the backend application code.

Working with our award‑winning support team, the customer developed a virtual patch that uses the NGINX JavaScript module to convert lowercase get and post to uppercase GET and POST before NGINX Plus proxies the request to the backend application. The NGINX JavaScript module enables you to use JavaScript to perform advanced configurations, with both our open source and commercial offerings. For ease of reading, this post refers to NGINX Plus, but it also applies to NGINX Open Source.

Editor – This post belongs to our series about the NGINX JavaScript module, which was formerly called nginScript. The first post discusses why NGINX, Inc. developed its own implementation of JavaScript, and presents a sample use case. The subsequent posts explore additional use cases:

Creating the Virtual Patch

The virtual patch works by adding a new TCP/UDP virtual server in front of the existing HTTP virtual server that’s proxying traffic to the unmodifiable backend application. TCP/UDP virtual servers (in the stream{} context) can filter content using the NGINX JavaScript module.

For this blog we are employing the conventional scheme for reading function‑specific configuration and JavaScript code into the http{} and stream{} contexts with include directives in the main configuration file, /etc/nginx/nginx.conf:

http {
    include conf.d/*.conf;
    include conf.d/*.js;
}

stream {
    include stream.d/*.conf;
    include stream.d/*.js;
}

Then create the conf.d and stream.d subdirectories in /etc/nginx and place function‑specific .conf and .js files there, for HTTP and TCP/UDP respectively.

Alternatively, you can place the directives directly in nginx.conf. In that case, make sure to place the snippets in the http{} or stream{} block as appropriate.

TCP/UDP Configuration for the Virtual Patch

To read the JavaScript code into the NGINX Plus configuration, we name it with the js_include directive in the stream{} block, on line 2 below. (For an analysis of the JavaScript code, see JavaScript Code for the Virtual Patch below.)

We define a new virtual server (lines 3–11) and invoke the JavaScript function with the js_filter directive (line 8). NGINX Plus proxies the converted request to the HTTP virtual server listening on port 81 (line 9). We enable the PROXY protocol (line 10) so that NGINX Plus can pass the original client IP address through to the HTTP virtual server.

HTTP Configuration for the Virtual Patch

We also modify the existing HTTP virtual server that handles requests made to the backend application, to listen on port 81 and to use the PROXY protocol (line 3), again so that the original client IP address is passed through.

JavaScript Code for the Virtual Patch

We put the following JavaScript code for the virtual patch in a file called methods.js in the /etc/nginx/stream.d directory. It converts requests that use lowercase get and post requests to use uppercase GET and POST.

The variable s (line 5) captures all the relevant information in the client request. The code skips past the end of the PROXY protocol header in the request, then captures chunked user data (up to the newline at the end of the request line) and writes it to the req variable.

The virtual patch uses a PCRE‑style regular expression (line 18) to convert the method name to uppercase and capture the rest of the request as is. Like most regular expressions, this one is not that easy to interpret, but the following example shows how it processes a request string.

Request Line get http://www.example.com/ HTTP/1.1
Regular expression ^(get|post)(\s\S+\sHTTP\/1\.\d)

 

The first capture group (in the first set of parentheses) does an exact match on either get or post.

In the second capture group:

Note: Although the regular expression theoretically matches HTTP/2.0 as well, that protocol uses a binary‑format header. The regular expression doesn’t match binary strings, so this virtual patch does not work for HTTP/2.0.

The matches from the two capture groups are stored in in the method and uri_version variables respectively: method holds the HTTP method and uri_version holds the URL and HTTP version (line 18). Then the standard JavaScript functions replace() (line 18) and toUpperCase() (line 19) convert the HTTP method to uppercase.

The updated RFC‑compliant request header is sent using s.send() on line 21.

Conclusion

We’ve explained how to use the NGINX JavaScript module to create a virtual patch. With the power of JavaScript, you can modify request and response data in NGINX Plus in any way you see fit. This provides for a powerful solution to quickly respond to and fix production issues.

Try out NGINX Plus and the JavaScript module for yourself – start your free 30-day trial today or contact us to discuss your use cases.

Learn More

To learn more about the NGINX JavaScript module, see the following resources:

Retrieved by Nick Shadrin from nginx.com website.