Flask message flashing fails across redirects - python

I'm currently working on a project using Flask and Google App Engine. Calling get_flashed_messages() returns empty when I flash a message then use a redirect():
#views.route('/todo/add', methods=["POST"])
def add_todo():
flash('hey')
return redirect(url_for('todo_list'))
However, if I comment out
# SERVER_NAME = 'localhost'
then it seems to work fine. My problem is that I have to use subdomains so I need SERVER_NAME to be set.
What is the deal?

I got it!
The trick is to set server name to something with dots.
So 'localhost' became 'app.local' and app.local should be added to /etc/hosts, pointing to the same address as localhost.
From the docs:
Please keep in mind that not only
Flask has the problem of not knowing
what subdomains are, your web browser
does as well. Most modern web browsers
will not allow cross-subdomain cookies
to be set on a server name without
dots in it. So if your server name is
'localhost' you will not be able to
set a cookie for 'localhost' and every
subdomain of it. Please chose a
different server name in that case,
like 'myapplication.local' and add
this name + the subdomains you want to
use into your host config or setup a
local bind.

did you set up cookies to work across all subdomains?
by default they are only readable on the domain that set them

Related

Flask app localhost is not a valid cookie domain

I need some help troubleshooting this userwarning message on my flask app. I am unfamiliar with this territory and couldn't find anything on the internet. The app is running in development mode and I am not using any cookies or sessions in my app. All it does is wait for client-side query to upload data to database. I set in the config.py, SERVER_NAME equal to localhost:5000. What do I need to do to get rid of this warning? Thanks for the help.
/miniconda3/envs/rq-redis/lib/python3.7/site-packages/flask/sessions.py:211:
UserWarning: "localhost" is not a valid cookie domain, it must contain a ".".
Add an entry to your hosts file, for example "localhost.localdomain", and use that instead.
' "{rv}.localdomain", and use that instead.'.format(rv=rv)
I was able to disable the warning by deleting the SERVER_NAME from the config file and use the built-in server which is good for development only.

Very Lost: Flack and NYU Osiris Challenges

I am going through some CTF challenges at https://recruit.osiris.cyber.nyu.edu/challenges.
I got to one for Template Programming where the task is to "Read /flag.txt from the server. http://recruit.osiris.cyber.nyu.edu:2000"
I am not asking for a solution, but I would like some better understanding of what is going on below:
What is this code doing?
Should I be worried about running out of Debugging mode and/or using host="0.0.0.0"?
What are some resources that could help me understand this? I tried reading through the Flask documentation and the tutorialspoint page, but I am unclear as to how this doesn't just set up a local server for testing as opposed to accessing a remote server...
If I ctrl+C do I need to worry about leaving a server still running on an open port when I am not in Debugging mode?
#!/usr/bin/env python3
from flask import Flask, request, abort, render_template_string
import os.path
app = Flask(__name__)
#app.route('/', methods=['GET'])
def index():
name = request.args.get('name')
if name is not None:
return render_template_string(open('templates/hello.html').read().format(name=name))
return render_template_string(open('templates/index.html').read())
if __name__ == "__main__":
app.run(host="0.0.0.0")
I think I can answer most of these.
As you probably already figured out, Flask is a fairly basic web framework. By the look of things, what you have there is a copy of the code running at the CTF site. It displays just two pages; one that contains the initial web form (templates/index.html) and another that uses a query string variable to greet the user (templates/hello.html) when a name has been provided.
You don't really have to run this code yourself. The 0.0.0.0 host address is catch-all that matches all IPv4 addresses on the local machine, which would include local addresses like 192.168.0.1 and 127.0.0.1 as well as the IP address used for incoming connections to the server.
Like I said, this is the code running on the remote server.
I think what you need to do is find some way of crafting a request to this web service in such a way that it reveals the contents of /flag.txt instead of (or perhaps in addition to) just saying hello. A quick search for something like "flask include file vulnerability" should give you some idea of how to attack this problem.

How to detect which of the two virtual hosts is being used in python and flask

I have a website developed in flask running on an apache2 server that responds on port 80 to two URLs
Url-1 http://www.example.com
Url-2 http://oer.example.com
I want to detect which of the two urls the user is coming in from and adjust what the server does and store the variable in a config variable
app.config['SITE'] = 'OER'
or
app.config['SITE'] = 'WWW'
Looking around on the internet I can find lots of examples using urllib2 the issue is that you need to pass it the url you want to slice and I cant find a way to pull that out as it may change between the two with each request.
I could fork the code and put up two different versions but that's as ugly as a box of frogs.
Thoughts welcome.
Use the Flask request object (from flask import request) and one of the following in your request handler:
hostname = request.environ.get('HTTP_HOST', '')
url = urlparse(request.url)
hostname = url.netloc
This will get e.g. oer.example.com or www.example.com. If there is a port number that will be included too. Keep in mind that this ultimately comes from the client request so "bad" requests might have it set wrong, although hopefully apache wouldn't route those to your app.

