Methods to protect a bottle.py webapplication against XSS and CSRF? - python

I'm writing a webapplication using bottle.py, Beaker for sessions and a custom AAA written module, as many I'm worried about security and the best method to protect against some targeted attack like the one I've mentioned.
As an example I have the following code:
#route('/manage/adddomain',method='POST')
def adddomain():
#This checks if user has enough power to create a domain
aaa.require(50,'/forbidden')
user = aaa.getusername() # This is retrieved from a server side session
domainname = request.forms.get('domain')
description = request.forms.get('description')
# Additional checks are performed in the sql module
# to protect against forged requests with valid login
return sql.createdomain(user,domainname,description)
What additional checks would you perform to secure your webapplication?

Blender pretty much covered what you need, but I would like to add another method. Instead of checking on each POST, you can add a wrapper like:
def wrap_requires_csrf(*methods):
def wrapper(fn):
#wraps(fn)
def wrapped(*args, **kwargs):
if request.method in methods:
if request.method == 'POST':
csrf = request.form.get('csrf')
elif request.method == 'GET':
csrf = request.args.get('csrf')
if not csrf or csrf != session.get('csrf'):
abort(400)
session['csrf'] = generate_csrf_token()
return fn(*args, **kwargs)
return wrapped
return wrapper
#app.route('/some/page', methods=['GET','POST'])
#wrap_requires_csrf('POST')
def some_page():
...
Then, in your template, you would provide the hidden field with
<input name="csrf" type="hidden" name="{{session.csrf}}" />

You need to include a CSRF token in every important form field and sanitize all output with your template engine.
Here's a Flask snippet that you can adapt to your Bottle app:
A common technique against CSRF attacks is to add a random string
to the session, and check that string against a hidden field in the
POST.
#app.before_request
def csrf_protect():
if request.method == "POST":
token = session.pop('_csrf_token', None)
if not token or token != request.form.get('_csrf_token'):
abort(403)
def generate_csrf_token():
if '_csrf_token' not in session:
session['_csrf_token'] = some_random_string()
return session['_csrf_token']
app.jinja_env.globals['csrf_token'] = generate_csrf_token
And then in your template:
<form method=post action="">
<input name=_csrf_token type=hidden value="{{ csrf_token() }}">
As for sanitizing, that's dependent upon your template engine. Jinja2 has the e or escape filter:
<h2>No results for {{ search_query|escape }}</h2>

I adapted #Blender's solution to bottle, and here it is:
from string import ascii_letters, digits
from random import choice
from bottle import Bottle, request, Jinja2Template, abort
app = Bottle()
#app.hook('before_request')
def csrf_protect():
if request.method == 'POST':
sess = request.environ['beaker.session']
req_token = request.forms.get('csrf_token')
# if no token is in session or it doesn't match the request one, abort
if 'csrf_token' not in sess or sess['csrf_token'] != req_token:
abort(403)
def str_random(length):
'''Generate a random string using range [a-zA-Z0-9].'''
chars = ascii_letters + digits
return ''.join([choice(chars) for i in range(length)])
def gen_token():
'''Put a generated token in session if none exist and return it.'''
sess = request.environ['beaker.session']
if 'csrf_token' not in sess:
sess['csrf_token'] = str_random(32)
return sess['csrf_token']
# allow access of the token generator using csrf_token
Jinja2Template.defaults['csrf_token'] = gen_token
Bottle does not come with sessions by default, so I'm using bottle-beaker.
The input which needs to be included in every form:
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">

Related

Submitting code created form data to POST

