Merge pull request #2896 from FinnStutzenstein/Issue2871

Prevent XSS-attacks (fixes #2871)
This commit is contained in:
Emanuel Schütze 2017-01-30 21:11:46 +01:00 committed by GitHub
commit 39037af662
8 changed files with 72 additions and 1 deletions

View File

@ -41,6 +41,7 @@ Core:
- Replaced angular-csv-import through Papa Parse for csv parsing. - Replaced angular-csv-import through Papa Parse for csv parsing.
- Added smooth projector scroll. - Added smooth projector scroll.
- Added watching permissions in client and change the view immediately on changes. - Added watching permissions in client and change the view immediately on changes.
- Validate HTML strings from CKEditor against XSS attacks.
Motions: Motions:
- Added adjustable line numbering mode (outside, inside, none) for each - Added adjustable line numbering mode (outside, inside, none) for each

View File

@ -1,4 +1,5 @@
from openslides.utils.rest_api import Field, ModelSerializer, ValidationError from openslides.utils.rest_api import Field, ModelSerializer, ValidationError
from openslides.utils.validate import validate_html
from .models import ( from .models import (
ChatMessage, ChatMessage,
@ -78,6 +79,10 @@ class ProjectorMessageSerializer(ModelSerializer):
model = ProjectorMessage model = ProjectorMessage
fields = ('id', 'message', ) fields = ('id', 'message', )
def validate(self, data):
data['message'] = validate_html(data.get('message', ''))
return data
class CountdownSerializer(ModelSerializer): class CountdownSerializer(ModelSerializer):
""" """

View File

@ -12,6 +12,7 @@ from openslides.utils.rest_api import (
SerializerMethodField, SerializerMethodField,
ValidationError, ValidationError,
) )
from openslides.utils.validate import validate_html
from .models import ( from .models import (
Category, Category,
@ -257,6 +258,10 @@ class MotionChangeRecommendationSerializer(ModelSerializer):
'text', 'text',
'creation_time',) 'creation_time',)
def validate(self, data):
data['text'] = validate_html(data.get('text', ''))
return data
class MotionSerializer(ModelSerializer): class MotionSerializer(ModelSerializer):
""" """
@ -305,6 +310,15 @@ class MotionSerializer(ModelSerializer):
'log_messages',) 'log_messages',)
read_only_fields = ('state', 'recommendation',) # Some other fields are also read_only. See definitions above. read_only_fields = ('state', 'recommendation',) # Some other fields are also read_only. See definitions above.
def validate(self, data):
data['text'] = validate_html(data.get('text', ''))
data['reason'] = validate_html(data.get('reason', ''))
validated_comments = []
for comment in data.get('comments', []):
validated_comments.append(validate_html(comment))
data['comments'] = validated_comments
return data
@transaction.atomic @transaction.atomic
def create(self, validated_data): def create(self, validated_data):
""" """

View File

@ -1,4 +1,5 @@
from openslides.utils.rest_api import ModelSerializer from openslides.utils.rest_api import ModelSerializer
from openslides.utils.validate import validate_html
from .models import Topic from .models import Topic
@ -10,3 +11,7 @@ class TopicSerializer(ModelSerializer):
class Meta: class Meta:
model = Topic model = Topic
fields = ('id', 'title', 'text', 'attachments', 'agenda_item_id') fields = ('id', 'title', 'text', 'attachments', 'agenda_item_id')
def validate(self, data):
data['text'] = validate_html(data.get('text', ''))
return data

View File

@ -0,0 +1,34 @@
import bleach
allowed_tags = [
'a', 'img', # links and images
'p', 'span', 'blockquote', # text layout
'strike', 'strong', 'u', 'em', 'sup', 'sub', 'pre', # text formatting
'h1', 'h2', 'h3', 'h4', 'h5', 'h6', # headings
'ol', 'ul', 'li', # lists
'table', 'caption', 'thead', 'tbody', 'th', 'tr', 'td', # tables
]
allowed_attributes = {
'*': ['class', 'style'],
'img': ['alt', 'src', 'title'],
'a': ['href', 'title'],
'th': ['scope'],
}
allowed_styles = [
'color', 'background-color', 'height', 'width', 'text-align'
]
def validate_html(html):
"""
This method takes a string and escapes all non-whitelisted html entries.
Every field of a model that is loaded trusted in the DOM should be validated.
"""
if isinstance(html, str):
return bleach.clean(
html,
tags=allowed_tags,
attributes=allowed_attributes,
styles=allowed_styles)
else:
return html

View File

@ -8,3 +8,4 @@ roman>=2.0,<2.1
setuptools>=18.5,<33.0 setuptools>=18.5,<33.0
Twisted>=16.2,<16.4 Twisted>=16.2,<16.4
Whoosh>=2.7,<2.8 Whoosh>=2.7,<2.8
bleach>=1.5.0,<1.6

View File

@ -235,7 +235,7 @@ class CreateMotion(TestCase):
config['motions_comments'] = [ config['motions_comments'] = [
{'name': 'comment1', 'public': True}, {'name': 'comment1', 'public': True},
{'name': 'comment2', 'public': False}] {'name': 'comment2', 'public': False}]
comments = ['comemnt1_sdpoiuffo3%7dwDwW&', 'comment2_iusd&D/TdskDWH&5DWas46WAd078'] comments = ['comemnt1_sdpoiuffo3%7dwDwW)', 'comment2_iusd_D/TdskDWH(5DWas46WAd078']
response = self.client.post( response = self.client.post(
reverse('motion-list'), reverse('motion-list'),
{'title': 'title_test_sfdAaufd56HR7sd5FDq7av', {'title': 'title_test_sfdAaufd56HR7sd5FDq7av',

View File

@ -0,0 +1,11 @@
from unittest import TestCase
from openslides.utils.validate import validate_html
class ValidatorTest(TestCase):
def test_XSS_protection(self):
data = 'tuveegi2Ho<a><p>tuveegi2Ho<script>kekj9(djwk</script></p>Boovai7esu</a>ee4Yaiw0ei'
self.assertEqual(
validate_html(data),
'tuveegi2Ho<a><p>tuveegi2Ho&lt;script&gt;kekj9(djwk&lt;/script&gt;</p>Boovai7esu</a>ee4Yaiw0ei')