Can't reach Eve REST API

I'm using Eve to create a REST API for MongoDB.
It's all working fine, except for the fact that I can't reach the API from any other computer (in the same network), or even a different URL (e.g.: if I set SERVER_NAME = 'localhost:29000', I will not be able to reach the API with 127.0.0.1 and vice versa).
I've been looking around for hours, and I can't seem to find an answer. I also tried other REST API's for MongoDB like Kule, and they seem to work just fine, but they don't have as many options as Eve has.
Eve's SERVER_NAME seems to be based on the configuration variable by the same name from Flask: See "More on server name" below the table in the Flask Configuration docs. So it's really just for the name (hostname / subdomain handling) - the actual network interfaces it binds to therfore are probably determined by the server that runs the WSGI application.
If you're just doing the
app = Eve()
app.run()
from the quickstart example, try
app.run(host='0.0.0.0')
instead and leave the server name empty (SERVER_NAME = '').
I've never used Eve, but from what I understand about how it's built that should work

Flask request.remote_addr is wrong on webfaction and not showing real user IP

I just deployed a Flask app on Webfaction and I've noticed that request.remote_addr is always 127.0.0.1. which is of course isn't of much use.
How can I get the real IP address of the user in Flask on Webfaction?
Thanks!
If there is a proxy in front of Flask, then something like this will get the real IP in Flask:
if request.headers.getlist("X-Forwarded-For"):
ip = request.headers.getlist("X-Forwarded-For")[0]
else:
ip = request.remote_addr
Update: Very good point mentioned by Eli in his comment. There could be some security issues if you just simply use this. Read Eli's post to get more details.
Werkzeug middleware
Flask's documentation is pretty specific about recommended reverse proxy server setup:
If you deploy your application using one of these [WSGI] servers behind an HTTP [reverse] proxy you will need to rewrite a few headers in order for the application to work [properly]. The two problematic values in the WSGI environment usually are REMOTE_ADDR and HTTP_HOST... Werkzeug ships a fixer that will solve some common setups, but you might want to write your own WSGI middleware for specific setups.
And also about security consideration:
Please keep in mind that it is a security issue to use such a middleware in a non-proxy setup because it will blindly trust the incoming headers which might be forged by malicious clients.
The suggested code (that installs the middleware) that will make request.remote_addr return client IP address is:
from werkzeug.contrib.fixers import ProxyFix
app.wsgi_app = ProxyFix(app.wsgi_app, num_proxies=1)
Note num_proxies which is 1 by default. It's the number of proxy servers in front of the app.
The actual code is as follows (lastest werkzeug==0.14.1 at the time of writing):
def get_remote_addr(self, forwarded_for):
if len(forwarded_for) >= self.num_proxies:
return forwarded_for[-self.num_proxies]
Webfaction
Webfaction's documentation about Accessing REMOTE_ADDR says:
...the IP address is available as the first IP address in the comma separated list in the HTTP_X_FORWARDED_FOR header.
They don't say what they do when a client request already contains X-Forwarded-For header, but following common sense I would assume they replace it. Thus for Webfaction num_proxies should be set to 0.
Nginx
Nginx is more explicit about it's $proxy_add_x_forwarded_for:
the “X-Forwarded-For” client request header field with the $remote_addr variable appended to it, separated by a comma. If the “X-Forwarded-For” field is not present in the client request header, the $proxy_add_x_forwarded_for variable is equal to the $remote_addr variable.
For Nginx in front of the app num_proxies should be left at default 1.
Rewriting the Ignas's answer:
headers_list = request.headers.getlist("X-Forwarded-For")
user_ip = headers_list[0] if headers_list else request.remote_addr
Remember to read Eli's post about spoofing considerations.
You can use request.access_route to access list of ip :
if len(request.access_route) > 1:
return request.access_route[-1]
else:
return request.access_route[0]
Update:
You can just write this:
return request.access_route[-1]
The problem is there's probably some kind of proxy in front of Flask. In this case the "real" IP address can often be found in request.headers['X-Forwarded-For'].

Categories