Set "secure" attribute for Flask cookies - python

I am running a Flask app using uWSGI and Nginx. I want make it compliant with PCI DSS. Running the scan gives the error Cookie Does Not Contain The "secure" Attribute. How do I set the secure attribute for cookies in Flask?
I have added the following line in my Nginx file but it didn't work.
proxy_cookie_path / "/; secure;";

The secure flag for Flask's session cookie can be enabled in the Flask configuration.
SESSION_COOKIE_SECURE = True
To set it for other cookies, pass the secure flag to response.set_cookie.
response = app.make_response('<p>Hello, World!</p>')
response.set_cookie('name', 'World', secure=True)

Related

Flaskdance doesn't generate a HTTPS uri

I'm trying to set up Google sign-in using Flask dance for a flask based website:
from flask_dance.contrib.google import make_google_blueprint, google
blueprint = make_google_blueprint(
client_id= "CLIENT_ID",
client_secret="CLIENT_SECRET",
scope=[
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/userinfo.email",
]
)
app.register_blueprint(blueprint, url_prefix="/google_login")
And as the documentation suggests, I have the view set up like this:
#app.route('/google_login')
def google_login():
if not google.authorized:
return redirect(url_for("google.login"))
resp = google.get("/oauth2/v2/userinfo")
assert resp.ok, resp.text
return "You are {email} on Google".format(email=resp.json()["email"])
When I was testing I set the environment variable, OAUTHLIB_INSECURE_TRANSPORT to 1 by using
export OAUTHLIB_INSECURE_TRANSPORT=1
And now even after I've removed the environment variable, for some reason the Flaskdance seems to always resolve the URI to a http instead of HTTPS.
This is evident from the redirect uri mismatch error I'm getting (here website refers to the domain name):
The redirect URI in the request,
http://"website"/google_login/google/authorized, does not match
the ones authorized for the OAuth client.
And here are the authorized redirect URIs I've set up in my Google cloud console:
https://"website"/google_login/google/authorized
https://www."website"/google_login/google/authorized
I tried unsetting the environment variable using this command:
unset OAUTHLIB_INSECURE_TRANSPORT
What am I missing here? Any help would be appreciated.
If Flask-Dance is generating http URLs instead of https, that indicates that Flask (not Flask-Dance, but Flask itself) is confused about whether the incoming request is an https request or not. Flask-Dance has some documentation about how to resolve this problem, and the most likely cause is a proxy server that handles the HTTPS separately from your application server.
The fix is to use a middleware like werkzeug's ProxyFix to teach Flask that it's behind a proxy server. Here's how you can use it:
from werkzeug.middleware.proxy_fix import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, x_for=1, x_proto=1)
I had the same problem and in my case adding this to my Apache VirtualHost config solved it:
RequestHeader set X-Forwarded-Proto "https"
My Flask is running behind an Apache proxy but Nginx would also have similar issues, potentially.

SESSION_COOKIE_SECURE does not encrypt session

