Thwarting Annoying Traffic

Along with being a seasoned Linux user, I’m also a security minded server admin. This one’s for you webmasters and admins out there who are tired of seeing bad actors trawling your sites for vulnerabilities to leverage. If you’re like me, you probably see a lot of bogus requests going to places on your server that either don’t exist, or shouldn’t be receiving outside requests. For this, I have a collection of resources that I use personally for stopping these. Credit to the original authors is due, and I do apologize if I don’t credit you directly. I’ll also likely update this post from time to time as I amass more solutions.

CSF Regex For Preventing WordPress Abuse

With WordPress being the most popular CMS in use, it’s also the most popular target amongst adversaries. Often times, you’ll come across someone sending bogus POST and GET requests to xmlrpc.php and wp-login.php. These are the most common targets for WordPress bruteforce and DoS attacks. Even if you don’t have a WordPress site, it’s a good idea to mitigate these requests so they aren’t being needlessly processed. Here enters these regular expressions for ConfigServer Firewall (I know not everyone has CSF, we’ll get to you).

# XMLRPC
if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/xmlrpc\.php.*" /)) {
return ("WP XMLPRC Attack",$1,"XMLRPC","3","80,443","1");
}

# WP-LOGIN
if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-login\.php.*" /)) {
return ("WP Login Attack",$1,"WPLOGIN","3","80,443","1");
}

They’re rather simple. Basically, after 3 bogus requests, LFD will block the offending IP. Of course, if you use a service that polls xmlrpc.php (like Jetpack), you’ll want to add its IP(s) to csf.allow in order to whitelist it. These rules can also be adapted for other commonly targeted scripts, and the number allowed requests can also be changed.

Setting this up is as equally simple. We just need to add these rules to CSF’s regex configuration, and tell it where to watch for occurrences.

To start, we’ll edit regex.custom.pm, and add the rules. Be sure to put these rules between these two blocks like the example below.

$ vim /etc/csf/regex.custom.pm

# Do not edit before this point
###############################################################################

# XMLRPC
if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/xmlrpc\.php.*" /)) {
return ("WP XMLPRC Attack",$1,"XMLRPC","3","80,443","1");
}

# WP-LOGIN
if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-login\.php.*" /)) {
return ("WP Login Attack",$1,"WPLOGIN","3","80,443","1");
}

# If the matches in this file are not syntactically correct for perl then lfd
# will fail with an error. You are responsible for the security of any regex
# expressions you use. Remember that log file spoofing can exploit poorly
# constructed regex's
###############################################################################
# Do not edit beyond this point

Now, we tell CSF which log to watch for these requests. In this case, we’ll be using Apache’s domlogs.

$ vim /etc/csf/csf.conf
# Change: CUSTOM1_LOG = "/var/log/customlog" to "/usr/local/apache/domlogs/*/*"

Save your changes, and restart CSF.

$ csf -ra

After doing so, you should begin to see entries like this in csf.deny:

45.142.122.9 # lfd: (WPLOGIN) WP Login Attack 45.142.122.9 (RU/Russia/lousycopper.aeza.network): 3 in the last 3600 secs - Thu Nov 10 12:13:21 2022

CSF Regex For Vulnerability Trolls

Recently, I came across an IP fishing for vulnerable sites by just flinging HEAD requests for common URLs at the site. Here’s a few examples from my web logs:

[04/Dec/2022:07:52:41 -0500] "HEAD /wordpress HTTP/1.1" 404 - "http://mogbox.net/wordpress" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36

[04/Dec/2022:07:52:45 -0500] "HEAD /old HTTP/1.1" 404 - "http://mogbox.net/old" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"

[04/Dec/2022:07:52:54 -0500] "HEAD /test HTTP/1.1" 404 - "http://mogbox.net/test" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.169 Safari/537.36"

So, I spent the time to come up with a regex solution for CSF that should catch this type of resource trolling. See the above section to see how to add this to your regex.custom.pm. Also, be mindful of the entries in this list, as you’ll likely want to remove any that you actually have a site under.

