Opened 3 weeks ago

Closed 27 hours ago

Last modified 27 hours ago

#37162 closed Cleanup/optimization (fixed)

Docs ContactForm example demonstrates poor practices

Reported by: Mike Edmunds Owned by: Mike Edmunds
Component: Documentation Version: 6.0
Severity: Normal Keywords:
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

The ContactForm used as a running example starting in the More on fields forms topic demonstrates poor practices for implementing a contact form that sends email:

  • It uses the contact's email address (sender) as the message's from_email. At best, this won't work (the message will be rejected by the outgoing server or silently ignored as spam at the receiving end). At worst, it creates vulnerabilities.
  • It passes through subject and message without any indication they originated in a web contact form. This can allow high-fidelity phishing attacks against the organization deploying the form (particularly when combined with an arbitrary sender address).

I realize the example needs to be kept simple, but Django's docs probably shouldn't be illustrating insecure approaches.

I'd suggest changing the current:

from django.core.mail import send_mail

if form.is_valid():
    subject = form.cleaned_data["subject"]
    message = form.cleaned_data["message"]
    sender = form.cleaned_data["sender"]
    cc_myself = form.cleaned_data["cc_myself"]

    recipients = ["info@example.com"]
    if cc_myself:
        recipients.append(sender)

    send_mail(subject, message, sender, recipients)
    return HttpResponseRedirect("/thanks/")

to something like (also renaming the sender form field to email throughout the page):

    from django.core.mail import EmailMessage

    if form.is_valid():
        subject = form.cleaned_data["subject"]
        message = form.cleaned_data["message"]
        email = form.cleaned_data["email"]
        cc_myself = form.cleaned_data["cc_myself"]

        # Send the message, directing replies to the contact's email.
        EmailMessage(
            subject=f"[via contact form] {subject}",
            body=f"Contact email: {email}\n\n{message}",
            to=["info@example.com"],
            cc=[email] if cc_myself else None,
            reply_to=[email],
        ).send()

        return HttpResponseRedirect("/thanks/")

Note that the cc handling there (and in the original) is also not ideal. Maybe we could come up with some other reason to include a boolean field in the form? ("Urgent"? "No reply necessary"?)

Or if something like that seems too complicated, maybe we should consider rewriting the page to use something other than a contact form.

[Noticed this while I was working on #34753. I was considering removing the contact-form-like example from the Email header injection section and replacing it with a ref to this actual-contact-form example.]

Change History (10)

comment:1 by Tim Graham, 12 days ago

Triage Stage: UnreviewedAccepted

comment:2 by Mike Edmunds, 12 days ago

Has patch: set

https://github.com/django/django/pull/21522

I ended up replacing the cc_myself field with an urgent option.

comment:3 by Mike Edmunds, 12 days ago

A version of that same ContactForm also appears in djangoproject.com/start/ under Intro to Django > Forms (but without the email sending function).

We may want to update it in djangoproject.com:djangoproject/templates/start.html.

comment:4 by Natalia Bidart, 3 days ago

Patch needs improvement: set

Setting as patch needs improvement to ensure other related references of the ContactForm are updated to match. On the djangoproject.com start page from comment:3, my recommendation is to remove the Python code for the form altogether and just leave the actual form definition to be found once the user follows the "read more" link.

comment:5 by Mike Edmunds, 2 days ago

Patch needs improvement: unset

comment:6 by Mike Edmunds, 2 days ago

Opened separate issue https://github.com/django/djangoproject.com/issues/2684 for djangoproject.com example.

comment:7 by Natalia Bidart, 27 hours ago

Triage Stage: AcceptedReady for checkin

comment:8 by nessita <124304+nessita@…>, 27 hours ago

Resolution: fixed
Status: assignedclosed

In 54495840:

Fixed #37162 -- Updated ContactForm docs example to use safe practices.

Updated the ContactForm examples in the "Forms" topic and reference
docs to avoid broken and unsafe practices around sending email:

  • Stopped using user-provided sender address as from_email. Instead, renamed to contact_email and used it as the reply_to address.
  • Removed cc_myself option to prevent using the form to send spam. Substituted an urgent field to demonstrate BooleanField use.
  • Identified email as coming from the contact form and added other content to reduce impersonation/phishing risks.

Also updated some example output in the forms reference docs where it
had drifted from the example code over time: email fields default to
maxlength=320; the ContactForm example has been using a TextArea
widget for its message field.

comment:9 by Natalia <124304+nessita@…>, 27 hours ago

In 472f84e:

[6.1.x] Fixed #37162 -- Updated ContactForm docs example to use safe practices.

Updated the ContactForm examples in the "Forms" topic and reference
docs to avoid broken and unsafe practices around sending email:

  • Stopped using user-provided sender address as from_email. Instead, renamed to contact_email and used it as the reply_to address.
  • Removed cc_myself option to prevent using the form to send spam. Substituted an urgent field to demonstrate BooleanField use.
  • Identified email as coming from the contact form and added other content to reduce impersonation/phishing risks.

Also updated some example output in the forms reference docs where it
had drifted from the example code over time: email fields default to
maxlength=320; the ContactForm example has been using a TextArea
widget for its message field.

Backport of 54495840a6a8b09ec40c793495e6541a3c0d3d5b from main.

comment:10 by Natalia <124304+nessita@…>, 27 hours ago

In 7c4439d:

[6.0.x] Fixed #37162 -- Updated ContactForm docs example to use safe practices.

Updated the ContactForm examples in the "Forms" topic and reference
docs to avoid broken and unsafe practices around sending email:

  • Stopped using user-provided sender address as from_email. Instead, renamed to contact_email and used it as the reply_to address.
  • Removed cc_myself option to prevent using the form to send spam. Substituted an urgent field to demonstrate BooleanField use.
  • Identified email as coming from the contact form and added other content to reduce impersonation/phishing risks.

Also updated some example output in the forms reference docs where it
had drifted from the example code over time: email fields default to
maxlength=320; the ContactForm example has been using a TextArea
widget for its message field.

Backport of 54495840a6a8b09ec40c793495e6541a3c0d3d5b from main.

Note: See TracTickets for help on using tickets.
Back to Top