Email Injection in Chi with Basic Auth
Email Injection in Chi with Basic Auth — how this specific combination creates or exposes the vulnerability
Email injection in the Chi web framework occurs when user-controlled data is placed directly into email headers without validation or sanitization. When Basic Authentication is used for route or handler protection, developers may assume the authentication layer limits who can trigger server-side logic. However, authentication and input validation are separate concerns. Basic Auth in Chi typically involves checking credentials in a request filter or middleware before routing to a handler. If a handler builds email messages using concatenation or interpolation with values such as user-supplied names, reply-to addresses, or subject lines, an attacker can inject additional headers or control the message body even when credentials are required.
For example, consider a Chi endpoint that sends a confirmation email and uses Basic Auth to ensure only authenticated users can request a confirmation. An authenticated user might supply their name via a query parameter or JSON body. If the server builds an email header like To or Subject by directly embedding this input, newline characters (\n or \r\n) can terminate the intended header and append arbitrary headers such as Cc:, Bcc:, or even a second To: header. This can redirect copies of the email, spoof sender identity, or trigger unintended delivery to third parties. In a Chi application using a server-side SMTP client, the resulting email may be constructed with the injected content, enabling abuse such as spam relay or information disclosure, even though the request presented valid Basic Auth credentials.
The interaction between Basic Auth and email composition becomes especially risky when handlers reuse authentication logic across multiple routes, some of which send emails. A developer might implement a per-route authentication check with req.authenticatedUser but forget to treat header input as untrusted. Because Basic Auth protects access to the route rather than the integrity of data produced by the route, the attack surface remains email-focused. Chi does not sanitize headers automatically; it is the developer’s responsibility to validate and encode any user data that becomes part of email headers. Without explicit header validation, authenticated requests can be weaponized to exploit email injection, undermining the perceived safety conferred by Basic Auth.
Basic Auth-Specific Remediation in Chi — concrete code fixes
Remediation focuses on two areas: ensuring authentication does not create a false sense of security, and strictly controlling how user input is used in email headers. In Chi, use structured header validation and avoid building email headers via string concatenation with user data. For email addresses, validate format rigorously and sanitize newlines. For subjects and other text, apply allow-listing or strict encoding. Below are concrete Chi handler examples demonstrating insecure patterns and their fixes.
Insecure Example
open Giraffe
open Microsoft.AspNetCore.Http
open System.Net.Mail
let sendEmailHandler (next: HttpFunc) (ctx: HttpContext) =
task {
let name = ctx.Request.Query.["name"].ToString() // user-controlled
let email = ctx.Request.Query.["email"].ToString()
let subject = "Confirmation for " + name // vulnerable to injection
use client = new SmtpClient("smtp.example.com")
use message = new MailMessage("noreply@example.com", email, subject, "Welcome")
do! client.SendMailAsync(message) |> Async.AwaitTask
return! setStatusCode 200 >=> text "Email sent" ctx
}
In this snippet, name is taken directly from query parameters and embedded into the subject line. Newline characters in name can inject additional headers. Basic Auth might be applied elsewhere to ensure only logged-in users reach this handler, but it does not prevent injection within the handler itself.
Secure Example with Validation and Encoding
open Giraffe
open Microsoft.AspNetCore.Http
open System.Net.Mail
open System.Text.RegularExpressions
let sanitizeEmail (input: string) : string option =
let trimmed = input.Trim()
if Regex.IsMatch(trimmed, ^"^[^@\s]+@[^@\s]+\.[^@\s]+$") then Some trimmed else None
let sanitizeName (input: string) : string option =
let trimmed = input.Trim()
if Regex.IsMatch(trimmed, ^"^[a-zA-Z0-9 _-]{1,50}$") then Some trimmed else None
let sendEmailHandler (next: HttpFunc) (ctx: HttpContext) =
task {
let! nameResult = ctx.GetQueryStringValue("name")
|> Option.ofObj
|> Option.bind sanitizeName
let! emailResult = ctx.GetQueryStringValue("email")
|> Option.ofObj
|> Option.bind sanitizeEmail
match nameResult, emailResult with
| Some name, Some email ->
let subject = "Confirmation for " + name // name is now safe
use client = new SmtpClient("smtp.example.com")
use message = new MailMessage("noreply@example.com", email, subject, "Welcome")
do! client.SendMailAsync(message) |> Async.AwaitTask
return! setStatusCode 200 >=> text "Email sent" ctx
| _ ->
return! setStatusCode 400 >=> text "Invalid input" ctx
}
In the secure version, sanitizeName and sanitizeEmail enforce strict patterns that disallow newlines and other control characters. By validating before constructing the MailMessage, the handler ensures that even if Basic Auth allows the request to proceed, the email composition cannot be subverted. This demonstrates that authentication and input validation must be addressed independently to prevent email injection in Chi applications.