Skip to content

Commit e88b686

Browse files
committed
Improved security
1 parent 26469cd commit e88b686

File tree

6 files changed

+92
-7
lines changed

6 files changed

+92
-7
lines changed

CHANGELOG.txt

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
* Fix overriding variables (in pattern properties, property names, unique items and contains)
44
* Fix string in const
5+
* Improve security: not generating code from any definition
56

67

78
=== 2.3 (2018-09-14) ===

fastjsonschema/draft04.py

+31-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import re
22

3+
from .exceptions import JsonSchemaDefinitionException
34
from .generator import CodeGenerator, enforce_list
45

56

@@ -65,7 +66,10 @@ def generate_type(self):
6566
{'type': ['string', 'number']}
6667
"""
6768
types = enforce_list(self._definition['type'])
68-
python_types = ', '.join(JSON_TYPE_TO_PYTHON_TYPE.get(t) for t in types)
69+
try:
70+
python_types = ', '.join(JSON_TYPE_TO_PYTHON_TYPE[t] for t in types)
71+
except KeyError as exc:
72+
raise JsonSchemaDefinitionException('Unknown type: {}'.format(exc))
6973

7074
extra = ''
7175
if ('number' in types or 'integer' in types) and 'boolean' not in types:
@@ -84,6 +88,8 @@ def generate_enum(self):
8488
'enum': ['a', 'b'],
8589
}
8690
"""
91+
if not isinstance(self._definition['enum'], (list, tuple)):
92+
raise JsonSchemaDefinitionException('enum must be an array')
8793
with self.l('if {variable} not in {enum}:'):
8894
self.l('raise JsonSchemaException("{name} must be one of {enum}")')
8995

@@ -192,20 +198,25 @@ def generate_not(self):
192198
def generate_min_length(self):
193199
with self.l('if isinstance({variable}, str):'):
194200
self.create_variable_with_length()
201+
if not isinstance(self._definition['minLength'], int):
202+
raise JsonSchemaDefinitionException('minLength must be a number')
195203
with self.l('if {variable}_len < {minLength}:'):
196204
self.l('raise JsonSchemaException("{name} must be longer than or equal to {minLength} characters")')
197205

198206
def generate_max_length(self):
199207
with self.l('if isinstance({variable}, str):'):
200208
self.create_variable_with_length()
209+
if not isinstance(self._definition['maxLength'], int):
210+
raise JsonSchemaDefinitionException('maxLength must be a number')
201211
with self.l('if {variable}_len > {maxLength}:'):
202212
self.l('raise JsonSchemaException("{name} must be shorter than or equal to {maxLength} characters")')
203213

204214
def generate_pattern(self):
205215
with self.l('if isinstance({variable}, str):'):
206-
self._compile_regexps['{}'.format(self._definition['pattern'])] = re.compile(self._definition['pattern'])
207-
with self.l('if not REGEX_PATTERNS["{}"].search({variable}):', self._definition['pattern']):
208-
self.l('raise JsonSchemaException("{name} must match pattern {pattern}")')
216+
safe_pattern = self._definition['pattern'].replace('"', '\\"')
217+
self._compile_regexps[self._definition['pattern']] = re.compile(self._definition['pattern'])
218+
with self.l('if not REGEX_PATTERNS["{}"].search({variable}):', safe_pattern):
219+
self.l('raise JsonSchemaException("{name} must match pattern {}")', safe_pattern)
209220

