Commit 4f53b3a0 authored by jacotsu's avatar jacotsu
Browse files

Merge branch 'master' into 'master'

URGENT: Fixed HTML/XSS injection through referer header

See merge request jackv/uus!3
parents c6394c65 c7ce14cf
......@@ -3,7 +3,8 @@ This is a url shortener written in python with flask
## Management CLI
A simple CLI is provided to add, modify and delete admin users. `REDIS_URL` must be provided unless it is the default. Also, `FLASK_APP=uus` must be set when not running in Docker (the Dockerfile already sets this).
A simple CLI is provided to add, modify and delete admin users. `REDIS_URL` must be provided unless it is the default.
Also, `FLASK_APP=uus` must be set when not running in Docker (the Dockerfile already sets this).
Run `flask` to get the list of commands.
......
FLASK_APP=uus \
FLASK_ENV=development \
flask run
......@@ -17,10 +17,11 @@ pure_string = re.compile(r'^\w+$')
def redis_key_for_user(user: str) -> str:
return "user.{}".format(user)
def redis_key_for_url(url: str) -> str:
return "url.{}".format(url)
def redis_key_for_referrer_count(url: str, referrer: str) -> str:
return "url.{}.referrers.{}.count".format(url, referrer)
def redis_key_for_count(url: str) -> str:
return "count.{}".format(url)
......@@ -31,6 +32,23 @@ def handle_redirect(path):
final_url = redis_store.get('url.' + path)
if final_url:
redis_store.incr('count.' + path)
referrer = flask.request.headers.get('referer')
if referrer:
safe_referrer = flask.escape(referrer)
if safe_referrer != referrer:
app.logger.warning('XSS injection attempt from {}'.format(
flask.request.remote_addr))
return flask.abort(400)
if not redis_store.exists(redis_key_for_referrer_count(path,
referrer)):
app.logger.debug('Adding {} as referer for {}'.format(
safe_referrer, path))
redis_store.set(redis_key_for_referrer_count(path,
safe_referrer),
0)
redis_store.incr(redis_key_for_referrer_count(path,
safe_referrer))
app.logger.debug('Increased counter for {}'.format(safe_referrer))
return flask.redirect(final_url)
else:
return flask.abort(404)
......@@ -45,6 +63,7 @@ def handle_login(username, password):
crypt_pw = crypt_pw.decode()
return crypt_pw == crypt.crypt(password, crypt_pw)
else:
app.logger.info('{} failed login'.format(username))
return False
......@@ -82,6 +101,9 @@ def del_url():
try:
redis_store.delete(redis_key_for_url(payload['name']))
redis_store.delete(redis_key_for_count(payload['name']))
for key in redis_store.scan_iter('url.{}.*'
.format(payload['name'])):
redis_store.delete(key)
return ''
except KeyError:
return flask.abort(400)
......@@ -92,15 +114,42 @@ def del_url():
@app.route('/api/v1/listurls')
@auth.login_required
def list_urls():
keys = map(lambda x: x.decode(), redis_store.keys('url.*'))
keys = filter(lambda z: not re.search('\\.referrers*', z),
map(lambda x: x.decode(), redis_store.keys('url.*')))
return_dict = {}
for key in keys:
app.logger.debug(key)
real_key = key.replace('url.', '', 1)
return_dict[real_key] = {'url': redis_store.get(key).decode(),
'count': int(redis_store.get(redis_key_for_count(real_key)).decode())}
return flask.jsonify(return_dict)
@app.route('/api/v1/listreferrers/<shortedurl>')
@auth.login_required
def list_referrers(shortedurl=None):
if shortedurl:
# According to this https://stackoverflow.com/questions/22023538/is-it-possible-to-run-a-string-injection-attack-on-a-redis-query
# string injection shouldn't be possible
keys = map(lambda x: x.decode(),
redis_store.scan_iter('url.{}.referrers.*'
.format(shortedurl)))
return_list = []
for key in keys:
referrer_url = re.sub('url\\.{}\\.referrers\\.|\\.count$'
.format(shortedurl),
'', key)
referrer_url_count = int(redis_store.get(
redis_key_for_referrer_count(shortedurl,
referrer_url)).decode())
app.logger.debug(referrer_url)
return_list.append({'url': referrer_url,
'count': referrer_url_count})
return flask.jsonify(return_list)
else:
return flask.jsonify([])
@app.route('/<path:path>', strict_slashes=False)
def redirect(path):
if pure_string.match(path):
......
var edit_field = '<input type="button" class="edit_btn" value="Edit"></input><input type="button" class="del_btn" value="Delete"></input>';
function list_to_sorted_table(list) {
var tmpString = '';
list.sort(function(a, b) {b.count - a.count});
for (var referrer in list) {
tmpString += '<tr><td>' + list[referrer].count + '</td><td>' + list[referrer].url + '</td></tr>';
}
return tmpString;
}
function refresh_list(){
$("table#link_table").find("tr:gt(0)").remove();
$.ajax({
type: "GET",
url: "/api/v1/listurls",
dataType: "json",
success: function(response){
$.each(response, function(index, value){
$("#link_table").append('<tr id="'+ index +'"><td>'+ index +
"</td><td>"+ value['url'] +
"</td><td>"+ value['count'] +
"</td><td>"+ edit_field +"</td>")
})
}
})
$("table#link_table").find("tr:gt(0)").remove();
$.ajax({
type: "GET",
url: "/api/v1/listurls",
dataType: "json",
success: function(response){
var referrers = null;
$.each(response, function(index, value){
$.ajax({
type: "GET",
url: "/api/v1/listreferrers/" + index,
dataType: "json",
success: function(response){
$("#link_table").append('<tr id="'+ index +'"><td>'+ index +
"</td><td>"+ value['url'] +
"</td><td>"+ value['count'] +
"</td><td><table><th>Count</th><th>Url</th>"+ list_to_sorted_table(response) +
"</table></td><td>"+ edit_field +"</td>")
}
});
})
}
})
}
......
......@@ -20,6 +20,7 @@
<th>Short Link Name</th>
<th>Redirect Url</th>
<th>View Count</th>
<th>Referrers</th>
<th>Options</th>
</tr>
</table>
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment