users: improve sockpuppet detection on signup.

Require new accounts to verify their email address if any of the
following conditions are true:

* Their IP is a proxy.
* Their IP is under a partial IP ban.
* They're creating a new account while logged in to another account.
* Somebody recently created an account from the same IP in the last week.

Changes from before:

* Allow logged in users to view the signup page and create new accounts.
  Creating a new account while logged in to your old account is now
  allowed, but it requires email verification. This is a honeypot.
* Creating multiple accounts from the same IP is now allowed, but they
  require email verification. Previously the same IP check was only for
  the last day (now it's the last week), and only for an exact IP match
  (now it's a subnet match, /24 for IPv4 or /64 for IPv6).
* New account verification is disabled for private IPs (e.g. 127.0.0.1,
  192.168.0.1), to make development or running personal boorus easier
  (fixes #4618).
This commit is contained in:
evazion
2020-12-27 22:47:52 -06:00
parent 7e8f859b24
commit 9dc788c0ce
5 changed files with 115 additions and 42 deletions

View File

@@ -59,7 +59,7 @@ class UsersController < ApplicationController
end
def create
requires_verification = IpLookup.new(CurrentUser.ip_addr).is_proxy? || IpBan.hit!(:partial, CurrentUser.ip_addr)
requires_verification = UserVerifier.new(CurrentUser.user, request).requires_verification?
@user = authorize User.new(
last_ip_addr: CurrentUser.ip_addr,

View File

@@ -1,3 +1,4 @@
require "danbooru/http/application_client"
require "danbooru/http/html_adapter"
require "danbooru/http/xml_adapter"
require "danbooru/http/cache"

View File

@@ -0,0 +1,46 @@
# Checks whether a new account seems suspicious and should require email verification.
class UserVerifier
attr_reader :current_user, :request
# current_user is the user creating the new account, not the new account itself.
def initialize(current_user, request)
@current_user, @request = current_user, request
end
def requires_verification?
return false if is_local_ip?
# we check for IP bans first to make sure we bump the IP ban hit count
is_ip_banned? || is_logged_in? || is_recent_signup? || is_proxy?
end
private
def ip_address
@ip_address ||= IPAddress.parse(request.remote_ip)
end
def is_local_ip?
ip_address.loopback? || ip_address.link_local? || ip_address.private? || ip_address.try(:unique_local?)
end
def is_logged_in?
!current_user.is_anonymous?
end
def is_recent_signup?(age: 24.hours)
subnet_len = ip_address.ipv4? ? 24 : 64
subnet = "#{ip_address}/#{subnet_len}"
User.where("last_ip_addr <<= ?", subnet).where("created_at > ?", age.ago).exists?
end
def is_ip_banned?
IpBan.hit!(:partial, ip_address.to_s)
end
def is_proxy?
IpLookup.new(ip_address).is_proxy?
end
end

View File

@@ -1,6 +1,10 @@
class UserPolicy < ApplicationPolicy
def create?
user.is_anonymous? && !sockpuppet?
true
end
def new?
true
end
def update?
@@ -31,10 +35,6 @@ class UserPolicy < ApplicationPolicy
user.is_admin? || record.id == user.id || !record.enable_private_favorites?
end
def sockpuppet?
User.where(last_ip_addr: request.remote_ip).where("created_at > ?", 1.day.ago).exists?
end
def permitted_attributes_for_create
[:name, :password, :password_confirmation, { email_address_attributes: [:address] }]
end