Skip to content

Commit 71fbe94

Browse files
committed
Merge pull request #58 from hydralabs/defusedxml
Switch to use defusedxml as the default xml loader.
2 parents 001ea4f + f081dbf commit 71fbe94

File tree

15 files changed

+175
-45
lines changed

15 files changed

+175
-45
lines changed

.travis.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ python:
44
- "2.7"
55
sudo: false
66

7+
cache:
8+
directories:
9+
- $HOME/.cache/pip
10+
711
env:
812
global:
913
- PYTHONPATH=~/gaesdk/google_appengine
@@ -25,6 +29,8 @@ env:
2529
- SQLALCHEMY_VERSION=">=0.9,<1.0"
2630
- TWISTED_VERSION=">=14,<15"
2731
- TWISTED_VERSION=">=15,<16"
32+
- LXML_VERSION=">=3.4,<3.5"
33+
- LXML_VERSION=">=3.5,<3.6"
2834
# special, see install_optional_dependencies.sh
2935
- GAESDK_VERSION=1.9.30
3036

CHANGES.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@ versions of PyAMF.
99
0.8 (Unreleased)
1010
----------------
1111
- Add support for Django>=1.8
12+
- *Backward incompatible* Wrapped all xml parsing in ``defusedxml`` to protect
13+
against any XML entity attacks. See
14+
https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Processing for more
15+
details. Thanks to Nicolas Grégoire (@Agarri_FR) for the report.
1216

1317
0.7.2 (2015-02-18)
1418
------------------

cpyamf/amf0.pxd

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
from cpyamf cimport codec, util, amf3
2+
3+
4+
cdef class Context(codec.Context):
5+
cdef amf3.Context amf3_context
6+
7+
8+
cdef class Decoder(codec.Decoder):
9+
cdef public bint use_amf3
10+
cdef readonly Context context
11+
cdef amf3.Decoder amf3_decoder
12+
13+
cdef object readAMF3(self)
14+
cdef object readLongString(self, bint bytes=?)
15+
cdef object readMixedArray(self)
16+
cdef object readReference(self)
17+
cdef object readTypedObject(self)
18+
cdef void readObjectAttributes(self, object obj_attrs)
19+
cdef object readBytes(self)
20+
cdef object readBoolean(self)
21+
22+
23+
cdef class Encoder(codec.Encoder):
24+
cdef public bint use_amf3
25+
cdef readonly Context context
26+
cdef amf3.Encoder amf3_encoder
27+
28+
cdef inline int _writeEndObject(self) except -1
29+
cdef int writeAMF3(self, o) except -1
30+
cdef int _writeDict(self, dict attrs) except -1
31+
cdef inline int writeReference(self, o) except -2

cpyamf/amf0.pyx

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,6 @@ cdef object UnknownClassAlias = pyamf.UnknownClassAlias
4545

4646

4747
cdef class Context(codec.Context):
48-
cdef amf3.Context amf3_context
49-
5048
cpdef int clear(self) except -1:
5149
codec.Context.clear(self)
5250

@@ -60,10 +58,6 @@ cdef class Decoder(codec.Decoder):
6058
"""
6159
"""
6260

63-
cdef public bint use_amf3
64-
cdef readonly Context context
65-
cdef amf3.Decoder amf3_decoder
66-
6761
def __cinit__(self):
6862
self.use_amf3 = 0
6963

@@ -72,7 +66,7 @@ cdef class Decoder(codec.Decoder):
7266
self.context = kwargs.pop('context', None)
7367

7468
if self.context is None:
75-
self.context = Context()
69+
self.context = Context(**kwargs)
7670

7771
codec.Codec.__init__(self, *args, **kwargs)
7872

@@ -244,7 +238,11 @@ cdef class Decoder(codec.Decoder):
244238

245239
cdef object readXML(self):
246240
cdef object data = self.readLongString()
247-
cdef object root = xml.fromstring(data)
241+
cdef object root = xml.fromstring(
242+
data,
243+
forbid_dtd=self.context.forbid_dtd,
244+
forbid_entities=self.context.forbid_entities
245+
)
248246

249247
self.context.addObject(root)
250248

