escape syndication target before displaying

closes possible XSS vector

fixes #51
This commit is contained in:
Kyle Mahan 2016-05-06 14:33:42 -07:00
parent 8cb2124251
commit 93e751c058
4 changed files with 30 additions and 13 deletions

View file

@ -1,7 +1,7 @@
import flask
import flask.ext.login as flask_login
import requests
from woodwind import util
api = flask.Blueprint('api', __name__)
@ -13,6 +13,9 @@ def publish():
content = flask.request.form.get('content')
syndicate_to = flask.request.form.getlist('syndicate-to[]')
if syndicate_to:
syndicate_to = [util.html_unescape(id) for id in syndicate_to]
data = {
'h': 'entry',
'syndicate-to[]': syndicate_to,

View file

@ -23,7 +23,7 @@
{% for target in current_user.get_setting('syndicate-to', []) %}
<div class="syndication-toggle">
<input id="sc-{{entry.id}}-{{loop.index}}" type="checkbox" name="syndicate-to[]" value="{{ target | syndication_target_id }}"{% if entry is syndicated_to(target) %} checked{% endif %} />
<input id="sc-{{entry.id}}-{{loop.index}}" type="checkbox" name="syndicate-to[]" value="{{ target | render_syndication_target_id }}"{% if entry is syndicated_to(target) %} checked{% endif %} />
<label for="sc-{{entry.id}}-{{loop.index}}">{{ target | render_syndication_target }}</label>
</div>

View file

@ -1,5 +1,6 @@
import pickle
import re
from xml.sax import saxutils
from flask import current_app
from redis import StrictRedis
@ -56,3 +57,13 @@ def clean(text):
if text is not None:
text = re.sub('<script.*?</script>', '', text, flags=re.DOTALL)
return bleach.clean(text, strip=True)
def html_escape(text):
# https://wiki.python.org/moin/EscapingHtml
return saxutils.escape(text, {'"': '&quot;', "'": '&apos;'})
def html_unescape(text):
# https://wiki.python.org/moin/EscapingHtml
return saxutils.unescape(text, {'&quot;': '"', '&apos;': "'"})

View file

@ -16,7 +16,6 @@ import pyquerystring
import requests
import re
import urllib
import cgi
import sqlalchemy
import sqlalchemy.sql.expression
@ -257,11 +256,11 @@ def login():
@micropub.authenticated_handler
def login_callback(resp):
if not resp.me:
flask.flash(cgi.escape('Login error: ' + resp.error))
flask.flash(util.html_escape('Login error: ' + resp.error))
return flask.redirect(flask.url_for('.index'))
if resp.error:
flask.flash(cgi.escape('Warning: ' + resp.error))
flask.flash(util.html_escape('Warning: ' + resp.error))
user = load_user(resp.me)
if not user:
@ -287,12 +286,12 @@ def authorize():
@micropub.authorized_handler
def micropub_callback(resp):
if not resp.me or resp.error:
flask.flash(cgi.escape('Authorize error: ' + resp.error))
flask.flash(util.html_escape('Authorize error: ' + resp.error))
return flask.redirect(flask.url_for('.index'))
user = load_user(resp.me)
if not user:
flask.flash(cgi.escape('Unknown user for url: ' + resp.me))
flask.flash(util.html_escape('Unknown user for url: ' + resp.me))
return flask.redirect(flask.url_for('.index'))
user.micropub_endpoint = resp.micropub_endpoint
@ -764,21 +763,25 @@ def font_awesome_class_for_service(service):
return 'fa fa-send'
@views.app_template_filter('syndication_target_id')
def syndication_target_id(target):
@views.app_template_filter('render_syndication_target_id')
def render_syndication_target_id(target):
if isinstance(target, dict):
return target.get('uid') or target.get('id')
return target
id = target.get('uid') or target.get('id')
else:
id = target
return util.html_escape(id)
@views.app_template_filter('render_syndication_target')
def render_syndication_target(target):
if isinstance(target, dict):
full_name = target.get('name')
return full_name
return util.html_escape(full_name)
return '<img src="{}" alt="{}" />&nbsp;{}'.format(
favicon_for_url(target), target, prettify_url(target))
favicon_for_url(target),
util.html_escape(target),
util.html_escape(prettify_url(target)))
@views.app_template_test('syndicated_to')