210221
def generate_format(self):
211222
"""
@@ -240,6 +251,8 @@ def _generate_format(self, format_name, regexp_name, regexp):
240251

241252
def generate_minimum(self):
242253
with self.l('if isinstance({variable}, (int, float)):'):
254+
if not isinstance(self._definition['minimum'], (int, float)):
255+
raise JsonSchemaDefinitionException('minimum must be a number')
243256
if self._definition.get('exclusiveMinimum', False):
244257
with self.l('if {variable} <= {minimum}:'):
245258
self.l('raise JsonSchemaException("{name} must be bigger than {minimum}")')
@@ -249,6 +262,8 @@ def generate_minimum(self):
249262

250263
def generate_maximum(self):
251264
with self.l('if isinstance({variable}, (int, float)):'):
265+
if not isinstance(self._definition['maximum'], (int, float)):
266+
raise JsonSchemaDefinitionException('maximum must be a number')
252267
if self._definition.get('exclusiveMaximum', False):
253268
with self.l('if {variable} >= {maximum}:'):
254269
self.l('raise JsonSchemaException("{name} must be smaller than {maximum}")')
@@ -258,20 +273,26 @@ def generate_maximum(self):
258273

259274
def generate_multiple_of(self):
260275
with self.l('if isinstance({variable}, (int, float)):'):
276+
if not isinstance(self._definition['multipleOf'], (int, float)):
277+
raise JsonSchemaDefinitionException('multipleOf must be a number')
261278
self.l('quotient = {variable} / {multipleOf}')
262279
with self.l('if int(quotient) != quotient:'):
263280
self.l('raise JsonSchemaException("{name} must be multiple of {multipleOf}")')
264281

265282
def generate_min_items(self):
266283
self.create_variable_is_list()
267284
with self.l('if {variable}_is_list:'):
285+
if not isinstance(self._definition['minItems'], int):
286+
raise JsonSchemaDefinitionException('minItems must be a number')
268287
self.create_variable_with_length()
269288
with self.l('if {variable}_len < {minItems}:'):
270289
self.l('raise JsonSchemaException("{name} must contain at least {minItems} items")')
271290

272291
def generate_max_items(self):
273292
self.create_variable_is_list()
274293
with self.l('if {variable}_is_list:'):
294+
if not isinstance(self._definition['maxItems'], int):
295+
raise JsonSchemaDefinitionException('maxItems must be a number')
275296
self.create_variable_with_length()
276297
with self.l('if {variable}_len > {maxItems}:'):
277298
self.l('raise JsonSchemaException("{name} must contain less than or equal to {maxItems} items")')
@@ -357,20 +378,26 @@ def generate_items(self):
357378
def generate_min_properties(self):
358379
self.create_variable_is_dict()
359380
with self.l('if {variable}_is_dict:'):
381+
if not isinstance(self._definition['minProperties'], int):
382+
raise JsonSchemaDefinitionException('minProperties must be a number')
360383
self.create_variable_with_length()
361384
with self.l('if {variable}_len < {minProperties}:'):
362385
self.l('raise JsonSchemaException("{name} must contain at least {minProperties} properties")')
363386

364387
def generate_max_properties(self):
365388
self.create_variable_is_dict()
366389
with self.l('if {variable}_is_dict:'):
390+
if not isinstance(self._definition['maxProperties'], int):
391+
raise JsonSchemaDefinitionException('maxProperties must be a number')
367392
self.create_variable_with_length()
368393
with self.l('if {variable}_len > {maxProperties}:'):
369394
self.l('raise JsonSchemaException("{name} must contain less than or equal to {maxProperties} properties")')
370395

371396
def generate_required(self):
372397
self.create_variable_is_dict()
373398
with self.l('if {variable}_is_dict:'):
399+
if not isinstance(self._definition['required'], (list, tuple)):
400+
raise JsonSchemaDefinitionException('required must be an array')
374401
self.create_variable_with_length()
375402
with self.l('if not all(prop in {variable} for prop in {required}):'):
376403
self.l('raise JsonSchemaException("{name} must contain {required} properties")')

fastjsonschema/draft06.py

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from .draft04 import CodeGeneratorDraft04, JSON_TYPE_TO_PYTHON_TYPE
2+
from .exceptions import JsonSchemaDefinitionException
23
from .generator import enforce_list
34

45

@@ -53,7 +54,10 @@ def generate_type(self):
5354
{'type': ['string', 'number']}
5455
"""
5556
types = enforce_list(self._definition['type'])
56-
python_types = ', '.join(JSON_TYPE_TO_PYTHON_TYPE.get(t) for t in types)
57+
try:
58+
python_types = ', '.join(JSON_TYPE_TO_PYTHON_TYPE[t] for t in types)
59+
except KeyError as exc:
60+
raise JsonSchemaDefinitionException('Unknown type: {}'.format(exc))
5761

