0
# XML-RPC Protection
1
2
Secure XML-RPC client and server protection with gzip bomb prevention. DefusedXML provides defused parsers and decompression limits for XML-RPC communications, protecting against both XML-based attacks and gzip compression bombs that can cause denial of service through excessive memory consumption.
3
4
## Capabilities
5
6
### Monkey Patching Functions
7
8
Functions to apply and remove system-wide XML-RPC security patches.
9
10
```python { .api }
11
def monkey_patch():
12
"""
13
Apply security patches to XML-RPC modules system-wide.
14
15
Replaces xmlrpc.client.FastParser with DefusedExpatParser,
16
xmlrpc.client.GzipDecodedResponse with DefusedGzipDecodedResponse,
17
and xmlrpc.client.gzip_decode with defused_gzip_decode.
18
19
Also patches xmlrpc.server.gzip_decode if available (Python 3).
20
21
This is a global monkey patch that affects all XML-RPC usage
22
in the current Python process.
23
"""
24
25
def unmonkey_patch():
26
"""
27
Remove security patches from XML-RPC modules.
28
29
Restores original XML-RPC implementations by reverting
30
monkey patches applied by monkey_patch().
31
32
Warning: This removes security protections and should only
33
be used if defused XML-RPC processing is causing compatibility issues.
34
"""
35
```
36
37
**Usage Examples:**
38
39
```python
40
import defusedxml.xmlrpc as xmlrpc_defused
41
42
# Apply system-wide XML-RPC security patches
43
xmlrpc_defused.monkey_patch()
44
45
# Now all XML-RPC operations use secure implementations
46
import xmlrpc.client
47
server = xmlrpc.client.ServerProxy('http://example.com/xmlrpc')
48
result = server.some_method() # Uses defused parser
49
50
# Remove patches if needed (not recommended)
51
xmlrpc_defused.unmonkey_patch()
52
```
53
54
### Secure Gzip Decompression
55
56
Secure gzip decompression with configurable size limits to prevent gzip bomb attacks.
57
58
```python { .api }
59
def defused_gzip_decode(data, limit=None):
60
"""
61
Decompress gzip-encoded data with size limits to prevent gzip bombs.
62
63
Args:
64
data (bytes): Gzip-compressed data to decompress
65
limit (int, optional): Maximum decompressed size in bytes (default: MAX_DATA)
66
67
Returns:
68
bytes: Decompressed data
69
70
Raises:
71
ValueError: If decompressed data exceeds size limit or data is invalid
72
NotImplementedError: If gzip module is not available
73
"""
74
```
75
76
**Usage Examples:**
77
78
```python
79
import defusedxml.xmlrpc as xmlrpc_defused
80
81
# Decompress with default limit (30MB)
82
compressed_data = get_gzip_data()
83
try:
84
decompressed = xmlrpc_defused.defused_gzip_decode(compressed_data)
85
print(f"Decompressed {len(compressed_data)} bytes to {len(decompressed)} bytes")
86
except ValueError as e:
87
print(f"Gzip decompression failed: {e}")
88
89
# Decompress with custom limit (10MB)
90
try:
91
decompressed = xmlrpc_defused.defused_gzip_decode(compressed_data, limit=10*1024*1024)
92
except ValueError as e:
93
print(f"Data exceeded 10MB limit: {e}")
94
95
# Disable limit (not recommended for untrusted data)
96
decompressed = xmlrpc_defused.defused_gzip_decode(trusted_data, limit=-1)
97
```
98
99
### Secure XML-RPC Parser
100
101
Secure XML-RPC parser with configurable security restrictions for processing XML-RPC requests and responses.
102
103
```python { .api }
104
class DefusedExpatParser:
105
"""
106
Secure XML-RPC parser using expat with configurable security restrictions.
107
108
Replaces the standard XML-RPC FastParser with security handlers
109
to prevent XML bomb attacks, DTD processing attacks, and external
110
entity attacks in XML-RPC communications.
111
"""
112
113
def __init__(self, target, forbid_dtd=False, forbid_entities=True, forbid_external=True):
114
"""
115
Initialize DefusedExpatParser for XML-RPC processing.
116
117
Args:
118
target: XML-RPC target handler for processing parsed data
119
forbid_dtd (bool): Forbid DTD processing (default: False)
120
forbid_entities (bool): Forbid entity expansion (default: True)
121
forbid_external (bool): Forbid external references (default: True)
122
"""
123
124
def defused_start_doctype_decl(self, name, sysid, pubid, has_internal_subset):
125
"""Handler that raises DTDForbidden when DTD processing is forbidden"""
126
127
def defused_entity_decl(self, name, is_parameter_entity, value, base, sysid, pubid, notation_name):
128
"""Handler that raises EntitiesForbidden when entity processing is forbidden"""
129
130
def defused_unparsed_entity_decl(self, name, base, sysid, pubid, notation_name):
131
"""Handler that raises EntitiesForbidden for unparsed entities when forbidden"""
132
133
def defused_external_entity_ref_handler(self, context, base, sysid, pubid):
134
"""Handler that raises ExternalReferenceForbidden when external references are forbidden"""
135
```
136
137
### Secure Gzip Response Handler
138
139
Secure gzip response handler with size limits for processing compressed XML-RPC responses.
140
141
```python { .api }
142
class DefusedGzipDecodedResponse:
143
"""
144
Secure gzip-decoded response handler with size limits.
145
146
Replaces the standard GzipDecodedResponse with size limits
147
to prevent gzip bomb attacks that can consume excessive memory
148
through maliciously crafted compressed responses.
149
"""
150
151
def __init__(self, response, limit=None):
152
"""
153
Initialize DefusedGzipDecodedResponse with size limits.
154
155
Args:
156
response: HTTP response object containing gzip-compressed data
157
limit (int, optional): Maximum decompressed size in bytes (default: MAX_DATA)
158
159
Raises:
160
ValueError: If response data exceeds size limit
161
NotImplementedError: If gzip module is not available
162
"""
163
164
def read(self, n):
165
"""
166
Read and decompress up to n bytes from response.
167
168
Args:
169
n (int): Maximum number of bytes to read
170
171
Returns:
172
bytes: Decompressed data
173
174
Raises:
175
ValueError: If total decompressed data exceeds size limit
176
"""
177
178
def close(self):
179
"""Close the response and cleanup resources"""
180
```
181
182
### Configuration Constants
183
184
```python { .api }
185
MAX_DATA = 30 * 1024 * 1024 # Maximum data size limit (30MB)
186
```
187
188
**Usage Example:**
189
190
```python
191
import defusedxml.xmlrpc as xmlrpc_defused
192
193
# Custom size limit
194
custom_limit = 5 * 1024 * 1024 # 5MB
195
response_handler = xmlrpc_defused.DefusedGzipDecodedResponse(http_response, limit=custom_limit)
196
197
try:
198
data = response_handler.read(1024)
199
while data:
200
process_data(data)
201
data = response_handler.read(1024)
202
finally:
203
response_handler.close()
204
```
205
206
## Common Usage Patterns
207
208
### System-wide XML-RPC Protection
209
210
```python
211
import defusedxml.xmlrpc as xmlrpc_defused
212
213
# Apply protection at application startup
214
xmlrpc_defused.monkey_patch()
215
216
# Now all XML-RPC usage is automatically protected
217
import xmlrpc.client
218
219
def make_xmlrpc_call(server_url, method_name, *args):
220
"""Make XML-RPC call with automatic security protection."""
221
try:
222
server = xmlrpc.client.ServerProxy(server_url)
223
result = getattr(server, method_name)(*args)
224
return result
225
except xmlrpc.client.Fault as e:
226
print(f"XML-RPC fault: {e}")
227
except Exception as e:
228
print(f"XML-RPC error: {e}")
229
return None
230
231
# Make calls normally - they're automatically secured
232
result = make_xmlrpc_call('http://example.com/rpc', 'get_data', 'param1')
233
```
234
235
### Manual Parser Configuration
236
237
```python
238
import defusedxml.xmlrpc as xmlrpc_defused
239
import xmlrpc.client
240
241
class SecureXMLRPCTransport(xmlrpc.client.Transport):
242
"""Custom XML-RPC transport with defused parser."""
243
244
def getparser(self):
245
"""Return defused parser instead of standard parser."""
246
target = xmlrpc.client.Unmarshaller()
247
parser = xmlrpc_defused.DefusedExpatParser(
248
target,
249
forbid_dtd=True,
250
forbid_entities=True,
251
forbid_external=True
252
)
253
return parser, target
254
255
# Use custom transport
256
transport = SecureXMLRPCTransport()
257
server = xmlrpc.client.ServerProxy('http://example.com/rpc', transport=transport)
258
result = server.method_name()
259
```
260
261
### Gzip Decompression with Error Handling
262
263
```python
264
import defusedxml.xmlrpc as xmlrpc_defused
265
266
def safe_gzip_decode(compressed_data, max_size=None):
267
"""Safely decompress gzip data with error handling."""
268
try:
269
limit = max_size or xmlrpc_defused.MAX_DATA
270
decompressed = xmlrpc_defused.defused_gzip_decode(compressed_data, limit=limit)
271
272
print(f"Successfully decompressed {len(compressed_data)} bytes to {len(decompressed)} bytes")
273
return decompressed
274
275
except ValueError as e:
276
if "max gzipped payload length exceeded" in str(e):
277
print(f"Gzip bomb detected: compressed data would exceed {limit} bytes")
278
elif "invalid data" in str(e):
279
print("Invalid gzip data format")
280
else:
281
print(f"Gzip decompression error: {e}")
282
return None
283
except NotImplementedError:
284
print("Gzip module not available")
285
return None
286
```
287
288
### XML-RPC Server Protection
289
290
```python
291
import defusedxml.xmlrpc as xmlrpc_defused
292
from xmlrpc.server import SimpleXMLRPCServer
293
294
# Apply patches before creating server
295
xmlrpc_defused.monkey_patch()
296
297
class SecureXMLRPCServer(SimpleXMLRPCServer):
298
"""XML-RPC server with defused XML processing."""
299
300
def __init__(self, *args, **kwargs):
301
super().__init__(*args, **kwargs)
302
# Server automatically uses defused parsers due to monkey patching
303
304
def _dispatch(self, method, params):
305
"""Dispatch method calls with additional security logging."""
306
print(f"XML-RPC call: {method} with {len(params)} parameters")
307
return super()._dispatch(method, params)
308
309
# Create and run secure server
310
server = SecureXMLRPCServer(('localhost', 8000))
311
server.register_function(lambda x: x * 2, 'double')
312
313
print("Starting secure XML-RPC server...")
314
server.serve_forever()
315
```
316
317
### Conditional Security Configuration
318
319
```python
320
import defusedxml.xmlrpc as xmlrpc_defused
321
import os
322
323
def configure_xmlrpc_security():
324
"""Configure XML-RPC security based on environment."""
325
326
# Check if we're in a development environment
327
if os.getenv('ENVIRONMENT') == 'development':
328
print("Development mode: XML-RPC security monitoring only")
329
# Could implement logging-only mode here
330
else:
331
print("Production mode: Applying XML-RPC security patches")
332
xmlrpc_defused.monkey_patch()
333
334
def safe_xmlrpc_call(url, method, *args, max_response_size=None):
335
"""Make XML-RPC call with optional response size limits."""
336
import xmlrpc.client
337
338
# Set custom gzip limit if specified
339
if max_response_size:
340
original_max = xmlrpc_defused.MAX_DATA
341
xmlrpc_defused.MAX_DATA = max_response_size
342
343
try:
344
server = xmlrpc.client.ServerProxy(url)
345
result = getattr(server, method)(*args)
346
return result
347
finally:
348
# Restore original limit
349
if max_response_size:
350
xmlrpc_defused.MAX_DATA = original_max
351
352
# Configure security at startup
353
configure_xmlrpc_security()
354
355
# Make calls with custom limits
356
result = safe_xmlrpc_call('http://api.example.com/rpc', 'get_large_dataset', max_response_size=50*1024*1024)
357
```
358
359
## Migration from Standard Library
360
361
DefusedXML XML-RPC protection is typically applied via monkey patching:
362
363
```python
364
# Before (vulnerable)
365
import xmlrpc.client
366
server = xmlrpc.client.ServerProxy('http://example.com/rpc')
367
result = server.method()
368
369
# After (secure)
370
import defusedxml.xmlrpc as xmlrpc_defused
371
xmlrpc_defused.monkey_patch() # Apply protection system-wide
372
373
import xmlrpc.client
374
server = xmlrpc.client.ServerProxy('http://example.com/rpc')
375
result = server.method() # Now automatically protected
376
```
377
378
The monkey patching approach ensures all XML-RPC usage in the application is automatically secured without requiring code changes.