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 is one of several that explore use cases for the NGINX JavaScript module. For a complete list, see Use Cases for the NGINX JavaScript Module.]
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:
- The
\s
matches the space afterget
orpost
. - The
\S+
matches one or more non‑whitespace characters, in this case the URL (orange text). - The second
\s
matches the space after the URL. - The
HTTP\/1\.\d
matches the stringHTTP/1.1
(green text). More precisely, the\d
matches any digit, so the regex also works forHTTP/1.0
requests.
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:
- Harnessing the Power and Convenience of JavaScript for Each Request with the NGINX JavaScript Module – Introductory blog post with examples
- About NGINX JavaScript – Details on what aspects of JavaScript are and aren’t supported
- Data Masking for User Privacy with the NGINX JavaScript Module – How to mask personal information in log files