作成しましたー
同じドメイン上で読み込んでいるものは上に、外部ドメインで読み込んでいるものは下階層からアクセスできます。
クリックするとwindow.openでソースを参照します。
各ドメインはアルファベット順、同じドメイン上のファイルは読み込んだ順でソートされます。
Twitter / esukei: @kyo_ago @teramako …
Twitter上でChromeの設定変更で回避できるとご指摘いただいたので削除しましたー
ありがとうございましたー
念のためソースはそのまま置いておきます(webNavigationとかwebRequestのサンプルにどうぞ)
kyo-ago/view_source_utf8 · GitHub
Chrome ウェブストア - JSとかCSSのソースを見た時に文字化けるのを回避するextension
ソースを見るたびに毎回「ツール」->「エンコード」から文字コード修正している方へ
これに関してTwitter上で「Shift+Ctrl+J(Windows)すると、デベロッパーツールを対象にしたデベロッパーツールが起動する」と教えてもらって解決しました。
https://twitter.com/syoichi/statuses/306059743473057795
ありがとうございました。
//——————————————————
JSDeferredは内部でthrowをcacheするので、そのままだとthrowでassertionするテストフレームワークが動作しない。
これは以下のようにDeferred.onerror内で再度throwし直すことで、テストフレームワークにassertionが飛ぶので正しく動作するようになる。
Deferred.onerror = function (e) {
throw e;
};
通常のコードでもDeferred内で発生した例外を見落とすことがあるので上記のコードは入れといたほうがいいかも。
別Windowで生成したオブジェクトのmethodを呼びだそうとすると動作しない。
(Callオブジェクトが変わったせい?)
これを回避するためには今のWindow上のClassからmethodを持ってくればい。
具体的には以下のようにする。
(別Window上で作成したFileEntry objectを使用する例)
FileEntry.file.call(another_window_file_entry, function (file) {
// fileは普通に使える
});
ただ、DirectoryEntryはglobal objectから直接参照できないので上記の方法が取れなかった。
これに関しては今のWindow上で作成したDirectoryEntry instanceの.constructor.prototypeをClassとして定義し、上記の方法を行うことで使用することができる。
(this.requestFileSystem || this.webkitRequestFileSystem)(TEMPORARY, 1, function (fs) {
this.DirectoryReader = fs.root.createReader().constructor.prototype;
this.DirectoryEntry = fs.root.constructor.prototype;
}.bind(this));
注意点として、DirectoryEntryから使用するDirectoryReaderも同じWindow上で生成したものを使用する必要がある。
(DirectoryEntry.createReader.call(another_window_directory_entry)する必要がある)
ただ、この方法でも生成したreaderからDirectoryReader.readEntries.call(reader)しても、毎回同じ結果が取得されて読み込みが進まない。
これに関しては一旦「setTimeoutを入れつつ3回呼んで返ってきた結果が同じだった場合は読み込み完了」として対応。
(「同じ結果か」はentry.fullPathで比較)
Google Analyticsが提供するJSは以下の様な初期化処理をおこなっている。
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-XXXXX-X']);
_gaq.push(['_trackPageview']);
(function() {
// script要素の動的読み込み
})();
</script>
Google Analyticsに限らす外部JSを読み込むライブラリの場合、「初期化は非同期で行いたいが、APIの読み出しは同期で行いたい」という要望がある。
通常こういった要望を叶えるためには、メインのJSが読み込み済みかどうか監視して処理を呼び分けるような長い初期化処理を貼り付けタグとして発行し各サイトに埋め込んでもらう必要がある。
ただ、貼り付けタグが長くなるとコピペミスも増えるし、貼り付けタグ部分で問題が発生した時に対処できなくなるため、貼り付けタグはできるだけ短いほうが嬉しい。
そこで最初に紹介したGoogle Analyticsのタグを見ると、初期化処理は実質「var _gaq = _gaq || [];」だけということがわかる。
ではこの初期化処理だけでどうやって「初期化は非同期だけど、API呼び出しは同期」を実現しているかというと、非同期で呼び出されるメインコード内に以下の様な処理があればいい。
(function () {
var __gaq = _gaq;
_gaq = { 'push' : function (log) {
// 先に追加済みの処理
}};
__gaq.forEach(_gaq.push);
})();
たったこれだけの処理で「初期化は非同期だけど、API呼び出しは同期」が実現できる。
このコードは「初期化は非同期だけど、API呼び出しは同期」といった場合以外にも「状態によって一時的に処理をキューに入れて置きたい」といった場合にも使えるので、「ログの表示要素が存在する場合のみログを表示して、存在しない場合にはログキューに入れる」という場合にも使える。
chrome.experimental.record - Google Chrome
名前からSelenium的な「ユーザの動作を記録して再生する」APIかと思ったが、「別プロセスでChromeを起動して動作記録を取る」APIのよう。
Windows、Mac、Stable、Dev共に動作しなかったので詳細不明(別プロセスのChromeの起動に失敗してるっぽい)
chrome.debugger - Google Chrome
DevToolsの内容を外部から操作できるAPIだが、「DevToolsを起動中はAPI経由での接続ができない」という制限があるためあまり実用的ではない。
(remove debugger用の通信プロトコルを叩けるようにしたAPIのようなので、元々1ページに1接続で十分な想定だったんだろう)
ドキュメントには今後DevToolsを起動中でもAPI経由で接続できるようになるような記述があるので、その点が修正されれば色々と面白いことが出来そう。
(SourceのLiveEditも触れるようになっているっぽい)
chrome.devtools.panels - Google Chrome
DevToolsの既存のパネル上に要素を配置できるAPI。
ただし、このAPIが呼ばれるコード内からは、上記chrome.debugger APIにアクセス出来ないのであまり実用性は高くない。
chrome.socket - Google Chrome
Windows 7環境ではsocket.accept周りがおかしい気がする。
(何回か接続、切断を繰り返すとacceptがcallbackされなくなる。acceptを取りこぼしてる感じ。Macでは正常。Stable, Dev, Canaryで再現)
まだ、socket.acceptはexperimentalなので正常版では直るのかな?
chrome.socket.connectでDNSの名前引きに失敗した場合、callbackに-105が渡される。-3が渡されることもあるけど、こちらは何かわからない。
disconnect、destroyは両方呼び出さないとちゃんと切断されなかったので両方呼び出しているが、socketの使い回しとかするのであればちゃんと呼び分けたほうがいいのかもしれない。
chrome.socket.listenは同じポートで複数回呼び出すとcallbackに0以外を渡すので確認する。
chrome.socket.acceptはcallback内でエラー処理が終わったら、即同期的に再度chrome.socket.acceptを呼び出す。
callbackを待たずに再度chrome.socket.acceptを読んだ場合は-2が返る。
(-2はエラー処理ではなく無視していいと思う)
-2と0以外の値でcallbackされた場合、エラーとして処理する。
chrome.socket.setNoDelayはあってもなくても動作は変わらない?Proxy作成時にブラウザと通信するsocketには有効にしてみた。
(特に変わった感じはなかった)
chrome.socket.readはまずenv.data.byteLengthを見てデータが読めたかどうか確認する。それ以上データが有るかどうかは読み込んだデータ内容を見て判断する。
サーバがデータを送らないのにchrome.socket.readするとcallbackが呼ばれずに延々待たされることになるので注意(当然だけど)
読み込んだevn.data.byteLengthが0の場合、接続元がコネクションを切断しているのでこちらも切断する。
chrome.socket.listenの第4引数(backlog)は「クライアントが接続してきた場合、backlog数分だけ待たせておく(それ以上はエラーにする)」数。ただ、現状有効なのか若干疑問(動いてないような?。特にWindows環境)
RDocの脆弱性情報
2013-02-06に以下の脆弱性情報が公開されました。
RDoc で生成した HTML ドキュメントにおける XSS 脆弱性 (CVE-2013-0256)
これはRDocの脆弱性情報ですが、実際にはdarkfish.jsというファイルの修正のみでありJSの問題であることがわかります。
問題のdarkfish.jsを確認すると該当の処理は「var anchor = window.location.hash.substring(1);から取得した値を$(“a[name=” + anchor + “]”);に渡した」処理であったことがわかります。
(このファイルが脆弱性情報のファイルと同じかは確認してないですが、ファイル名とコードから同一と判断しました)
修正方法としては$(“a[name=” + anchor + “]”)でのセレクター埋め込みをやめて$(“a[name]”).eachのeach内で要素と変数を比較するようにした($(this).attr(“name”) == anchor)なっています。
何が問題だったのか
外部から受け取った値(location.hash)を$(“a[name=” + anchor + “]”)に渡すことでXSSが可能になっています。
これは作者が$()で要素の選択のみを想定していたはずが、意図せずに要素の生成まで出来てしまうことが原因です。
この問題は「location.hashでページ内の要素をJSで指定する」場合の実装に多く存在しており、jQueryを使ったサイトで脆弱性がある場合高い頻度で報告されます。
jQueryにおけるXSSを引き起こしやすい問題について - 金利0無利息キャッシング – キャッシングできます - subtech
どう回避すればいいのか
RDocの脆弱性情報では変数をセレクターに埋め込むことをやめて変数比較にしていますが、jQueryを使う場合には以下のようなコードでも回避することが可能です。
要素の選択系APIを使う
$(“a[name]”).filter(“[name=”+anchor+”]”)
$()は「要素の生成」と「要素の選択」両方の機能を持っているため「意図しない要素の生成」が発生しますが、.filterや.findは「要素の選択」の機能しか持っていないため「意図しない要素の生成」が発生しません。
(ただし、極端にブラウザのリソースを奪うことはできる可能性があるので、それが問題になる場合は次の回避策を使ってください)
functionを使って要素を選択する
$(“a[name]”).filter(function () { return $(this).attr(“name”) === anchor })
.filterは引数にfunctionを受け取り、その中でtrueを返した要素を選択する機能があるため、この機能を使うことで意図しない要素の生成が発生しません。
$()に渡す内容を制限する
$(“a[name=” + anchor.replace(/\W/g, ”) + “]”)
anchorの内容を[a-zA-Z0-9_]に制限することで意図しない要素の生成を回避出来ます。
(ただし、RDocのname属性が全て\wで記述されているかは確認しています)
そもそもの問題点
そもそもの問題点として、コード内で文字列を組み立てて外部のシステムに渡す構成自体が安全ではありません。
- SQLインジェクション(SQL文字列に変数を埋め込む場合に多く発生)
- コマンドインジェクション(コマンドライン文字列に変数を埋め込む場合に多く発生)
- XSS(html文字列に変数を埋め込む場合に多く発生)
システム的に文字列で値をやり取りすること自体はやむを得ない場合もありますが、こういったコードを書く場合は常にインジェクション系の問題が発生しないか注意してコードを書く必要があります。
類似問題
今回の脆弱性情報とか無関係ですが、同じようにlocation.hash経由でよく指定される内容として「サーバサイドのファイル名」があります。
これは以下の様なコードで、やはり脆弱性があります。
$.get(location.hash.substring(1), function (html) {
$('body').html(html);
});
古い(XHR level2をサポートしていない)ブラウザであれば、上記のコードで取得できるのは同じドメイン上のファイルのみとなるため、同じドメイン上に問題のあるファイルが存在しない場合安全ですが、XHR level2をサポートしているブラウザであれば上記コードでは外部サーバのデータを取得することが可能なため危険なコードとなります。
この問題はlocation.hashを「ファイルパスでしか使用できない文字列に制限する」ことで回避可能ではありますが、ChromeやIEはロングIPがサポートされていることと、スキーマの省略をできるので文字列制限だけでは回避が難しい問題があります。
// XMLHttpRequest cannot load http://192.168.0.1/.
$.get('//3232235521', function (html) {
$('body').html(html);
});
IPアドレス - Wikipedia
The Sexy Assassinで紹介されてるCSS HTML Attribute Readerがどこまで危険か検証してみた
まず、外部から任意のファイルを指定してのアクセスできる構成はできるだけ避けるほうが無難ですが、もしそういう構成をとらざるをえない場合以下の点に注意してください。
- アクセスできるディレクトリを指定する
アクセスできるディレクトリを「/html/」等の文字列を追加することでschemeを省略したURLや同じドメイン上の想定していないディレクトリへのアクセスを回避出来ます。また、アクセスできるディレクトリ以下には実際に使用するhtml以外のファイルは存在しないようにしましょう(ユーザがファイルをアップできないように)。 - .replace(/.+/g, ‘.’)を行う 「../」と言った方法で指定ディレクトリ外へアクセスされることを制限します。これに関しては単純に.replace(/../g, ”)といったコードでは「../」で回避できてしまうことに注意してください。
この問題は古いjQuery Mobileでも存在した問題で、元々安全に実装するのが難しい構成です。
可能であればファイル名を\wに制限し、以下の様な実装をおすすめしますが、この構成でもサーバ上の実装によっては危険な状況になる場合もあるので注意してください。
$.get('/html/'+(location.hash.replace(/\W/g, ''))+'.html', function (html) {
$('body').html(html);
});