@@ -143,6 +143,7 @@ def fast_func():
143143
144144# ==============================================================================
145145import xml .sax
146+ import xml .sax .handler
146147
147148class SimpleHandler (xml .sax .ContentHandler ):
148149 def __init__ (self ):
@@ -447,3 +448,203 @@ def test_dtd_not_possible():
447448
448449 d = xmltodict .parse (dtd_retrieval )
449450 assert hit_dtd == False
451+
452+ # ==============================================================================
453+ import xml .dom .minidom
454+
455+ class TestMinidom :
456+ @staticmethod
457+ @expects_timeout
458+ def test_billion_laughs ():
459+ xml .dom .minidom .parseString (billion_laughs )
460+
461+ @staticmethod
462+ @expects_timeout
463+ def test_quardratic_blowup ():
464+ xml .dom .minidom .parseString (quadratic_blowup )
465+
466+ @staticmethod
467+ def test_ok_xml ():
468+ doc = xml .dom .minidom .parseString (ok_xml )
469+ assert doc .documentElement .tagName == "test"
470+ assert doc .documentElement .childNodes [0 ].data == "hello world"
471+
472+ @staticmethod
473+ def test_xxe ():
474+ # disabled by default
475+ doc = xml .dom .minidom .parseString (local_xxe )
476+ assert doc .documentElement .tagName == "test"
477+ assert doc .documentElement .childNodes == []
478+
479+ # but can be turned on
480+ parser = xml .sax .make_parser ()
481+ parser .setFeature (xml .sax .handler .feature_external_ges , True )
482+ doc = xml .dom .minidom .parseString (local_xxe , parser = parser )
483+ assert doc .documentElement .tagName == "test"
484+ assert doc .documentElement .childNodes [0 ].data == "SECRET_FLAG"
485+
486+ # which also works remotely
487+ global hit_xxe
488+ hit_xxe = False
489+
490+ parser = xml .sax .make_parser ()
491+ parser .setFeature (xml .sax .handler .feature_external_ges , True )
492+ _doc = xml .dom .minidom .parseString (remote_xxe , parser = parser )
493+ assert hit_xxe == True
494+
495+ @staticmethod
496+ def test_dtd ():
497+ # not possible by default
498+ global hit_dtd
499+ hit_dtd = False
500+
501+ _doc = xml .dom .minidom .parseString (dtd_retrieval )
502+ assert hit_dtd == False
503+
504+ # but can be turned on
505+ parser = xml .sax .make_parser ()
506+ parser .setFeature (xml .sax .handler .feature_external_ges , True )
507+ _doc = xml .dom .minidom .parseString (dtd_retrieval , parser = parser )
508+ assert hit_dtd == True
509+
510+ # ==============================================================================
511+ import xml .dom .pulldom
512+
513+ class TestPulldom :
514+ @staticmethod
515+ @expects_timeout
516+ def test_billion_laughs ():
517+ doc = xml .dom .pulldom .parseString (billion_laughs )
518+ # you NEED to iterate over the items for it to take long
519+ for event , node in doc :
520+ pass
521+
522+ @staticmethod
523+ @expects_timeout
524+ def test_quardratic_blowup ():
525+ doc = xml .dom .pulldom .parseString (quadratic_blowup )
526+ for event , node in doc :
527+ pass
528+
529+ @staticmethod
530+ def test_ok_xml ():
531+ doc = xml .dom .pulldom .parseString (ok_xml )
532+ for event , node in doc :
533+ if event == xml .dom .pulldom .START_ELEMENT :
534+ assert node .tagName == "test"
535+ elif event == xml .dom .pulldom .CHARACTERS :
536+ assert node .data == "hello world"
537+
538+ @staticmethod
539+ def test_xxe ():
540+ # disabled by default
541+ doc = xml .dom .pulldom .parseString (local_xxe )
542+ found_flag = False
543+ for event , node in doc :
544+ if event == xml .dom .pulldom .START_ELEMENT :
545+ assert node .tagName == "test"
546+ elif event == xml .dom .pulldom .CHARACTERS :
547+ if node .data == "SECRET_FLAG" :
548+ found_flag = True
549+ assert found_flag == False
550+
551+ # but can be turned on
552+ parser = xml .sax .make_parser ()
553+ parser .setFeature (xml .sax .handler .feature_external_ges , True )
554+ doc = xml .dom .pulldom .parseString (local_xxe , parser = parser )
555+ found_flag = False
556+ for event , node in doc :
557+ if event == xml .dom .pulldom .START_ELEMENT :
558+ assert node .tagName == "test"
559+ elif event == xml .dom .pulldom .CHARACTERS :
560+ if node .data == "SECRET_FLAG" :
561+ found_flag = True
562+ assert found_flag == True
563+
564+ # which also works remotely
565+ global hit_xxe
566+ hit_xxe = False
567+ parser = xml .sax .make_parser ()
568+ parser .setFeature (xml .sax .handler .feature_external_ges , True )
569+ doc = xml .dom .pulldom .parseString (remote_xxe , parser = parser )
570+ assert hit_xxe == False
571+ for event , node in doc :
572+ pass
573+ assert hit_xxe == True
574+
575+ @staticmethod
576+ def test_dtd ():
577+ # not possible by default
578+ global hit_dtd
579+ hit_dtd = False
580+
581+ doc = xml .dom .pulldom .parseString (dtd_retrieval )
582+ for event , node in doc :
583+ pass
584+ assert hit_dtd == False
585+
586+ # but can be turned on
587+ parser = xml .sax .make_parser ()
588+ parser .setFeature (xml .sax .handler .feature_external_ges , True )
589+ doc = xml .dom .pulldom .parseString (dtd_retrieval , parser = parser )
590+ for event , node in doc :
591+ pass
592+ assert hit_dtd == True
593+
594+ # ==============================================================================
595+ import xml .parsers .expat
596+
597+ class TestExpat :
598+ # this is the underlying parser implementation used by the rest of the Python
599+ # standard library. But people are probably not using this directly.
600+
601+ @staticmethod
602+ @expects_timeout
603+ def test_billion_laughs ():
604+ parser = xml .parsers .expat .ParserCreate ()
605+ parser .Parse (billion_laughs , True )
606+
607+ @staticmethod
608+ @expects_timeout
609+ def test_quardratic_blowup ():
610+ parser = xml .parsers .expat .ParserCreate ()
611+ parser .Parse (quadratic_blowup , True )
612+
613+ @staticmethod
614+ def test_ok_xml ():
615+ char_data_recv = []
616+ def char_data_handler (data ):
617+ char_data_recv .append (data )
618+
619+ parser = xml .parsers .expat .ParserCreate ()
620+ parser .CharacterDataHandler = char_data_handler
621+ parser .Parse (ok_xml , True )
622+
623+ assert char_data_recv == ["hello world" ]
624+
625+ @staticmethod
626+ def test_xxe ():
627+ # not vuln by default
628+ char_data_recv = []
629+ def char_data_handler (data ):
630+ char_data_recv .append (data )
631+
632+ parser = xml .parsers .expat .ParserCreate ()
633+ parser .CharacterDataHandler = char_data_handler
634+ parser .Parse (local_xxe , True )
635+
636+ assert char_data_recv == []
637+
638+ # there might be ways to make it vuln, but I did not investigate futher.
639+
640+ @staticmethod
641+ def test_dtd ():
642+ # not vuln by default
643+ global hit_dtd
644+ hit_dtd = False
645+
646+ parser = xml .parsers .expat .ParserCreate ()
647+ parser .Parse (dtd_retrieval , True )
648+ assert hit_dtd == False
649+
650+ # there might be ways to make it vuln, but I did not investigate futher.
0 commit comments