Web Attacks
This model shows a store and provides a number of requests to modify the store. It then shows how
to use the store with a good user Alice
and have a bad user Eve
that do not give any credentials.
Eve should therefore never be able to login or buy anything.
Preamble
Order the Token and State sigs which will be used later. (The includes (open
) must come first.)
We also define Item and Token which are needed later but only used for their identity. We
require some of them to be there because otherwise you run into the really nasty problem
that any comprehension seems to succeed because it misses atoms for one of the variables.
module store
open util/ordering[Token] as tk
open util/ordering[State] as st
some sig Item, Token {}
Actions
We later create a serial trace of actions that are performed in the store. The following actions are defined:
enum Action {
INIT,
BUY,
LOGIN,
STUTTER
}
The STUTTER
action is necessary to not block the trace. I.e. if there are no more items and
everybody is logged in then there are no alternative actions so then stutter can fill the gap.
Authentication
We use a simple userid to digest table. The digest
(total) function calculates a digest. In this case
we use the same atom for ease of use but this should be a one-way hash function like the SHA-x
algorithm.
We add the credentials for Alice but no credentials for Eve. That is, Eve has no access to the store.
fun digest[password : String ] : String { password }
pred authenticate[userid,password : String] {
userid->digest[password] in
("Alice" -> digest["Bob123"])
}
State
We need to create a trace of actions. We maintain this state in, ehh, State
. For convenience
this State maintains the state for both the browser (the cookies) and the server (the stock,
token generator, etc.).
Notice that the order is very relevant here because it defines how the sig is displayed in the
table view. You should try to create ‘sentences’ with the order. For example, Eve BUY item
It is important to realize that the cookies are shared state. This means that the browser is free to ignore or violete the contract of cookies. We can therefore not create any facts that rely in the cookies.
sig State {
browser : lone Browser,
action : Action,
bought : lone Item,
// Reflects the browser state
cookies : Browser -> Token,
// Reflects the server state
stock : set Item,
cart : Token -> (Item+String),
token : lone Token,
nextToken : Token,
}
Action Predicates
Later we define a trace of actions. A trace is a sequence of State atoms. We require
that the trace is constrained by the action predicates. That is, going from Staten to
Staten+1 requires that one of the action predicates is true
.
Login
The first action predicate is login
. A browser gives us a userid and a password. These credentials
must be authenticated before the login succeeds.
If this login is successful then we require a cookie with a token. This token is a capability, a browser that has that token can then buy in the store.
The login fails if the user is already logged in. It might be interesting to succeed a failed attempt due to lack of proper credentials. This would then record the attack in the trace. That is not done here.
In the cart we record the user id as the first bought item, this is a bit hackish. It records the name of the user of that cart, but it also creates at least one entry in the cart. I.e. it activates the cart. (Never realized you could hack in models?)
pred login[ s, s' : lone State ] {
let browser = s'.browser,
userid = browser.userid,
password = browser.password {
one userid
one password
no s.cart.userid
authenticate[ userid,password ]
s'.action = LOGIN
s'.nextToken = s.nextToken.next
s'.stock = s.stock
s'.cart = s.cart + (s.nextToken->userid)
s'.token = s.nextToken
s'.bought = none
s'.cookies = s.cookies + (browser->s.nextToken)
}
}
Buy
After login the browser has a cookie with a token. We use this token to record the sale in the corresponding cart.
pred buy[ s, s' : State ] {
let item = s'.bought,
tkn = s'.token {
some item
item in s.stock
one tkn
some s.cart[tkn]
s'.action = BUY
s'.nextToken = s.nextToken
s'.stock = s.stock-item
s'.cart = s.cart + (tkn -> item)
s'.token = tkn
s'.bought = item
s'.cookies = s.cookies
}
}
Stutter
A stutter action is there to fill gaps. If the trace cannot make progress then it can always
provide a stutter step. They are kind of annoying because a trace with just stutters is
then a perfectly valid solution. You have to filter out those solutions with your run
command.
For check
commands they are generally irrelevant.
pred stutter[s, s' : State ] {
s'.action = STUTTER
s'.nextToken = s.nextToken
s'.stock = s.stock
s'.cart = s.cart
s'.browser = none
s'.token = none
s'.bought = none
s'.cookies = s.cookies
}
The Clients
We’re now ready to define the client side. Our model is that we have a Browser, a.k.a the User Agent.
We’re cheating a bit because we do not record the cookies in the browser, we rely on the shared state to store the cookies. (This is dangerous because it is now easy to make facts that assume the server knows about the browser.)
We are also defining two users: Alice and Eve. Alice is the good girl and gets a password. Eve is evil and we do not constrain her use of credentials. In practice, this means that Alloy will use all possible credentials for Eve.
abstract sig Browser {
userid : String,
password : String
}
one sig Alice extends Browser {} {
userid = "Alice"
password = "Bob123"
}
one sig Eve extends Browser {}
Trace
We now create a trace of every combination of all possible actions. We constrain
the actions to those allowed by the action predicates login
, buy
, and stutter
.
That is, each action predicate is used to constrain Staten -> Staten+1
fact {
st/first.nextToken = tk/first
st/first.stock = Item
no st/first.bought
no st/first.cart
no st/first.browser
no st/first.cookies
st/first.action = INIT
all s' : State - first, s : s'.prev {
login[s,s']
or
buy[s,s']
or
stutter[s,s']
}
}
Checking
We do not want Eve to be able to buy anything, only Alice’s credentials are recorded in the password database so Eve should not be able to login nor buy anything.
So we can check that Eve can never buy anything nor can she login:
assert Evil {
no s : State | s.browser = Eve
}
check Evil for 4
If we run this then it gives us the following output.
┏━━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━┳━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━┓
┃this/State┃browser┃action ┃bought┃cookies ┃stock┃cart ┃token ┃nextToken┃
┠──────────╊━━━━━━━╋━━━━━━━━╋━━━━━━╋━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━╋━━━━━━╋━━━━━━━━━┫
┃State⁰ ┃ ┃INIT⁰ ┃ ┃ ┃Item⁰┃ ┃ ┃Token⁰ ┃
┠──────────╊━━━━━━━╋━━━━━━━━╋━━━━━━╋━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━╋━━━━━━╋━━━━━━━━━┫
┃State¹ ┃ ┃STUTTER⁰┃ ┃ ┃Item⁰┃ ┃ ┃Token⁰ ┃
┠──────────╊━━━━━━━╋━━━━━━━━╋━━━━━━╋━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━╋━━━━━━╋━━━━━━━━━┫
┃State² ┃ ┃STUTTER⁰┃ ┃ ┃Item⁰┃ ┃ ┃Token⁰ ┃
┠──────────╊━━━━━━━╋━━━━━━━━╋━━━━━━╋━━━━┯━━━━━━╋━━━━━╋━━━━━━┯━━━━━━━╋━━━━━━╋━━━━━━━━━┫
┃State³ ┃Eve⁰ ┃LOGIN⁰ ┃ ┃Eve⁰│Token⁰┃Item⁰┃Token⁰│"Alice"┃Token⁰┃Token¹ ┃
┗━━━━━━━━━━┻━━━━━━━┻━━━━━━━━┻━━━━━━┻━━━━┷━━━━━━┻━━━━━┻━━━━━━┷━━━━━━━┻━━━━━━┻━━━━━━━━━┛
You can see this also in the Browser table. We see that Eve has stolen the credentials of Alice.
┏━━━━━━━━━━━━┳━━━━━━━┳━━━━━━━━┓
┃this/Browser┃userid ┃password┃
┠────────────╊━━━━━━━╋━━━━━━━━┫
┃Alice⁰ ┃"Alice"┃"Bob123"┃
┠────────────╊━━━━━━━╋━━━━━━━━┫
┃Eve⁰ ┃"Alice"┃"Bob123"┃
┗━━━━━━━━━━━━┻━━━━━━━┻━━━━━━━━┛
Attacks
This is a bit of a conumdrum but it shows the power of Alloy. This immediately shows how fragile passwords are. In the real world we demand from the users that they keep their passwords secret. We therefore record this (stupid!) assumption in a fact.
fact NoSharedPassword {
// all disj b1, b2 : Browser | b1.userid = b2.userid => b1.password != b2.password
}
Uncomment the previous fact and the run the EveBuying command again. This gives the following output
┏━━━━━━━━━━┳━━━━━━━┳━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━┳━━━━━┳━━━━━━━━━━━━━━┳━━━━━━┳━━━━━━━━━┓
┃this/State┃browser┃action ┃bought┃cookies ┃stock┃cart ┃token ┃nextToken┃
┠──────────╊━━━━━━━╋━━━━━━━━╋━━━━━━╋━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━╋━━━━━━╋━━━━━━━━━┫
┃State⁰ ┃ ┃INIT⁰ ┃ ┃ ┃Item⁰┃ ┃ ┃Token⁰ ┃
┠──────────╊━━━━━━━╋━━━━━━━━╋━━━━━━╋━━━━━━━━━━━━━╋━━━━━╋━━━━━━━━━━━━━━╋━━━━━━╋━━━━━━━━━┫
┃State¹ ┃ ┃STUTTER⁰┃ ┃ ┃Item⁰┃ ┃ ┃Token⁰ ┃
┠──────────╊━━━━━━━╋━━━━━━━━╋━━━━━━╋━━━━━━┯━━━━━━╋━━━━━╋━━━━━━┯━━━━━━━╋━━━━━━╋━━━━━━━━━┫
┃State² ┃Alice⁰ ┃LOGIN⁰ ┃ ┃Alice⁰│Token⁰┃Item⁰┃Token⁰│"Alice"┃Token⁰┃Token¹ ┃
┠──────────╊━━━━━━━╋━━━━━━━━╋━━━━━━╋━━━━━━┿━━━━━━╋━━━━━╋━━━━━━┿━━━━━━━╋━━━━━━╋━━━━━━━━━┫
┃State³ ┃Eve⁰ ┃BUY⁰ ┃Item⁰ ┃Alice⁰│Token⁰┃ ┃Token⁰│"Alice"┃Token⁰┃Token¹ ┃
┃ ┠───────╂────────╂──────╂──────┴──────┨ ┃ ├───────╂──────╂─────────┨
┃ ┃ ┃ ┃ ┃ ┃ ┃ │Item⁰ ┃ ┃ ┃
┗━━━━━━━━━━┻━━━━━━━┻━━━━━━━━┻━━━━━━┻━━━━━━━━━━━━━┻━━━━━┻━━━━━━┷━━━━━━━┻━━━━━━┻━━━━━━━━━┛
Since Eve can no longer succeed in using Alice’s credentials, it tries to steal the token that the server handed to Alice’s browser. It can do this by sniffing in on a wireless network at Starbucks when Alice uses normal HTTP, not encrypted HTTPS.
It is interesting to see that the item ends up in Alice’s cart.
So we need to record a fact that our model can assume that in the real world the cookies are protected using HTTPS and cannot be guessed. In our model that means we can ‘trust’ the cookies to be protected. (Another indication of the fragility of the web.)
Again uncomment for making it active.
fact HTTPS {
// all s : State | one s.token implies s.token = s.cookies[s.browser]
}
Instead of doing a run, we now want to make sure Eve cannot buy anything ever … So if you run the check again it should fail to find a counter example.
So now we know the web is safe! #not