Skip to content

Commit 939c2e6

Browse files
committed
Fix XSS vulnerabilities in the instant search plugin. Fix code style errors
- Substitute the 'throttle' function by the 'debounce' - Add a striping html tags from a response result - Add a validation of a search result url
1 parent f8eaeb4 commit 939c2e6

File tree

5 files changed

+75
-35
lines changed

5 files changed

+75
-35
lines changed

app/Resources/assets/js/jquery.instantSearch.js

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,17 @@
1-
// jQuery plugin for an instant searching
1+
/*
2+
* This file is part of the Symfony package.
3+
*
4+
* (c) Fabien Potencier <[email protected]>
5+
*
6+
* For the full copyright and license information, please view the LICENSE
7+
* file that was distributed with this source code.
8+
*/
9+
10+
/**
11+
* jQuery plugin for an instant searching.
12+
*
13+
* @author Oleg Voronkovich <[email protected]>
14+
*/
215
(function($) {
316
$.fn.instantSearch = function(config) {
417
return this.each(function() {
@@ -7,25 +20,53 @@
720
};
821

922
var defaultConfig = {
23+
allowedTags: '',
1024
minQueryLength: 2,
1125
maxPreviewItems: 10,
1226
previewDelay: 500,
1327
noItemsFoundMessage: 'No items found'
1428
};
1529

16-
var throttle = (function(){
17-
var timer = 0;
18-
return function(callback, ms){
19-
clearTimeout (timer);
20-
timer = setTimeout(callback, ms);
30+
function debounce(fn, delay) {
31+
var timer = null;
32+
return function () {
33+
var context = this, args = arguments;
34+
clearTimeout(timer);
35+
timer = setTimeout(function () {
36+
fn.apply(context, args);
37+
}, delay);
2138
};
22-
})();
39+
}
40+
41+
function isValidUrl(url) {
42+
var parser = document.createElement('a');
43+
try {
44+
parser.href = url;
45+
return !!parser.hostname;
46+
} catch (e) {
47+
return false;
48+
}
49+
}
50+
51+
// See http://phpjs.org/functions/strip_tags/
52+
function stripTags(input, allowed) {
53+
allowed = (((allowed || '') + '').toLowerCase().match(/<[a-z][a-z0-9]*>/g) || []).join('');
54+
55+
var tags = /<\/?([a-z][a-z0-9]*)\b[^>]*>/gi;
56+
var commentsAndPhpTags = /<!--[\s\S]*?-->|<\?(?:php)?[\s\S]*?\?>/gi;
57+
58+
return input.replace(commentsAndPhpTags, '').replace(tags, function($0, $1) {
59+
return allowed.indexOf('<' + $1.toLowerCase() + '>') > -1 ? $0 : '';
60+
});
61+
}
2362

2463
var initInstantSearch = function(el, config) {
2564
var $input = $(el);
26-
var $form = $input.parents('form').first();
65+
var $form = $input.closest('form');
2766
var $preview = $('<ul class="search-preview list-group"></ul>').appendTo($form);
2867

68+
config.noItemsFoundMessage = stripTags(config.noItemsFoundMessage);
69+
2970
var setPreviewItems = function(items) {
3071
$preview.empty();
3172

@@ -34,7 +75,7 @@
3475
return;
3576
}
3677

37-
addItemToPreview(item)
78+
addItemToPreview(item);
3879
});
3980
}
4081

@@ -54,15 +95,29 @@
5495
}
5596

5697
$.getJSON($form.attr('action') + '?' + $form.serialize(), function(items) {
57-
if (items.length === 0) {
98+
// Sanitize items
99+
var sanitizedItems = [];
100+
$.each(items, function(index, item) {
101+
// Url can contains a 'javascript:' code
102+
if (isValidUrl(item.url)) {
103+
sanitizedItems.push({
104+
url: item.url,
105+
result: stripTags(item.result, config.allowedTags)
106+
});
107+
}
108+
});
109+
110+
if (sanitizedItems.length === 0) {
58111
noItemsFound();
59112
return;
60113
}
61114

62-
setPreviewItems(items);
115+
setPreviewItems(sanitizedItems);
63116
});
64117
}
65118

119+
var debouncedUpdatePreview = debounce(updatePreview, config.previewDelay);
120+
66121
$input.focusout(function(e) {
67122
$preview.fadeOut();
68123
});
@@ -73,7 +128,7 @@
73128
});
74129

75130
$input.keyup(function(e) {
76-
throttle(updatePreview, config.previewDelay);
131+
debouncedUpdatePreview();
77132
});
78133
}
79134
})(window.jQuery);

app/Resources/views/base.html.twig

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -160,15 +160,15 @@
160160
<script src="{{ asset('js/app.js') }}"></script>
161161

162162
<script>
163-
$(document).ready(function() {
164-
hljs.initHighlightingOnLoad();
165-
});
166-
167163
$(function() {
168164
$('.search-bar input[name="q"]').instantSearch({
169165
noItemsFoundMessage: '{{ 'post.no_posts_found'|trans }}'
170166
});
171167
});
168+
169+
$(document).ready(function() {
170+
hljs.initHighlightingOnLoad();
171+
});
172172
</script>
173173
{% endblock %}
174174

src/AppBundle/Controller/BlogController.php

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,8 @@ public function searchAction(Request $request)
155155

156156
foreach ($posts as $post) {
157157
array_push($results, array(
158-
'result' => $post->getTitle(),
158+
// We should never trust to admin user
159+
'result' => strip_tags($post->getTitle()),
159160
'url' => $this->generateUrl('blog_post', array('slug' => $post->getSlug())),
160161
));
161162
}

web/css/app.css

Lines changed: 1 addition & 17 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

web/js/app.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)