Flask POST + mod_security

software development

#1

Hello all,

I’m working on a site using Flask and Passenger (fastCGI), and I’ve got things more or less up and running, but I’ve found that mod_security seems to be blocking all of my attempts to POST to my site.

right now my form is pretty bare-bones (just a text field named ‘email’), and it gets posted to the url ‘/recvpost’. The error I’m seeing in my logs is:

When I disable “Extra Web Security” in the dreamhost panel, I am able to post successfully, but that seems a bit aggressive, and I’ve been unable to figure out what config settings to put in a .htaccess file.

So far I’ve tried the following just for the sake of trying them, all with no change in results:

SecFilterScanPOST Off SecFilterCheckURLEncoding Off SecFilterCheckUnicodeEncoding Off SecFilterEngine Off

Any apache gurus out there know what I need to turn on/off to get this to work? :slight_smile:


#2

Have you tried turning it off in Panel and then turning it on selectively via htaccess rules?


#3

I haven’t, but it’s not entirely clear to me when I’ll want to turn it on or off, because I don’t know what rule in the security config I’m breaking. Disabling the security module on any page that requires a POST still seems aggressive… especially since this POST will be used for user log-in (so I would probably want some of the security module’s functionality still in place).

Thanks for the speedy reply


#4

From the message you’ve shown, it almost looks as though mod_security is having trouble making sense of your form submission at all — it thinks it’s being cut off prematurely. Is there any chance you can show us the HTML form that you’re using here?


#5

Certainly. It’s a pretty simple/standard form, unless I dropped the ball and did something stupid. For completeness I’ll post the key components of all of the relevant files

login.html:

...
<form name="loginForm" method="POST" action="{{ url_for('recvpost') }}">
Username: <input type="text" name="email">
<input type="submit">
</form>
...

My flask main app:

from flask import Flask
from authview import login, recvpost

app = Flask(__name__)
app.add_url_rule('/login',    view_func = login,    methods = ['GET', 'POST'])
app.add_url_rule('/recvpost', view_func = recvpost, methods = ['POST'])

if __name__ == "__main__":
    app.run(debug=True)

And my authview module:

...
def login():
    return render_template('login.html')

def recvpost():
    return "received a post!"

I should also mention that I’m not using any special directives in my .htaccess file (aside from what I tried in my first post). I’m using whatever default config mod_security has when you check the “Extra Web Secuity” box. And as I said before, when i turn off mod_security, everything works fine, so mod_security must be getting tripped up on something…


#6

Well I managed to sort it out through some black magic. I think mod_security was barfing in an effort to prevent CSRF attacks… no idea why this was becoming an issue in my simple example, but I have it working for now at least.

For anyone else who runs into this issue, here is a workable solution (though I’m not crazy about it).

login.html:

...
<form name="loginForm" method="POST" action="{{ url_for('recvpost') }}">
Username: <input type="text" name="email">
<input type="submit">
<input name="_csrf_token" type=hidden value="{{ csrf_token() }}">
</form>

and in your main flask app, prior to calling app.run(), insert:

@app.before_request
def csrfProtect():
    if request.method == "POST":
        token = session.pop('_csrf_token', None)
        if not token or token != request.form.get('_csrf_token'):
            abort(403)


def generateCsrfToken():
    if '_csrf_token' not in session:
        session['_csrf_token'] = generateRandomString()
    return session['_csrf_token']


def generateRandomString(length = 24):
    return ''.join(random.choice(string.letters + string.digits) for x in range(length))


app.jinja_env.globals['csrf_token'] =  generateCsrfToken   
app.secret_key = os.urandom(24)

Giving credit where credit is due, the modifications to my main flask app were taken from:

http://flask.pocoo.org/snippets/3/

Hope this helps anyone else who runs into this issue in the future.