1
0
mirror of https://github.com/vichan-devel/vichan.git synced 2025-01-09 21:11:44 +01:00

Merge pull request #858 from vichan-devel/removecurrentantibot

Remove current antibot system
This commit is contained in:
Lorenzo Yario 2024-12-20 02:27:40 -06:00 committed by GitHub
commit b2ca26dba5
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 3 additions and 356 deletions

View File

@ -1,199 +1,5 @@
<?php
/*
* Copyright (c) 2010-2013 Tinyboard Development Group
* Anti-bot.php has been deprecated and removed due to its functions not being necessary and being easily bypassable, by both customized and uncustomized spambots.
*/
defined('TINYBOARD') or exit;
class AntiBot {
public $salt;
public $inputs = [];
public $index = 0;
public static function randomString($length, $uppercase = false, $special_chars = false, $unicode_chars = false) {
$chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
if ($uppercase) {
$chars .= 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
}
if ($special_chars) {
$chars .= ' ~!@#$%^&*()_+,./;\'[]\\{}|:<>?=-` ';
}
if ($unicode_chars) {
$len = strlen($chars) / 10;
for ($n = 0; $n < $len; $n++) {
$chars .= mb_convert_encoding('&#' . mt_rand(0x2600, 0x26FF) . ';', 'UTF-8', 'HTML-ENTITIES');
}
}
$chars = preg_split('//u', $chars, -1, PREG_SPLIT_NO_EMPTY);
$ch = [];
// fill up $ch until we reach $length
while (count($ch) < $length) {
$n = $length - count($ch);
$keys = array_rand($chars, $n > count($chars) ? count($chars) : $n);
if ($n == 1) {
$ch[] = $chars[$keys];
break;
}
shuffle($keys);
foreach ($keys as $key) {
$ch[] = $chars[$key];
}
}
$chars = $ch;
return implode('', $chars);
}
public static function make_confusing($string) {
$chars = preg_split('//u', $string, -1, PREG_SPLIT_NO_EMPTY);
foreach ($chars as &$c) {
if (mt_rand(0, 3) != 0) {
$c = utf8tohtml($c);
} else {
$c = mb_encode_numericentity($c, [ 0, 0xffff, 0, 0xffff ], 'UTF-8');
}
}
return implode('', $chars);
}
public function __construct(array $salt = []) {
global $config;
if (!empty($salt)) {
// Create a salted hash of the "extra salt"
$this->salt = implode(':', $salt);
} else {
$this->salt = '';
}
shuffle($config['spam']['hidden_input_names']);
$input_count = mt_rand($config['spam']['hidden_inputs_min'], $config['spam']['hidden_inputs_max']);
$hidden_input_names_x = 0;
for ($x = 0; $x < $input_count ; $x++) {
if ($hidden_input_names_x === false || mt_rand(0, 2) == 0) {
// Use an obscure name
$name = $this->randomString(mt_rand(10, 40), false, false, $config['spam']['unicode']);
} else {
// Use a pre-defined confusing name
$name = $config['spam']['hidden_input_names'][$hidden_input_names_x++];
if ($hidden_input_names_x >= count($config['spam']['hidden_input_names'])) {
$hidden_input_names_x = false;
}
}
if (mt_rand(0, 2) == 0) {
// Value must be null
$this->inputs[$name] = '';
} elseif (mt_rand(0, 4) == 0) {
// Numeric value
$this->inputs[$name] = (string)mt_rand(0, 100000);
} else {
// Obscure value
$this->inputs[$name] = $this->randomString(mt_rand(5, 100), true, true, $config['spam']['unicode']);
}
}
}
public static function space() {
if (mt_rand(0, 3) != 0) {
return ' ';
}
return str_repeat(' ', mt_rand(1, 3));
}
public function html($count = false) {
$elements = [
'<input type="hidden" name="%name%" value="%value%">',
'<input type="hidden" value="%value%" name="%name%">',
'<input name="%name%" value="%value%" type="hidden">',
'<input value="%value%" name="%name%" type="hidden">',
'<input style="display:none" type="text" name="%name%" value="%value%">',
'<input style="display:none" type="text" value="%value%" name="%name%">',
'<span style="display:none"><input type="text" name="%name%" value="%value%"></span>',
'<div style="display:none"><input type="text" name="%name%" value="%value%"></div>',
'<div style="display:none"><input type="text" name="%name%" value="%value%"></div>',
'<textarea style="display:none" name="%name%">%value%</textarea>',
'<textarea name="%name%" style="display:none">%value%</textarea>'
];
$html = '';
if ($count === false) {
$count = mt_rand(1, (int)abs(count($this->inputs) / 15) + 1);
}
if ($count === true) {
// All elements
$inputs = array_slice($this->inputs, $this->index);
} else {
$inputs = array_slice($this->inputs, $this->index, $count);
}
$this->index += count($inputs);
foreach ($inputs as $name => $value) {
$element = false;
while (!$element) {
$element = $elements[array_rand($elements)];
$element = str_replace(' ', self::space(), $element);
if (mt_rand(0, 5) == 0) {
$element = str_replace('>', self::space() . '>', $element);
}
if (strpos($element, 'textarea') !== false && $value == '') {
// There have been some issues with mobile web browsers and empty <textarea>'s.
$element = false;
}
}
$element = str_replace('%name%', utf8tohtml($name), $element);
if (mt_rand(0, 2) == 0) {
$value = $this->make_confusing($value);
} else {
$value = utf8tohtml($value);
}
if (strpos($element, 'textarea') === false) {
$value = str_replace('"', '&quot;', $value);
}
$element = str_replace('%value%', $value, $element);
$html .= $element;
}
return $html;
}
public function reset() {
$this->index = 0;
}
public function hash() {
global $config;
// This is the tricky part: create a hash to validate it after
// First, sort the keys in alphabetical order (A-Z)
$inputs = $this->inputs;
ksort($inputs);
$hash = '';
// Iterate through each input
foreach ($inputs as $name => $value) {
$hash .= $name . '=' . $value;
}
// Add a salt to the hash
$hash .= $config['cookies']['salt'];
// Use SHA1 for the hash
return sha1($hash . $this->salt);
}
}

