From 7a36f6bbb9a0bb6e57b21f0adcd47cbb1cb43338 Mon Sep 17 00:00:00 2001 From: Dimitri Huisman Date: Tue, 27 Sep 2022 06:46:32 +0000 Subject: [PATCH] Use hmac.compare_digest to prevent timing attacks. --- core/admin/mailu/api/common.py | 8 ++++++-- core/admin/mailu/api/v1/alias.py | 15 +++++++-------- core/admin/mailu/api/v1/user.py | 27 ++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/core/admin/mailu/api/common.py b/core/admin/mailu/api/common.py index 0805c5a3..26372bd9 100644 --- a/core/admin/mailu/api/common.py +++ b/core/admin/mailu/api/common.py @@ -2,6 +2,7 @@ from .. import models, utils from . import v1 from flask import request import flask +import hmac from functools import wraps from flask_restx import abort @@ -19,10 +20,13 @@ def api_token_authorization(func): client_ip = flask.request.headers.get('X-Real-IP', flask.request.remote_addr) if utils.limiter.should_rate_limit_ip(client_ip): abort(429, 'Too many attempts from your IP (rate-limit)' ) - if request.args.get('api_token') != v1.api_token: + if (request.args.get('api_token') == '' or + request.args.get('api_token') == None): + abort(401, 'A valid API token is expected as query string parameter') + if not hmac.compare_digest(request.args.get('api_token'), v1.api_token): utils.limiter.rate_limit_ip(client_ip) flask.current_app.logger.warn(f'Invalid API token provided by {client_ip}.') - abort(401, 'A valid API token is expected as query string parameter') + abort(403, 'A valid API token is expected as query string parameter') else: flask.current_app.logger.info(f'Valid API token provided by {client_ip}.') return func(*args, **kwds) diff --git a/core/admin/mailu/api/v1/alias.py b/core/admin/mailu/api/v1/alias.py index 65cbd25c..29c03195 100644 --- a/core/admin/mailu/api/v1/alias.py +++ b/core/admin/mailu/api/v1/alias.py @@ -7,19 +7,18 @@ db = models.db alias = api.namespace('alias', description='Alias operations') -alias_fields = api.model('Alias', { - 'email': fields.String(description='the alias email address', example='user@example.com', required=True), - 'comment': fields.String(description='a comment'), - 'destination': fields.List(fields.String(description='alias email address', example='user@example.com', required=True)), - 'wildcard': fields.Boolean(description='enable SQL Like wildcard syntax') -}) - -alias_fields_update = api.model('AliasUpdate', { +alias_fields_update = alias.model('AliasUpdate', { 'comment': fields.String(description='a comment'), 'destination': fields.List(fields.String(description='alias email address', example='user@example.com')), 'wildcard': fields.Boolean(description='enable SQL Like wildcard syntax') }) +alias_fields = alias.inherit('Alias',alias_fields_update, { + 'email': fields.String(description='the alias email address', example='user@example.com', required=True), + 'destination': fields.List(fields.String(description='alias email address', example='user@example.com', required=True)), + +}) + @alias.route('') class Aliases(Resource): diff --git a/core/admin/mailu/api/v1/user.py b/core/admin/mailu/api/v1/user.py index 5345e007..1826963d 100644 --- a/core/admin/mailu/api/v1/user.py +++ b/core/admin/mailu/api/v1/user.py @@ -9,9 +9,8 @@ db = models.db user = api.namespace('user', description='User operations') -user_fields_get = api.model('UserGet', { - 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='_email'), - 'password': fields.String(description='PBKDF2-HMAC-SHA256 based password of the user. For more info see passlib.hash.pbkdf2_sha256', example='$pbkdf2-sha256$1$.6UI/S.nXIk8jcbdHx3Fhg$98jZicV16ODfEsEZeYPGHU3kbrUrvUEXOPimVSQDD44'), +""" +base_model = { 'comment': fields.String(description='A description for the user. This description is shown on the Users page', example='my comment'), 'quota_bytes': fields.Integer(description='The maximum quota for the user’s email box in bytes', example='1000000000'), 'global_admin': fields.Boolean(description='Make the user a global administrator'), @@ -30,6 +29,28 @@ user_fields_get = api.model('UserGet', { 'spam_enabled': fields.Boolean(description='Enable the spam filter'), 'spam_mark_as_read': fields.Boolean(description='Enable marking spam mails as read'), 'spam_threshold': fields.Integer(description='The user defined spam filter tolerance', example='80'), +} + +user_fields_get = api.model('UserGet', { + 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='_email'), + 'password': fields.String(description='PBKDF2-HMAC-SHA256 based password of the user. For more info see passlib.hash.pbkdf2_sha256', example='$pbkdf2-sha256$1$.6UI/S.nXIk8jcbdHx3Fhg$98jZicV16ODfEsEZeYPGHU3kbrUrvUEXOPimVSQDD44'), + +}.update(base_model)) + +user_fields_post = api.model('UserCreate', { + 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='_email', required=True), + 'raw_password': fields.String(description='The raw (plain text) password of the user. Mailu will hash the password using PBKDF2-HMAC-SHA256', example='secret', required=True), +}.update(base_model)) + +user_fields_put = api.model('UserUpdate', { + 'raw_password': fields.String(description='The raw (plain text) password of the user. Mailu will hash the password using PBKDF2-HMAC-SHA256', example='secret'), +}.update(base_model)) +""" + +user_fields_get = api.model('UserGet', { + 'email': fields.String(description='The email address of the user', example='John.Doe@example.com', attribute='_email'), + 'password': fields.String(description='PBKDF2-HMAC-SHA256 based password of the user. For more info see passlib.hash.pbkdf2_sha256', example='$pbkdf2-sha256$1$.6UI/S.nXIk8jcbdHx3Fhg$98jZicV16ODfEsEZeYPGHU3kbrUrvUEXOPimVSQDD44'), + }) user_fields_post = api.model('UserCreate', {