Building Web Apps
w/ Python & Flask
Lecture 4 - Databases & Injection Attacks
Recap of Flask Forms
-
Create form class that extends flask_wtf.FlaskForm
-
-
Create form in Jinja2 template
-
-
-
Make sure to include action and method attributes (specifies where to send the data to)
- method should (almost) always be “post” because we want to send data with a POST
request
- Empty string (“”) for action will send the data to the current route
- Make sure the route you send to accepts POST requests
Make sure to include CSRF token
- Requires setting SECRET_KEY in app configs
Have a different flow for processing GET and POST requests.
-
-
Contains fields and validators (specifies behavior of form in Python)
POST requests should validate and process incoming form data.
Redirect to a route at the end of the POST request
-
Use the redirect() function in Flask (and you may also want to use url_for() provided by Flask)
redirect(request.page) redirects to the same route the form is on
Goals
●
●
Databases overview
Injection Attacks
○
○
○
●
SQL
XSS
Protection
MongoDB
○
Flask-PyMongo
Databases
●
●
●
Store stuff
Structured set of data held in computer storage
Usually managed by a
database management system (DBMS)
○
●
Software that manages storage & retrieval of data
2 major kinds
○
○
SQL
NoSQL
Stack Overflow
2023
SQL Databases
●
●
●
●
Structured Query Language
Relational database
○ Store stuff in tables
○ Data points are related to each other
○ Easy to categorize & understand
Most popular database management language
MySQL, SQLite, PostgreSQL, etc.
NoSQL Databases
●
Store stuff in database models other than
relational model
○
●
●
●
Document, key-value, graph, wide-column
Simplistic
Likely less organized
e.g. MongoDB (we’ll be using this!)
Injection Attacks
●
●
●
Code is unexpectedly input and executed
One of most common cyber attacks
Attacker can inject whatever they want
○
○
○
○
●
Malicious code
Commands
Scripts
Input
Can protect against by sanitizing input
XSS
●
●
●
Cross Site Scripting
Attacker gets victim to run a script
3 types
○
○
○
Stored
Reflected
Document Object Model (DOM)
Stored XSS
●
●
●
Attacker stores script on site’s server
Stored in comments, images, etc.
Example:
○
○
○
Scrolling through bad unsecure version of Facebook
Load post with caption & picture
Looks fine, but runs a script on your device when loaded
Reflected XSS
●
●
●
Attacker uses phishing to direct victim to malicious server
Victim clicks on a link that has the script in the link itself
How it works
○
○
○
○
○
●
Victim clicks on link
Victim is directed to server
Server echoes script code back to victim
Script is run from that echo
Big bad beep bop boop
NOT CSRF
○
○
Not cookie-centered
Focuses on running a script rather than performing a legitimate action
SQL Injection
●
Attacker injects SQL commands
in a text field
○
●
e.g. login fields
Can add, edit, & delete, retrieve data
○
Basically whatever you can do with SQL
https://xkcd.com/327/
SQL Injection - Example
●
SELECT * FROM users
WHERE username = '$username' AND password = '$password'
○
●
●
What does this do? Selects all users matching the given username and password
■ ** Passwords should always be stored as salted, hashed strings (more next week)
How can we attack this? If the server executing this statement is not
sanitizing input, we can enter ' or 1=1 -- into the username field
This alters the above statement into:
SELECT * FROM users
WHERE username = '' or 1=1-- ' AND password = ''
○
○
So we authenticate
This is a classic example, and variations of this attack are still commonly used for testing.
Injection Attacks - Protection
●
SQL Injection
○
○
○
●
Sanitizing input
Passing query parameters
Literals & identifiers
XSS
○
○
○
Flask! (Flask-Talisman)
HTTP security headers
We’ll cover this another time
Consequences of these attacks...
●
https://haveibeenpwned.com/
○ Enter your email to see if your data has been breached…
●
https://www.wired.com/story/null-license-plate-landed-one-hacker-ticket-hell/
MongoDB
●
●
●
Document-oriented DBMS (database management system)
NoSQL, so we use JSON documents conforming to defined schema
Started in 2007, and went open-source in 2009
○
●
Actually stores its data as BSON (basically binary JSON)
○
●
https://github.com/mongodb/mongo
BSON was developed by MongoDB
Since we’ll be using it from Python, we can think of the documents as a
searchable collection of Python dictionaries (index by keys)
MongoDB Atlas (Preferred for projects)
-
Provides an instance of MongoDB in the cloud
Create a free project/cluster
-
Settings like cloud provider and datacenter location can be whatever you want
Security Quickstart (under the Security section on the left side)
1. Authenticate with username and password (can set username and password
to whatever you want)
2. Select “Connect from my local environment” and click on “Add my Current IP
Address”
a.
If you change IP addresses (may happen without you knowing on school WiFi), you’ll have to
do this step again otherwise you’ll fail to connect to the MongoDB database.
Get MongoDB Atlas Connection URI
1.
2.
3.
Click “Connect” on the cluster
Select Drivers => Python (3.12)
Copy the connection URI at the
bottom
Example:
mongodb+srv://someuser:<password>@
cluster0.lograwo.mongodb.net/?retryWrit
es=true&w=majority
Get MongoDB Atlas Connection URI
mongodb+srv://someuser:<password>@cluster0.lograwo.mongodb.net/?retryWrit
es=true&w=majority
Before you can use this in your project:
1. Replace <password> with the user password you set in Atlas
2. Add a database name after the “mongodb.net/”
a.
b.
Clusters can have multiple databases. If the database with that name doesn’t exist when
querying, MongoDB creates it for you automatically.
Example:
mongodb+srv://someuser:<password>@cluster0.lograwo.mongodb.net/my_database?retryW
rites=true&w=majority
MongoDB (local installation)
Use Atlas if you can because local installation may not work well on all platforms.
● To use Mongo, we need to download and install it
○
○
●
●
Then, we want to use Flask-PyMongo pip3 install Flask-PyMongo
○
●
Glue between Flask and PyMongo
After installing, use the relevant command for your OS
○
○
●
https://docs.mongodb.com/manual/installation/#mongodb-community-edition-installation-tutorials
■
Show in class
Follow the instructions for your relevant OS
Windows, as a Windows service: "C:\Program Files\MongoDB\Server\4.2\bin\mongo.exe"
macOS: mongo
If you’re having errors connecting, try restarting the service
○
○
Services console in Windows
brew services restart mongodb-community for macOS
MongoDB
●
Connecting MongoDB to your app
○
○
○
Start MongoDB, then connect to the localhost:port:
mongodb://localhost:27017/<my-database>
In this case, I’ll use "mongodb://localhost:27017/my_database"
What this basically looks like:
from flask import Flask
from flask_pymongo import PyMongo
app = Flask(__name__)
app.config["MONGO_URI"] = "mongodb://localhost:27017/my_database"
mongo = PyMongo(app)
MongoDB
●
●
Now we can use the mongo object’s db attribute to add to our database
Using the example WelcomeForm from last class, we can
○ Accept a user’s submitted, validated form
○ Create a JSON document with the data
○ Add the document to any collection in our database.
■
■
■
○
Collections can be created using db.posts or db[‘posts’]
● The collection is named “posts”
Just like creating a dictionary,
you can just create the key and then operate on the collection.
How to add? mongo.db.users.insert_one(user)
Collections can be visualized as a list of dictionaries.
JSON -- an aside
●
●
JavaScript Object Notation - JSON
A collection of dictionaries and lists
Back to MongoDB
●
●
Using the data from the WelcomeForm, we can create a dictionary user
Then we insert into the users collection
...
if form.validate():
user = {
'name': form.name.data,
'location': form.location.data,
'age': form.age.data,
}
mongo.db.users.insert_one(user)
...
Back to MongoDB
●
●
Great! Now let’s display a message to the user to welcome them to our
website. To do this, we can use the find_one() method of a collection
Flask-PyMongo will return None if a search query fails, knowing this:
message = None
user = mongo.db.users.find_one()
if user is not None:
message = f'Welcome {user["name"]}, age {user["age"]}, of
{user["location"]}'
●
This creates a valid message string, and now we can pass this to the
render_template() function
return render_template('index.html', title='Front page', message=message, form=form)
MongoDB - multiple documents
●
●
What if our form allows a user of our website to enter multiple instances of
data? We can use the insert_many()function (left snippet)
And querying multiple documents? (right snippet)
new_users = [{
'name': 'Yashas'
'location': 'Finland',
'age': 42,
},
{
'name': 'Kenton',
'location': 'Peru',
'age': 31,
}]
mongo.db.users.insert_many(new_users)
for user in mongo.db.users.find():
print(user['name'])
MongoDB - custom queries
●
So far we’ve just been getting one document back, or all documents back,
with no filtering of the data we might actually receive.
○
○
○
We can use custom queries to get back data.
Recall the insertion from the last slide
Let’s get back all the users whose age is 31
new_users = [{
'name': 'Yashas'
for user in mongo.db.users.find({'age': 31}):
'location': 'Finland',
print(user['name'])
'age': 42,
},
{
'name': 'Kenton',
'location': 'Peru',
'age': 31,
}]
mongo.db.users.insert_many(new_users)
MongoDB - custom queries
●
If we want to get back all users whose age is above 20, then we can use
advanced queries
○
●
https://docs.mongodb.com/manual/reference/operator/ - a reference document for operators
We are searching for ages greater than 20, using the special key $gt
new_users = [{
'name': 'Yashas',
for user in mongo.db.users.find({'age': {'$gt': 20}}):
'location': 'Finland',
print(user['name'])
'age': 42,
},
{
'name': 'Kenton',
'location': 'Peru',
'age': 31,
}]
mongo.db.users.insert_many(new_users)
MongoDB - creating indices
●
Every document automatically has the _id key created, which is unique, but
we might want to create another unique key for data like the email, so
○
●
We create an index, with the unique parameter set to True.
Note that you can create indexes that are not unique
○
Useful for fields you query a lot because those queries can be done faster (instead of having
to look through the entire collection).
db.users.create_index([('name', flask_pymongo.ASCENDING)], unique=True)
print(list(db.profiles.index_information()))
>>>[u'_id_', u'name_1']
Final Notes
●
●
Examples from class are on the GitHub
Project 3 released today
○
Due in 2 weeks.