Skip to content

Commit 4d890dd

Browse files
committed
Polish flask_mail tests and code
1 parent 48cd506 commit 4d890dd

3 files changed

Lines changed: 116 additions & 0 deletions

File tree

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# https://pythonhosted.org/Flask-Mail/
2+
# https://github.com/mattupstate/flask-mail/blob/1709c70d839a7cc7b1f7eeb97333b71cd420fe32/flask_mail.py#L239
3+
4+
# tmp: this test cover RFS to any part of the message, but can be shortened to a specific part (body&html) once we decide the objective of the query.
5+
from flask_mail import Mail, Message
6+
7+
app = Flask(__name__)
8+
mail = Mail(app)
9+
10+
@app.route("/send")
11+
def send():
12+
msg = Message(subject=request.args["subject"],
13+
sender=request.args["sender"],
14+
recipients=list(request.args["recipient"]),
15+
body=request.args["body"],
16+
html=request.args["html"])
17+
18+
# The message can contain a body and/or HTML:
19+
msg.body = "test"
20+
msg.html = "<b>test</b>"
21+
22+
mail.send(msg)
23+
24+
@app.route("/connect")
25+
def connect():
26+
"""
27+
Minimal example to test mail.connect() usage
28+
"""
29+
with mail.connect() as conn:
30+
msg = Message(subject=request.args["subject"],
31+
sender=request.args["sender"],
32+
recipients=list(request.args["recipient"]),
33+
body=request.args["html"])
34+
conn.send(msg)

python/ql/src/experimental/semmle/python/Frameworks.qll

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44

55
private import experimental.semmle.python.frameworks.Stdlib
66
private import experimental.semmle.python.frameworks.LDAP
7+
private import experimental.semmle.python.frameworks.Flask
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/**
2+
* Provides classes modeling security-relevant aspects of the `flask` PyPI package.
3+
* See https://flask.palletsprojects.com/en/1.1.x/.
4+
*/
5+
6+
private import python
7+
private import semmle.python.dataflow.new.DataFlow
8+
private import experimental.semmle.python.Concepts
9+
private import semmle.python.ApiGraphs
10+
11+
private module Flask {
12+
private API::Node flaskMail() { result = API::moduleImport("flask_mail") }
13+
14+
private API::Node flaskMailInstance() { result = flaskMail().getMember("Mail").getReturn() }
15+
16+
private DataFlow::CallCfgNode flaskMessageCall() {
17+
result = flaskMail().getMember("Message").getACall()
18+
}
19+
20+
private class FlaskMail extends DataFlow::CallCfgNode, EmailSender {
21+
/** A message variable to avoid multiple results in case consequential results are needed */
22+
DataFlow::CallCfgNode message;
23+
24+
FlaskMail() {
25+
this =
26+
[flaskMailInstance(), flaskMailInstance().getMember("connect").getReturn()]
27+
.getMember("send")
28+
.getACall()
29+
}
30+
31+
override DataFlow::Node getPlainTextBody() {
32+
result in [flaskMessageCall().getArg(2), flaskMessageCall().getArgByName("body")]
33+
or
34+
exists(DataFlow::AttrWrite bodyWrite |
35+
bodyWrite.getObject().getALocalSource() = flaskMessageCall() and
36+
bodyWrite.getAttributeName() = "body" and
37+
result = bodyWrite.getValue()
38+
)
39+
}
40+
41+
override DataFlow::Node getHtmlBody() {
42+
result in [flaskMessageCall().getArg(3), flaskMessageCall().getArgByName("html")]
43+
or
44+
exists(DataFlow::AttrWrite bodyWrite |
45+
bodyWrite.getObject().getALocalSource() = flaskMessageCall() and
46+
bodyWrite.getAttributeName() = "html" and
47+
result = bodyWrite.getValue()
48+
)
49+
}
50+
51+
override DataFlow::Node getTo() {
52+
result in [flaskMessageCall().getArg(1), flaskMessageCall().getArgByName("recipients")]
53+
or
54+
exists(DataFlow::AttrWrite bodyWrite |
55+
bodyWrite.getObject().getALocalSource() = flaskMessageCall() and
56+
bodyWrite.getAttributeName() = "recipients" and
57+
result = bodyWrite.getValue()
58+
)
59+
}
60+
61+
override DataFlow::Node getFrom() {
62+
result in [flaskMessageCall().getArg(5), flaskMessageCall().getArgByName("sender")]
63+
or
64+
exists(DataFlow::AttrWrite bodyWrite |
65+
bodyWrite.getObject().getALocalSource() = flaskMessageCall() and
66+
bodyWrite.getAttributeName() = "sender" and
67+
result = bodyWrite.getValue()
68+
)
69+
}
70+
71+
override DataFlow::Node getSubject() {
72+
result in [flaskMessageCall().getArg(0), flaskMessageCall().getArgByName("subject")]
73+
or
74+
exists(DataFlow::AttrWrite bodyWrite |
75+
bodyWrite.getObject().getALocalSource() = flaskMessageCall() and
76+
bodyWrite.getAttributeName() = "subject" and
77+
result = bodyWrite.getValue()
78+
)
79+
}
80+
}
81+
}

0 commit comments

Comments
 (0)