I'm having a difficult time deleting all cookies using the following code. What seems to happen is that the domain is changed to append a dot in front of it. Instead of deleting all cookies, I get duplicate cookies with slightly different domains. Is there any way to completely remove all cookies no matter what their domain looks like?
Thanks for any help!
//DeleteCookies deletes all cookies
func DeleteCookies(w http.ResponseWriter, r *http.Request) {
for _, c := range r.Cookies() {
deleted := &http.Cookie{
Name: c.Name,
Path: c.Path,
//Expires: time.Unix(0, 0),
MaxAge: -10,
HttpOnly: c.HttpOnly,
Domain: c.Domain,
Secure: c.Secure,
Value: "",
}
http.SetCookie(w, deleted)
}
}
What you try to do doesn't work well as cookies do not work that way.
The easy things first:
HttpOnly, Domain and Secure: These values are not transmitted in a HTTP client request, these fields of c will always be empty. The client uses these fields to determine whether to send a (Name,Value)-pair in the Cookie header or not but doesn't send these values.
For HttpOnly, Secure (and SameSite) this does not matter as these (and MaxAge and Expires) do not contribute to the cookie identity.
Cookie identity is based on the tripple (Domain,Path,Name) as sent in a SetCookie header. Often Domain and Path are implicit but they do have defined values on the client.
Now to delete a cookie with identity (Domain-X, Path-X, Name-X) you must send a cookie with the same identity (Domain-X, Path-X, Name-X) and MaxAge=-1. But as explained above the cookie you receive doesn't contain Domain and Path.
There are two ways out:
You must know whether your cookies are domain or host cookies and which path they were set for and use that information to delete them. (I would recommend this.)
Delete all possible cookies. Upon a request to path /foo/bar/wuz the cookies from the client might stem from path /, /foo or /foo/bar (if I remember correctly; test and look it up in RFC 6265). So delete the cookie with name "Name-X" for all these paths. Do the same for the Domain attribute which unfortunately is more complicated. Delete the host cookie (Domain=="") and delete the domain cookies (Domain!=""). Make sure to get the right domain name (the effective TLD plus one).
As you see 2 is pretty complicated. But that is how cookies are designed: The server is expected to know what cookies the servers sets i.e. the server is expected to know the cookie identity (Domain,Path,Name) of all its cookies. The responsibility of the client is to send back the appropriate (Name,Value) pair for a certain request only. If the server wishes to delete a cookie it just sets MaxAge of that cookie to -1. Note that "that cookie" is something the server is expected to know and not infer from a client request.
Related
A couple of tutorials on oAuth use the Flask session to store state parameters and access tokens in the flask session. (Brendan McCollam's very useful presentation from Pycon is an example)
I understand that Flask stores the session in cookies on the client side and that they are fairly easy to expose (see Michael Grinberg's how-secure-is-the-flask-user-session). I tried this myself and was able to see the token the expiration, etc.
Is it correct to store the state and tokens in the flask session or they should be stored somewhere else?
Code example:
#app.route('/login', methods=['GET'])
def login():
provider = OAuth2Session(
client_id=CONFIG['client_id'],
scope=CONFIG['scope'],
redirect_uri=CONFIG['redirect_uri'])
url, state = provider.authorization_url(CONFIG['auth_url'])
session['oauth2_state'] = state
return redirect(url)
#app.route('/callback', methods=['GET'])
def callback():
provider = OAuth2Session(CONFIG['client_id'],
redirect_uri=CONFIG['redirect_uri'],
state=session['oauth2_state'])
token_response = provider.fetch_token(
token_url=CONFIG['token_url'],
client_secret=CONFIG['client_secret'],
authorization_response=request.url)
session['access_token'] = token_response['access_token']
session['access_token_expires'] = token_response['expires_at']
transfers = provider.get('https://transfer.api.globusonline.org/v0.10/task_list?limit=1')
return redirect(url_for('index'))
#app.route('/')
def index():
if 'access_token' not in session:
return redirect(url_for('login'))
transfers = requests.get('https://transfer.api.globusonline.org/v0.10/task_list?limit=1',
headers={'Authorization': 'Bearer ' + session['access_token']})
return render_template('index.html.jinja2',
transfers=transfers.json())
I think some tutorials over-simplify in order to show simpler code. A good rule of thumb is to use session cookies only for information that MUST be known by your application and your user's browser, and is not private. That normally translates into a Session ID and possibly other non sensitive information such as a language selection.
Applying that rule of thumb, I'd suggest the next to each of the tokens:
Authorization Token: this data is by definition known to both the user and the application, so it shouldn't be a security concern to expose it in the cookie. However, there really is no need to keep this token once you're given an access code, so I advice against keeping it locally or in your cookies.
Access Code: this data must be considered secret, and must only be known by your application and the provider. There is no reason to make it know to any other parties, including the user, therefore it should NOT be included in cookies. If you need to store it, keep it locally in your servers (perhaps in your database, referencing your users session ID).
CSRF State Token: this data is ideally included as a hidden form field and validated against a server side variable, so cookies seem like an unnecessary complication. But I wouldn't be concerned about this data being in a cookie, since it's part of the response anyways.
Keep in mind there are extensions such as flask-sessions, with which practically the same code uses server side variables instead of cookie variables.
It looks like cookieJar.saveFromResponse(...) will not be called if Cookie.parseAll(...) returns an empty Cookie List: (https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/internal/http/HttpEngine.java#L867)
and Cookie.parseAll(...) excludes response cookies based on domain matching and other logic:(https://github.com/square/okhttp/blob/master/okhttp/src/main/java/okhttp3/Cookie.java#L302)
Is it possible to bypass the Cookie.parse domain matching logic so that cookieJar.saveFromResponse(...) is called with response cookies that haven't been whittled down by domain?
I'm trying to set a persistent cookie for several consecutive requests. The idea is that if a request does not contain the cookie in the first place, a uuid4 identifier is set in a cookie under 'mykey' and the response is sent. This all happens via requests made to a REST api from a javascript client.
I can see the cookie when inspecting the response on my browser, but if I issue a second request—which should not set a new cookie, as 'mykey' is already populated—the cookie is reset with a new uuid4 identifier.
This is the code:
def some_view(request):
cookie = request.get_signed_cookie('mykey', salt='foobar', default=False)
# do stuff
response = HttpResponse(content='foo')
if not cookie:
value = str(uuid.uuid4())
response.set_signed_cookie('mykey', value, salt='foobar')
return response
Any ideas? Thnx!
A.
With the signed cookies, you are probably running into problems with HTTPOnly. You could try this:
set_signed_cookie(key, value, salt='', httponly=False)
Currently, my software has the following workflow
User performs an search through a REST API and selects an item
Server performs the same search again to validate the user's selection
In order to implement step 2, the user has to send the URL params that he used for his search as a string (ex. age=10&gender=M).
The server will then http_get(url + "?" + params_str_submitted_by_user)
Can a malicious user make the server connect to an unintended server by manipulating params_str_submitted_by_user?
What is the worst case scenario if even newlines are left in and the user can arbitrarily manipulate the HTTP headers?
As you are appending params_str_submitted_by_user to the base URL after the ? delimiter, you are safe from this type of attack used where the context of the domain is changed to a username or password:
Say URL was http://example.com and params_str_submitted_by_user was #evil.com and you did not have the / or ? characters in your URL string concatenation.
This would make your URL http://example.com#evil.com which actually means username example.com at domain evil.com.
However, the username cannot contain the ? (nor slash) character, so you should be safe as you are forcing the username to be concatenated. In your case URL becomes:
http://example.com?#evil.com
or
http://example.com/?#evil.com
if you include the slash in your base URL (better practise). These are safe as all it does is pass your website evil.com as a query string value because #evil.com will no longer be interpretted as a domain by the parser.
What is the worst case scenario if even newlines are left in and the user can arbitrarily manipulate the HTTP headers?
This depends on how good your http_get function is at sanitizing values. If http_get does not strip newlines internally it could be possible for an attacker to control the headers sent from your application.
e.g. If http_get internally created the following request
GET <url> HTTP/1.1
Host: <url.domain>
so under legitimate use it would work like the following:
http_get("https://example.com/foo/bar")
generates
GET /foo/bar HTTP/1.1
Host: example.com
an attacker could set params_str_submitted_by_user to
<space>HTTP/1.1\r\nHost: example.org\r\nCookie: foo=bar\r\n\r\n
this would cause your code to call
http_get("https://example.com/" + "?" + "<space>HTTP/1.1\r\nHost: example.org\r\nCookie: foo=bar\r\n\r\n")
which would cause the request to be
GET / HTTP/1.1
Host: example.org
Cookie: foo=bar
HTTP/1.1
Host: example.com
Depending on how http_get parses the domain this might not cause the request to go to example.org instead of example.com - it is just manipulating the header (unless example.org was another site on the same IP address as your site). However, the attacker has managed to manipulate headers and add their own cookie value. The advantage to the attacker depends on what can be gained under your particular setup from them doing this - there is not necessarily any general advantage, it would be more of a logic flaw exploit if they could trick your code into behaving in an unexpected way by causing it to make requests under the control of the attacker.
What should you do?
To guard against the unexpected and unknown, either use a version of http_get that handles header injection properly. Many modern languages now deal with this situation internally.
Or - if http_get is your own implementation, make sure it sanitizes or rejects URLs that contain invalid characters like carriage returns or line feeds and other parameters that are invalid in a URL. See this question for list of valid characters.
At the end of an OAuth2 token exchange, I'm [typically] left with a JSON array of user data that I've un-marshalled into a struct (say, GoogleUser) with the fields I care about.
What is the sensible way of recording that data to my DB? Just call a CreateUser function from the callback handler, pass the struct and save it (the obvious way to me), after checking that the user doesn't already exist in the DB?
I assume I should then create a session token (i.e. session.Values["authenticated"] == true) in the callback handler, store that in a cookie (with a reasonable expiry date) and simply just check for if authenticated == true on any handler functions that expect a logged-in user? Or, for admin handlers: if admin_user == true. What are the risks here (if any) presuming I'm talking over HTTPS and using secure cookies?
Apologies for the basic questions: just trying to get a grip on "best practice" ways to log users in w/ OAuth.
With regards to your first question, It's usually recommended to do the check and insert in a single transaction. It depends on what DB you're using, but these are usually referred to as UPSERT statements. In PLSQL it looks a bit like this (modify to taste):
CREATE FUNCTION upsert_user(emailv character varying, saltv character varying, hashv character varying, date_createdv timestamp without time zone) RETURNS void
LANGUAGE plpgsql
AS $$;
BEGIN
LOOP
-- first try to update the key
UPDATE users SET (salt, hash) = (saltv, hashv) WHERE email = emailv;
IF found THEN
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently,
-- we could get a unique-key failure
BEGIN
INSERT INTO users(email, salt, hash, date_created) VALUES (emailv, saltv, hashv, date_createdv);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing, and loop to try the UPDATE again
END;
END LOOP;
END;
$$;
In regards to your second question, usually Secure cookies over HTTPS is enough. I'd set the HttpOnly option, and usually the Path option as well.
HttpOnly means that the cookie can't be accessed by JS (only HTTP or HTTPS), and the Path option allows you to specify what path (in the URL) the cookie is valid for.
The Access Token in OAuth standard have a expiry. It's usually determined by authorization server. In your case I assume you are on authorization server side.
Read RFC 6750 for example:
Typically, a bearer token is returned to the client as part of anOAuth 2.0 [RFC6749] access token response. An example of such a response is:
HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache
{
"access_token":"mF_9.B5f-4.1JqM",
"token_type":"Bearer",
"expires_in":3600,
"refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA"
}
Also read concept of Access Token in RFC 6749:
The access token provides an abstraction layer, replacing different
authorization constructs (e.g., username and password) with a single
token understood by the resource server. This abstraction enables
issuing access tokens more restrictive than the authorization grant
used to obtain them, as well as removing the resource server's need
to understand a wide range of authentication methods.
So in your case, I don't think a "cookie" or "admin handler" is needed. You only have to generate Access Token & Refresh Token for each users logged in just like OAuth spec says, and store its expiry as well. You can also provide a hash method related with Access Token to make sure it's a legal request. For example, users use their access token to generate a signature with hash & salt method, send access token & signature to server to verify. Read Public Key Encryption for more details.
Furthermore, you don't need to save these tokens into your DB because they are all temporary resources. You can also save all user informations in memory and implement a cache layer to save these informations which truly important into DB periodically(which I'm currently using now) to lower DB pressure.