diff options
Diffstat (limited to 'inc')
| -rw-r--r-- | inc/anti-bot.php | 83 | ||||
| -rw-r--r-- | inc/config.php | 26 | ||||
| -rw-r--r-- | inc/functions.php | 8 | ||||
| -rw-r--r-- | inc/template.php | 2 |
4 files changed, 91 insertions, 28 deletions
diff --git a/inc/anti-bot.php b/inc/anti-bot.php index e8c26113..cd2ff795 100644 --- a/inc/anti-bot.php +++ b/inc/anti-bot.php @@ -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; +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)); - if($print_the_rest) - return $hidden_inputs_twig[$salt_str]->html(0); + if($thread) + $query = prepare('UPDATE `antispam` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread'); else - return $hidden_inputs_twig[$salt_str]->html(rand(1, 5)); -} - -function hiddenInputsHash(array $salt) { - global $hidden_inputs_twig; + $query = prepare('UPDATE `antispam` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` IS NULL'); - $salt_str = implode(':', $salt); + $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)); - if(!isset($hidden_inputs_twig[$salt_str])) - $hidden_inputs_twig[$salt_str] = new AntiBot($salt); + $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)); - return $hidden_inputs_twig[$salt_str]->hash(); + 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; } diff --git a/inc/config.php b/inc/config.php index 94b7bdd7..b7e3a243 100644 --- a/inc/config.php +++ b/inc/config.php @@ -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', diff --git a/inc/functions.php b/inc/functions.php index 3761b608..da58675c 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -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']) )); diff --git a/inc/template.php b/inc/template.php index b348f153..fc9f167d 100644 --- a/inc/template.php +++ b/inc/template.php @@ -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),
|
