This commit is contained in:
2026-01-07 19:53:32 +00:00
parent 42742a72e4
commit 851a99ebac
2 changed files with 774 additions and 10 deletions

60
app.py
View File

@@ -44,7 +44,7 @@ DB_PASSWORD = load_secret('DB_DW_PASS')
app.config.update(
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_SAMESITE='Lax',
SESSION_COOKIE_SAMESITE='Strict', # ← FIX: Strict au lieu de Lax
MAX_CONTENT_LENGTH=16 * 1024 * 1024, # 16MB max
PERMANENT_SESSION_LIFETIME=datetime.timedelta(hours=1),
# Protection contre les attaques de session
@@ -83,12 +83,31 @@ limiter = Limiter(
storage_uri="memory://"
)
# --- SÉCURITÉ : HEADERS ---
@app.after_request
def set_security_headers(response):
"""Ajoute les headers de sécurité"""
response.headers['Content-Security-Policy'] = (
"default-src 'self'; "
"img-src 'self'; "
"style-src 'self' 'unsafe-inline'; "
"script-src 'self'"
)
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block'
return response
# --- FONCTIONS UTILITAIRES SÉCURISÉES ---
def get_real_ip():
"""Récupère l'IP réelle derrière le proxy Traefik"""
return request.remote_addr or '0.0.0.0'
def hash_ip(ip):
"""Hash l'IP pour anonymisation (RGPD-friendly)"""
return hashlib.sha256(f"{ip}{app.secret_key}".encode()).hexdigest()[:8]
def validate_image_file(file_stream):
"""
Validation stricte du fichier image avec python-magic
@@ -212,11 +231,12 @@ def index():
if request.method == 'POST':
client_ip = get_real_ip()
hashed_ip = hash_ip(client_ip) # ← FIX: Hash l'IP
# Vérification Anti-Spam
cursor.execute(
"SELECT created_at FROM posts WHERE ip_address = %s ORDER BY created_at DESC LIMIT 1",
(client_ip,)
(hashed_ip,)
)
last_post = cursor.fetchone()
@@ -273,7 +293,7 @@ def index():
try:
cursor.execute(
"INSERT INTO posts (content, image_filename, ip_address) VALUES (%s, %s, %s)",
(content, filename, client_ip)
(content, filename, hashed_ip) # ← FIX: Utilise hashed_ip
)
conn.commit()
success = "Message posté avec succès !"
@@ -330,6 +350,23 @@ def index():
font-weight: 500;
}
nav a:hover { text-decoration: underline; }
nav form {
display: inline;
margin: 0;
}
nav button {
background: none;
border: none;
color: #0066cc;
cursor: pointer;
font-weight: 500;
font-size: inherit;
font-family: inherit;
padding: 0;
}
nav button:hover {
text-decoration: underline;
}
.flag {
background: #fff3cd;
padding: 8px 12px;
@@ -378,7 +415,7 @@ def index():
resize: vertical;
min-height: 100px;
}
button {
button[type="submit"] {
background: #0066cc;
color: white;
border: none;
@@ -389,8 +426,8 @@ def index():
font-weight: 500;
transition: background 0.2s;
}
button:hover { background: #0052a3; }
button:active { transform: scale(0.98); }
button[type="submit"]:hover { background: #0052a3; }
button[type="submit"]:active { transform: scale(0.98); }
.post {
background: white;
padding: 20px;
@@ -424,7 +461,10 @@ def index():
<div>
<a href="/">🏠 Accueil</a>
{% if session.get('logged_in') %}
<a href="/logout">🚪 Déconnexion</a>
<form method="post" action="{{ url_for('logout') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<button type="submit">🚪 Déconnexion</button>
</form>
{% else %}
<a href="/login">🔐 Connexion Admin</a>
{% endif %}
@@ -469,11 +509,11 @@ def index():
{% for post in posts %}
<div class="post">
<div class="meta">
👤 <b>{{ post.ip_address }}</b> •
👤 <b>User-{{ post.ip_address }}</b> •
📅 {{ post.created_at.strftime('%d/%m/%Y à %H:%M') }}
</div>
{% if post.content %}
<div class="post-content">{{ post.content }}</div>
<div class="post-content">{{ post.content | e }}</div>
{% endif %}
{% if post.image_filename %}
<img
@@ -689,7 +729,7 @@ def login():
return render_template_string(html, error=error)
@app.route('/logout')
@app.route('/logout', methods=['POST']) # ← FIX: Force POST
def logout():
"""Déconnexion sécurisée"""
session.clear()