@@ -301,10 +299,6 @@ cdef class Encoder(codec.Encoder):
301299
The AMF0 Encoder.
302300
"""
303301

304-
cdef public bint use_amf3
305-
cdef readonly Context context
306-
cdef amf3.Encoder amf3_encoder
307-
308302
def __cinit__(self):
309303
self.use_amf3 = 0
310304

@@ -314,7 +308,7 @@ cdef class Encoder(codec.Encoder):
314308
self.context = kwargs.pop('context', None)
315309

316310
if self.context is None:
317-
self.context = Context()
311+
self.context = Context(**kwargs)
318312

319313
codec.Codec.__init__(self, *args, **kwargs)
320314

cpyamf/amf3.pyx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,11 @@ cdef class Decoder(codec.Decoder):
510510
self.stream.read(&buf, ref)
511511
s = PyString_FromStringAndSize(buf, ref)
512512

513-
x = xml.fromstring(s)
513+
x = xml.fromstring(
514+
s,
515+
forbid_dtd=self.context.forbid_dtd,
516+
forbid_entities=self.context.forbid_entities
517+
)
514518
self.context.addObject(x)
515519

516520
return x

cpyamf/codec.pxd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@ cdef class Context(object):
5050
cdef dict unicodes
5151
cdef dict _strings
5252
cdef public dict extra
53+
cdef public bint forbid_dtd
54+
cdef public bint forbid_entities
5355

5456
cpdef int clear(self) except -1
5557
cpdef object getClassAlias(self, object klass)

cpyamf/codec.pyx

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,12 +229,17 @@ cdef class Context(object):
229229

230230
def __cinit__(self):
231231
self.objects = IndexedCollection()
232+
self.forbid_entities = True
233+
self.forbid_dtd = True
232234

233235
self.clear()
234236

235-
def __init__(self):
237+
def __init__(self, forbid_dtd=True, forbid_entities=True, **kwargs):
236238
self.clear()
237239

240+
self.forbid_entities = forbid_entities
241+
self.forbid_dtd = forbid_dtd
242+
238243
cpdef int clear(self) except -1:
239244
self.objects.clear()
240245

@@ -341,7 +346,8 @@ cdef class Codec(object):
341346
self.strict = 0
342347
self.timezone_offset = None
343348

344-
def __init__(self, stream=None, strict=False, timezone_offset=None):
349+
def __init__(self, stream=None, strict=False, timezone_offset=None,
350+
forbid_entities=True, forbid_dtd=True):
345351
if not isinstance(stream, BufferedByteStream):
346352
stream = BufferedByteStream(stream)
347353

@@ -350,6 +356,9 @@ cdef class Codec(object):
350356

351357
self.timezone_offset = timezone_offset
352358

359+
self.context.forbid_entities = <bint>forbid_entities
360+
self.context.forbid_dtd = <bint>forbid_dtd
361+
353362

354363
cdef class Decoder(Codec):
355364
"""

install_optional_dependencies.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ function install_twisted {
2727
pip install "Twisted${TWISTED_VERSION}"
2828
}
2929

30+
function install_lxml {
31+
if [ -z "${LXML_VERSION}" ]; then
32+
return 0
33+
fi
34+
35+
pip install "lxml${LXML_VERSION}"
36+
}
37+
3038
function install_gae_sdk {
3139
if [ -z "${GAESDK_VERSION}" ]; then
3240
return 0
@@ -40,4 +48,5 @@ function install_gae_sdk {
4048
install_django
4149
install_sqlalchemy
4250
install_twisted
51+
install_lxml
4352
install_gae_sdk

pyamf/amf0.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -145,8 +145,8 @@ class Decoder(codec.Decoder):
145145
Decodes an AMF0 stream.
146146
"""
147147

148-
def buildContext(self):
149-
return Context()
148+
def buildContext(self, **kwargs):
149+
return Context(**kwargs)
150150

151151
def getTypeFunc(self, data):
152152
# great for coverage, sucks for readability
@@ -380,7 +380,11 @@ def readXML(self):
380380
Read XML.
381381
"""
382382
data = self.readLongString()
383-
root = xml.fromstring(data)
383+
root = xml.fromstring(
384+
data,
385+
forbid_dtd=self.context.forbid_dtd,
386+
forbid_entities=self.context.forbid_entities,
387+
)
384388

385389
self.context.addObject(root)
386390

@@ -401,8 +405,8 @@ def __init__(self, *args, **kwargs):
401405

402406
self.use_amf3 = kwargs.pop('use_amf3', False)
403407

404-
def buildContext(self):
405-
return Context()
408+
def buildContext(self, **kwargs):
409+
return Context(**kwargs)
406410

407411
def getTypeFunc(self, data):
408412
if self.use_amf3:

pyamf/amf3.py

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -607,14 +607,14 @@ class Context(codec.Context):
607607
@type classes: C{list}
608608
"""
609609

610-
def __init__(self):
610+
def __init__(self, **kwargs):
611611
self.strings = codec.ByteStringReferenceCollection()
612612
self.classes = {}
613613
self.class_ref = {}
614614

615615
self.class_idx = 0
616616

617-
codec.Context.__init__(self)
617+
codec.Context.__init__(self, **kwargs)
618618

619619
def clear(self):
620620
"""
@@ -765,8 +765,8 @@ def __init__(self, *args, **kwargs):
765765

766766
codec.Decoder.__init__(self, *args, **kwargs)
767767

768-
def buildContext(self):
769-
return Context()
768+
def buildContext(self, **kwargs):
769+
return Context(**kwargs)
770770

771771
def getTypeFunc(self, data):
772772
if data == TYPE_UNDEFINED:
@@ -1079,7 +1079,11 @@ def readXML(self):
10791079

10801080
xmlstring = self.stream.read(ref >> 1)
10811081

1082-
x = xml.fromstring(xmlstring)
1082+
x = xml.fromstring(
1083+
xmlstring,
1084+
forbid_dtd=self.context.forbid_dtd,
1085+
forbid_entities=self.context.forbid_entities,
1086+
)
10831087
self.context.addObject(x)
10841088

10851089
return x
@@ -1136,8 +1140,8 @@ def __init__(self, *args, **kwargs):
11361140

11371141
codec.Encoder.__init__(self, *args, **kwargs)
11381142

1139-
def buildContext(self):
1140-
return Context()
1143+
def buildContext(self, **kwargs):
1144+
return Context(**kwargs)
11411145

11421146
def getTypeFunc(self, data):
11431147
"""

0 commit comments

Comments
 (0)