I'm trying to put some security on my Flask web app. As a first step I'm going to make my session cookie secure by setting SESSION_COOKIE_SECURE to true.
But after I get my session cookie from "inspect element" I can decode session cookie easily and there is no difference whether I add SESSION_COOKIE_SECURE or not.
Here is my code:
from flask import Flask, request, app, render_template, session, send_file, redirect
MyApp = Flask(__name__)
MyApp.secret_key = "something"
application = MyApp
if __name__ == "__main__":
MyApp.debug = False
MyApp.config.update(
SESSION_COOKIE_SECURE=True,
SESSION_COOKIE_HTTPONLY=True,
SESSION_COOKIE_SAMESITE='Lax',
)
MyApp.config["SESSION_PERMANENT"] = True
MyApp.run()
I also tried to add this attribute using the following syntax but this made no difference:
MyApp.config['SESSION_COOKIE_SECURE'] = True
When I try to print SESSION_COOKIE_SECURE I get this error
Traceback (most recent call last):
File "...", line ..., in <module>
print(MyApp.session_cookie_secure)
AttributeError: 'Flask' object has no attribute 'session_cookie_secure'
My Flask version is 1.0.2, and I'm on HTTPS.
Setting SESSION_COOKIE_SECURE does not encrypt the cookie value, no. When set, this causes Flask to create cookies with the "Secure" flag set. This means that a browser can only return the cookie to the server over an encrypted connection, nothing more. The setting doesn't change anything about the cookie value itself.
Flask produces cookies that are cryptographically signed, by default. That means that the cookie contents can be decoded but not altered, because a third party without access to the server secret can't create a valid signature for the cookie.
You generally don't need to encrypt your session cookie if you a) use HTTPS (which encrypts the data from outsiders) and b) protect your web app from XSS attacks. Without an XSS attack vector, attackers can't get access to your cookie contents at all anyway.
You certainly don't need to do so here, as SESSION_COOKIE_HTTPONLY means that the browser will never expose the cookie to JavaScript, and only someone with full access to the browser can see the cookie value.
Flask doesn't have a 'encrypt cookie' setting, because it is not deemed necessary when you can secure the cookie in other ways. You should not store information in a session cookie so sensitive that it should be protected from the end-user with access to the browser storage; keep such data on the server and only store a unique identifier in the session to retrieve that secret data later on.
If for some reason you can't keep such secrets out of the session cookie and are unwilling to accept that the end-user can read this data, then you'll have to encrypt the cookie yourself or use an alternative session provider for Flask, such as EncryptedSession.
As for the attribute error: only a few configuration settings are accessible as attributes on the Flask object. To print arbitrary configuration settings, use the app.config object:
print(MyApp.config['SESSION_COOKIE_SECURE'])

Cookie not setting with Flask

I am trying to set a cookie with Flask after login and redirect on the front end in Javascript.
#app.route("/login")
#auth.login_required
def get_auth_token():
token = g.user.generate_auth_token()
request = make_response()
token = str(token.decode("ascii"))
request.set_cookie("token", value = token)
return request, 200
No matter if I have the redirect in or not, the cookie never sets. I've tried commenting out my redirect on the front end, I've tried setting my cookie with secure = false but none of that seems to work. What am I missing? If needed, I can provide the generate_suth_token function, but I know that is working properly. I am serving on localhost:5000 and using Flask 0.12.2, and received no cookie warnings in the server log.
If Flask service and client service are being hosted on different domains (e. g Flask uses 127.0.0.1:8080 and a client uses 127.0.0.1:3000) in this case, cookies should be set with domain parameter otherwise they will not be available.
resp.set_cookie('cookie_key', value="cookie_value", domain='127.0.0.1')
Find more info about domain parameter here

How to force Google App Engine [python] to use SSL (https)?

I write a web app on Google App Engine using Python.
Users can access my site at http://[youraccount].appspot.com and https://[youraccount].appspot.com
How do I redirect the http traffic to the https site.
In other words, how do I force this site to use SSL(https) for security purpose (and for better SEO)?
Just add a secure parameter to the app.yaml file.
handlers:
- url: /youraccount/.*
script: accounts.py
login: required
secure: always
See Configuring Secure URLs in app.yaml
Google App Engine supports secure connections via HTTPS for URLs using
the *.appspot.com domain. When a request accesses a URL using HTTPS,
and that URL is configured to use HTTPS in the app.yaml file, both the
request data and the response data are encrypted by the sender before
they are transmitted, and decrypted by the recipient after they are
received. Secure connections are useful for protecting customer data,
such as contact information, passwords, and private messages.
For a Django project running on Google App Engine in the Flexible Environment, setting secure: always in app.yaml doesn't work [Google Cloud docs].
Instead, in my settings.py file, I added the following [Django docs]:
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
Note that SECURE_PROXY_SSL_HEADER is needed because the servers sit behind a load balancer.

Why does the session cookie work when serving from a domain but not when using an IP?

