説明
信頼できないXMLの外部エンティティを解決すると、ローカルファイルやネットワークリソースへアクセスする可能性があります。github.com/lestrrat-go/libxml2/parser の XMLParseNoEnt は、名前とは異なりエンティティの置換を有効にします。影響はlibxml2のバージョン、ローダー設定、アプリケーション権限、結果の利用方法によって異なります。
想定される影響
- パーサーが読み取れる内部ファイルが、処理結果やエラーから漏えいするおそれがあります。
- 有効なネットワークローダーを通じて、内部サービスへリクエストが送られる可能性があります。
- エンティティの展開や大きな入力により、CPUやメモリが枯渇するおそれがあります。
対処方法
- 信頼できないXMLには、不要なエンティティ置換や外部DTDの読み込みを有効にしないでください。
- インストールされたGoバインディングとlibxml2で、外部リソースの読み込みが禁止されているか確認してください。ネットワークの遮断だけでは、ローカルファイルへのアクセスは防げません。
- 読み取り時点から入力サイズを制限し、必要なスキーマ検証を行ってください。スキーマやContent-Typeの検証は、外部エンティティの禁止に代わるものではありません。
- ファイルとネットワークの権限を最小限にし、管理されたテストで外部リソースを読み取らないことを確認してください。
例
Goバインディングとlibxml2が必要なHTTP処理の例です。本番用の認証とTLS構成は省略しています。変更前の ContentLength による割り当てと1回だけの Read も、長さが不明な入力や部分的な読み取りを安全に扱えません。
変更前
go
package main
import (
"fmt"
"net/http"
"github.com/lestrrat-go/libxml2/parser"
)
// 変更前: アップロードされたXMLをそのまま解析
func parseXMLUnsafe(w http.ResponseWriter, r *http.Request) {
body := make([]byte, r.ContentLength)
if _, err := r.Body.Read(body); err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
// CWE-611: エンティティ置換と外部エンティティの読み込みを有効化
p := parser.New(parser.XMLParseNoEnt)
doc, err := p.ParseString(string(body))
if err != nil {
http.Error(w, "parse error", http.StatusBadRequest)
return
}
defer doc.Free()
fmt.Fprintln(w, "XML parsed (unsafe)")
}
func main() {
http.HandleFunc("/upload", parseXMLUnsafe)
http.ListenAndServe(":8080", nil)
}
変更後
go
package main
import (
"fmt"
"io"
"net/http"
"github.com/lestrrat-go/libxml2/parser"
)
// 入力サイズを制限し、エンティティ置換オプションを使わない
func parseXMLSafe(w http.ResponseWriter, r *http.Request) {
const maxBody = 1 << 20
body, err := io.ReadAll(io.LimitReader(r.Body, maxBody+1))
if err != nil {
http.Error(w, "read error", http.StatusBadRequest)
return
}
if len(body) > maxBody {
http.Error(w, "body too large", http.StatusRequestEntityTooLarge)
return
}
// 1) エンティティ置換や外部DTDの読み込みを有効にしない
p := parser.New() // XMLParseNoEntを指定しない
// 2) 必要なスキーマ検証は別途行う
// スキーマ検証は外部リソースの禁止に代わるものではない
doc, err := p.ParseString(string(body))
if err != nil {
http.Error(w, "parse error", http.StatusBadRequest)
return
}
defer doc.Free()
fmt.Fprintln(w, "XML parsed safely")
}
func main() {
http.HandleFunc("/upload", parseXMLSafe)
http.ListenAndServe(":8080", nil)
}
解説:
- 変更前:
XMLParseNoEntがエンティティ置換を有効にします。次のようなファイル参照が処理されるかは、ローダーと権限に依存します。ファイルが読まれても、応答へ漏れるかはアプリケーションの処理によって異なります。
xml
<!DOCTYPE d [<!ENTITY x SYSTEM "file:///etc/passwd">]>
<data>&x;</data>
- 変更後: 読み取りを1 MiBより1バイト多い範囲に制限し、超過した入力を拒否します。
parser.New()には置換や外部DTDの読み込みオプションを渡しません。他のローダーやグローバル設定が外部アクセスを再び許可していないかも確認してください。入力サイズの制限だけで、すべての解析時のリソース枯渇を防げるわけではありません。