Email Injection in Fastapi with Jwt Tokens
Email Injection in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Email Injection in a FastAPI API that uses JWT tokens for authentication can occur when user-controlled data is reflected into email headers or message bodies without proper validation, and the API relies on JWT tokens to identify the requesting user. Even though JWT tokens themselves are not directly responsible for the injection, misuse or unsafe handling of identity data (e.g., the sub, email, or username claims) can contribute to unsafe behavior that enables injection. For example, if an endpoint accepts an email address from the client and embeds it into SMTP communication or logs, and the endpoint also enforces authorization via JWT tokens, the token may indicate a privileged identity while the email input remains untrusted and unvalidated.
Consider an endpoint that updates a user profile and sends a confirmation email. The request includes a valid JWT access token in the Authorization header, and the server decodes the token to determine the user identity. If the server then takes an email field from the request body and places it directly into email headers or into a message template without sanitization, an attacker can inject additional headers such as Cc:, Bcc:, or newlines to alter the routing or content of the email. This is a classic email injection vulnerability, and it is distinct from the authentication mechanism (JWT). The combination can be misleading: a valid JWT may make the request appear authorized, but the unchecked email input still leads to unintended message delivery or information disclosure.
In the context of middleBrick’s checks, this behavior would appear in findings such as Input Validation and Data Exposure, with the JWT context helping to clarify that the identity assertion is trusted while the email field is not. Attack patterns like SMTP header injection and log injection are relevant here. For instance, newline characters (\n, \r) in the email value can break header boundaries and allow injection of additional headers. This does not imply that JWT tokens are insecure; it highlights that secure design requires validating and sanitizing all user-supplied data, including fields tied to identity claims, especially when those values influence external systems such as email delivery.
An example of a vulnerable FastAPI route:
from fastapi import FastAPI, Header, Depends, HTTPException
from pydantic import BaseModel
import smtplib
from email.message import EmailMessage
app = FastAPI()
class ProfileUpdate(BaseModel):
email: str
display_name: str
def get_current_user(token: str = Header(...)):
# Simplified: in practice, decode and verify JWT
return {"sub": "user-123", "email": "[email protected]"}
@app.post("/profile")
async def update_profile(profile: ProfileUpdate, user: dict = Depends(get_current_user)):
msg = EmailMessage()
msg["Subject"] = "Profile update confirmation"
msg["From"] = "[email protected]"
msg["To"] = profile.email # <-- unsanitized user input
msg["Cc"] = user["email"] # <-- identity-derived but still untrusted input if provided by client
msg.set_content(f"Hello {profile.display_name}, your profile has been updated.")
# In a real app, this would call an SMTP server
# smtplib.SMTP("localhost").send_message(msg)
return {"status": "queued"}
If an attacker sends an email value containing newline characters or extra headers, they can manipulate the message routing. Even though the route uses JWT-derived user information, the lack of validation on the email field creates the injection vector. This illustrates why endpoint security must treat identity tokens and input data independently: JWT tokens establish identity, but they do not cleanse or validate other inputs.
Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on strict input validation and safe handling of any data that reaches email generation, regardless of the authentication mechanism. With FastAPI, you can enforce validation at the model level, normalize email values, and avoid direct concatenation of user input into headers. Below are concrete, working code examples that demonstrate secure handling of email fields in endpoints that also use JWT tokens.
1) Validate and sanitize the email field using Pydantic validators and normalized email representation:
from fastapi import FastAPI, Header, Depends, HTTPException
from pydantic import BaseModel, validator, EmailStr
import re
app = FastAPI()
# Simple regex to detect obvious header injection patterns
INJECTION_PATTERN = re.compile(r'[\r\n]')
class ProfileUpdate(BaseModel):
email: EmailStr # Pydantic EmailStr enforces basic email format
display_name: str
@validator("email")
def reject_injection_chars(cls, v):
if INJECTION_PATTERN.search(v):
raise ValueError("email contains invalid characters")
return v.strip()
def get_current_user(token: str = Header(...)):
# JWT decoding and verification would happen here
return {"sub": "user-123", "email": "[email protected]"}
@app.post("/profile")
async def update_profile(profile: ProfileUpdate, user: dict = Depends(get_current_user)):
# Safe usage: validated email is used as a recipient address
to_email = profile.email
cc_email = user["email"] # trusted identity-derived value
msg = f"To: {to_email}\r\nCc: {cc_email}\r\nSubject: Profile update confirmation\r\n\r\nHello {profile.display_name}, your profile has been updated."
# smtplib.SMTP("localhost").sendmail(msg["From"], [to_email, cc_email], msg)
return {"status": "queued", "to": to_email, "cc": cc_email}
2) Explicitly limit and encode headers to prevent injection, and avoid placing untrusted data directly into header keys:
from fastapi import FastAPI, Header, Depends
from pydantic import BaseModel, EmailStr, validator
import email.utils
app = FastAPI()
class NotificationRequest(BaseModel):
recipient: EmailStr
subject: str
body: str
@validator("recipient")
def safe_email(cls, v):
# Normalize and ensure no extra whitespace or line breaks
parsed = email.utils.parseaddr(v)
return parsed[1]
@app.post("/notify")
async def notify(payload: NotificationRequest, user: dict = Depends(lambda: {"email": "[email protected]"})):
# Build headers safely; do not allow user input to dictate header names
headers = {
"To": payload.recipient,
"Cc": user["email"],
"Subject": f"Notification: {payload.subject[:50]}",
}
# In a real integration, you would pass `headers` and `payload.body` to a trusted mailer
return {"headers": headers, "body_preview": payload.body[:100]}
3) For integrations that build MIME messages, use a MIME library and set addresses via structured APIs rather than string concatenation:
from fastapi import FastAPI, Depends
from email.message import EmailMessage
from pydantic import BaseModel, EmailStr
app = FastAPI()
class EmailRequest(BaseModel):
to: EmailStr
subject: str
@app.post("/sendmail")
async def sendmail(req: EmailRequest, user: dict = Depends(lambda: {"email": "[email protected]"})):
msg = EmailMessage()
msg["Subject"] = req.subject
msg["From"] = "[email protected]"
# EmailMessage handles proper encoding and header separation
msg["To"] = req.to
msg["Cc"] = user["email"]
msg.set_content("This is a safe notification.")
# smtplib.SMTP("localhost").send_message(msg)
return {"status": "sent", "to": req.to, "cc": user["email"]}
These examples emphasize that JWT tokens provide identity context but do not replace input validation. Always validate and normalize email addresses, avoid inserting untrusted strings into header structures, and prefer structured APIs (e.g., EmailMessage) over manual header assembly. By combining JWT-based authentication with strict input controls, you reduce the likelihood of email injection while preserving the utility of identity claims.