# Watch for vulnerability trolls
if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/wordpress.*" /)) {
return ("Vulnerability Trolling: HEAD /wordpress",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/WordPress.*" /)) {
return ("Vulnerability Trolling: HEAD /Wordpress",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/WORDPRESS.*" /)) {
return ("Vulnerability Trolling: HEAD /WORDPRESS",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/WordPress.*" /)) {
return ("Vulnerability Trolling: HEAD /WordPress",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/wp.*" /)) {
return ("Vulnerability Trolling: HEAD /wp",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/Wp.*" /)) {
return ("Vulnerability Trolling: HEAD /Wp",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/WP.*" /)) {
return ("Vulnerability Trolling: HEAD /WP",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/wp-old.*" /)) {
return ("Vulnerability Trolling: HEAD /wp-old",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/old.*" /)) {
return ("Vulnerability Trolling: HEAD /old",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/Old.*" /)) {
return ("Vulnerability Trolling: HEAD /Old",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/OLD.*" /)) {
return ("Vulnerability Trolling: HEAD /OLD",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/oldsite.*" /)) {
return ("Vulnerability Trolling: HEAD /oldsite",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/old-site.*" /)) {
return ("Vulnerability Trolling: HEAD /old-site",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/new.*" /)) {
return ("Vulnerability Trolling: HEAD /new",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/New.*" /)) {
return ("Vulnerability Trolling: HEAD /New",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/NEW.*" /)) {
return ("Vulnerability Trolling: HEAD /NEW",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/2022.*" /)) {
return ("Vulnerability Trolling: HEAD /2022",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/2022\/.*" /)) {
return ("Vulnerability Trolling: HEAD /2022/",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/2021.*" /)) {
return ("Vulnerability Trolling: HEAD /2021",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/2020.*" /)) {
return ("Vulnerability Trolling: HEAD /2020",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/2019.*" /)) {
return ("Vulnerability Trolling: HEAD /2019",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/2018.*" /)) {
return ("Vulnerability Trolling: HEAD /2018",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/backup.*" /)) {
return ("Vulnerability Trolling: HEAD /backup",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/test.*" /)) {
return ("Vulnerability Trolling: HEAD /test",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/Test.*" /)) {
return ("Vulnerability Trolling: HEAD /Test",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/TEST.*" /)) {
return ("Vulnerability Trolling: HEAD /TEST",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/demo.*" /)) {
return ("Vulnerability Trolling: HEAD /demo",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/bc.*" /)) {
return ("Vulnerability Trolling: HEAD /bc",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/www.*" /)) {
return ("Vulnerability Trolling: HEAD /www",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/WWW.*" /)) {
return ("Vulnerability Trolling: HEAD /WWW",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/Www.*" /)) {
return ("Vulnerability Trolling: HEAD /Www",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/main.*" /)) {
return ("Vulnerability Trolling: HEAD /main",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/bk.*" /)) {
return ("Vulnerability Trolling: HEAD /bk",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/Backup.*" /)) {
return ("Vulnerability Trolling: HEAD /Backup",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/BACKUP.*" /)) {
return ("Vulnerability Trolling: HEAD /BACKUP",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/bak.*" /)) {
return ("Vulnerability Trolling: HEAD /bak",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/bac.*" /)) {
return ("Vulnerability Trolling: HEAD /bac",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/shop.*" /)) {
return ("Vulnerability Trolling: HEAD /shop",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/Shop.*" /)) {
return ("Vulnerability Trolling: HEAD /Shop",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/SHOP.*" /)) {
return ("Vulnerability Trolling: HEAD /SHOP",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/sitio.*" /)) {
return ("Vulnerability Trolling: HEAD /sitio",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/sito.*" /)) {
return ("Vulnerability Trolling: HEAD /sito",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/site.*" /)) {
return ("Vulnerability Trolling: HEAD /site",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/Site.*" /)) {
return ("Vulnerability Trolling: HEAD /Site",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/SITE.*" /)) {
return ("Vulnerability Trolling: HEAD /SITE",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/blog.*" /)) {
return ("Vulnerability Trolling: HEAD /blog",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/Blog.*" /)) {
return ("Vulnerability Trolling: HEAD /Blog",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:HEAD) \/BLOG.*" /)) {
return ("Vulnerability Trolling: HEAD /BLOG",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp\.php.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp.php",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp_mnay\.php.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp_mnay.php",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-includes\/wlwmanifest\.xml.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-includes/wlwmanifest.xml",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/public\/_ignition\/health-check\/.*" /)) {
return ("Vulnerability Trolling: GET/POST /public/_ignition/health-check/",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/_ignition\/health-check\/.*" /)) {
return ("Vulnerability Trolling: GET/POST /_ignition/health-check/",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-includes\/Requests\/index.php.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-includes/Requests/index.php",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-includes\/index.php.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-includes/index.php",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-includes\/Text\/.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-includes/Text/",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-includes\/PHPMailer\/.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-includes/PHPMailer/",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-includes\/IXR\/.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-includes/IXR/",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-includes\/ID3\/.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-includes/ID3/",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-content\/themes\/seotheme\/.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-content/themes/seotheme/",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/hetong\.js.*" /)) {
return ("Vulnerability Trolling: GET/POST /hetong.js",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/check\.js.*" /)) {
return ("Vulnerability Trolling: GET/POST /check.js",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-config\.txt.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-config.txt",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-config\.old.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-config.old",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-config\.inc.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-config.inc",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/\.vscodei\/sftp\.json.*" /)) {
return ("Vulnerability Trolling: GET/POST /.vscode/sftp.json",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/sftp-config\.json.*" /)) {
return ("Vulnerability Trolling: GET/POST /sftp-config.json",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/showLogin\.cc.*" /)) {
return ("Vulnerability Trolling: GET/POST /showLogin.cc",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-includes\/fonts\/index\.php.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-includes/fonts/index.php",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/wp-content\/plugins\/wp-freeform\/wawe\.php.*" /)) {
return ("Vulnerability Trolling: GET/POST /wp-content/plugins/wp-freeform/wawe.php",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/\?rest_route=\/wp\/v2\/users\/.*" /)) {
return ("Vulnerability Trolling: GET/POST /?rest_route=/wp/v2/users/",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/debug\/default\/view\?panel=config.*" /)) {
return ("Vulnerability Trolling: GET/POST /debug/default/view?panel=config",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/info\.php.*" /)) {
return ("Vulnerability Trolling: GET/POST /info.php",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/ecp\/Current\/exporttool\/microsoft\.exchange\.ediscovery\.exporttool\.application.*" /)) {
return ("Vulnerability Trolling: GET/POST /ecp/Current/exporttool/microsoft.exchange.ediscovery.exporttool.application",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/\.git\/config.*" /)) {
return ("Vulnerability Trolling: GET/POST /.git/config",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/telescope\/requests.*" /)) {
return ("Vulnerability Trolling: GET/POST /telescope/requests",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/server-status.*" /)) {
return ("Vulnerability Trolling: GET/POST /server-status",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/s\/0363e2638313e2532323e27363\/_\/\;\/META-INF\/maven\/com\.atlassian\.jira\/jira-webapp-dist\/pom\.properties.*" /)) {
return ("Vulnerability Trolling: GET/POST /s/0363e2638313e2532323e27363/_/;/META-INF/maven/com.atlassian.jira/jira-webapp-dist/pom.properties",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/login\.action.*" /)) {
return ("Vulnerability Trolling: GET/POST /login.action",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/api\/search\?folderIds=0.*" /)) {
return ("Vulnerability Trolling: GET/POST /api/search?folderIds=0",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/v2\/_catalog.*" /)) {
return ("Vulnerability Trolling: GET/POST /v2/_catalog",$1,"RSRCTROLL","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/config.json.*" /)) {
return ("Vulnerability Trolling: GET/POST /config.json",$1,"RSRCTROLL","1","80,443","1");
}
Expand

After implementing this, you should start to see entries like this in your csf.deny:

207.180.240.189 # lfd: (RSRCTROLL) Vulnerability Trolling: HEAD /wordpress 207.180.240.189 (DE/Germany/vmi996701.contaboserver.net): 1 in the last 3600 secs - Wed Dec  7 01:54:31 2022

CSF Regex For Cobaltstrike Beacon Probes

Here’s another bit of activity I recently came across in my logs:

[05/Dec/2022:07:45:06 -0500] "GET /ab2g HTTP/1.1" 301 20 "-" "Mozilla/5.0 zgrab/0.x"

[05/Dec/2022:07:45:07 -0500] "GET /ab2h HTTP/1.1" 301 20 "-" "Mozilla/5.0 zgrab/0.x"

After doing a little digging, I found that this is the product of a Python library named dissect.cobaltstrike (write up available here) that’s meant to be used by researchers to find Cobaltstrike beacons out in the wild. Well, the IP that happened to probe me was a Digital Ocean host, and their current abuse reports don’t lend much confidence toward this being a researcher. Even if it were, I still don’t like being a guinea pig. So, here’s another regex for CSF that’ll ban IPs probing for these URLs.

# Cobaltstrike beacon probing
if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/ab2g.*" /)) {
return ("Probing for Cobaltstrike beacon /ab2g",$1,"CBLTSTRK","1","80,443","1");
}

if (($globlogs{CUSTOM1_LOG}{$lgfile}) and ($line =~ /(\S+).*] "\w*(?:GET|POST) \/ab2h.*" /)) {
return ("Probing for Cobaltstrike beacon /ab2h",$1,"CBLTSTRK","1","80,443","1");
}

Implementation instructions are the same for the expressions above, so scroll up if you need a refresher. csf.deny entries will also look like the one below:

143.198.0.190 # lfd: (CBLTSTRK) Searching for Cobaltstrike beacon /ab2g 143.198.0.190 (US/United States/-): 1 in the last 3600 secs - Tue Dec  6 17:21:07 2022

Configure CSF to Use the AbuseIPDB Blocklist

This is something majorly beneficial for everyone, regardless of the type of site you run. All it takes is registering for a free AbuseIPDB account, generating an API key, and configuring CSF to use their list. Do note that if you wish to poll for more than 10,000 IPs, you will need a commercial account.

Once you have an account and key in hand, just follow these steps. First, we add the AbuseIPDB blocklist to CSF’s list, and insert your key in the relevant part of the URL.

$ vim /etc/csf/csf.blocklists
# AbuseIPDB blacklist
# Details: https://docs.abuseipdb.com/#blacklist-endpoint
ABUSEIPDB|86400|10000|https://api.abuseipdb.com/api/v2/blacklist?key=YOUR_API_KEY&plaintext

Now, we restart CSF, and that wraps this up!

$ csf -ra

Configure CSF to use RJM’s Blocklist

Here’s a goody from RJM that can be an excellent supplement to the rest of this. He curates his own list for CSF, and now offers it for free. He stated in his honeypot thread (linked below in the next section) that it used to be reserved for private use amongst himself and personal friends, and he used to charge for public use. It’s now freely available for everyone to use, but still consider showing him a little appreciation for all of his hard work. He definitely makes life a little easier for admins.

Now, let’s get on to the set up. The instructions are essentially the same for the AbuseIPDB list, except no API key is required. The first thing we do is edit CSF’s list of blocklists, and add the necessary block.

$ vim /etc/csf/csf.blocklists
# RJM Blocklist Fresh IP List
# Details: https://rjmblocklist.com
RJMFRESHIPS|3600|1000|https://www.rjmblocklist.com/sizzling/freships.txt

After this, we restart CSF, and that’s it!

$ csf -ra

Simple PHP Honeypot by RJM

So, let’s say you don’t have CSF, or you’re on a shared hosting plan where you can’t modify CSF. What are you to do? Well, here’s a rather simple, but effective solution created by RJM_Web_Design. It utilizes htaccess to direct fishing expeditions to a PHP script that reports the offending IP to AbuseIPDB. For this, you’ll need to sign up for a free AbuseIPDB account, and generate an API key to use in the PHP script. It also stores all instances in a MySQL database that you can form your own blocklist from. While not as robust as using a firewall, it can be rather handy. I’ve also included a link down below to a zip archive containing an SQL dump (as well as the script and htaccess for the uninitiated) that you can import since I know not everyone has the know-how to manually create tables and whatnot. You’ll just need to create a database and user, and import it using either PHPMyAdmin, or the command line. I expect most of you know how to do this, so I won’t be covering these steps.

Some of the htaccess rules will likely need to be removed or modified if you have a CMS installed that uses one or more of these paths. Alternatively, you can look into changing and obfuscating the locations of common targets. You can also easily adapt an IP whitelist to this so that only you are allowed to send requests to these. This is useful for those who do have a CMS component that they want to protect without blocking themselves (“admin.php”, for example). I’ve included an example beneath this first block. Just be sure to replace the instances of “0.0.0.0” with your own IP, and add rules as needed.

Update Oct 31, 2022 – PHP 8 is a bit more literal with its SQL query handling. The query on line 16 was not compatible, so I’ve modified it a bit to get it to cooperate with PHP 8. The old query (“SELECT * FROM table WHERE (ip LIKE ‘$ip’ AND time >= ‘$fresh’)) will throw a syntax error because the reports table and ip4 column are not explicitly denoted. This takes care of that. The download link still contains the original script, so look below for the updated version.

download php_trap.zip

htaccess:

RewriteEngine On
RewriteCond %{REQUEST_URI} /admin.php [NC,OR]
RewriteCond %{REQUEST_URI} /install.php [NC,OR]
RewriteCond %{REQUEST_URI} /lequ.php [NC,OR]
RewriteCond %{REQUEST_URI} /login.php [NC,OR]
RewriteCond %{REQUEST_URI} /setup.php [NC,OR]
RewriteCond %{REQUEST_URI} /shell.php [NC,OR]
RewriteCond %{REQUEST_URI} /user.php [NC,OR]
RewriteCond %{REQUEST_URI} /webconfig.txt.php [NC,OR]
RewriteCond %{REQUEST_URI} /wlwmanifest.xml [NC,OR]
RewriteCond %{REQUEST_URI} /wp-config.php [NC,OR]
RewriteCond %{REQUEST_URI} /wp-contacts.php [NC,OR]
RewriteCond %{REQUEST_URI} /wp-login.php [NC,OR]
RewriteCond %{REQUEST_URI} /xmlrpc [NC,OR]
RewriteCond %{REQUEST_URI} /xmlrpc.php [NC,OR]
RewriteCond %{REQUEST_URI} ^/2020/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/2019/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/admin/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/administrator/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/config/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/blog/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/cms/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/data/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/demo/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/fckeditor/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/.git/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/inc/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/install/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/magento/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/manager/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/media/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/news/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/old/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/plus/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/setup/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/shop/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/site/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/sito/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/staging/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/templates/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/test/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/web/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/website/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/wordpress/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/wp/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/wp1/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/wp2/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/wp-admin/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/wp-contacts/(.*)$ [NC,OR]
RewriteCond %{REQUEST_URI} ^/wp-includes/(.*)$ [NC]
RewriteRule . /trap\.php?req=%{THE_REQUEST} [PT,L]
Expand

htaccess with IP whitelist:

RewriteCond %{REMOTE_ADDR} !=127.0.0.1
RewriteRule xmlrpc.php$ /trap\.php?req=%{THE_REQUEST} [R=301,L]

RewriteCond %{REMOTE_ADDR} !=127.0.0.1
RewriteCond %{REMOTE_ADDR} !=1.2.3.4
RewriteRule admin.php$ /trap\.php?req=%{THE_REQUEST} [R=301,L]

RewriteCond %{REMOTE_ADDR} !=0.0.0.0
RewriteRule ^/demo/(.*)$ /trap\.php?req=%{THE_REQUEST} [R=301,L]

trap.php (updated for PHP 8):

<?php
header('X-PHP-Response-Code: 401', true, 401);
date_default_timezone_set('America/New_York');
$ip=$_SERVER['REMOTE_ADDR'];
$origRequest =  $_GET['req'];
putenv("TZ=US/Eastern");
$timeNow = time();
$fresh = time() - 900;
$domain = "example.com"; // the domain the script is installed on
$currentDateTime = (date("M d, Y h:i:s a"));
$ports="80,443";
$categories="15,21"; // for AbuseIPDB Report

$con = mysqli_connect("localhost","database_user","password","database");
    if (!$con) { die('Could not connect: ' . mysqli_error($con)); }
    $result = mysqli_query($con, "SELECT * FROM reports WHERE (ip4 LIKE '$ip' AND time >= '$fresh')");
    $row = mysqli_fetch_array($result);
        $reportDate = $row['datetime'];
        if (empty($reportDate)) {
        // sanitize
        $timeNow = mysqli_real_escape_string($con, $timeNow);
        $ip = mysqli_real_escape_string($con, $ip);
        $domain = mysqli_real_escape_string($con, $domain);
        $currentDateTime = mysqli_real_escape_string($con, $currentDateTime);
        $origRequest =  mysqli_real_escape_string($con, $origRequest);
        $comment=$origRequest; // for AbuseIPDB Report and database entry
        /* If origRequest is stripped out by mysqli_real_escape_string, it means it contained malicious SQL code. Therefore:*/
        if (empty($origRequest)) {
            $comment = "Web-based SQL injection attempt";
            $categories = "15,16,21";
        }
        // insert to db
        mysqli_select_db($con, "database");
        $sql = "INSERT INTO reports (datetime, time, ip4, ports, categories, domain, origRequest, comment) VALUES ('$currentDateTime','$timeNow','$ip','$ports','$categories','$domain','$origRequest','$comment')";
        if (!mysqli_query($con,$sql)) {
            echo("Error description: " . mysqli_error($con));
            }
        // make report
        $data = (array(
            "ip"  => $ip,
            "categories" => $categories,
            "comment" => $comment
        ));
        $headers =  array('Key: the-abuseipdb-key-goes-here', 'Accept: application/json');
        $ch = curl_init("https://api.abuseipdb.com/api/v2/report");
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1 ); // Set to 0 for testing to display response from AbuseIPDB
        curl_setopt($ch, CURLOPT_POST,           1 );
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
        $output=curl_exec($ch);
        curl_close($ch);
    }
?>
<!DOCTYPE HTML>
<html>
<head>
    <title>Busted</title>
</head>
<body>
    <p style="text-align: center"><img src="/Images/401.png" style="max-width: 90%" alt="Outstretched hand with palm facing user in a halt gesture inside a circle with a slash."></p>
</body>
</html>
Expand

Be sure you replace “database_user”, “password”, and both instances of “database” with the proper information. You’ll also likely want to change the domain of the site the script will be running on. There’s also a little HTML at the bottom that you can customize with your own error image and page title. Oh, and don’t forget to insert your AbuseIPDB API key in the relevant section.

Geoblocking With htaccess

This solution really isn’t recommended, but it is effective. This is another one for those of you on shared hosting, or lacking a firewall. Why is it not recommended, though? Well, you’ll more than likely end up blocking something that doesn’t need to be blocked, like yourself, or legitimate web crawlers. This also has to be consistently updated, and an htaccess with tons of rules can impact web server performance. Google Ads will also penalize sites that use geoblocking in this manner. For this, it’s much more recommended to employ a firewall of some kind, or a plugin that focuses on this. If you’re still adamant about doing this, though, you can use this site to generate the needed rules. Just select the countries you’d like to block, and the format to use (Apache .htaccess Deny in this case).