5862
extra = ''
5963

@@ -70,11 +74,15 @@ def generate_type(self):
7074

7175
def generate_exclusive_minimum(self):
7276
with self.l('if isinstance({variable}, (int, float)):'):
77+
if not isinstance(self._definition['exclusiveMinimum'], (int, float)):
78+
raise JsonSchemaDefinitionException('exclusiveMinimum must be an integer or a float')
7379
with self.l('if {variable} <= {exclusiveMinimum}:'):
7480
self.l('raise JsonSchemaException("{name} must be bigger than {exclusiveMinimum}")')
7581

7682
def generate_exclusive_maximum(self):
7783
with self.l('if isinstance({variable}, (int, float)):'):
84+
if not isinstance(self._definition['exclusiveMaximum'], (int, float)):
85+
raise JsonSchemaDefinitionException('exclusiveMaximum must be an integer or a float')
7886
with self.l('if {variable} >= {exclusiveMaximum}:'):
7987
self.l('raise JsonSchemaException("{name} must be smaller than {exclusiveMaximum}")')
8088

fastjsonschema/exceptions.py

+6
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,9 @@ class JsonSchemaException(ValueError):
77
def __init__(self, message):
88
super().__init__(message)
99
self.message = message
10+
11+
12+
class JsonSchemaDefinitionException(JsonSchemaException):
13+
"""
14+
Exception raised by generator of validation function.
15+
"""

tests/conftest.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@
1010
import pytest
1111

1212
from fastjsonschema import JsonSchemaException, compile
13-
from fastjsonschema.draft04 import CodeGeneratorDraft04
13+
from fastjsonschema.draft07 import CodeGeneratorDraft07
1414

1515

1616
@pytest.fixture
1717
def asserter():
1818
def f(definition, value, expected):
1919
# When test fails, it will show up code.
20-
code_generator = CodeGeneratorDraft04(definition)
20+
code_generator = CodeGeneratorDraft07(definition)
2121
print(code_generator.func_code)
2222
pprint(code_generator.global_state)
2323

tests/test_security.py

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import pytest
2+
3+
from fastjsonschema import JsonSchemaDefinitionException, compile
4+
5+
6+
@pytest.mark.parametrize('schema', [
7+
{'type': 'validate(10)'},
8+
{'enum': 'validate(10)'},
9+
{'minLength': 'validate(10)'},
10+
{'maxLength': 'validate(10)'},
11+
{'minimum': 'validate(10)'},
12+
{'maximum': 'validate(10)'},
13+
{'multipleOf': 'validate(10)'},
14+
{'minItems': 'validate(10)'},
15+
{'maxItems': 'validate(10)'},
16+
{'minProperties': 'validate(10)'},
17+
{'maxProperties': 'validate(10)'},
18+
{'required': 'validate(10)'},
19+
{'exclusiveMinimum': 'validate(10)'},
20+
{'exclusiveMaximum': 'validate(10)'},
21+
])
22+
def test_not_generate_code_from_definition(schema):
23+
with pytest.raises(JsonSchemaDefinitionException):
24+
compile({
25+
'$schema': 'http://json-schema.org/draft-07/schema',
26+
**schema
27+
})
28+
29+
30+
@pytest.mark.parametrize('schema,value', [
31+
({'const': 'validate(10)'}, 'validate(10)'),
32+
({'pattern': '" + validate("10") + "'}, '" validate"10" "'),
33+
({'pattern': "' + validate('10') + '"}, '\' validate\'10\' \''),
34+
({'pattern': "' + validate(\"10\") + '"}, '\' validate"10" \''),
35+
({'properties': {
36+
'validate(10)': {'type': 'string'},
37+
}}, {'validate(10)': '10'}),
38+
])
39+
def test_generate_code_with_proper_variable_names(asserter, schema, value):
40+
asserter({
41+
'$schema': 'http://json-schema.org/draft-07/schema',
42+
**schema
43+
}, value, value)

0 commit comments

Comments
 (0)