mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-01-03 09:25:42 +10:00
Fix security issues in tag editor widgets
- Fix case-sensitivity mismatch in remove_tags (use name__iexact) - Fix XSS vulnerability by removing onclick attributes - Use data attributes and event delegation instead - Apply DOM APIs to prevent injection attacks Co-authored-by: Nick Sweeting <pirate@users.noreply.github.com>
This commit is contained in:
@@ -534,9 +534,13 @@ class SnapshotAdmin(SearchResultsAdminMixin, ConfigEditorMixin, BaseModelAdmin):
|
||||
messages.warning(request, "No tags specified.")
|
||||
return
|
||||
|
||||
# Parse comma-separated tag names and find matching Tag objects
|
||||
# Parse comma-separated tag names and find matching Tag objects (case-insensitive)
|
||||
tag_names = [name.strip() for name in tags_str.split(',') if name.strip()]
|
||||
tags = list(Tag.objects.filter(name__in=tag_names))
|
||||
tags = []
|
||||
for name in tag_names:
|
||||
tag = Tag.objects.filter(name__iexact=name).first()
|
||||
if tag:
|
||||
tags.append(tag)
|
||||
|
||||
print('[-] Removing tags', [t.name for t in tags], 'from Snapshots', queryset)
|
||||
for obj in queryset:
|
||||
|
||||
@@ -75,7 +75,7 @@ class TagEditorWidget(forms.Widget):
|
||||
pills_html += f'''
|
||||
<span class="tag-pill" data-tag="{self._escape(tag)}">
|
||||
{self._escape(tag)}
|
||||
<button type="button" class="tag-remove-btn" onclick="removeTag_{widget_id}(this, '{self._escape(tag)}')">×</button>
|
||||
<button type="button" class="tag-remove-btn" data-tag-name="{self._escape(tag)}">×</button>
|
||||
</span>
|
||||
'''
|
||||
|
||||
@@ -151,7 +151,7 @@ class TagEditorWidget(forms.Widget):
|
||||
}});
|
||||
}};
|
||||
|
||||
window.removeTag_{widget_id} = function(btn, tagName) {{
|
||||
window.removeTag_{widget_id} = function(tagName) {{
|
||||
currentTags_{widget_id} = currentTags_{widget_id}.filter(function(t) {{
|
||||
return t.toLowerCase() !== tagName.toLowerCase();
|
||||
}});
|
||||
@@ -166,13 +166,31 @@ class TagEditorWidget(forms.Widget):
|
||||
var pill = document.createElement('span');
|
||||
pill.className = 'tag-pill';
|
||||
pill.setAttribute('data-tag', tag);
|
||||
pill.innerHTML = escapeHtml(tag) +
|
||||
'<button type="button" class="tag-remove-btn" onclick="removeTag_{widget_id}(this, \\'' +
|
||||
escapeHtml(tag).replace(/'/g, "\\\\'") + '\\')">×</button>';
|
||||
|
||||
var tagText = document.createTextNode(tag);
|
||||
pill.appendChild(tagText);
|
||||
|
||||
var removeBtn = document.createElement('button');
|
||||
removeBtn.type = 'button';
|
||||
removeBtn.className = 'tag-remove-btn';
|
||||
removeBtn.setAttribute('data-tag-name', tag);
|
||||
removeBtn.innerHTML = '×';
|
||||
pill.appendChild(removeBtn);
|
||||
|
||||
container.appendChild(pill);
|
||||
}});
|
||||
}};
|
||||
|
||||
// Add event delegation for remove buttons
|
||||
document.getElementById('{widget_id}_pills').addEventListener('click', function(event) {{
|
||||
if (event.target.classList.contains('tag-remove-btn')) {{
|
||||
var tagName = event.target.getAttribute('data-tag-name');
|
||||
if (tagName) {{
|
||||
removeTag_{widget_id}(tagName);
|
||||
}}
|
||||
}}
|
||||
}});
|
||||
|
||||
window.handleTagKeydown_{widget_id} = function(event) {{
|
||||
var input = event.target;
|
||||
var value = input.value.trim();
|
||||
@@ -285,7 +303,7 @@ class InlineTagEditorWidget(TagEditorWidget):
|
||||
pills_html += f'''
|
||||
<span class="tag-pill" data-tag="{self._escape(td['name'])}" data-tag-id="{td['id']}">
|
||||
<a href="/admin/core/snapshot/?tags__id__exact={td['id']}" class="tag-link">{self._escape(td['name'])}</a>
|
||||
<button type="button" class="tag-remove-btn" onclick="removeInlineTag_{widget_id}(event, {td['id']}, '{self._escape(td['name'])}')">×</button>
|
||||
<button type="button" class="tag-remove-btn" data-tag-id="{td['id']}" data-tag-name="{self._escape(td['name'])}">×</button>
|
||||
</span>
|
||||
'''
|
||||
|
||||
@@ -362,10 +380,7 @@ class InlineTagEditorWidget(TagEditorWidget):
|
||||
document.getElementById('{widget_id}_input').value = '';
|
||||
}};
|
||||
|
||||
window.removeInlineTag_{widget_id} = function(event, tagId, tagName) {{
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
|
||||
window.removeInlineTag_{widget_id} = function(tagId) {{
|
||||
fetch('/api/v1/core/tags/remove-from-snapshot/', {{
|
||||
method: 'POST',
|
||||
headers: {{
|
||||
@@ -399,14 +414,37 @@ class InlineTagEditorWidget(TagEditorWidget):
|
||||
pill.className = 'tag-pill';
|
||||
pill.setAttribute('data-tag', td.name);
|
||||
pill.setAttribute('data-tag-id', td.id);
|
||||
pill.innerHTML = '<a href="/admin/core/snapshot/?tags__id__exact=' + td.id + '" class="tag-link">' +
|
||||
escapeHtml(td.name) + '</a>' +
|
||||
'<button type="button" class="tag-remove-btn" onclick="removeInlineTag_{widget_id}(event, ' +
|
||||
td.id + ', \\'' + escapeHtml(td.name).replace(/'/g, "\\\\'") + '\\')">×</button>';
|
||||
|
||||
var link = document.createElement('a');
|
||||
link.href = '/admin/core/snapshot/?tags__id__exact=' + td.id;
|
||||
link.className = 'tag-link';
|
||||
link.textContent = td.name;
|
||||
pill.appendChild(link);
|
||||
|
||||
var removeBtn = document.createElement('button');
|
||||
removeBtn.type = 'button';
|
||||
removeBtn.className = 'tag-remove-btn';
|
||||
removeBtn.setAttribute('data-tag-id', td.id);
|
||||
removeBtn.setAttribute('data-tag-name', td.name);
|
||||
removeBtn.innerHTML = '×';
|
||||
pill.appendChild(removeBtn);
|
||||
|
||||
container.appendChild(pill);
|
||||
}});
|
||||
}};
|
||||
|
||||
// Add event delegation for remove buttons
|
||||
document.getElementById('{widget_id}_pills').addEventListener('click', function(event) {{
|
||||
if (event.target.classList.contains('tag-remove-btn')) {{
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
var tagId = parseInt(event.target.getAttribute('data-tag-id'), 10);
|
||||
if (tagId) {{
|
||||
removeInlineTag_{widget_id}(tagId);
|
||||
}}
|
||||
}}
|
||||
}});
|
||||
|
||||
window.handleInlineTagKeydown_{widget_id} = function(event) {{
|
||||
event.stopPropagation();
|
||||
var input = event.target;
|
||||
|
||||
Reference in New Issue
Block a user