字幕列表 影片播放
[MUSIC PLAYING]
BRIAN YU: OK, let's get started.
Welcome, everyone, to the final day of CS50 Beyond.
And goal for today is going to be to take a look at things
at a bit of a higher level.
There is going to be less code in today's lecture.
The focus of today is on two main topics--
security and scalability-- which are both important as you
begin to think about, you're writing all this code for your web application.
You're ready to deploy it so that people can actually use it.
What are the sorts of considerations you need to bear in mind?
What are the security considerations in making
sure that wherever you're hosting the application, you and the application
itself is secure and that your users are secure from potential vulnerabilities
or potential threats?
And also, from a scalability perspective,
we've been designing applications that so far probably only you
or a couple other people have been using.
But what sorts of things do you need to think about
as your applications begin to scale, as more and more people begin to use it,
and you have to begin to think about this idea of multiple people trying
to use the same application at the same time?
So a number of different considerations come about there.
We'll show a couple of code examples.
But the main idea of this is going to be high level, just thinking abstractly,
sort of trying to design the product, trying to design the project,
trying to figure out how exactly we need to be adjusting our application
to make sure that it's secure and to make sure that it's scalable.
So we'll go ahead and start with security.
And on the topic of security, we're going
to look at a number of different security considerations
as we move all throughout the week, from the beginning of the week
until the end of the week, thinking about the types of security
implications that come about.
And so one of the first things we introduced in the class was Git,
the version control tool that we were using
to keep track of different versions of our code
in order to manage different branches of our code, so on and so forth.
And so a couple of important security considerations to be aware with
regards to Git.
You all probably created GitHub repositories
over the course of this week, maybe for the first time.
And GitHub repositories by default are public.
And this is in the spirit of the idea of open source software, the idea
that anyone can see the code.
Anyone can contribute to the code.
And that, of course, comes with its trade offs.
On one hand, everyone being able to see the code certainly
means that anyone can help you to find bugs and identify bugs.
But it also means that anyone on the internet can see the code,
look for potential vulnerabilities, and then
potentially take advantage of those vulnerabilities.
So definitely, trade offs, costs, and benefits that
come along with open source software.
And another thing just to be aware of, we mentioned this earlier in the week,
but your Git commit history is going to store the entire history of any
of the commits that you have made, as the name might imply.
And so if you make a commit and you do something
you shouldn't have done, for instance-- you make a commit that accidentally
includes database credentials inside of the commit somewhere
or includes a password inside of the commit
somewhere-- you can later on remove those credentials
and make another commit and remove the credentials.
But the credentials are still there inside of the history.
If you go back, you could still find the credentials
if you had access to the entire Git repository
and could go back and find that point in Git's history.
So what are the potential solutions for if you do something like this,
accidentally expose credentials at some point in the repository
and then remove them?
What could you do?
Yeah?
AUDIENCE: Change the credentials.
BRIAN YU: Certainly.
Changing the credentials, something you should almost definitely do.
Change the password.
It's not enough just to remove them and make another commit.
And there's also something you can do known as Git purge, where
you can effectively purge the history of commit, sort of overwrite history,
so to speak, in order to replace that, as well.
But even that, if it's been online on GitHub,
who knows who may have been able to access the credentials?
So definitely always a good idea to remove those, as well.
On the first day, we also took a look at HTML.
We were designing basic HTML pages.
And there are a number of security vulnerabilities
you could create just with HTML alone.
Perhaps one of the most basic is just the idea that the contents of a link
can differ from where the link takes you to.
There's probably a pretty obvious point where you often
have text that links you to a particular page.
But this can often be misleading and is commonly
used in phishing email attacks, for instance,
whereby you have a link that takes you to URL one,
but by default, it shows you URL two, which can be misleading, for sure.
Or I can have situations where I could--
let's go into link.html--
I have a link that presumably takes me to google.com.
But if I click on google.com, it could take me anywhere else--
to some other site, for instance.
And the way that it does that is quite simply by just
having a link that takes you to a URL, but the contents of that URL
are something different or something else entirely.
And so that alone is something to be aware of.
But that problem is compounded when you consider the idea
that even though your server-side code-- application code
you write in Python and Flask, for instance--
you can keep secret from your users, HTML code is not
kept secret from users.
Any users can see HTML and do whatever they want with it.
And so on the first day, you may have been
trying to take a look at an HTML page and try and replicate it
using your own HTML and CSS, for example.
The simplest way to do something like that
would just be to copy the source code.
So I could go to bankofamerica.com, for instance, Control-Click on the page,
view the page source, and all right.
Here's all the HTML on Bank of America's home page.
I could copy that, create a new file, and call it bank.html.
Paste the contents of it in here.
Go ahead and save that.
And now, open up bank.html.
And now, I've got a page that basically looks like Bank of America's website.
And now, I could go in.
I could modify the links, change where Sign In takes you to,
make it take you to somewhere else entirely.
And so these are potential threats, vulnerabilities,
to be aware of on the internet that are quite easy to actually do.
So this is less about when you're designing your own web applications
but, when you're using web applications, the types of security
concerns to definitely be aware of.
So let's keep moving forward in the week-- yeah, question?
AUDIENCE: Can you copy JavaScript source code in the same way?
BRIAN YU: Yes.
Any JavaScript code that is on the client, you can access
and you can modify.
You can change variables and so on and so forth.
And this is actually a pretty easy thing to do.
So if I go to like, I don't know, The New York Times website, for instance,
and I look at the source code there--
let me go ahead and inspect the element, and I'll
try and hover over a main headline.
OK.
This is the name of a CSS class.
You could access any JavaScript.
You can also run any JavaScript in the console arbitrarily.
So I could say, all right, document.query selector all let's
get everything with that CSS class.
Or maybe it's just the first one, because it's two CSS classes.
All right.
Great.
I'll take the first one, set its inner HTML to be,
like, welcome to CS50 Beyond.
And you can play around with websites in order to mess around, change them.
So all of the JavaScript CSS classes, all of that,
is accessible to anyone who is using the page, for example.
Other questions before I go on?
Yeah.
AUDIENCE: Any thoughts on JavaScript obfuscation?
BRIAN YU: JavaScript obfuscation-- certainly something you can do.
So since JavaScript is available to anyone who has access to the web page,
there are programs called JavaScript obfuscators gators
that basically take plain old looking JavaScript
and convert it into something that's still JavaScript
but that's very difficult for any human to decipher.
It changes variable names and does a bunch of tricks in JavaScript
to still execute the exact same way but that looks quite obscure.
Definitely something you can do.
Still not totally foolproof, because there are ways
of trying to deobfuscate JavaScript code, at least to some extent.
So it's not perfect, but definitely something that you can do.
Other things?
All right.
Let's take a look at--
OK, when we were writing Flask applications,
we were writing web servers.
And so one thing that's just good to know from a security perspective
is the difference between HTTP, the Hypertext Transfer Protocol,
and the secure version of it, HTTPS.
And that has to do with the idea that on the internet,
we have computer servers that are trying to communicate
with each other that are trying to send information back and forth.
And when these computers are trying to send information back and forth,
we would like for that to happen securely,
that when one computer is sending information to another computer,
that information is going through a number of different routers.
And each of those routers could hypothetically
have information that's intercepted.
Someone could try and intercept a package on its way from computer number
one to computer number two.
So how do we securely try and transfer information from one location
to the other?
And this has to do with the entire field of cryptography,
which is a huge field that we're only going to be
able to barely scratch the surface of.
But the basic idea here is that we would like some way
to encrypt our information, that if I have some plain text that I would like
to send from my computer to someone else's computer,
I would like to encrypt that plain text, send it across in some encrypted way,
such that the person on the other end could decrypt it.
And so this is perhaps a more sophisticated version
of what you might have done in CS50's problem set two
when you were using the Caesar or the Vigenere cipher
in order to encrypt something.
The ciphers that are used in computing on the internet, for instance,
are just much more secure, for example.
But they follow a similar principle.
And so one form of cryptography is called secret-key cryptography,
where the idea is that if I am a computer up here
and I have some plain text that I want to encrypt,
I also have some key that only I know.
And I can take the plain text, and I can take that key
and run an algorithm on it.
And that generates some ciphertext, some encrypted version of the plain text
that was encrypted using the key.
I can then send that ciphertext along to the other person.
And so long as the other person has both the ciphertext and the key
to encrypt it, they can do the same process
and just decrypt it, generating the plain text from it.
That way, the ciphertext is transferred, not the plain text,
from one side to the other side of this communication.
And so long as both parties in this instance have access to the same key,
they can encrypt and decrypt messages at will.
Why doesn't this quite work on the internet, though?
What is the problem with this model?
Yeah?
AUDIENCE: If you're sending the key as well as the ciphertext,
then it's just revealed as sending the plain text that you have one.
BRIAN YU: Exactly.
When we transfer the ciphertext across, the other person
also needs access to the key.
We need to transfer the key across the internet,
as well, to give it to the other person.
And so anyone who is intercepting the ciphertext
could also have intercepted the key and therefore could
have decrypted the information and gotten the plain text
as a result of it.
So this secret-key cryptography, ultimately, it
doesn't work in the context of the internet
if it needs to be the case that the key is just
transferred across the internet.
Now, you could try encrypting the key, for example.
But then whenever key you used to encrypt the key,
that also needs to be sent across the internet,
and you end up with this problem where you can never figure out a way in order
to make sure that information can be transferred securely.
So the solution to this lies in a different idea called public-key
cryptography, where the idea here is that instead of having one key,
we'll have two keys--
one called a public key, one called a private key.
And the idea here is that a public key is something you can share with anyone.
Doesn't matter who has it.
And a private key is a key that you keep to yourself
that you don't give to anyone, even the person that you're
trying to communicate with.
And because we have two keys, each key is going to serve a different purpose.
They're going to be mathematically related.
And take a theory of computing class if you
want to understand the exact mathematics behind this.
But the basic idea is that the public key can be used to encrypt messages,
and the private key can be used to decrypt messages that
were encrypted using the public key.
And so what does this model look like?
Well, I have some public and private key.
And if I want some other person to send me information,
I will give them my public key.
Just give the other person the public key so that they have access to it.
Remember, the public key is used to encrypt data.
So they can use the public key and encrypt the plain text,
generate some ciphertext.
And then all the other person needs to do is send me that ciphertext.
The ciphertext comes across to me.
And I now have the private key, the key that I
can use to decrypt the information.
And using the private key and the ciphertext,
I can then decrypt the message and generate the plain text.
So this is the basic idea of public-key cryptography,
this idea that we use a public key to encrypt information and a private key
to decrypt information.
And by separating this out into two different keys,
we can share the public key freely without needing
to worry about the potential for internet traffic
to be intercepted and decrypted, for example.
And so this is the basis on which internet security works.
Yeah?
AUDIENCE: What if someone else intercepts the ciphertext
and they also have a private key?
Would they be able to decrypt it?
BRIAN YU: If someone else intercepts the ciphertext and they have a private key,
they won't be able to decrypt it, because the private key
and the public key are mathematically related in such a way
that if you encrypt something with a public key,
you can only decrypt it with the corresponding private key.
And so generally speaking, you'll generate both the public
and the private key at the same time, such that only messages encrypted
with one can be decrypted with the other.
So you can't just have some other random private key and decrypt the message.
It can only decrypt messages from the public key.
AUDIENCE: So how did this person get that specific [INAUDIBLE]??
BRIAN YU: So this person down here generated both the public
and the private key at the same time.
There's just an algorithm that you can use to randomly generate
a public and private key.
You share the public key with anyone you want to be able to send you messages.
That person you share it with can use the public key to encrypt the message.
And then you, the person who generated these keys,
can take the encrypted message, use the private key that you generated,
and get the plain text out of that.
Yeah?
AUDIENCE: How difficult is it to get the private key from the public key?
Is it impossible?
BRIAN YU: How difficult is it to get the private key from the public key?
Long story short, we don't really know.
We think it is very difficult to do.
We think that it would take a very long time.
If you took a computer and tried to get it to go from the public key
to the private key, we think it would probably take billions, trillions, more
years if a computer was operating at top speed trying to do this calculation.
But no one has been able to technically prove that it is difficult.
And so this is a big open question in computing right now.
You can take a theory of computation class
for more information on this sort of thing.
But there are some open unsolved problems in computing,
and this happens to be one of them.
Yeah?
AUDIENCE: Is it based on primes and very large primes, and you
multiply them together?
BRIAN YU: Yes, this is basically the idea of very large prime numbers
that you multiply together.
The long story short of it is it's based on the idea
that there is some mathematical operations that are easy
and some mathematical operations that are believed to be difficult.
And if you take two very big prime numbers,
a computer can multiply those numbers very easily
and calculate what the product of those two numbers is.
It's just a simple multiplication algorithm.
But if you have that result, that big multiplied prime number,
it's very difficult to factor that number
and figure out which two prime numbers were multiplied together
in order to generate that number.
And nobody has been able to come up with an efficient algorithm for factoring
it.
And so as a result, because we believe factoring numbers to be
a very difficult problem, we use it as the basis
for computing security on the internet.
Brief teaser of theory of computation.
Take any of the 120 series here at Harvard, at least,
for more information about that.
Other things?
Some other security considerations when designing web applications
to be aware of-- we mentioned this before,
but when it comes to storing credentials,
you should generally always store credentials
in environment variables inside of your application
rather than have inside of your Python code some password,
whether it's the secret key of your application,
whether it's the credentials to your database,
whether it's some other credentials for an API key,
for example, that you're using the server to access.
Usually best not to put that in the code in case someone else
gets access to the code.
Generally best to put it in an environment
variable, a variable that's just stored in the command line environment
where your server's being run from.
And then add code that just pulls the credentials from the environment.
You can use in Python, at least, os.environ.get
to mean get some information from the application's environment.
And this is generally going to be a more secure way of doing the same thing.
Yeah?
AUDIENCE: How do we do that in Heroku if we
want to upload our code to the website?
BRIAN YU: Yeah.
So if you're uploading this to Heroku, if you go to your Heroku application
and go to the Settings panel, there is a section,
I think it's called config vars, that basically just lets you add environment
variables to the Heroku application.
And that will automatically set those environment variables such
that when you run the application, it can
draw from those environment variables.
Yeah?
AUDIENCE: Is it [INAUDIBLE] yesterday, or is that something
you can't have access to?
Because if you just did [INAUDIBLE] and then the key,
it goes away when you close the terminal, correct?
BRIAN YU: Yes.
So that's true.
So you can certainly, on your own computer,
set aliases or environment variables inside
of your profile that automatically set credentials in a particular way.
The idea is that you never want to be taking those credentials
and committing them to a repository that other people might
be able to see, for instance.
That's where things start to get less secure.
OK.
Moving on in the week to talk about some other security considerations.
We'll talk about SQL, the idea of databases.
And when we introduce databases, there are a lot of security considerations
that come about.
But we'll just touch on a couple of them.
The first is how you store passwords.
So you can imagine that inside of a database,
you might be storing users and passwords together.
And maybe we have a whole users table that has an ID column,
a column for people's usernames, and a column for people's passwords.
And you could imagine just storing passwords inside of the row.
But why is this not particularly secure?
Yeah?
AUDIENCE: If anyone gets access to the data table,
they can see what all the passwords are.
BRIAN YU: Exactly.
If anyone gets access to the database, they immediately
have access to all of the passwords.
And this is probably not a secure way to go about things,
because you probably hear in the news from time
to time that databases aren't perfectly secure, that every once in a while,
there's some big security vulnerability where someone's able to get access
to passwords inside of a database.
And that becomes a major security concern.
And so one way to try and mitigate this problem
is, instead of storing passwords inside of the database,
store a hashed version of the password.
A hash function, as you might recall from CS50, just takes some input
and returns some deterministic output.
And a hash function can generally take any input password
and turn it into what looks like a whole bunch of random sequences of letters
and numbers.
And the idea here is that it's deterministic.
The same password will always result in the same hash value
whereby when someone tries to log in, when they type in their password,
rather than just literally compare their password
and say does the password match up with the password in this column,
you can say, all right, let's hash the password first.
And if the hashes match up, then with very high probability,
the user actually signed in to the website with the correct password.
And you can then log the user in.
And now, if someone was able to get access to the database,
they wouldn't get access to all the passwords.
They would only get access to the password hashes.
Now, it's still a security vulnerability,
because someone could, in theory, be able to figure out
information about the password from the password hashes.
But better, certainly, than literally storing the raw text
of the password in the database.
Yeah?
AUDIENCE: Do we know how the hash functions generate that code?
BRIAN YU: Yeah.
The hash functions tend to be deterministic,
and you look up what the hash functions themselves are.
So there are a couple of quite popular hash functions
that are out there that do this sort of thing.
But the idea of the hash function is similar to the idea
of public and private keys, that it's very easy to hash something,
and it's very difficult to go in the other direction.
I can easily hash a password and generate
something that looks like this.
But it's a difficult operation to take something that looks like this
and go backwards and figure out what it was that the original password was.
And so that's one of the properties of a good hash function.
Yes?
AUDIENCE: Did you actually hash these, or did you just hit the keyboard?
BRIAN YU: I think these are probably--
there might be hidden messages here if you look carefully.
But separate issue.
Other things?
OK.
So how is it that potential data is leaked as a result of using a database?
Well, there are a number of ways that applications can inadvertently
leak information.
Take a simple example.
Oftentimes, you'll see websites that have a Forgot Your Password
screen where you type in an email address, and you click Reset Password.
And that helps you to send you an email that allows you
to reset your password, for example.
And you imagine that you type in an email address,
and you get, OK, password reset email has been sent.
But maybe some applications work such that if you type
in an email address that doesn't exist, then
you get an error that says, OK, error.
There is no user with that email address.
What data has this application now exposed?
What information can you get just by using this part of a web application,
for instance?
Yeah?
AUDIENCE: You know that that email address is not in the system,
so you know that person is not using that app.
BRIAN YU: Yeah, exactly.
Just using the Forgot Password part of this application,
you can tell exactly who has an account for this application
and who doesn't just by typing email addresses and seeing what comes back.
So there's potential vulnerabilities in terms of data
that gets leaked there, as well.
And there are all sorts of different ways that information can get leaked.
Oftentimes, there's a growing field whereby
you can tell just based on the amount of time it takes for an HTTP request
to come back whether or not--
you can get information about the data inside of a database
based on that whereby if you make a request that takes a long time, that
can tell you something different than if a request comes back
very quickly, because that might mean fewer database requests
were required in order to make that particular operation work
or any number of different things.
And so there are security vulnerabilities there, as well.
Final one.
I'll briefly mention the SQL injection.
We've already talked about that.
But again, something to be aware of just to make sure
that whenever you're making database queries,
you're protecting yourself against SQL injection,
that you're making sure to either use a library that takes care of this for you
or escape any characters that you might be using that
could ultimately result in vulnerabilities in SQL.
Yeah?
AUDIENCE: How about the websites or tools
like LastPass that store your credentials for other sites?
Don't they have to have some way of reversing their own hash on it
in order to give you that credential when you go to another site?
So when it auto fills your username and password,
it has to-- if they're storing a hashed version on their side but filling
in the plain text version in the password field,
how are they able to reverse that in a way that is secure?
They would have to have a table of keys or something
that then is just as vulnerable as leaving the password.
BRIAN YU: Yeah.
So for password manager-type applications, it's a good question.
I think the way most of them do this is that you have a master password that
unlocks the entire database of the passwords that are stored there.
And the idea would be that they're encrypted
using the master password as the key to be the unlocker such
that they're encrypted.
And only by getting the master password correct
can you then decrypt the information and then
access the plain text version of the passwords that are inside.
And so hashing and encryption and decryption are slightly different.
In the case of encryption and decryption,
you still want to be able to go from the ciphertext back to the plain text,
whereas in the case of the password hashing,
you don't really care about the ability to reverse engineer it to go backwards.
All right.
And finally, on the topic of security, we'll
talk a little bit about JavaScript.
JavaScript opens a whole host of different potential vulnerabilities
from a security standpoint.
But we'll talk about a couple.
The first is this idea called cross-site scripting,
or the idea of taking a script and being effectively able to inject it
into some other site by putting some JavaScript that the web
application didn't intend into the web application itself.
And so here's a very simple web application written in Flask.
And this is the entire web application.
It's got a route, a default route, called / that just returns, "Hello,
world!"
And it's got an error handler that we didn't really see in the class.
But basically, it handles whenever there's
a 404 error, whenever you're trying to access a page that was not found.
And it just returns, "Not found," followed by request.path, whatever it
is that was the URL that you requested.
And so I could run this application.
I'll go ahead and start up Chrome, and I'll go ahead
and go to the source code for XSS1.
I'll run this application.
Go here.
It says, "Hello, world!"
And if I go to helloworld/foo, for example, some route that doesn't exist,
I get not found, /foo, because that's not a route that's available on this
page.
I go to /bar.
Not found, /bar.
What could go wrong here?
Where's the security vulnerability, again,
thinking in the context of JavaScript?
The page my application is returning is literally just "not found"
followed by whatever was typed into the request path.
And so what I could do is you could imagine that instead of running /foo,
I could instead make a request that looks something like /script
alert('hi) and then /script, for instance,
injecting some JavaScript into the request path whereby if I do that,
I say, OK, /script alert('hi') /script.
Press Return.
And OK, Chrome is being smart about this.
Chrome actually isn't allowing me to do this,
because Chrome has some more advanced features that are basically
saying Chrome detected unusual code on this page
and blocked it to protect your personal information and error blocked
by XSS auditor.
That's cross-site scripting.
So Chrome is automatically auditing for this.
But not all browsers are like that.
And I can, I think--
let's see if I can disable--
if I disable cross-site scripting protections,
I think I can get this to-- yeah, OK.
Disabling cross-site scripting productions,
we can still type in the URL and actually get some JavaScript
that the page didn't intend to still run on this particular web page.
And so if someone were to send you a link that took you to this page,
/script alert('hi'), you could get JavaScript to run that you
didn't intend.
And maybe that's not a big deal.
But it could be a bigger deal in a situation that
looks like this, where we have JavaScript
and document.write is a function that just add something to the page.
And here, we're loading an image, img src,
and the source is some hacker's website.
And then we say, cookie= and then document.cookie.
Document.cookie stores the cookie for this particular page.
And so effectively, what's happening in this script
is that your page, when you load it, is going to make a web
request to the hacker's URL.
And it's going to provide it as an argument whatever
the value of your cookie is, for instance.
And that cookie could be something that you use in order
to log in as the credentials for some website,
like a bank application or whatnot.
And as a result, the hacker now has access
to whatever the value of your cookie is, because they
can look at their list of all the requests
that have been made to the application much in the same way
that you've been able to do in the terminal
to see all the requests for your Flask application.
And they can see that someone requested hacker_url?cookie= this cookie,
and they can then use that cookie to be able to sign in to other sites,
as well.
So most modern browsers, like Chrome, are
pretty good at defending against this sort of thing.
But definitely something that is a potential vulnerability, especially
for older browsers.
Questions about this cross-site scripting?
Yeah?
AUDIENCE: Are you getting the user's cookie,
or whose cookie are you getting there?
BRIAN YU: Whoever opens the page.
So the user's cookie, potentially on an entirely different site.
The idea is that if your site is vulnerable to cross-site
scripting in this form, then you open up a possibility
where someone could generate a link to your website that
includes some JavaScript injected like this whereby someone else could
steal the cookies of your users on your website.
And they could get the cookies for themselves
and use those cookies to sign into your website
and pretend to be people that they're not, for example.
There's a potential security threat there.
So cross-site scripting is one example of a JavaScript vulnerability.
Another vulnerability is called cross-site request forgery.
Imagine that you have a bank website, for instance,
and that bank gives you a way to transfer money.
And if you go to that URL /transfer and then you provide arguments as to who
you're transferring money to and how much money you're transferring,
you can transfer money.
Might be a web request that allows you to do that.
Imagine some other website, some website where
hackers are trying to steal money, where they have code that
looks a little something like this.
They have a link that says, "Click Here!"
And when you click on the link, that takes you to yourbank.com/transfer
transferring to a particular person, transferring a particular amount.
And some unsuspecting user on this website could click the button.
And as a result, that takes them to their bank.
And if they happen to be logged into their bank at the time,
that could result in actually making that transfer.
So cross-site request forgery is the idea
that some other site can make a request on your site as by, in this case,
linking to it.
This still isn't an amazing threat, because the person actually still needs
to click on the button in order to be able to load in order to actually go
to yourbank.com/transfer/whatever.
But you can imagine that a clever hacker might be able to get around this
by doing something like this--
rendering an image, for example, and saying the source of the image
is going to be this.
And when an HTML sees an image tag, the browser is just going to go to that URL
and try and download that image.
It's going to go to the URL, try and fetch that resource.
And here, that resource is yourbank.com/transfer and then
transferring that money.
So the user doesn't even have to click on anything.
And by making a GET request to yourbank.com/transfer,
if yourbank.com isn't implemented particularly securely and just allows
you to go to a URL like this to transfer money, then that could be the result.
So how do you protect against this?
How would you protect against your website
being able to do something like this?
Because your website probably wants some way
of being able to transfer money if you have a bank application,
but you don't want to allow people to make requests like that.
Answer, yeah?
AUDIENCE: Yeah.
It's facetious.
BRIAN YU: Go for it.
AUDIENCE: You get a better bank.
BRIAN YU: Get a better bank.
OK.
Certainly something that would work.
Other thoughts?
Yeah?
AUDIENCE: Change the form request type so it's not literally in your own
[INAUDIBLE].
BRIAN YU: Yeah.
Change the form request type so that it's not literally here.
So this right here is a GET request.
You might imagine that instead, it's a form that's submitted by a POST,
like a POST request, a form that you actually
have to submit, click on a Submit button, in order to submit that form.
And so now, you could imagine that someone could still
create a vulnerability by doing something like this.
They have a form whose action is yourbank.com/transfer submitting
by a method POST.
And now, they have these input that are type hidden,
which are just input fields that don't show up inside of a page.
And they can have hidden input fields that
specify who it's to, what the amount is, and then just some button that says,
"Click Here!"
And if they click here, then unwittingly,
the user could be submitting a form to the bank that's
initiating some transfer.
And in fact, if the hacker is being particularly clever,
you don't even need the user to click anything,
because we can use event listeners to get around this.
I could say body onload--
in other words, when the body of the page is done loading,
run this JavaScript.
Document.forms returns an array of all the forms in the web document.
Square bracket 0 says get the first form.
And there's a function in JavaScript called .submit that submits a form.
So you can say, all right, get all the forms, get the first form,
and run submit.
And that's going to result in submitting this form,
making a POST request to yourbank.com/transfer,
which results in some amount being transferred.
So this is a potential vulnerability, as well.
If you're writing this bank application, you
don't want to allow a code like this to be able to get through your security,
because that opens up a whole host of potential security vulnerabilities.
And in general, the way that people tend to deal
with this is by adding what's called a CSRF token, a Cross-Site Request
Forgery token, basically adding some special value that changes
into their own forms and then, anytime someone submits
the form, checking to make sure the value of that token
is, in fact, a valid token.
And that way, someone couldn't fake it because some other form
on some other hacker's website isn't going to have a valid CSRF
token inside of their form page.
And so larger scale web application frameworks, like Django,
offer easy ways to add CSRF tokens to your forms, as well.
But just something to be aware of as you begin
to think about, when you're designing a web application,
how could someone exploit it?
How could someone make requests on behalf of users
that they don't intend to in order to get
some malicious result to come about?
So lots of security things to be thinking about.
Questions about security or any of the security topics
that we've covered or talked about?
Yeah?
AUDIENCE: [INAUDIBLE] the token is generated [INAUDIBLE] event,
or it's a unique token for every user?
BRIAN YU: Yeah.
Imagine that in the case of CS50 Finance,
for instance, that when I click on the Buy page that takes me
to the page where I can buy stocks, my route for buy
is going to basically generate a new token
and insert it into the form that then gets displayed to me.
And then when I submit that form, it gets submitted back
to the same application.
And the application can then check.
Did the token that came back match the token that I inserted into the page?
And if they do, in fact, match, then that's
a way of sort of verifying that the user was actually
submitting the actual form and not some fake form
that they were tricked into submitting.
All right.
In that case, let's switch gears a little bit,
and let's talk about scalability.
Here again, there's going to be even less code.
And the idea is just going to be, all right, what happens when
we begin to scale our web application?
We've got some web server, and we've got some users
that are using that web server, which we're going to represent as that line.
And so what happens when that server starts
to have more users that are all trying to use
the application at the same time?
What do we do?
Well, the first thing to probably do is figure out how many users
our website can actually support.
How many can it handle before it stops being able to support users?
And so this is where benchmarking is quite important.
Benchmarking is just this process by which we can test and sort of load test
our application to see what we can do to see how many users we could potentially
handle on our server.
And so what happens if we find out via benchmarking that,
OK, our server can only hold 100 users?
What if we need to support 101 users or 102 users?
What can we do?
One thing we can do is called vertical scaling, where the idea here
is, all right, we have a server.
And that server only supports 100 users.
All right, well, let's just get a bigger server, right?
Let's get a server that supports 200 users or 300 users.
And that's going to be able to better handle that load.
But there's a limit to this, right?
There's a limit to how much you can just increase the size of a server
and increase its ability to handle load.
And so what could you do to be able to handle more users?
AUDIENCE: More servers.
BRIAN YU: More servers.
Great.
And this is an idea called horizontal scaling, where
the idea is that we have some server.
And let's say, instead of having one server,
let's go ahead and have two servers that are running the exact same web
application.
And now, we have two servers that are able to run the application
and handle twice as many people.
What problems come about now, logistically?
User tries to access our website, and now what?
Yeah?
AUDIENCE: That means you could have a race condition situation
or how the servers communicate to each other [INAUDIBLE]..
BRIAN YU: Yeah.
How do the servers communicate with each other?
Certainly, race conditions become a threat, as well.
And then a fundamental problem is a user comes to the site,
and which server do they go to, right?
We need some way of deciding which server to direct a particular user to.
And so generally, this is solved by adding yet another piece of hardware
into the mix, adding some load balancer in between the user
and the servers whereby a user, when they request the page,
rather than going straight to the server, they go to the load balancer
first.
And from there on, the load balancer can split people up,
say certain people go to this server, certain people go to that server,
and try and decide how it is that people are going to be
divided into the different servers.
And so how could a load balancer decide?
If there are five servers and a user comes along,
how should a load balancer decide which server to send a user to?
There is no one right answer to this.
There are a number of possible options, a number of different
what are called load balancing methods.
But how could you decide where to send a user?
Yeah?
AUDIENCE: The server with the least amount of users currently.
BRIAN YU: Sure.
The server with the fewest users currently, what's often
called the fewest connections load balancing method.
You try and figure out which server has the fewest people on it.
And whichever one has the fewest people on it, send the user there.
Definitely good for trying to make sure that each one has about an equal load,
but potentially computationally expensive.
You're doing a lot of calculation now, so there's a trade off.
Yeah?
AUDIENCE: You could just do it randomly.
BRIAN YU: You could do it randomly.
You could just generate a random number between 1 and 5
and randomly assign someone to a particular server.
Definitely something you could do.
Other things?
Certainly the random approach is quick.
It doesn't involve having to do any calculation across all
the different servers.
But if you're unlucky, you could end up putting
a lot of people on server number two and not many people on server number eight
or whatnot.
And so what else could we do?
Yeah?
AUDIENCE: Just set up a counter [INAUDIBLE]..
BRIAN YU: Sure.
Some sort of counter.
If you only have two, you just alternate odd, even, odd, even.
Go to this server.
Go to that one.
If you've got eight, you just rotate amongst the eight--
1, 2, 3, 4, 5, 6, 7, 8 and go back to 1.
And so these are probably three of the most common load balancing methods--
random choice, whereby you just pick a random server, direct the user there;
round robin, where we do exactly that, just basically go one up until the end
and then go back to server number one; and then fewest connections, whereby
you try and actually calculate which server currently
has the fewest number of people on it and then
try and direct the user to that one with the fewest connections.
There are other methods in addition to this,
but these are perhaps three of the most intuitive
where you can start to see their trade offs.
Depending upon the type of user experience
you want, depending on how computationally
expensive certain operations are, you might choose different load balancing
methods.
Yeah?
AUDIENCE: [INAUDIBLE] benchmarking, and what are some common ways to do that?
BRIAN YU: Yeah, there are software tools that can do this.
There are a number of different ones-- the names are escaping me
at the moment--
where you can basically test on a particular URL
and get a sense for how well it's able to handle that load.
And if you have particular use cases, I can chat with you about that, as well.
So all right, let's imagine we have two servers now.
And every time a user makes an HTTP request
to a server, every time they request a page,
we direct them to one server or the other server using
one of these methods, either by choosing randomly or by round robin
or by figuring out which one currently has the fewest users connected to it
or is handling the fewest connections.
What can go wrong?
Whenever we're dealing with issues of scale, we just try and solve a problem
and figure out what new problems have arisen.
Yeah?
AUDIENCE: You only have five servers, and now you need six.
BRIAN YU: Yeah.
Certainly, if you only have five servers and suddenly you need six,
that could potentially become a problem, as well.
But let's even assume that we have enough servers.
We have five servers, and every time someone load a page,
they get sent to a different server based on one of these methods.
What can still go wrong with the user experience?
And in particular, I'll give you a hint.
Let's think about sessions.
What can go wrong?
Remember, sessions were ways of storing information-- in our case,
inside of the server--
about the user's current interaction with the server.
It stored which user was logged in.
It stored the current state of the tic-tac-toe game.
It stored other information.
Yeah?
AUDIENCE: You have to pick one [INAUDIBLE]..
BRIAN YU: Yeah, exactly.
If I initially load a page and I go to server one and some information
about me is stored in the session, like whether I'm logged in
or the current state of my game or something else,
and then I load another page and it takes
me to server four this time, well, now, that server
doesn't have access to the same session information
that server one had if the information about the session
was stored in the server.
And now, that information is lost.
So I could load a page, and suddenly, now, I'm
logged out of the page for no apparent reason
even though I've logged in just a moment ago.
And then I could go to another page, and maybe by chance,
I'm back to server one, and now I'm logged in again.
So strange things can begin to happen.
And so to solve that, what could we do?
How can we make sure that sessions are preserved
when the user is requesting pages?
Again, no one correct answer.
Multiple possibilities here.
How do we solve this problem?
Yeah?
AUDIENCE: Would there any way to store the session on the load balancer?
BRIAN YU: Store the session on the load balancer.
That's a good idea.
And that will actually get me at the first idea here,
which is this idea of sticky sessions.
And this is slightly different.
Rather than store all the session information in the load balancer,
it just needs to store for this particular user which
server has their session information.
So if I went to server number one initially,
the load balancer will remember me based on my IP address, cookie, or whatever
and say, all right, next time I try and request a page,
let me direct them back to server number one, for instance.
That way, whenever I come back, I'm always going to go to the same place.
There are other ways to solve this problem, as well.
You could store session information in the database
that all the servers have access to.
You could store session information on the client side, whereby
it doesn't matter what server you go to, because all the session information is
inside the client.
So there are a number of ways to solve this problem,
but these generally fall under the heading of session-aware load
balancing.
Someone mentioned the problem of, OK, well, I have five servers,
but what happens when I need six?
To solve this in the world of cloud computing,
where nowadays most people don't maintain their own hardware
for their web applications, they just rent out
hardware on someone else's servers, for instance, on AWS, for instance,
use Amazon servers--
you can take advantage of auto scaling, which automatically will grow or shrink
the number of servers based upon load, whereby you could initially
have two servers.
But if more users come about and you need more,
we can add a third server into the mix.
More people come out, we need even more.
We add a fourth server.
And auto scaling goes in both directions.
So if suddenly we find, all right, we had a lot of load
at this particular peak time of the day but now there are
fewer users on the site, the auto load balancer can sort of say,
all right, we don't need four servers anymore.
Let's go back to three and then later on, if it needs doing,
go back up to four again.
And it can automatically, dynamically reconfigure the number of servers
in order to figure out what the optimal number is
given the number of users that are currently using the application.
What happens, though, when one of the servers fails for some reason?
The server just dies, for instance.
The load balancer doesn't necessarily know about that.
And so if it's still directing people across four different servers,
it could direct users to that server that is no longer operational.
Any thoughts on how we might solve that problem?
Yeah?
AUDIENCE: Have the load balancer ping the server at determined intervals
to see if it's still there.
BRIAN YU: Yeah, some sort of ping to make sure
That the server is still there.
And often, one of the easiest ways that this is done
is via what's called a heartbeat, whereby each of the servers
gives off a heartbeat every fixed number of seconds or minutes, for instance,
whereby if every 10 seconds the server pings the heartbeat,
that gets sent to the load balancer.
If ever the load balancer doesn't hear the heartbeat from the server,
it can know that that server is no longer operational, and it can say,
all right, you know what?
Let's stop sending users there and only send users to the other three servers.
Questions about that or any of the ideas of how we scale our servers
to be able to handle load?
We decided, all right, if too many people are on one server,
we need to split up into two different servers.
But that introduced a bunch of problems that we
had to solve-- problems about load balancing, problems about what to do
about sessions, so on and so forth.
Yeah?
AUDIENCE: Do you hear a lot about distributed servers?
I'm wondering how they [INAUDIBLE].
BRIAN YU: Sure.
How do servers share data?
Well, they use databases.
And of course, as we start to figure out what to do with more and more servers,
we also need to figure out what to do about databases,
figure out how to scale databases and make sure that as we scale them,
the databases are able to handle that load, as well.
And so in the past, we've had, all right, a load balancer.
We've got servers.
And in our model right now, we have a database that both of these servers
are connected to.
But of course, the problem is soon going to arise of, all right,
now we've got a lot of servers that are all
trying to connect to the same database.
And now, we've got yet another single point
where things could potentially go wrong or where
we could potentially be overloaded.
So how do we solve this type of problem?
One of the most common ways is database partitioning.
One form of database partitioning you've, in fact, already seen,
and it's just an extension of what we've been doing with SQL,
whereby we have this flights table.
And we could say, all right, rather than store the origin and the origin code,
let's go ahead and separate what's in one table
into a couple different tables.
Let's separate the flights table into a locations table
where the locations table has a number for each possible location.
And then it also, in the flights table, now,
only needs to store a single number for the origin ID and the destination ID.
We could also separate tables in different ways.
If we have some general way we could partition
a table into different parts that are generally
going to be queried separately, then we can
do another partition where I could say, all right,
my flight's table is getting big.
Let's split it up.
And all right, at my airline, the international departures and arrivals
are handled separately from the domestic departures and arrivals.
So no need for those to be in the same table.
Let me just go ahead and take flights and separate it
into a domestic flights table and an international flights table,
for instance.
One way to just partition things into two different tables that
could potentially be stored in different places that ultimately
allows for handling of scale.
But ultimately, all of these are problems
that are still going to lead to the fundamental problem of if I only
have one database and 10 or dozens of servers that are all
trying to communicate with that same database,
we're going to run into problems.
The database can only handle some fixed number of connections.
And so one solution to this is database replication.
So all right, how does database replication work?
Well, probably the simplest form of database replication
is what's called single primary replication, whereby
I have one what's called primary database and maybe
three databases in total, but only one that I'm
going to consider the primary one.
And you can read data from any of the databases.
You can get data out of any of the three databases,
whereby if there are three servers and each one wants to read data,
they can just share among the three databases reading data
to make sure that we're not overloading any one
database with too many connections.
But you can only write data to a single database.
And by only writing data to a single database,
that means that anytime this database is updated,
then this database, our primary database,
just needs to update the other two databases.
Say, all right, there's been a change made to the primary database.
And it's the primary database's responsibility
to then communicate to the other two databases what those changes are.
And so that's single-primary replication.
Yeah?
AUDIENCE: How is that more efficient than just communicating with all three
of them?
Because I think you're sending information
from the first database to the second and third.
[INAUDIBLE] information sent that's just rewriting to all three of them.
BRIAN YU: That's true, though.
Databases could potentially batch information
together into transactions and things and groups
so as to be a little bit more efficient.
So certainly ways around that problem.
But yeah, a good point.
Of course, this helps the read problem.
It makes it easier to be able to read data out of databases.
But it leaves open a potential vulnerability
or a potential scalability problem with regard to writing data,
because there is still only a single database on which I can actually
write data to if that one database is responsible for updating
all of the other databases.
And so a more complex version of this is what's
known as multi-primary replication, where
the idea is that each database can be read to and written from.
But now, updates get a lot more complicated.
All of the databases need to have some notion and some way
of being able to update each other.
And there, conflicts begin to arrive.
You can have update conflicts where two different databases
have updated the same row.
All right, how do you resolve that problem?
You can have uniqueness conflicts, whereby
if you add a row to each of two databases at the same time, maybe
they get the same ID.
Maybe this one only has 27 rows, so this database
adds a new row with ID number 28, and this database does the same thing.
And now, when they try to update each other,
we have two rows with the same ID.
And now, we need some way of resolving those,
because the IDs are supposed to be unique.
And so that can create problems, as well.
And then there are other types of conflicts, too-- delete conflicts,
whereby one database tries to delete a row at the same time
another database tries to update a row.
So which do you do?
Do you update the row?
Do you delete the row?
And so these are all conflicts that when you're setting up
a multi-primary replication system, you need
to figure out how you're going to ultimately resolve those conflicts.
You gain the ability to write to all the databases,
but new problems arise as you begin to do that.
Yeah?
AUDIENCE: So is the information in each database the same?
Are they [INAUDIBLE] with each other?
BRIAN YU: Yeah.
In this model, the databases in general are
going to be the same, though they're not always perfectly going
to be in sync, which is yet another problem, whereby there might
be some time after I write to this database
before that data propagates through all of the databases, for instance.
AUDIENCE: So why not keep it in one?
BRIAN YU: You could keep all the information in one database.
But a single database server can only handle so many connections.
And so you might imagine that having three different servers, three
different computers that are all able to handle incoming requests,
just increases the capacity of your application
to be able to handle that kind of load.
All right.
Questions about databases, database replication, any of the scale problems
that come about there?
All right.
Final thing I'll mention on the topic of scaling that can be helpful
is just the idea of caching.
Caching is something we've talked about a lot before.
But a general idea could be that in order to try and solve this problem
of constantly having to request information from the database,
if we could store data in some other place-- in particular,
inside of a cache--
then we don't need to access the database as often, because we've
got the information already stored.
And so one way to do this is via client-side caching.
And so inside of the HTTP headers, when an HTTP response
is sending back information to a user, you
can add an HTTP header called cache control that basically
says for up to this number of seconds, you can just store information
about this page and not request it again if you try
and request the page for a second time.
And this helps to make sure that if the browser tries to request
the page again, it doesn't need to.
It can just use the version that's stored inside of the cache.
And a more recent development is this idea of an ETag, or an entity tag.
And the idea here is that if we have some web resource, some document,
some piece of data from a database that our web application is sending out
to users, when I send users that resource, that document,
I'll send that document, and I'll also send an entity tag that
corresponds to that particular version of the document
and send them both to the user.
And imagine this is a big document.
It's a lot of data, so it's expensive to query and to send to the user.
The next time the user tries to request this page, what the user can do
is the user can send the entity tag, the ETag, along with their request.
I would like to request this resource, and, oh, by the way,
I already have this version of the entity stored
locally inside of my computer's cache.
And if the web application then looks at that ETag and says,
all right, you know what?
That's the latest version of the document.
The web application can just respond--
in particular, with an HTTP status code of 304, meaning not modified,
to just say, you know what?
This entity tag is the most recent entity tag.
Don't bother trying to request the document again.
Just use the version you saved locally in your cache.
And if, on the off chance, the document's been updated
and therefore has a new ETag value, then the web application
goes through the process of sending that entire document back to the user.
But by taking advantage of technologies like this,
this can allow us to make sure that we're not
making too many requests to the database,
that we don't make redundant requests if a particular resource hasn't changed.
So caching can be done on the client side.
Caching can also be done on the server side, which
changes our diagram slightly so as to look a little bit more
like this, whereby now, we've got some more complications here.
We've got some load balancer that's communicating
with a bunch of different servers.
All of those servers have to interact with the database,
and maybe you've got multiple databases going on here that are each able to do
reads and writes, either in a single-primary model
or a multi-primary model.
And those servers also have access to some cache that makes it easier
to access data quickly, in a sense, saying,
if there's some expensive database query,
don't bother performing the database query again and again and again.
Take the results of that database query once.
Save it inside of the cache.
And from then on, the server can just look to the cache
and get information out of there.
So lot of security and scalability concerns
that can potentially come about as you begin web application development.
And so goal of today was really just to give you
a sense for the types of concerns to be aware of,
the types of things to be thinking about,
and the types of issues that will come about
if you decide to take a web application and begin to have more and more people
actually start to use it.
So questions about that or about any of the other topics
we've covered this week?
All right.
So with the remainder of this morning, between now and about 12:30 or so,
we'll leave it open to more project time, an opportunity
to work on any of the projects you've worked on
so far over the course of this week and also an opportunity to work
on something new if you would like to.
I know many of you yesterday decided to start on new projects, projects
of your own choosing built in React or Flask
or using JavaScript or any of the other technologies
we've talked about this week.
Before we conclude, though, I do have to say a couple of thank yous,
first to David for helping to advise the class, to the teaching fellows--
Josh and Christian and Athena and Julia--
for being excellent in helping to answer questions
and helping to make sure that the course can run smoothly, to Andrew up
in the back, who's been taking care of the production side of everything
over the course of this week, making sure that all the lectures are recorded
and making sure they're posted online, such that afterwards, you,
when you're here or when you're not here,
are able to come online to see them.
So thank you to everyone for helping to make the course possible.
Thank you to all of you for coming to the course.
Hope you enjoyed it.
Hope you got things out of it.
We've really only scratched the surface, though,
of a lot of the topics that we've covered
over the course of the past week.
There's a lot more to CSS and HTML and JavaScript and Flask and Python
and React than we were really able to touch on over the course of the week.
It was really meant to be more of an opportunity
to give you some exposure to some of the fundamentals of these ideas,
some of the tools and the concepts that you can ultimately
use them as you begin to design web applications of your own.
So I do hope that you've learned something from the week but,
in particular, that you found things that are interesting to you, such
that you continue to take those ideas and explore them.
Go beyond just what we've been able to cover over the course of this week
and explore what else these technologies and these tools and these ideas
ultimately have to offer.
So thank you so much.
We'll stick around until 12:30 to help with project time.
[APPLAUSE]
But this was CS50 Beyond.