Browse Source

[mod] limiter: add random token to the limiter URL

By adding a random component in the limiter URL a bot can no longer send a ping
by request a static URL.

Related: https://github.com/searxng/searxng/pull/2357#issuecomment-1518525094
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
Markus Heiser 2 years ago
parent
commit
5226044c13
3 changed files with 30 additions and 5 deletions
  1. 24 1
      searx/plugins/limiter.py
  2. 1 1
      searx/templates/simple/base.html
  3. 5 3
      searx/webapp.py

+ 24 - 1
searx/plugins/limiter.py

@@ -14,6 +14,8 @@ Enable the plugin in ``settings.yml``:
 """
 
 import re
+import string
+import random
 from flask import request
 
 from searx import redisdb
@@ -54,6 +56,27 @@ def ping():
     redis_client.set(secret_hash(ping_key), 1, ex=600)
 
 
+def get_token():
+    redis_client = redisdb.client()
+    if not redis_client:
+        # This function is also called when limiter is inactive / no redis DB
+        # (see render function in webapp.py)
+        return '12345678'
+    token = redis_client.get(TOKEN_KEY)
+    if token:
+        token = token.decode('UTF-8')
+    else:
+        token = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(8))
+        redis_client.set(TOKEN_KEY, token, ex=600)
+    return token
+
+
+def token_is_valid(token):
+    valid = token == get_token()
+    logger.debug("token is valid --> %s", valid)
+    return valid
+
+
 def is_accepted_request() -> bool:
     # pylint: disable=too-many-return-statements
     redis_client = redisdb.client()
@@ -83,7 +106,7 @@ def is_accepted_request() -> bool:
         c_burst = incr_sliding_window(redis_client, 'IP limit, burst' + x_forwarded_for, 20)
         c_10min = incr_sliding_window(redis_client, 'IP limit, 10 minutes' + x_forwarded_for, 600)
         if c_burst > c_burst_max or c_10min > c_10min_max:
-            logger.debug("BLOCK %s: to many request", x_forwarded_for)
+            logger.debug("BLOCK %s: too many request", x_forwarded_for)
             return False
 
         if len(request.headers.get('Accept-Language', '').strip()) == '':

+ 1 - 1
searx/templates/simple/base.html

@@ -18,7 +18,7 @@
   <link rel="stylesheet" href="{{ url_for('static', filename='css/searxng.min.css') }}" type="text/css" media="screen" />
   {% endif %}
   {% if get_setting('server.limiter') %}
-  <link rel="stylesheet" href="/limiter.css" type="text/css" media="screen" />
+  <link rel="stylesheet" href="{{ url_for('limiter_css', token=limiter_token) }}" type="text/css" media="screen" />
   {% endif %}
   {% block styles %}{% endblock %}
   <!--[if gte IE 9]>-->

+ 5 - 3
searx/webapp.py

@@ -416,6 +416,7 @@ def render(template_name: str, **kwargs):
     kwargs['endpoint'] = 'results' if 'q' in kwargs else request.endpoint
     kwargs['cookies'] = request.cookies
     kwargs['errors'] = request.errors
+    kwargs['limiter_token'] = limiter.get_token()
 
     # values from the preferences
     kwargs['preferences'] = request.preferences
@@ -642,9 +643,10 @@ def health():
     return Response('OK', mimetype='text/plain')
 
 
-@app.route('/limiter.css', methods=['GET', 'POST'])
-def limiter_css():
-    limiter.ping()
+@app.route('/limiter<token>.css', methods=['GET', 'POST'])
+def limiter_css(token=None):
+    if limiter.token_is_valid(token):
+        limiter.ping()
     return Response('', mimetype='text/css')