diff --git a/python/ql/src/experimental/semmle/python/Frameworks.qll b/python/ql/src/experimental/semmle/python/Frameworks.qll index 75df222a060..523561d1631 100644 --- a/python/ql/src/experimental/semmle/python/Frameworks.qll +++ b/python/ql/src/experimental/semmle/python/Frameworks.qll @@ -5,3 +5,4 @@ private import experimental.semmle.python.frameworks.Stdlib private import experimental.semmle.python.frameworks.LDAP private import experimental.semmle.python.frameworks.Flask +private import experimental.semmle.python.frameworks.Django diff --git a/python/ql/src/experimental/semmle/python/frameworks/Django.qll b/python/ql/src/experimental/semmle/python/frameworks/Django.qll new file mode 100644 index 00000000000..a8d4dfbf63e --- /dev/null +++ b/python/ql/src/experimental/semmle/python/frameworks/Django.qll @@ -0,0 +1,63 @@ +/** + * Provides classes modeling security-relevant aspects of the `django` PyPI package. + * See https://www.djangoproject.com/. + */ + +private import python +private import semmle.python.dataflow.new.DataFlow +private import experimental.semmle.python.Concepts +private import semmle.python.ApiGraphs + +private module Django { + private API::Node django() { result = API::moduleImport("django") } + + /** https://docs.djangoproject.com/en/3.2/topics/email/ */ + private API::Node djangoMail() { result = django().getMember("core").getMember("mail") } + + private class DjangoSendMail extends DataFlow::CallCfgNode, EmailSender { + DjangoSendMail() { this = djangoMail().getMember("send_mail").getACall() } + + override DataFlow::Node getPlainTextBody() { + result in [this.getArg(1), this.getArgByName("message")] + } + + override DataFlow::Node getHtmlBody() { + result in [this.getArg(8), this.getArgByName("html_message")] + } + + override DataFlow::Node getTo() { + result in [this.getArg(3), this.getArgByName("recipient_list")] + } + + override DataFlow::Node getFrom() { + result in [this.getArg(2), this.getArgByName("from_email")] + } + + override DataFlow::Node getSubject() { + result in [this.getArg(0), this.getArgByName("subject")] + } + } + + /** https://github.com/django/django/blob/ca9872905559026af82000e46cde6f7dedc897b6/django/core/mail/__init__.py#L90-L121 */ + private class DjangoMailInternal extends DataFlow::CallCfgNode, EmailSender { + DjangoMailInternal() { + this = djangoMail().getMember(["mail_admins", "mail_managers"]).getACall() + } + + override DataFlow::Node getPlainTextBody() { + result in [this.getArg(1), this.getArgByName("message")] + } + + override DataFlow::Node getHtmlBody() { + result in [this.getArg(4), this.getArgByName("html_message")] + } + + override DataFlow::Node getTo() { none() } + + override DataFlow::Node getFrom() { none() } + + override DataFlow::Node getSubject() { + result in [this.getArg(0), this.getArgByName("subject")] + } + } +} diff --git a/python/ql/test/experimental/query-tests/Security/CWE-079/django_mail.py b/python/ql/test/experimental/query-tests/Security/CWE-079/django_mail.py new file mode 100644 index 00000000000..e4be8d12872 --- /dev/null +++ b/python/ql/test/experimental/query-tests/Security/CWE-079/django_mail.py @@ -0,0 +1,24 @@ +# https://data-flair.training/blogs/django-send-email/ +# Using flask for RFS and django.core.mail as email library + +from flask import request, Flask +from django.core.mail import send_mail, mail_admins, mail_managers + +app = Flask(__name__) + +@app.route("/send") +def send(): + """ + https://github.com/django/django/blob/ca9872905559026af82000e46cde6f7dedc897b6/django/core/mail/__init__.py#L38 + + Apparently there's no html_message in send_mass_mail: https://github.com/django/django/blob/ca9872905559026af82000e46cde6f7dedc897b6/django/core/mail/__init__.py#L64 + """ + send_mail("Subject", "body", "from@example.com", ["to@example.com"], html_message=request.args("html")) + +@app.route("/internal") +def internal(): + """ + https://github.com/django/django/blob/ca9872905559026af82000e46cde6f7dedc897b6/django/core/mail/__init__.py#L90-L121 + """ + mail_admins("Subject", "body", html_message=request.args("html")) + mail_managers("Subject", "body", html_message=request.args("html"))