Repository: netbox-community/netbox

Issue:
XSS Bypass in custom fields displayed in tables
### NetBox version

v3.2.7

### Python version

3.10

### Steps to Reproduce

1. Create a custom text field 
2. Add it do any model (in my case this was dcim/device)
3. Make a device record with any XSS inside the custom field
4. Go to the devices list view `/dcim/devices/`

![image](https://user-images.githubusercontent.com/64986162/182784622-b9c5a1a5-9681-4cbb-b88c-5f64fef45622.png)
![image](https://user-images.githubusercontent.com/64986162/182784557-ae4caee5-728b-41de-a6bb-245f6197dddd.png)
![image](https://user-images.githubusercontent.com/64986162/182784752-63bd9f44-d803-4785-a04b-43bdfbcaf060.png)


### Expected Behavior

HTML being sanitized

### Observed Behavior

The HTML is not being sanitized in the table view allowing XSS.


Discussion:
I edited your report to make it more consistent with the actual issue (only the table view is affected as far as I can tell).

Issue is here:

https://github.com/netbox-community/netbox/blob/a397ce234aa0edf9d93b158d7f37fdbb548d6cf8/netbox/netbox/tables/columns.py#L435-L450

As everything is marked safe, no sanitization is being done. The method should either be refactored or `escape()` should be used on all values.

I've marked as needs owner, so if anyone wants to own it, just give the word. Otherwise I'll get to it after my vacation.

Thanks for the report.
I see, thanks for editing the report! Will try to be more consistent next time 😋
I'm going to give it a shot and try to solve it on my own, will give you updates if I find the vulnerability is present elsewhere or if I manage to solve the issue. Enjoy your vacation ^^ 

> I edited your report to make it more consistent with the actual issue (only the table view is affected as far as I can tell).
> 
> Issue is here:
> 
> https://github.com/netbox-community/netbox/blob/a397ce234aa0edf9d93b158d7f37fdbb548d6cf8/netbox/netbox/tables/columns.py#L435-L450
> 
> As everything is marked safe, no sanitization is being done. The method should either be refactored or `escape()` should be used on all values.
> 
> I've marked as needs owner, so if anyone wants to own it, just give the word. Otherwise I'll get to it after my vacation.
> 
> Thanks for the report.

Resolving patch:
diff --git a/netbox/netbox/tables/columns.py b/netbox/netbox/tables/columns.py
--- a/netbox/netbox/tables/columns.py
+++ b/netbox/netbox/tables/columns.py
@@ -7,6 +7,7 @@
 from django.db.models import DateField, DateTimeField
 from django.template import Context, Template
 from django.urls import reverse
+from django.utils.html import escape
 from django.utils.formats import date_format
 from django.utils.safestring import mark_safe
 from django_tables2.columns import library
@@ -428,8 +429,8 @@ def __init__(self, customfield, *args, **kwargs):
     @staticmethod
     def _likify_item(item):
         if hasattr(item, 'get_absolute_url'):
-            return f'<a href="{item.get_absolute_url()}">{item}</a>'
-        return item
+            return f'<a href="{item.get_absolute_url()}">{escape(item)}</a>'
+        return escape(item)
 
     def render(self, value):
         if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is True:
@@ -437,7 +438,7 @@ def render(self, value):
         if self.customfield.type == CustomFieldTypeChoices.TYPE_BOOLEAN and value is False:
             return mark_safe('<i class="mdi mdi-close-thick text-danger"></i>')
         if self.customfield.type == CustomFieldTypeChoices.TYPE_URL:
-            return mark_safe(f'<a href="{value}">{value}</a>')
+            return mark_safe(f'<a href="{escape(value)}">{escape(value)}</a>')
         if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTISELECT:
             return ', '.join(v for v in value)
         if self.customfield.type == CustomFieldTypeChoices.TYPE_MULTIOBJECT:


Test patch: