mirror of
https://github.com/ArchiveBox/ArchiveBox.git
synced 2026-01-04 09:55:33 +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.")
|
messages.warning(request, "No tags specified.")
|
||||||
return
|
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()]
|
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)
|
print('[-] Removing tags', [t.name for t in tags], 'from Snapshots', queryset)
|
||||||
for obj in queryset:
|
for obj in queryset:
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ class TagEditorWidget(forms.Widget):
|
|||||||
pills_html += f'''
|
pills_html += f'''
|
||||||
<span class="tag-pill" data-tag="{self._escape(tag)}">
|
<span class="tag-pill" data-tag="{self._escape(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>
|
</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) {{
|
currentTags_{widget_id} = currentTags_{widget_id}.filter(function(t) {{
|
||||||
return t.toLowerCase() !== tagName.toLowerCase();
|
return t.toLowerCase() !== tagName.toLowerCase();
|
||||||
}});
|
}});
|
||||||
@@ -166,13 +166,31 @@ class TagEditorWidget(forms.Widget):
|
|||||||
var pill = document.createElement('span');
|
var pill = document.createElement('span');
|
||||||
pill.className = 'tag-pill';
|
pill.className = 'tag-pill';
|
||||||
pill.setAttribute('data-tag', tag);
|
pill.setAttribute('data-tag', tag);
|
||||||
pill.innerHTML = escapeHtml(tag) +
|
|
||||||
'<button type="button" class="tag-remove-btn" onclick="removeTag_{widget_id}(this, \\'' +
|
var tagText = document.createTextNode(tag);
|
||||||
escapeHtml(tag).replace(/'/g, "\\\\'") + '\\')">×</button>';
|
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);
|
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) {{
|
window.handleTagKeydown_{widget_id} = function(event) {{
|
||||||
var input = event.target;
|
var input = event.target;
|
||||||
var value = input.value.trim();
|
var value = input.value.trim();
|
||||||
@@ -285,7 +303,7 @@ class InlineTagEditorWidget(TagEditorWidget):
|
|||||||
pills_html += f'''
|
pills_html += f'''
|
||||||
<span class="tag-pill" data-tag="{self._escape(td['name'])}" data-tag-id="{td['id']}">
|
<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>
|
<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>
|
</span>
|
||||||
'''
|
'''
|
||||||
|
|
||||||
@@ -362,10 +380,7 @@ class InlineTagEditorWidget(TagEditorWidget):
|
|||||||
document.getElementById('{widget_id}_input').value = '';
|
document.getElementById('{widget_id}_input').value = '';
|
||||||
}};
|
}};
|
||||||
|
|
||||||
window.removeInlineTag_{widget_id} = function(event, tagId, tagName) {{
|
window.removeInlineTag_{widget_id} = function(tagId) {{
|
||||||
event.stopPropagation();
|
|
||||||
event.preventDefault();
|
|
||||||
|
|
||||||
fetch('/api/v1/core/tags/remove-from-snapshot/', {{
|
fetch('/api/v1/core/tags/remove-from-snapshot/', {{
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {{
|
headers: {{
|
||||||
@@ -399,14 +414,37 @@ class InlineTagEditorWidget(TagEditorWidget):
|
|||||||
pill.className = 'tag-pill';
|
pill.className = 'tag-pill';
|
||||||
pill.setAttribute('data-tag', td.name);
|
pill.setAttribute('data-tag', td.name);
|
||||||
pill.setAttribute('data-tag-id', td.id);
|
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>' +
|
var link = document.createElement('a');
|
||||||
'<button type="button" class="tag-remove-btn" onclick="removeInlineTag_{widget_id}(event, ' +
|
link.href = '/admin/core/snapshot/?tags__id__exact=' + td.id;
|
||||||
td.id + ', \\'' + escapeHtml(td.name).replace(/'/g, "\\\\'") + '\\')">×</button>';
|
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);
|
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) {{
|
window.handleInlineTagKeydown_{widget_id} = function(event) {{
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
var input = event.target;
|
var input = event.target;
|
||||||
|
|||||||
Reference in New Issue
Block a user