Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions app/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,17 @@ readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"tamga>=1.4.0",
"flask>=3.1.2",
"flask==3.1.3",
"flask-sqlalchemy>=3.1.1",
"flask-wtf>=1.2.2",
"passlib>=1.7.4",
"wtforms>=3.2.1",
"markdown2>=2.5.4",
"markdown2==2.5.5",
"bleach>=6.3.0",
"python-dotenv>=1.2.1",
"python-dotenv==1.2.2",
"ruff==0.15.4",
"filelock==3.25.0",
"requests==2.33.0",
]

[project.optional-dependencies]
Expand Down
6 changes: 6 additions & 0 deletions app/routes/admin_panel_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ def admin_panel_users():
if not user:
return redirect("/")

if user.role != "admin":
Log.error(
f"{request.remote_addr} tried to reach user admin panel without being admin"
)
return redirect("/")

if request.method == "POST":
if "user_delete_button" in request.form:
Log.info(
Expand Down
7 changes: 7 additions & 0 deletions app/routes/login.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
)
from passlib.hash import sha512_crypt as encryption
from sqlalchemy import func
from urllib.parse import urlparse

from models import User
from settings import Settings
Expand All @@ -33,6 +34,12 @@ def login(direct):
401: If the login is unsuccessful.
"""
direct = direct.replace("&", "/")
if direct:
parsed = urlparse(direct)
if parsed.netloc or parsed.scheme or not direct.startswith("/"):
direct = "/"
else:
direct = "/"
Comment on lines +37 to +42

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
from urllib.parse import urlparse
for t in ["/\\evil.com", "//evil.com", "/\\/\\evil.com", "javascript:alert(1)"]:
    p = urlparse(t)
    print(repr(t), "netloc=", repr(p.netloc), "scheme=", repr(p.scheme), "startswith/=", t.startswith("/"))
PY

Repository: DogukanUrker/FlaskBlog

Length of output: 405


🏁 Script executed:

#!/bin/bash
set -euo pipefail

python3 - <<'PY'
from pathlib import Path

path = Path("app/routes/login.py")
print("FILE_EXISTS", path.exists())
print("LINE_COUNT", sum(1 for _ in path.open()))

with path.open() as f:
    lines = f.readlines()

for start, end in [(1, 120)]:
    print(f"\n--- {path} lines {start}-{end} ---")
    for i in range(start-1, min(end, len(lines))):
        print(f"{i+1:4d}: {lines[i].rstrip()}")
PY

Repository: DogukanUrker/FlaskBlog

Length of output: 4187


Reject backslash-based redirect targets in app/routes/login.py:37-42. urlparse() leaves /\evil.com with empty netloc/scheme, so it passes this guard even though browsers can interpret the backslash as a slash and treat the redirect as off-site. Block \ (and //) before calling redirect().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/routes/login.py` around lines 37 - 42, The login redirect validation in
the direct-target handling allows backslash-based and scheme-relative targets to
slip through because urlparse() does not flag them as off-site. Update the
redirect guard in the login route’s direct redirect logic to reject any target
containing backslashes or starting with double slashes before reaching
redirect(), alongside the existing scheme/netloc checks, so only safe relative
paths are accepted.

if Settings.LOG_IN:
if "username" in session:
Log.error(f'User: "{session["username"]}" already logged in')
Expand Down
11 changes: 8 additions & 3 deletions app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,14 +134,19 @@ class Settings:
# SMTP Mail Configuration
SMTP_SERVER = os.environ.get("SMTP_SERVER", "smtp.gmail.com")
SMTP_PORT = int(os.environ.get("SMTP_PORT", 587))
SMTP_MAIL = os.environ.get("SMTP_MAIL", "flaskblogdogukanurker@gmail.com")
SMTP_PASSWORD = os.environ.get("SMTP_PASSWORD", "icovdnrxcgfdswal")
SMTP_MAIL = os.environ.get("SMTP_MAIL", "")
SMTP_PASSWORD = os.environ.get("SMTP_PASSWORD", "")

# Default Admin Account Configuration
DEFAULT_ADMIN = _bool(os.environ.get("DEFAULT_ADMIN", "True"))
DEFAULT_ADMIN_USERNAME = os.environ.get("DEFAULT_ADMIN_USERNAME", "admin")
DEFAULT_ADMIN_EMAIL = os.environ.get("DEFAULT_ADMIN_EMAIL", "admin@flaskblog.com")
DEFAULT_ADMIN_PASSWORD = os.environ.get("DEFAULT_ADMIN_PASSWORD", "admin")
DEFAULT_ADMIN_PASSWORD = os.environ.get("DEFAULT_ADMIN_PASSWORD")
if DEFAULT_ADMIN and not DEFAULT_ADMIN_PASSWORD:
raise RuntimeError(
"DEFAULT_ADMIN_PASSWORD must be set when DEFAULT_ADMIN=True. "
"Set a strong password via the environment variable."
)
DEFAULT_ADMIN_POINT = int(os.environ.get("DEFAULT_ADMIN_POINT", 0))
DEFAULT_ADMIN_PROFILE_PICTURE = os.environ.get(
"DEFAULT_ADMIN_PROFILE_PICTURE",
Expand Down
Loading
Loading