Building Secure Web Apps:
Python versus the OWASP Top 10
Jacob Kaplan-Moss
[email protected]
Slide 2
Slide 2 text
The web is a scary place...
Slide 3
Slide 3 text
No content
Slide 4
Slide 4 text
No content
Slide 5
Slide 5 text
No content
Slide 6
Slide 6 text
“Risk”
Slide 7
Slide 7 text
Risk = Likelihood × Impact
Slide 8
Slide 8 text
The OWASP Top 10
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
https://www.owasp.org/index.php/Top_10_2013
Slide 9
Slide 9 text
Injection
Slide 10
Slide 10 text
http://xkcd.com/327
Slide 11
Slide 11 text
db = psycopg2.connect()
@app.route("/transactions/")
def show_transactions(user):
c = db.cursor()
query = "SELECT * FROM transactions WHERE user = '%s'" % username
c.execute(query)
return flask.render_template('txns.html',
transactions=c.fetchall())
Slide 12
Slide 12 text
No content
Slide 13
Slide 13 text
GET /activity/jacob
Slide 14
Slide 14 text
GET /activity/jacob
SELECT * FROM activity WHERE user = 'jacob'
Slide 15
Slide 15 text
GET /activity/jacob
SELECT * FROM activity WHERE user = 'jacob'
GET /activity/%27+or+1+%3D+1
Slide 16
Slide 16 text
GET /activity/jacob
SELECT * FROM activity WHERE user = 'jacob'
GET /activity/%27+or+1+%3D+1
SELECT * FROM activity WHERE user = '' or 1 = 1
Slide 17
Slide 17 text
GET /activity/jacob
SELECT * FROM activity WHERE user = 'jacob'
GET /activity/%27+or+1+%3D+1
SELECT * FROM activity WHERE user = '' or 1 = 1
GET /activity/%27%3B+DELETE+FROM+transactions%3B
Slide 18
Slide 18 text
GET /activity/jacob
SELECT * FROM activity WHERE user = 'jacob'
GET /activity/%27+or+1+%3D+1
SELECT * FROM activity WHERE user = '' or 1 = 1
GET /activity/%27%3B+DELETE+FROM+transactions%3B
SELECT * FROM activity WHERE user = ''; DELETE FROM activity;
Slide 19
Slide 19 text
Preventing SQLi
‣ Use a database abstraction layer or ORM.
‣ Flask: SQLAlchemy
‣ Django: django.db.models
‣ If you must write raw SQL by hand, always use bind parameters:
‣ Good: cursor.execute(query, params)
‣ Bad: cursor.execute(query % params)
SSL ALL THE THINGS!
‣ Flask: https://github.com/kennethreitz/flask-sslify
‣ Django: http://django-secure.rtfd.org/
Slide 24
Slide 24 text
Session security
‣ Use your framework’s session’s APIs; don’t reinvent the wheel.
‣ Flask:
‣ Django: django.contrib.sessions
‣ Don’t store data in cookies directly.
‣ Watch your SECRET_KEY!
‣ Always consider session data insecure. Even if it’s stored in the
database, it make have been set via a poisoned session.
GET /sayhi/%3Cscript%3Ealert%28%27hax0red%27%29%3C%2Fscript%3E
Slide 38
Slide 38 text
GET /sayhi/jacob
Hello, jacob!
GET /sayhi/%3Cscript%3Ealert%28%27hax0red%27%29%3C%2Fscript%3E
Hello, <script>alert("hax0red")</script>!
Slide 39
Slide 39 text
Insecure direct object references
Slide 40
Slide 40 text
Insecure direct object references
Slide 41
Slide 41 text
Insecure direct object references
Bad URLs
Slide 42
Slide 42 text
class JobApplication(DeclarativeBase)
__tablename__ = 'jobapps'
id = Column(Integer, primary_key=True)
...
@app.route("/jobs/application/")
def job_application(id):
a = JobApplication.query.filter(JobApplication.id == id)
return flask.render_template('application.html',
application=a)
Slide 43
Slide 43 text
Dear Jacob -‐
Thanks for applying to work at Initech! If you need to
make changes to your job application, you can do so at:
http://initech.com/jobs/application/6749
Good luck,
A. Drone
Director, Human Resources
Slide 44
Slide 44 text
class JobApplication(DeclarativeBase)
__tablename__ = 'jobapps'
id = Column(Integer, primary_key=True)
slug = Column(String(100))
...
@app.route("/jobs/application/")
def job_application(id):
a = JobApplication.query.filter(JobApplication.slug == slug)
return flask.render_template('application.html',
application=a)
Slide 45
Slide 45 text
Dear Jacob -‐
Thanks for applying to work at Initech! If you need to make
changes to your job application, you can do so at:
http://example.com/jobs/application/jacobkaplanmoss-‐2013
Good luck,
A. Drone
Director, Human Resources
Slide 46
Slide 46 text
Security misconfiguration
Slide 47
Slide 47 text
No silver bullet
‣ Read the docs:
‣ Flask: http://flask.pocoo.org/docs/security/
‣ Django: http://django.me/security
‣ Have a deployment checklist:
‣ Django: http://django-secure.rtfd.org/
‣ Don’t forget to turn debug mode off!
‣ Flask: app.debug = False
‣ Django: settings.py → DEBUG = False
Slide 48
Slide 48 text
Sensitive data exposure
Slide 49
Slide 49 text
This is a reminder, sent out once a month, about your
list.example.com mailing list memberships. It includes your
subscription info and how to use it to change it or unsubscribe
from a list.
...
Passwords for [email protected]:
List Password
-‐-‐-‐-‐ -‐-‐-‐-‐-‐-‐-‐-‐
[email protected] hunter2
[email protected] 2hunter
Slide 50
Slide 50 text
Password storage
‣ Use bcrypt.
‣ Flask: http://pythonhosted.org/passlib/
‣ Django: http://django.me/bcrypt
‣ (PBKDF2 is an acceptable substitute, but use bcrypt anyway.)
‣ Properly hashed passwords are hard — but not impossible —
to break. So keep them safe, and consider implementing
password security rules.
from flask.ext.login import login_required, current_user
@app.route("/jobs/application/")
@login_required
def job_application(id):
a = JobApplication.query.filter(JobApplication.id == id,
JobApplication.applicant == current_user)
return flask.render_template('application.html',
application=a)
Slide 54
Slide 54 text
‣ Treat access control holistically, not piecemeal.
‣ Flask: https://flask-login.rtfd.org/
‣ Django: django.contrib.auth
‣ Don’t rely on security through obscurity.
Handling access control
‣ Don’t allow GET requests to have side effects.
‣ Protect POST requests with a CSRF token
‣ Flask: http://sjl.bitbucket.org/flask-csrf/
‣ Flask: http://pythonhosted.org/Flask-WTF/
‣ Django: (built in)
‣ Protect your SECRET_KEY!
Preventing CSRF
‣ [this is hard]
‣ Update dependencies often.
‣ A good test suite helps!
‣ Follow relevant mailing lists
‣ Flask: http://flask.pocoo.org/mailinglist/
‣ Django: https://groups.google.com/group/django-announce
‣ https://bundlescout.com/ (non-free)
Staying up-to-date
Slide 65
Slide 65 text
Unvalidated redirects
and forwards
Slide 66
Slide 66 text
No content
Slide 67
Slide 67 text
GET /forgot-‐my-‐password/
Host: good.com
Slide 68
Slide 68 text
Dear Jacob,
You can reset your password by following this link:
http://good.com/password/reset/a02be53ccf9245
Your friends at good.com.
Slide 69
Slide 69 text
GET /forgot-‐my-‐password/
Host: evil.com
Slide 70
Slide 70 text
Dear Jacob,
You can reset your password by following this link:
http://evil.com/password/reset/a02be53ccf9245
Your friends at good.com.
Slide 71
Slide 71 text
‣ Whitelisting seems to be the only solid approach.
‣ Django: ALLOWED_HOSTS
‣ Be very cautious when issuing redirects!
‣ Flask: flask.redirect is unsafe.
‣ Django: django.shortcuts.redirect is unsafe.
Validating redirects/forwards
Slide 72
Slide 72 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 73
Slide 73 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 74
Slide 74 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 75
Slide 75 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 76
Slide 76 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 77
Slide 77 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 78
Slide 78 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 79
Slide 79 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 80
Slide 80 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 81
Slide 81 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django
Slide 82
Slide 82 text
1. Injection
2. Broken authentication and
session management
3. XSS
4. Insecure direct
object references
5. Security misconfiguration
6. Sensitive data exposure
7. Missing function-level
access control
8. CSRF
9. Components with
known vulnerabilities
10. Unvalidated redirects
The Top 10 vs Flask/Django