I have a Flask application with sessions that works well on my local development machine. However, when I try to deploy it on an Amazon server, sessions do not seem to work.
More specifically, the session cookie is not set. I can, however, set normal cookies. I made sure I have a static secure key, as others have indicated that might be an issue. The only difference is in how the server is set up. During development, I use
app.run()
to run locally. When deployed, I use
app.config['SERVER_NAME'] = '12.34.56.78' # <-- insert a "real" IP
app.run(host='0.0.0.0', port=80)
I suspect the problem might be in the above, but am not completely certain.
The session does seem to work on Firefox, but not Chrome.
The following small application demonstrates the problem, with the configuration differences at the bottom:
from flask import Flask, make_response, request, session
app = Flask(__name__)
app.secret_key = 'secretKey'
# this is to verify that cookies can be set
#app.route('/setcookie')
def set_cookie():
response = make_response('Cookie set')
response.set_cookie('cookie name', 'cookie value')
return response
#app.route('/getcookie')
def get_cookie():
if 'cookie name' in request.cookies:
return 'Cookie found. Its value is %s.' % request.cookies['cookie name']
else:
return 'Cookie not found'
# this is to check if sessions work
#app.route('/setsession')
def set_session():
session['session name'] = 'session value'
return 'Session set'
#app.route('/getsession')
def get_session():
if 'session name' in session:
return 'Session value is %s.' % session['session name']
else:
return 'Session value not found'
if __name__ == '__main__':
app.debug = True
# windows, local development
#app.run()
# Ubuntu
app.config['SERVER_NAME'] = '12.34.56.78' # <-- insert a "real" IP
app.run(host='0.0.0.0', port=80)
This is a "bug" in Chrome, not a problem with your application. (It may also affect other browsers as well if they change their policies.)
RFC 2109, which describes how cookies are handled, seems to indicate that cookie domains must be an FQDN with a TLD (.com, .net, etc.) or be an exact match IP address. The original Netscape cookie spec does not mention IP addresses at all.
The Chrome developers have decided to be more strict than other browsers about what values they accept for cookie domains. While at one point they corrected a bug that prevented cookies on IP addresses, they have apparently backpedaled since then and don't allow cookies on non-FQDN domains (including localhost) or IP addresses. They have stated they will not fix this, as they do not consider it a bug.
The reason "normal" cookies are working but the session cookie is not is that you are not setting a domain for the "normal" cookies (it's an optional parameter), but Flask automatically sets the domain for the session cookie to the SERVER_NAME. Chrome (and others) accept cookies without domains and auto-set them to the domain of the response, hence the observed difference in behavior. You can observer normal cookies failing if you set the domain to the IP address.
During development, you can get around this by running the app on localhost rather than letting it default to 127.0.0.1. Flask has a workaround that won't send the domain for the session cookie if the server name is localhost. app.run('localhost')
In production, there aren't any real solutions. You could serve this on a domain rather than an IP, which would solve it but might not be possible in your environment. You could mandate that all your clients use something besides Chrome, which isn't practical. Or you could provide a different session interface to Flask that does the same workaround for IPs that it already uses for localhost, although this is probably insecure in some way.
Chrome does not allow cookies with IPs for the domain, and there is no practical workaround.
It is possible to create session in the Chrome browser using IP.
My config file has these configurations:
SERVER_NAME = '192.168.0.6:5000'
SESSION_COOKIE_DOMAIN = '192.168.0.6:5000'
It allowed me to use a local virtual machine and the cookie worked perfectly on Chrome, without the need for a local FQDN.
Notice that in the workaround for localhost that #davidism posted -- https://github.com/mitsuhiko/flask/blob/master/flask/sessions.py#L211-L215 , you can patch the Flask code and change if rv == '.localhost': rv = None to simply rv = None and then the cookie domain won't be set and your cookies will work.
You wouldn't want to do this on a real production app, but if your server is just some kind of testing/staging server without sensitive data it might be fine. I just did this to test an app over a LAN on a 192.168.x.x address and it was fine for that purpose.

Categories

Resources