Spam next= middleware detection and prevention.

modified:   make_post_sell/__init__.py
	modified:   make_post_sell/request_methods.py
	modified:   make_post_sell/views/__init__.py
This commit is contained in:
Russell Ballestrini 2024-12-15 09:34:55 -05:00
parent 4e4c459e3f
commit e101026d98
3 changed files with 58 additions and 24 deletions

View file

@ -2,6 +2,46 @@ from pyramid.config import Configurator
from pyramid.session import SignedCookieSessionFactory
from pyramid.session import JSONSerializer
import re
class SpamBlockerMiddleware:
"""
Middleware to block spammy requests based on query string patterns.
"""
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
# Extract the query string
query_string = environ.get("QUERY_STRING", "")
# Define regex patterns
unescaped_next_count = len(re.findall(r"(?i)\bnext=", query_string))
escaped_next_count = len(
re.findall(r"(?i)\bnext%25|next%3D|next%253D|next%2525", query_string)
)
# Check if the request is spammy
if unescaped_next_count + escaped_next_count > 1:
# Respond with 401 Unauthorized
print("next= query spam detected.")
status = "401 Unauthorized"
headers = [("Content-Type", "text/plain")]
start_response(status, headers)
return [b"Request appears malformed or spammy.\n"]
# Forward the request to the next application/middleware
return self.app(environ, start_response)
def __getattr__(self, name):
"""
Delegate attribute access to the wrapped Pyramid app.
This ensures that attributes like 'registry' are accessible.
"""
return getattr(self.app, name)
def get_int_or_bool_or_none_or_str(value):
"""
@ -76,4 +116,10 @@ def main(global_config, **settings):
# scan each of these includes for additional configuration.
config.scan()
return config.make_wsgi_app()
# generate app.
app = config.make_wsgi_app()
# Wrap the Pyramid app with SpamBlockerMiddleware.
app = SpamBlockerMiddleware(app)
return app

View file

@ -106,32 +106,13 @@ def includeme(config):
def add_spam(request):
"""Test if request looks spammy. Returns HTTP Error or False."""
import re
from pyramid.httpexceptions import HTTPUnauthorized
query_string = request.query_string
# Check for double or nested "next=" patterns
unescaped_next_count = len(re.findall(r"(?i)\bnext=", query_string))
escaped_next_count = len(
re.findall(r"(?i)\bnext%25|next%3D|next%253D|next%2525", query_string)
)
if unescaped_next_count + escaped_next_count > 1:
print(
f"Blocked spam request from {request.remote_addr}: "
f"Multiple or nested 'next=' patterns detected."
)
return HTTPUnauthorized("Request appears malformed or spammy.")
# Existing hidden spam field detection
if request.params.get("email2", "") != "":
print(
f"Blocked spam request from {request.remote_addr}: Hidden field populated."
)
return HTTPUnauthorized("You smell like a spammer.")
# If no spam detected, return False
return False
def add_app(request):

View file

@ -11,25 +11,32 @@ def user_required(
flash_msg="You must log in to access that area.",
flash_level="error",
redirect_to_route_name="",
max_redirects=3, # Limit the number of redirects
):
"""This view requires that the request has a user."""
def wrapped(fn):
def inner(request):
if request.user and request.user.authenticated:
# Reset redirect count on successful authentication
request.session.pop("redirect_count", None)
return fn(request)
# Track redirection attempts
redirect_count = request.session.get("redirect_count", 0)
if redirect_count >= max_redirects:
# Redirect to a safe default page if max redirects reached
request.session.flash(("Too many redirects, please try again later.", "error"))
return HTTPFound(request.route_url("home"))
# Increment redirect count
request.session["redirect_count"] = redirect_count + 1
# Flash message
request.session.flash((flash_msg, flash_level))
# Redirect to the login route
if redirect_to_route_name:
return HTTPFound(request.route_url(redirect_to_route_name))
return HTTPFound(get_referer_or_home(request))
return inner
return wrapped
# view decorator.
def shop_is_ready_required(
flash_msg="Sorry, this shop is not ready to make sales yet. Please try again later.",