I'm trying to submit form data to a route, similar to how a browser would, but am having trouble. I've simplified the code provided to the minimal I need to reproduce the problem. I'm probably not using a function I should or doing something else wrong but I can't find the answer in all the other posts I've searched. I don't want the data to be on the URL but rather accessible via request.form. I am using Python 2.7.9. I know the code shown below currently does NOT do what I want (it puts the formdata dictionary on the command line) and returns an endless loop because the formdata is never read. I understand this, I just did this to show that I'm trying to pass the formdata to the post function, I just don't know how to do it and also use redirect at the same time. If I'm reading the redirect documentation correctly it seems to say you can't do this. So is there some other function I can use that will pass the parameters to a URL and also provide the form data? I'm not using the parameters in the code below but my actual code has a similar function structure where I do use them. The main goal here is to have the same function (URL path) used when "choice" is or is not submitted via a form. Any help is appreciated. Thanks.
from flask import Flask, redirect, url_for, request, render_template
web_server = Flask(__name__)
#web_server.route("/test_<param1>_<param2>",methods=['POST','GET'])
def test1(param1,param2):
if request.method == 'GET':
choice1 = 'No choice made yet'
templateData = {'choice':choice1}
return render_template('form_page.html', **templateData)
elif request.method == 'POST':
if 'choice' in request.form:
choice1 = request.form['choice']
templateData = {'choice':choice1}
return render_template('form_page.html', **templateData)
else:
formdata = {'choice':'QQQ'}
p1='AAA'
p2='BBB'
# the following line is NOT what I want to do. See question
return redirect(url_for('.test1',param1=p1,param2=p2,data=formdata),code=307)
# run server
if __name__ == "__main__":
web_server.run(host='0.0.0.0',port=80,debug=True)
HTML:
Choice was {{choice}}<br><br>
<form method="post">
Make a choice:
<input type="text" name="choice"><br><br>
<input type="submit">
</form>
<form method="post">
Do anything else:
<input type="text" name="other"><br><br>
<input type="submit">
</form>
lets see if this is what you mean, see quick and dirty code below. Have a close look at the request.args statement, that is where you can get the variables stored in the url.
from flask import Flask, redirect, url_for, request, render_template
app = Flask(__name__)
#app.route('/',methods=['POST','GET'])
def test1():
if request.method == 'POST':
if 'choice' in request.form:
choice = request.form['choice']
return render_template('test.html', choice = choice)
else:
choice = 'QQQ'
p1 = 'AAA'
p2 = 'BBB'
return redirect(url_for('.test1',param1 = p1, param2 = p2, choice = choice))
choice = request.args.get("choice")
if choice == None:
choice = 'No choice made yet'
return render_template('test.html', choice = choice)
if __name__ == "__main__":
app.run(host='0.0.0.0',port=5000,debug=True)

login_required encoding next parameter, redirect failing

I am using the Flask-login's #login_required decorator for some routes in my app. When navigating to those routes while not logged in I am redirected to the login page. So far so good.
The redirected url looks like this: /login?next=%2Fusers
Looks like it url-encoded the next parameter, something I haven't seen in the examples I've run across. After logging in the redirect back to next is always failing and falling back to the index page. I think this is because next is url-encoded.
Should I be going about this a different way? I'm just starting to dive into the Flask framework and working off of examples so I don't know much about the best ways to do things.
Here's an example route:
login_manager.login_view = 'login'
#app.route('users')
#login_required
def users():
return 'Test'
And my login route looks like this:
#app.route('/login')
def login():
error = None
next = request.args.get('next')
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
if authenticate_user(username, password):
user = User.query.filter_by(username=username).first()
if login_user(user):
flash("You have logged in")
session['logged_in'] = True
return redirect(next or url_for('index', error=error))
error = "Login failed"
return render_template('login.html', login=True, next=next, error=error)
Thanks in advance for the help.
I figured it out, navigating to a login-protected route (/requests in this example) would cause a redirect to the login page with the next parameter.
/login?next=%2Frequests
In my login template, I had this:
<form action="{{ url_for('login') }}" method='POST'>
This caused the form to be posted to the /login route without any parameters. Removing the action attribute or changing it to action="" causes the form to be posted to its own url, which includes the original query string. Another option would be including next=next in url_for.
<form action="{{ url_for('login', next=next) }}" method='POST'>
Here you go:
import urllib
#app.route('/login')
def login():
error = None
next = urllib.unquote_plus(request.args.get('next'))
...
In case anyone finds this question like I did, here was the implementation that worked for me. The issue was that the login's POST form was only going to /login, causing the next URL is get thrown away. Following Kevan's suggestion, if you add next=request.args.get('next')) to the login form's action, it will pass the next argument with it.
Inside my login function
#app.route('/login', methods = ['GET', 'POST'])
def login():
next = request.args.get('next')
if request.method == 'POST':
...Confirm User...
if next:
return redirect(next)
else:
return redirect('/')
Inside my login form
<form method="post" action="{{ url_for('login', next=request.args.get('next')) }}" enctype="multipart/form-data">
For security reasons, it should be considered the check for the next parameter, as suggested in
https://flask-login.readthedocs.io/en/latest/#login-example
#app.route('/login', methods=['GET', 'POST'])
def login():
# Here we use a class of some kind to represent and validate our
# client-side form data. For example, WTForms is a library that will
# handle this for us, and we use a custom LoginForm to validate.
form = LoginForm()
if form.validate_on_submit():
# Login and validate the user.
# user should be an instance of your `User` class
login_user(user)
flask.flash('Logged in successfully.')
next = flask.request.args.get('next')
# is_safe_url should check if the url is safe for redirects.
# See http://flask.pocoo.org/snippets/62/ for an example.
if not is_safe_url(next):
return flask.abort(400)
return flask.redirect(next or flask.url_for('index'))
return flask.render_template('login.html', form=form)

