Improved Tinyboard anti-bot/spam filter. See large comment in inc/config.php for details.
This commit is contained in:
@@ -12,8 +12,7 @@ if(realpath($_SERVER['SCRIPT_FILENAME']) == str_replace('\\', '/', __FILE__)) {
|
||||
$hidden_inputs_twig = array();
|
||||
|
||||
class AntiBot {
|
||||
public $inputs = array(), $index = 0;
|
||||
private $salt;
|
||||
public $salt, $inputs = array(), $index = 0;
|
||||
|
||||
public static function randomString($length, $uppercase = false, $special_chars = false) {
|
||||
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
@@ -90,12 +89,14 @@ class AntiBot {
|
||||
$this->inputs[$name] = (string)rand(0, 100);
|
||||
} else {
|
||||
// Obscure value
|
||||
$this->inputs[$name] = $this->randomString(rand(5, 100));
|
||||
$this->inputs[$name] = $this->randomString(rand(5, 100), true, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function html($count = false) {
|
||||
global $config;
|
||||
|
||||
$elements = array(
|
||||
'<input type="hidden" name="%name%" value="%value%">',
|
||||
'<input type="hidden" value="%value%" name="%name%">',
|
||||
@@ -110,7 +111,11 @@ class AntiBot {
|
||||
|
||||
$html = '';
|
||||
|
||||
if($count == 0) {
|
||||
if($count === false) {
|
||||
$count = rand(1, count($this->inputs) / 15);
|
||||
}
|
||||
|
||||
if($count === true) {
|
||||
// all elements
|
||||
$inputs = array_slice($this->inputs, $this->index);
|
||||
} else {
|
||||
@@ -134,7 +139,10 @@ class AntiBot {
|
||||
$value = $this->make_confusing($value);
|
||||
else
|
||||
$value = utf8tohtml($value);
|
||||
|
||||
|
||||
if(strpos($element, 'textarea') === false)
|
||||
$value = str_replace('"', '"', $value);
|
||||
|
||||
$element = str_replace('%value%', $value, $element);
|
||||
|
||||
$html .= $element;
|
||||
@@ -162,36 +170,42 @@ class AntiBot {
|
||||
// Use SHA1 for the hash
|
||||
return sha1($hash . $this->salt);
|
||||
}
|
||||
};;
|
||||
|
||||
|
||||
function hiddenInputs(array $salt, $print_the_rest = false) {
|
||||
global $hidden_inputs_twig;
|
||||
|
||||
$salt_str = implode(':', $salt);
|
||||
|
||||
if(!isset($hidden_inputs_twig[$salt_str]))
|
||||
$hidden_inputs_twig[$salt_str] = new AntiBot($salt);
|
||||
|
||||
if($print_the_rest)
|
||||
return $hidden_inputs_twig[$salt_str]->html(0);
|
||||
else
|
||||
return $hidden_inputs_twig[$salt_str]->html(rand(1, 5));
|
||||
}
|
||||
|
||||
function hiddenInputsHash(array $salt) {
|
||||
global $hidden_inputs_twig;
|
||||
function _create_antibot($board, $thread) {
|
||||
global $config;
|
||||
|
||||
$salt_str = implode(':', $salt);
|
||||
$antibot = new AntiBot(array($board, $thread));
|
||||
|
||||
if(!isset($hidden_inputs_twig[$salt_str]))
|
||||
$hidden_inputs_twig[$salt_str] = new AntiBot($salt);
|
||||
query('DELETE FROM `antispam` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error($query));
|
||||
|
||||
return $hidden_inputs_twig[$salt_str]->hash();
|
||||
if($thread)
|
||||
$query = prepare('UPDATE `antispam` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread');
|
||||
else
|
||||
$query = prepare('UPDATE `antispam` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` IS NULL');
|
||||
|
||||
$query->bindValue(':board', $board);
|
||||
if($thread)
|
||||
$query->bindValue(':thread', $thread);
|
||||
$query->bindValue(':expires', $config['spam']['hidden_inputs_expire']);
|
||||
$query->execute() or error(db_error($query));
|
||||
|
||||
$query = prepare('INSERT INTO `antispam` VALUES (:board, :thread, CRC32(:hash), UNIX_TIMESTAMP(), NULL, 0)');
|
||||
$query->bindValue(':board', $board);
|
||||
$query->bindValue(':thread', $thread);
|
||||
$query->bindValue(':hash', $antibot->hash());
|
||||
$query->execute() or error(db_error($query));
|
||||
|
||||
if($query->rowCount() == 0) {
|
||||
// there was no database entry for this hash. most likely expired.
|
||||
return true;
|
||||
}
|
||||
|
||||
return $antibot;
|
||||
}
|
||||
|
||||
function checkSpam(array $extra_salt = array()) {
|
||||
global $config;
|
||||
global $config, $pdo;
|
||||
|
||||
if(!isset($_POST['hash']))
|
||||
return true;
|
||||
@@ -231,6 +245,25 @@ function checkSpam(array $extra_salt = array()) {
|
||||
// Use SHA1 for the hash
|
||||
$_hash = sha1($_hash . $extra_salt);
|
||||
|
||||
return $hash != $_hash;
|
||||
if($hash != $_hash)
|
||||
return true;
|
||||
|
||||
$query = prepare('UPDATE `antispam` SET `passed` = `passed` + 1 WHERE `hash` = CRC32(:hash)');
|
||||
$query->bindValue(':hash', $hash);
|
||||
$query->execute() or error(db_error($query));
|
||||
if($query->rowCount() == 0) {
|
||||
// there was no database entry for this hash. most likely expired.
|
||||
return true;
|
||||
}
|
||||
|
||||
$query = prepare('SELECT `passed` FROM `antispam` WHERE `hash` = CRC32(:hash)');
|
||||
$query->bindValue(':hash', $hash);
|
||||
$query->execute() or error(db_error($query));
|
||||
$passed = $query->fetchColumn(0);
|
||||
|
||||
if($passed > $config['spam']['hidden_inputs_max_pass'])
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@@ -170,9 +170,33 @@
|
||||
// Skip checking certain IP addresses against blacklists (for troubleshooting or whatever)
|
||||
$config['dnsbl_exceptions'][] = '127.0.0.1';
|
||||
|
||||
// Spam filter
|
||||
/*
|
||||
* Introduction to Tinyboard's spam filter:
|
||||
*
|
||||
* In simple terms, whenever a posting form on a page is generated (which happens whenever a
|
||||
* post is made), Tinyboard will add a random amount of hidden, obscure fields to it to
|
||||
* confuse bots and upset hackers. These fields and their respective obscure values are
|
||||
* validated upon posting with a 160-bit "hash". That hash can only be used as many times
|
||||
* as you specify; otherwise, flooding bots could just keep reusing the same hash.
|
||||
* Once a new set of inputs (and the hash) are generated, old hashes for the same thread
|
||||
* and board are set to expire. Because you have to reload the page to get the new set
|
||||
* of inputs and hash, if they expire too quickly and more than one person is viewing the
|
||||
* page at a given time, Tinyboard would return false positives (depending on how long the
|
||||
* user sits on the page before posting). If your imageboard is quite fast/popular, set
|
||||
* $config['spam']['hidden_inputs_max_pass'] and $config['spam']['hidden_inputs_expire'] to
|
||||
* something higher to avoid false positives.
|
||||
*
|
||||
* See also: http://tinyboard.org/docs/?p=Your_request_looks_automated
|
||||
*
|
||||
*/
|
||||
|
||||
// Number of hidden fields to generate
|
||||
$config['spam']['hidden_inputs_min'] = 4;
|
||||
$config['spam']['hidden_inputs_max'] = 12;
|
||||
// How many times can a "hash" be used to post?
|
||||
$config['spam']['hidden_inputs_max_pass'] = 30;
|
||||
// How soon after regeneration do hashes expire (in seconds)?
|
||||
$config['spam']['hidden_inputs_expire'] = 60 * 60 * 2; // two hours
|
||||
// These are fields used to confuse the bots. Make sure they aren't actually used by Tinyboard, or it won't work.
|
||||
$config['spam']['hidden_input_names'] = array(
|
||||
'user',
|
||||
|
@@ -13,7 +13,6 @@ require_once 'inc/display.php';
|
||||
require_once 'inc/template.php';
|
||||
require_once 'inc/database.php';
|
||||
require_once 'inc/events.php';
|
||||
require_once 'inc/anti-bot.php';
|
||||
require_once 'inc/lib/gettext/gettext.inc';
|
||||
|
||||
// the user is not currently logged in as a moderator
|
||||
@@ -210,6 +209,11 @@ function _syslog($priority, $message) {
|
||||
}
|
||||
}
|
||||
|
||||
function create_antibot($board, $thread = null) {
|
||||
require_once 'inc/anti-bot.php';
|
||||
|
||||
return _create_antibot($board, $thread);
|
||||
}
|
||||
|
||||
function rebuildThemes($action) {
|
||||
// List themes
|
||||
@@ -1175,6 +1179,7 @@ function buildIndex() {
|
||||
$content['pages'] = $pages;
|
||||
$content['pages'][$page-1]['selected'] = true;
|
||||
$content['btn'] = getPageButtons($content['pages']);
|
||||
$content['antibot'] = create_antibot($board['uri']);
|
||||
file_write($filename, Element('index.html', $content));
|
||||
|
||||
if(isset($md5) && $md5 == md5_file($filename)) {
|
||||
@@ -1492,6 +1497,7 @@ function buildThread($id, $return=false, $mod=false) {
|
||||
'config' => $config,
|
||||
'id' => $id,
|
||||
'mod' => $mod,
|
||||
'antibot' => $mod ? false : create_antibot($board['uri'], $id),
|
||||
'boardlist' => createBoardlist($mod),
|
||||
'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['uri'] . '/' . $config['file_index'])
|
||||
));
|
||||
|
@@ -24,7 +24,7 @@ function load_twig() {
|
||||
|
||||
$loader = new Twig_Loader_Filesystem($config['dir']['template']);
|
||||
$loader->setPaths($config['dir']['template']);
|
||||
$twig = new Twig_Environment($loader, Array(
|
||||
$twig = new Twig_Environment($loader, array(
|
||||
'autoescape' => false,
|
||||
'cache' => "{$config['dir']['template']}/cache",
|
||||
'debug' => ($config['debug'] ? true : false),
|
||||
|
Reference in New Issue
Block a user