(new Soapbox())->shout(array_map('strtoupper', $opinions)); //Shaun's blog

Me, elsewhere

Miscellaneous public code

A PHP API client for Reddit

I don't tweet much

XMPP chat
(Pidgin, Miranda, Swift, etc.)

Perfect is the enemy of good enough.

SFSQuery, a PHP class to query the StopForumSpam API and DNSBL

Posted June 27, 2018 by shaun

If you have a web application that accepts any user input, you're probably dealing with spam bots. There's been a steady increase in automated spamming of blog comments, contact forms, order forms, and other types of interactive web resources. If they can post to it, they're trying to spam it, even in cases where it makes no sense, like submitting advertising pitches to API endpoints or search forms where no human will ever see the message.

Web spammers have been busy escalating and evolving. The adversary is no longer some guy in Elbonia running XRumer over his dial-up connection; these days you're likely to encounter dedicated and distributed web spamming networks. In these cases, the spammer spins up multiple VPSes at different providers, all communicating with one another as part of the spam operation. Often a recon visit will come from one IP address, followed immediately by several POST requests from other hosts in the spam network. This method attempts to fly under the radar of rate limiting and other anti-abuse tactics: - - [25/Jun/2018:12:32:20 -0500] "GET /about/contact-us/ HTTP/1.1" 200 9439 on "-" - - [25/Jun/2018:12:32:43 -0500] "POST /about/contact-us/ HTTP/1.1" 403 3384 on "-" - - [25/Jun/2018:12:32:50 -0500] "POST /about/contact-us/ HTTP/1.0" 403 3384 on "-" - - [25/Jun/2018:12:32:53 -0500] "POST /about/contact-us/ HTTP/1.1" 403 3384 on "-"

In the above log excerpt, you can see that a scouting bot showed up to verify that the "Contact Us" page was still there; over the following 10 seconds, that bot and two others tried to spam the form. Tried to being the operative term. All of the spam attempts resulted in a 403 Forbidden response and the messages were disregarded thanks to SFSQuery.

SFSQuery PHP Class

One effective way to mitigate web spam is to use a community-based blacklist of spammer IPs. There are several of these lists available, but StopForumSpam is my favorite because it's free, it's tailored specifically to web spam, and its large user base ensures that a lot of spammers get reported.

I created the SFSQuery PHP class to make checking StopForumSpam's database as easy as possible. You can call this class from any PHP script or application to help gauge whether or not an IP address is likely to belong to a spammer. The Github page has more thorough documentation, but the most basic implementation is via an anonymous object, like this:

use parseword\SFSQuery\SFSQuery;

if ((new SFSQuery($_SERVER['REMOTE_ADDR']))->wasReportedInPastDays(3)) {
    //Possible spammer, reject the action
    header('HTTP/1.1 403 Forbidden');
    echo "Sorry, you don't have access to this resource.";
else {
    //Process the action

Or you can instantiate an SFSQuery object if you want to base your acceptance decision on multiple data points about an IP. This can help eliminate false positives:

use parseword\SFSQuery\SFSQuery;

$sfs = new SFSQuery($_SERVER['REMOTE_ADDR']);
if ($sfs->wasReportedInPastDays(30) && $sfs->getConfidence() > 25) {
    //Likely spammer, reject the action
    header('HTTP/1.1 403 Forbidden');
    echo "Sorry, you don't have access to this resource.";
else {
    //Process the action

When querying StopForumSpam's web API, there are six data points available, all of which are exposed by SFSQuery:

  • appears: Whether or not the target IP appears in the StopForumSpam database
  • frequency: The number of times the IP has been reported as a spammer
  • lastSeen: The date and time that the IP was last reported as a spammer
  • confidence: A spammer confidence level from 0-100 calculated by StopForumSpam
  • country: The country to which the target IP is allocated
  • asn: The Autonomous System Number (ASN) that announces the target IP

I've recently added support for querying StopForumSpam's DNSBL as well as their web API. The DNSBL service is generally faster than the web API (smaller packets, no TCP overhead), and responses get cached by the DNS infrastructure which improves performance and reduces load. The downside is that DNSBL responses don't include the IP's country or ASN.

Check out SFSQuery on Github or SFSQuery on Packagist to start blocking spammers in your PHP script or application.

Recent articles

📰 Caveat with Vantec SATA/IDE to USB 2.0 Adapter and Macrium software

📰 Jay Niffley, Man of Mystery

📰 Compiling Doxygen on FreeBSD without LaTeX and Ghostscript

📰 Introducing Snuze, a PHP client for the Reddit API

📰 jisusaiche: Java's installer telemetry

📰 BIND client log error "query_find: query_getdb failed"

📰 Resolving "The lang/perl5.24 port has been deleted: Has expired" portmaster error

📰 Armagaddon2 interim fix for Firefox 56 and other old versions

📰 Strange DNS queries: qname "miep", qtype ANY

📰 Undeliverable as addressed: A massive broken spam campaign?

📰 Using WITH_META_MODE and ccache for FreeBSD build boosts

📰 Resolving subversion error E000013: Unable to create pristine install stream

📰 Enhancements to SmokePing's AnotherDNS probe

📰 Generating vanity DNSSEC key tags

📰 DDoS involving forged packets from

▲ Back to top | Permalink to this page