Flask-WTF SelectField with CSRF protection enabled

I'm having a problem when submitting a form containing a dynamically populated SelectField. For some reason when Flask tries to validate the CSRF token it always fails when the SelectField is in the form. When I remove the SelectField from the form, it validates the CSRF token successfully.
Has anyone come across this behavior?
EDIT
Form:
class AddToReportForm(Form):
selectReportField = SelectField(u'Reports',choices=[('test1','test')])
def __init__(self, *args, **kwargs):
"""
Initiates a new user form object
:param args: Python default
:param kwargs: Python default
"""
Form.__init__(self, *args, **kwargs)
def validate(self,id_list):
rv = Form.validate(self)
if not rv:
print False
#Check for the CSRF Token, if it's not there abort.
return False
print True
return True
Jinja2:
<form method=post name="test">
{{ form.hidden_tag()}}
{{ form.selectReportField }}
Add to report
</form>
Rendering:
form = AddToReportForm()
return render_template('random',title='add reports',form=form
Where are you setting SECRET_KEY? It must be available either in the Form class:
class AddToReportForm(Form):
selectReportField = SelectField(u'Reports',choices=[('test1','test')])
SECRET_KEY = "myverylongsecretkey"
def __init__(self, *args, **kwargs):
"""
Initiates a new user form object
:param args: Python default
:param kwargs: Python default
"""
Form.__init__(self, *args, **kwargs)
def validate(self,id_list):
rv = Form.validate(self)
if not rv:
print False
#Check for the CSRF Token, if it's not there abort.
return False
return True
or in the application bootstrap:
app = Flask(__name__)
app.secret_key = 'myverylongsecretkey'
or in the constructor:
form = AddToReportForm(secret_key='myverylongsecretkey')
return render_template('random',title='add reports',form=form)
I still can't see any connection between SelectField and CSRF. The validate method is little suspicious and the extra argument would trip the following testcase, but as it stands this seems to work just fine:
from flask import Flask, render_template_string
from flaskext.wtf import Form, SelectField
app = Flask(__name__)
app.debug = True
app.secret_key = 's3cr3t'
class AddToReportForm(Form):
selectReportField = SelectField(u'Reports', choices=[('test1', 'test')])
#app.route('/test', methods=['GET', 'POST'])
def test():
form = AddToReportForm()
if form.validate_on_submit():
print 'OK'
return render_template_string('''\
<form method=post name="test">
{{ form.hidden_tag()}}
{{ form.selectReportField }}
<input type="submit">
</form>
''', form=form)
app.run(host='0.0.0.0')
Recommended use:
app.secret_key = 'key here' # key user defined

Unable to use flask.g to access variables in other functions

I'm trying to use flask.g to store variables that can be accessed in other functions, but I don't seem to be doing something correctly. The application generates the following error when I try to access g.name: AttributeError: '_RequestGlobals' object has no attribute 'name'.
The documentation for flask.g says:
Just store on this whatever you want. For example a database
connection or the user that is currently logged in.
Here's a complete, minimal example that illustrates the error that I receive when trying to access the variable outside of the function it was created in. Any help would be greatly appreciated.
#!/usr/bin/env python
from flask import Flask, render_template_string, request, redirect, url_for, g
from wtforms import Form, TextField
application = app = Flask('wsgi')
#app.route('/', methods=['GET', 'POST'])
def index():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
name = form.name.data
g.name = name
# Need to create an instance of a class and access that in another route
#g.api = CustomApi(name)
return redirect(url_for('get_posts'))
else:
return render_template_string(template_form, form=form)
#app.route('/posts', methods=['GET'])
def get_posts():
# Need to access the instance of CustomApi here
#api = g.api
name = g.name
return render_template_string(name_template, name=name)
class LoginForm(Form):
name = TextField('Name')
template_form = """
{% block content %}
<h1>Enter your name</h1>
<form method="POST" action="/">
<div>{{ form.name.label }} {{ form.name() }}</div><br>
<button type="submit" class="btn">Submit</button>
</form>
{% endblock %}
"""
name_template = """
{% block content %}
<div>"Hello {{ name }}"</div><br>
{% endblock %}
"""
if __name__ == '__main__':
app.run(debug=True)
The g object is a request-based object and does not persist between requests, i.e. g is recreated between your request to index and your request to get_posts.
Application Globals in Flask:
Flask provides you with a special object that ensures it is only valid for the active request and that will return different values for each request. In a nutshell: it does the right thing, like it does for request and session.
For persistent storage of tiny data between requests use sessions instead. You may (but should not) get away with storing the data in the app object directly for global (all sessions) application state, similar to what config does, if you find a really good reason to do so.
For more complex data use databases.
If you need to track authentication information, I'd suggest one of the Flask plugins like Flask-Login or Flask-Principal.
For example, we use Flask-Principal. It raises the identity-loaded signal when somebody authenticates (or it detects an authentication cookie). We then map their logged-in identity with a user in our database. Something like this:
# not actual code
#identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
user = Person.query.filter(Person.username==identity.person.username).one()
g.user = user
and then we can use g.user in any controller or template. (We're actually ripping a lot of this out, it was a easy, lazy hack that's caused more trouble than it's worth.)
If you don't want to use a module, there's a built-in signal you can hook into at the start of every request:
http://flask.pocoo.org/docs/tutorial/dbcon/
# This runs before every request
#app.before_request
def before_request():
g.user = your_magic_user_function()
and g.user would then be magically available everywhere.
I hope that helps!
Just use sessions in flask. In your case, you just want to save the user/name in your request and the easiest way is to use sessions.
from flask import session
app.secret_key = 'some key for session'
Then, your functions could be changed as below:
#app.route('/', methods=['GET', 'POST'])
def index():
form = LoginForm(request.form)
if request.method == 'POST' and form.validate():
session['name'] = form.name.data
return redirect(url_for('get_posts'))
else:
return render_template_string(template_form, form=form)
#app.route('/posts', methods=['GET'])
def get_posts():
if 'name' in session:
name = session['name']
else:
name = "Unknown"
return render_template_string(name_template, name=name)
I will like to shed more light on the use of g global in storing data. g only store data with a request and when redirecting to another route, the g global is set back to null i.e it reset back to nothing. This means whatever set to g in one request can't be access in another request. Use sessions to store data that will be accessed across request.
One benefit of using g global is when connecting to a database to fetct a user. For example, may be the admin from the database. The admin can be store in the g global using the below method.
from flask import Flask, g
app = Flask(__name__)
#app.before_request
def text():
g.a = User.query.filter_by(email='admin#gmail.com')
#app.route("/getTrue", methods=['GET', 'POST'])
def getTrue():
form = UserForm()
if form.validate_on_submit():
if g.a == form.email.data:
return "Admin is logged in"
else:
return "User is logged in"
return render_template('login.html', form=form)
In the example above, the g can be use to save data which will be use in another request. I hope this help. Thanks

Determining if a simple_form object has been submitted yet

I am building a Form object using pyramid_simpleform. I would like to have one method that builds the form object and once submitted it attempts to validate it.
If the input valid, I'll redirect the user to a confirmation page. Otherwise, I'll render an error page. I am using form.validate() to see if the input data fits the schema.
If the form has not been submitted, I'd like to display the form using a render_to_response. How do I detect whether the form has been submitted?
Quick answer
Like Furbeenator answered:
if request.method == "POST":
Long answer
You should split your logic in two different view functions.
Also, you should return a form renderer to be used by your template instead of rendering the form straight in the view function. For example:
./forms.py:
from formencode import Schema, validators
class RegistrationForm(Schema):
email = validators.Email(not_empty=True, strip=True)
password = validators.UnicodeString(not_empty=True)
password_confirm = validators.UnicodeString()
chained_validators = [validators.FieldsMatch(
'password', 'password_confirm',
messages=dict(invalidNoMatch=u'Password does not match'))]
./views/accounts.py:
from pyramid_simpleform import Form
from pyramid_simpleform.renderers import FormRenderer
from pyramid.view import view_config
from pyramid.httpexceptions import HTTPFound
from pyramid.url import route_url
from ..forms import RegistrationForm
from ..models import DBSession, User
#view_config(route_name='accounts_register', renderer='/accounts/register.mako',
request_method='GET')
def register(request):
"""Shows the empty form."""
form = Form(request, schema=RegistrationForm)
return {'renderer': FormRenderer(form)}
#view_config(route_name='accounts_register', renderer='/accounts/register.mako',
request_method='POST')
def register_post(request):
"""Receives the posted form."""
form = Form(request, schema=RegistrationForm)
if form.validate():
user = User(form.data['email'], form.data['password'])
DBSession.add(user)
return HTTPFound(route_url('profile', request))
return {'renderer': FormRenderer(form)}
./templates/accounts/register.mako:
<form action="" method="post">
<div class="${' error' if renderer.is_error('email') else ''}">
<label for="email">Email</label>
${renderer.text('email', type='email', placeholder='Email')}
% if renderer.is_error('email'):
<span class="errors">${', '.join(renderer.errors_for('email'))}</span>
% endif
</div>
<!-- ... --->
</form>
If you have the request object, you can test it's method for "POST".
if request.method == "POST":

Categories