View File

@ -275,83 +275,6 @@
// To prevent bump attacks; returns the thread to last position after the last post is deleted.
$config['anti_bump_flood'] = false;
/*
* Introduction to vichan's spam filter:
*
* In simple terms, whenever a posting form on a page is generated (which happens whenever a
* post is made), vichan 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, vichan 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: https://github.com/vichan-devel/vichan/wiki/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'] = 12;
// How soon after regeneration do hashes expire (in seconds)?
$config['spam']['hidden_inputs_expire'] = 60 * 60 * 3; // three hours
// Whether to use Unicode characters in hidden input names and values.
$config['spam']['unicode'] = true;
// These are fields used to confuse the bots. Make sure they aren't actually used by vichan, or it won't work.
$config['spam']['hidden_input_names'] = array(
'user',
'username',
'login',
'search',
'q',
'url',
'firstname',
'lastname',
'text',
'message'
);
// Always update this when adding new valid fields to the post form, or EVERYTHING WILL BE DETECTED AS SPAM!
$config['spam']['valid_inputs'] = array(
'hash',
'board',
'thread',
'mod',
'name',
'email',
'subject',
'post',
'body',
'password',
'sticky',
'lock',
'raw',
'embed',
'g-recaptcha-response',
'h-captcha-response',
'captcha_cookie',
'captcha_text',
'spoiler',
'page',
'file_url',
'json_response',
'user_flag',
'no_country',
'tag',
'simple_spam'
);
// Enable simple anti-spam measure. Requires the end-user to answer a question before making a post.
// Works very well against uncustomized spam. Answers are case-insensitive.
// $config['simple_spam'] = array (

View File

@ -391,12 +391,6 @@ function define_groups() {
ksort($config['mod']['groups']);
}
function create_antibot($board, $thread = null) {
require_once dirname(__FILE__) . '/anti-bot.php';
return _create_antibot($board, $thread);
}
function sprintf3($str, $vars, $delim = '%') {
$replaces = array();
foreach ($vars as $k => $v) {
@ -1528,43 +1522,6 @@ function purge_old_antispam() {
return $query->rowCount();
}
function _create_antibot($board, $thread) {
global $config, $purged_old_antispam;
$antibot = new AntiBot([$board, $thread]);
// Delete old expired antispam, skipping those with NULL expiration timestamps (infinite lifetime).
if (!isset($purged_old_antispam) && $config['auto_maintenance']) {
$purged_old_antispam = true;
query('DELETE FROM ``antispam`` WHERE `expires` < UNIX_TIMESTAMP()') or error(db_error());
}
// Keep the now invalid timestamps around for a bit to enable users to post if they're still on an old version of
// the HTML page.
// By virtue of existing, we know that we're making a new version of the page, and the user from now on may just reload.
if ($thread) {
$query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` = :thread AND `expires` IS NULL');
} else {
$query = prepare('UPDATE ``antispam`` SET `expires` = UNIX_TIMESTAMP() + :expires WHERE `board` = :board AND `thread` IS NULL AND `expires` 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));
// Insert an antispam with infinite life as the HTML page of a thread might last well beyond the expiry date.
$query = prepare('INSERT INTO ``antispam`` VALUES (:board, :thread, :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 $antibot;
}
function checkSpam(array $extra_salt = array()) {
global $config, $pdo;
@ -1632,7 +1589,6 @@ function buildIndex($global_api = "yes") {
$catalog_api_action = generation_strategy('sb_api', array($board['uri']));
$pages = null;
$antibot = null;
if ($config['api']['enabled']) {
$api = new Api(
@ -1684,21 +1640,12 @@ function buildIndex($global_api = "yes") {
if ($wont_build_this_page) continue;
}
if ($config['try_smarter']) {
$antibot = create_antibot($board['uri'], 0 - $page);
$content['current_page'] = $page;
}
elseif (!$antibot) {
$antibot = create_antibot($board['uri']);
}
$antibot->reset();
if (!$pages) {
$pages = getPages();
}
$content['pages'] = $pages;
$content['pages'][$page-1]['selected'] = true;
$content['btn'] = getPageButtons($content['pages']);
$content['antibot'] = $antibot;
if ($mod) {
$content['pm'] = create_pm_header();
}
@ -2282,7 +2229,6 @@ function buildThread($id, $return = false, $mod = false) {
error($config['error']['nonexistant']);
$hasnoko50 = $thread->postCount() >= $config['noko50_min'];
$antibot = $mod || $return ? false : create_antibot($board['uri'], $id);
$options = [
'board' => $board,
@ -2293,7 +2239,6 @@ function buildThread($id, $return = false, $mod = false) {
'mod' => $mod,
'hasnoko50' => $hasnoko50,
'isnoko50' => false,
'antibot' => $antibot,
'boardlist' => createBoardlist($mod),
'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index'])
];
@ -2330,20 +2275,17 @@ function buildThread($id, $return = false, $mod = false) {
} elseif ($action == 'rebuild') {
$noko50fn = $board['dir'] . $config['dir']['res'] . link_for($thread, true);
if ($hasnoko50 || file_exists($noko50fn)) {
buildThread50($id, $return, $mod, $thread, $antibot);
buildThread50($id, $return, $mod, $thread);
}
file_write($board['dir'] . $config['dir']['res'] . link_for($thread), $body);
}
}
function buildThread50($id, $return = false, $mod = false, $thread = null, $antibot = false) {
function buildThread50($id, $return = false, $mod = false, $thread = null) {
global $board, $config;
$id = round($id);
if ($antibot)
$antibot->reset();
if (!$thread) {
$query = prepare(sprintf("SELECT * FROM ``posts_%s`` WHERE (`thread` IS NULL AND `id` = :id) OR `thread` = :id ORDER BY `thread`,`id` DESC LIMIT :limit", $board['uri']));
$query->bindValue(':id', $id, PDO::PARAM_INT);
@ -2405,7 +2347,6 @@ function buildThread50($id, $return = false, $mod = false, $thread = null, $anti
'mod' => $mod,
'hasnoko50' => $hasnoko50,
'isnoko50' => true,
'antibot' => $mod ? false : ($antibot ? $antibot : create_antibot($board['uri'], $id)),
'boardlist' => createBoardlist($mod),
'return' => ($mod ? '?' . $board['url'] . $config['file_index'] : $config['root'] . $board['dir'] . $config['file_index'])
];

View File

@ -1,9 +1,6 @@
<form name="post" onsubmit="return doPost(this);" enctype="multipart/form-data" action="{{ config.post_url }}" method="post">
{{ antibot.html() }}
{% if id %}<input type="hidden" name="thread" value="{{ id }}">{% endif %}
{{ antibot.html() }}
<input type="hidden" name="board" value="{{ board.uri }}">
{{ antibot.html() }}
{% if current_page %}
<input type="hidden" name="page" value="{{ current_page }}">
{% endif %}
@ -12,11 +9,9 @@
{% if not config.field_disable_name or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri)) %}<tr>
<th>
{% trans %}Name{% endtrans %}
{{ antibot.html() }}
</th>
<td>
<input type="text" name="name" size="25" maxlength="35" autocomplete="off"> {% if config.allow_no_country and config.country_flags %}<input id="no_country" name="no_country" type="checkbox"> <label for="no_country">{% trans %}Don't show my flag{% endtrans %}</label>{% endif %}
{{ antibot.html() }}
</td>
</tr>{% endif %}
{% if not config.field_disable_email or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri)) %}<tr>
@ -26,7 +21,6 @@
{% else %}
{% trans %}Email{% endtrans %}
{% endif %}
{{ antibot.html() }}
</th>
<td>
{% if (mod and not post.mod|hasPermission(config.mod.bypass_field_disable, board.uri) and config.field_email_selectbox) or (not mod and config.field_email_selectbox) %}
@ -39,17 +33,14 @@
{% else %}
<input type="text" name="email" size="25" maxlength="40" autocomplete="off">
{% endif %}
{{ antibot.html() }}
{% if not (not (config.field_disable_subject or (id and config.field_disable_reply_subject)) or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri))) %}
<input accesskey="s" style="margin-left:2px;" type="submit" name="post" value="{% if id %}{{ config.button_reply }}{% else %}{{ config.button_newtopic }}{% endif %}" />{% if config.spoiler_images %} <input id="spoiler" name="spoiler" type="checkbox"> <label for="spoiler">{% trans %}Spoiler Image{% endtrans %}</label> {% endif %}
{% endif %}
{{ antibot.html() }}
</td>
</tr>{% endif %}
{% if not (config.field_disable_subject or (id and config.field_disable_reply_subject)) or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri)) %}<tr>
<th>
{% trans %}Subject{% endtrans %}
{{ antibot.html() }}
</th>
<td>
<input style="float:left;" type="text" name="subject" size="25" maxlength="100" autocomplete="off">
@ -60,11 +51,9 @@
<tr>
<th>
{% trans %}Comment{% endtrans %}
{{ antibot.html() }}
</th>
<td>
<textarea name="body" id="body" rows="5" cols="35"></textarea>
{{ antibot.html() }}
{% if not (not (config.field_disable_subject or (id and config.field_disable_reply_subject)) or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri))) %}
{% if not (not config.field_disable_email or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri))) %}
<input accesskey="s" style="margin-left:2px;" type="submit" name="post" value="{% if id %}{{ config.button_reply }}{% else %}{{ config.button_newtopic }}{% endif %}" />{% if config.spoiler_images %} <input id="spoiler" name="spoiler" type="checkbox"> <label for="spoiler">{% trans %}Spoiler Image{% endtrans %}</label>{% endif %}
@ -80,11 +69,9 @@
{% endif %}
<th>
{% trans %}Verification{% endtrans %}
{{ antibot.html() }}
</th>
<td>
<div class="g-recaptcha" data-sitekey="{{ config.captcha.recaptcha.sitekey }}"></div>
{{ antibot.html() }}
</td>
</tr>
{% endif %}
@ -92,11 +79,9 @@
<tr>
<th>
{% trans %}Verification{% endtrans %}
{{ antibot.html() }}
</th>
<td>
<div class="h-captcha" data-sitekey="{{ config.captcha.hcaptcha.sitekey }}"></div>
{{ antibot.html() }}
</td>
</tr>
{% endif %}
@ -173,14 +158,12 @@
<input style="display:inline" type="text" id="file_url" name="file_url" size="35">
</div>
{% endif %}
{{ antibot.html() }}
</td>
</tr>
{% if config.enable_embedding %}
<tr id="upload_embed">
<th>
{% trans %}Embed{% endtrans %}
{{ antibot.html() }}
</th>
<td>
<input type="text" name="embed" value="" size="30" maxlength="120" autocomplete="off">
@ -211,27 +194,21 @@
{% if not config.field_disable_password or (mod and post.mod|hasPermission(config.mod.bypass_field_disable, board.uri)) %}<tr>
<th>
{% trans %}Password{% endtrans %}
{{ antibot.html() }}
</th>
<td>
<input type="text" name="password" value="" size="12" maxlength="18" autocomplete="off">
<span class="unimportant">{% trans %}(For file deletion.){% endtrans %}</span>
{{ antibot.html() }}
</td>
</tr>{% endif %}
{% if config.simple_spam and not id %}<tr>
<th>
{{ config.simple_spam.question }}
{{ antibot.html() }}
</th>
<td>
<input type="text" name="simple_spam" value="" size="12" maxlength="18" autocomplete="off">
{{ antibot.html() }}
</td>
</tr>{% endif %}
</table>
{{ antibot.html(true) }}
<input type="hidden" name="hash" value="{{ antibot.hash() }}">
</form>
<script type="text/javascript">{% verbatim %}