Admin, just ban VPNs from uploading files.
Create a new table called blocked_ranges in your tinyboard database.
Create this Python script file to do that (with the correct DB login credentials).
I usually like to put scripts in /opt directory, e.g /opt/tinyboard.
I prefer tab indentation instead of space indentation but you disabled [code] tags so whatever.
create_blocked_ranges_table.pyimport mysql.connector
from mysql.connector import errorcode
# Login credentials
config = {
'user': 'INSERT_USERNAME',
'password': 'INSERT_PASSWORD',
'host': 'localhost',
'database': 'INSERT_DB_NAME'
}
# SQL statement to create the table
create_table_query = """
CREATE TABLE blocked_ranges (
id INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
start_ip BIGINT UNSIGNED NOT NULL,
end_ip BIGINT UNSIGNED NOT NULL,
INDEX (start_ip, end_ip)
);
"""
try:
# Connect to the database
connection = mysql.connector.connect(**config)
cursor = connection.cursor()
# Execute the query to create the table
cursor.execute(create_table_query)
connection.commit()
print("Table 'blocked_ranges' created successfully.")
except mysql.connector.Error as err:
if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
print("Access denied: Check your username or password.")
elif err.errno == errorcode.ER_BAD_DB_ERROR:
print("Database does not exist.")
else:
print(f"An error occurred: {err}")
finally:
if connection.is_connected():
cursor.close()
connection.close()
print("MySQL connection closed.")
Run it:
sudo python3 /path/to/create_blocked_ranges_table.py
Create a Python script file (with the correct DB login credentials) which you will use for updating or filling in this table containing VPN IP ranges.
You will be using this list:
https://github.com/X4BNet/lists_vpnupdate_blocked_ranges_table.pyimport mysql.connector
from mysql.connector import errorcode
import requests
import io
import ipaddress
import time
import sys
# Login credentials
config = {
'user': 'INSERT_USERNAME',
'password': 'INSERT_PASSWORD',
'host': 'localhost',
'database': 'INSERT_DB_NAME'
}
# Function to convert CIDR to start and end IPs
def cidr_to_range(cidr):
network = ipaddress.ip_network(cidr)
return int(network.network_address), int(network.broadcast_address)
# Function to insert CIDR ranges in batches
def insert_cidr_ranges(cidr_list, connection, batch_size=10000, silent=False):
cursor = connection.cursor()
batch = []
for cidr in cidr_list:
start_ip, end_ip = cidr_to_range(cidr)
batch.append((start_ip, end_ip))
if len(batch) >= batch_size:
cursor.executemany("INSERT INTO blocked_ranges (start_ip, end_ip) VALUES (%s, %s)", batch)
connection.commit()
batch.clear()
time.sleep(0.001) # Pause 1 ms to prevent high CPU usage
# Insert any remaining entries
if batch:
cursor.executemany("INSERT INTO blocked_ranges (start_ip, end_ip) VALUES (%s, %s)", batch)
connection.commit()
cursor.close()
def debug_message(message: str, silent: bool):
if not silent:
print(message)
def main(url, batch_size=10000, silent=False):
try:
# Connect to the database
connection = mysql.connector.connect(**config)
cursor = connection.cursor()
# Download the CIDR list
debug_message(f"Downloading CIDR list from {url}…", silent)
response = requests.get(url)
response.raise_for_status()
# Read the CIDR data
cidr_data = io.StringIO(response.text)
cidr_list = [line.strip() for line in cidr_data if line.strip()]
# Clear existing entries in the table
debug_message("Cleared existing entries in 'blocked_ranges' table.", silent)
cursor.execute("TRUNCATE TABLE blocked_ranges")
connection.commit()
# Insert CIDR ranges into the database
debug_message("Inserting CIDR ranges into the database, may take a while…", silent)
insert_cidr_ranges(cidr_list, connection, batch_size, silent)
debug_message("CIDR ranges inserted successfully.", silent)
except mysql.connector.Error as err:
if err.errno == errorcode.ER_ACCESS_DENIED_ERROR:
debug_message("Access denied: Check your username or password.", silent)
elif err.errno == errorcode.ER_BAD_DB_ERROR:
debug_message("Database does not exist.", silent)
else:
debug_message(f"An error occurred: {err}", silent)
except requests.exceptions.RequestException as e:
debug_message(f"Failed to download CIDR list: {e}", silent)
except Exception as e:
debug_message(f"An error occurred: {e}", silent)
finally:
if connection.is_connected():
connection.close()
debug_message("MySQL connection closed.", silent)
if __name__ == "__main__":
url = "
https://raw.githubusercontent.com/X4BNet/lists_vpn/refs/heads/main/output/datacenter/ipv4.txt"
batch_size = 10000
silent = any(arg in ['-s', '-silent', '-q', '-quiet'] for arg in sys.argv)
Run it:
sudo python3 /path/to/update_blocked_ranges_table.py
You may automate it to update yearly using cron:
sudo crontab -e
Append this line (with the correct script path):
0 0 1 1 * /usr/bin/python3 /path/to/update_blocked_ranges_table.py -silent
Modify the file post.php of tinyboard to make use of this new blocked_ranges table.
I would place it between lines 369 and 371 of
https://github.com/savetheinternet/Tinyboard/blob/master/post.php if ($post['has_file'] && (!isset($_POST['mod']) || !$_POST['mod'])) {
// Convert the remote address to an integer representation
$ip = $_SERVER['REMOTE_ADDR'];
$ip_int = sprintf('%u', ip2long($ip));
// Check if the IP is in the blocked_ranges table
$query = prepare("SELECT COUNT(*) as count FROM blocked_ranges WHERE start_ip <= :ip_int AND end_ip >= :ip_int");
$query->bindValue(':ip_int', $ip_int, PDO::PARAM_INT);
$query->execute() or error(db_error());
// If the IP is blocked, show an error message
if ($query->fetch(PDO::FETCH_ASSOC)["count"] > 0) {
error(_('Your IP is listed in github.com/X4BNet/lists_vpn/blob/main/output/datacenter/ipv4.txt. You cannot upload files with a VPN.'));
}
}
Make sure to have these dependencies first:
sudo apt update
sudo apt install python3 python3-pip
sudo pip3 install requests
Make sure you also have these Domain Name System Blacklists (DNSBLs) enabled in your relevant inc/config.php or inc/instance-config.php file in tinyboard. Nowhere near as comprehensive as the above VPN list, but blocks all known public proxies and Tor exit nodes. It won't stop the CP spammer because he uses VPNs / data centers mostly but will make low-effort raids a bit harder.
$config['dnsbl'][] = 'rbl.efnetrbl.org';
$config['dnsbl'][] = 'dnsbl-1.uceprotect.net';
$config['dnsbl'][] = 'dnsbl.dronebl.org';
$config['dnsbl'][] = 'torexit.dan.me.uk';
$config['dnsbl'][] = 'dnsbl.tornevall.org';
And of course, before running any scripts from anonymous users, always verify them first in Copilot, ChatGPT or Gemini, with login credentials removed.