xml解析器默认无超时,恶意外部实体可致线程阻塞;须在http层设超时(如python用requests、java配url连接超时、go用context),禁用xxe,并拆分获取与解析步骤。

XML解析器默认不设超时,恶意外部实体会卡死进程
Python的xml.etree.ElementTree、Java的DocumentBuilder、Go的encoding/xml等主流解析器,底层读取输入流时完全不检查耗时。一旦遇到构造的恶意XML(比如递归实体或超大文本节点),服务可能在parse()调用里无限等待,CPU不飙高但线程彻底阻塞。
- 典型现象:
ElementTree.parse()卡住数分钟无返回,strace显示进程停在read()系统调用 - 真实场景:接收第三方上传的XML配置、SOAP请求、RSS源,且未做预检
- 根本原因:XML解析是同步阻塞IO,超时必须由上层控制,解析器自身不提供timeout参数
Python中用urllib.request或requests加超时再喂给ElementTree
不能对ElementTree.parse()本身设超时,得把“获取XML内容”和“解析”拆开,前者加超时,后者只处理已下载的字节流。
- 错误写法:
ElementTree.parse("http://evil.com/malicious.xml")—— URL直接传入,底层用urlopen无超时 - 正确做法:先用
requests.get(url, timeout=5)拉取,再用ElementTree.fromstring(r.content) - 注意
fromstring()和parse()区别:parse()接受文件路径或file-like对象,fromstring()只接受bytes/str;若用parse(),需构造io.BytesIO(r.content) - 别忽略
requests.exceptions.Timeout和requests.exceptions.ConnectionError,它们比XML解析异常更早抛出
Java用SAXParser或DocumentBuilder前禁用外部实体并设置连接超时
Java的DocumentBuilder默认开启http://xml.org/sax/features/external-parameter-entities,攻击者可利用xxe触发DNS外连或本地文件读取,同时造成阻塞。光关XXE不够,网络层超时也得配。
- 必须关闭外部实体:
dbf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true) - 连接超时要设在
URL.openStream()之前:URL url = new URL(xmlUrl); url.openConnection().setConnectTimeout(3000); - 避免用
DocumentBuilder.parse(String uri)这种便利方法,它内部new URL(uri).openStream(),没机会设timeout - 如果用
SAXParser,同样需提前获取InputStream并确保其底层Socket已设setSoTimeout(5000)
Go的encoding/xml需配合context.WithTimeout控制HTTP请求生命周期
Go标准库encoding/xml本身无超时机制,但它的输入源是io.Reader,所以超时必须落在HTTP客户端层。关键点在于:超时要作用于整个请求过程,包括DNS解析、TLS握手、响应体读取。
- 错误写法:
http.Get(url)—— 无超时,可能永久挂起 - 正确写法:
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second); resp, err := http.DefaultClient.Do(req.WithContext(ctx)) - 即使设置了
http.Client.Timeout,也要注意它不覆盖DNS和TLS时间,context.WithTimeout才是全链路控制 - 解析时用
xml.NewDecoder(resp.Body).Decode(&v),此时Body已受context保护,读取超时会自动触发context.DeadlineExceeded
真正难处理的是流式XML解析场景——比如解析一个持续推送的XML事件流,这时候超时不能简单设固定值,得结合心跳检测或最大字节数限制。另外,所有语言都要记得关闭响应Body,否则连接池会耗尽。










