自由にアクセス制御可能なJavaScriptサンドボックスを作る

06 Dec 2007

追記:早速破られた!

 
safeCall("var w=(function(){return this;}).call();w.alert(w.document.cookie);");
id:kazuhooku - http://b.hatena.ne.jp/kazuhooku/20071206#bookmark-6709542

サイ本よく見ると確かに「オブジェクトのメソッドとして呼び出された関数内でも、入れ子にされた関数内のthisはグローバルオブジェクトを参照します」て書いてあった。あとMDCにも http://developer.mozilla.org/ja/docs/Core_JavaScript_1.5_Reference:Operators:Special_Operators:this_Operator

=========

面白い記事があった、GreasemonkeyのunsafeWindowを安全にすることは可能か

[JavaScript] GM_xmlhttpRequestを使えなくする - 実用
GM評価のsandbox - ゼロメムはてな支店

この記事はGreasemonkeyのunsafeWindowについて(GM実行中サイトが悪意を持っていた場合 or XSSの穴が合った場合対策)だけど 自分もちょうど、JavaScriptを安全に実行する方法が無いか考えていたので、ここに書いてみる。まず目的は

・自サイト上でユーザにJavaScriptを(限定的に)許可したい

例えば、はてなダイアリー等でユーザがブログパーツを張れるよう許可するとか。但し
・document.cookie等、危険な機能は操作不能にする
・あくまでサイト上でJSを許可する(iframeは不可)

で、幾つか考えられるのが以下。

・iframe内で実行
iframeを生成して、そのwindowの中で実行させる。これなら確実に安全なサンドボックスが用意できそうだなと思うけど、今回はスルー

・Facebookアプローチ(変数・関数名にプレフィックスを付けて無害化)
Facebookがユーザに許可するJSでは、変数・関数名にプレフィックスを付けて無害化するらしい
[Facebook] FBJSでFacebookアプリ内でJavaScriptを利用 - Kawa.netブログ(川崎有亮)
面白いな、とも思うけど無害化するロジックに穴があるとセキュリティホールになりそうな気がする。

で、色々調べていて一つ思ったんだけど、こんなのどうだろう

function safeCall(users_code){
  var sandbox = {
    window: null,
    document: null,
    alert: null
  };
  with(sandbox){
    console.log(eval(users_code));
  }
}

これならsandboxオブジェクト内に列挙したwindow, document, alert,,,等は、無効化してevalできる筈

sandbox1

確かに列挙したオブジェクトはnullになる、逆に許可したいオブジェクトは含めなければ良い筈。Sandbox.myDocument.myGetElementByIdみたいに許可するネームスペース/クラスを決めてそれを叩いて貰えば制御可能になるかな。と思ったけど、deleteでプロパティ削除されると意味ない

sandbox2

じゃあvarで宣言すればどうだろ?

function sandbox(){
  var window = null;
  var document = null;
  var alert = null;
  this.do = function(code){
    with(this){
      console.log(eval(code));
    }
  }
}

原則varで宣言した変数はdelete不可能な筈、これで一応大丈夫?

sandbox3

どうだろ、やっぱり抜け穴がある気がしてならない。。 あと、実際にはwindowのプロパティを列挙する必要がある。

window_properties

で実際に運用する時は、無害なタグとして記述して貰ってsandbox経由に実行すれば良いかな。今流行の俺俺スクリプト風に

<script type="text/my-safe-javascript">
  alert("Hello world from sandbox!!");
</script>

とか書いて貰って、ページ起動時に //script[@type="text/my-safe-javascript"] な要素を探してsandbox経由で実行みたいな。//script[@type="text/javascript"] はサーバ側のフィルタで潰す

うーん、どうだろ?やっぱ何か抜けがあるかな、ある気がする。きっとあるな。

Google OpenSocial API - mixiに期待しつつJavaScript Gadgetを試す

05 Nov 2007

sandbox.orkut.com のアカウントが使えるようになったので、GoogleのOpenSocial APIを試してみた。OpenSocial Protocolについて詳細はこちらのサイトが詳しいようです

OpenSocial Protocol - たけまる

このブログに書いてある通り、OpenSocial Protocolは

  • クライアント用のJavaScript API
  • サーバ用のOpenSocial Data API

の2種類があって、サーバ側 OpenSocial Data APIの実装はまだ無いみたい。ということで今回試したのはJavaScriptクライアントの方。ドキュメントへ書いてある通り、Orkut上で試してみる。あとこのドキュメントを開いとく

OpenSocial API Developer's Guide

sandbox.orkut.comへ登録

http://code.google.com/support/opensocialsignup/ から登録、数時間でメールが届いた。普通のwww.orkut.comとデータの互換性はある、唯一アプリケーションがsandboxでしか有効にならない。

Google Gadgets Editor

便利なので Google Gadgets Editorを利用する。

http://code.google.com/apis/gadgets/docs/gs.html#Scratchpad で自分のiGoogleホームに追加しておくと便利かも。自分のサーバがあれば、そこにXMLを置くだけでもOK。

Google Gadgets Editor

デフォルトのHello worldアプリで試してみた、右のhello.xmlのリンクURLをコピー。http://sandbox.orkut.com/MyApps.aspx を開いて「Add an application by URL:」へ張り付けて「add application」を実行。プロフィールを表示して「Hello, world!」が表示されればOK

OpenSocialAPIを叩く

まず試しということで「OpenSocial APIから自分(Owner)の情報を取ってプロフィールURLを見つける、そのURLをfooo.nameから検索して結果を画面に表示」をやってみた。

//自分の情報を取得
var req = opensocial.newDataRequest();
req.add(req.newFetchPersonRequest('OWNER'), 'owner');
req.send(onLoadFriends);

複数件の情報取得を一度のリクエストでまとめて取ることを想定しているらしい。他にも色々とある http://code.google.com/apis/opensocial/docs/javascript/reference/opensocial.DataRequest.html

で、結果をコールバック関数のonLoadFriendsで処理する。ちなみにOwnerなのにonLoadFriendsという名前なのはサンプルコードをコピッてるから:)

var owner = dataResponse.get('owner').getData();
var url = owner.getField(opensocial.Person.Field.PROFILE_URL);

指定したキー(owner)でPersonオブジェクトを取得、この仕様に従って情報を抜き出す、今回はプロファイルURLを取った。あとはいつも通りJavaScriptで書けばOK

Open social gadget test

OrkutのプロファイルURLからfooo.nameで検索、結果を表示。自分の持ってる他のページ一覧が表示されている。本当は「フレンドの〜」てのがSNSぽくて面白いと思ったけど、Orkutのフレンドがいないのでできなかった:(

やってみて特別難しいことは無い、このパーツも結局IFrameで動くので普段通りJavaScriptで作ればOK、Google Gadgets作った経験のある人はさらに楽なのかも。作ったパーツがMySpace/mixi/VOX?/Friendster なんかで汎用的に動くのは良い。ただ今現在だと「OrkutでGoogle gadgetsが動く」という状況と変わらないのであんまり面白くないな、まずはmixiの対応に期待かも。

最初 GoogleがMySpaceがFacebookが、という話を聞いてる時点では割とどうでも良かったけど、mixiが参加するなら日本としても面白くなりそう。

適当だけど一応書いたコードこれ ↓ 途中PROFILE_URLが相対パスで「死ね」と思った、しかもパーツが動くドメインは変なドメインなので 「相対パスを絶対パスに変換する」この手も使えなかった。http://www.orkut.comベタ書き、駄目だなー

<?xml version="1.0" encoding="UTF-8" ?>
<Module>
 <ModulePrefs title="Open Social Test">
   <Require feature="opensocial-0.5"/>
 </ModulePrefs>
 <Content type="html">
 <![CDATA[
 <script type="text/javascript">
  function getData() {
    var req = opensocial.newDataRequest();
    req.add(req.newFetchPersonRequest('OWNER'), 'owner');
    req.send(onLoadFriends);
  }
  function onLoadFriends(dataResponse) {
    var owner = dataResponse.get('owner').getData();
    var url = owner.getField(opensocial.Person.Field.PROFILE_URL);
    url = absolutePath(url);
    insertScript("http://fooo.name/tako3/json/likely/"+url);
  }
  function tako3(res){
    var output = "";
    for(var i=0; i < res.length; i++){
      output += "<li>"+res[i]+"</li>";
    }
    document.getElementById("foooList").innerHTML += "<ul>"+output+"</ul>";
  }
  function insertScript(url){
    var script = document.createElement('script');
    script.type = 'text/javascript';
    script.src = url;
    document.body.appendChild(script);
  }
  function absolutePath(path){
    return "http://www.orkut.com"+path;
  }
  _IG_RegisterOnloadHandler(getData);
  </script>
  <div id="foooList"> </div>
  ]]>
  </Content>
</Module>

fooo.nameから検索して一発でLDR購読するブックマークレットとGreasemonkeyスクリプト

04 Nov 2007

何か見ているページに関連するページをfooo.nameから検索して、一発でlivedoor readerのフィード購読画面へ飛ばすブックマークレットを作った↓

関連ページをLDRで購読

何かページを見ているときにこのブックマークレットを実行すると、そのURLの持ち主が持っている他のページをfooo.nameから検索、見つかった場合はLDRのフィード購読画面へ飛ばす。こんな感じ↓

tkmrLDRDescrip

この場合だと http://blog.tkmr.org/ の持ち主である自分が持ってる、はてブ/del.icio.us/LDR/twitter/tumblr・・・なんかのRSSが列挙されている。見つからなかった場合はfooo.nameの情登録ページへ飛びます。もし判る情報があれば登録・追加して頂けると助かります:)
ちなみに fooo.name の説明はこちら

あと、一人当たり数十件くらい楽に表示されて煩雑になるので、そのフィードの概要を確認するためのGreasemonkeyスクリプトも書いた。

feedDescriptionForLDR.user.js

LDRのフィード購読画面で↑の画面のように、最新4件のエントリタイトルを表示する。
このGreasemonkeyスクリプトを入れておけば。

Webを眺めているときに気になる人(ページ)を見つけた
 
その持ち主が持っている他のページをfooo.nameから検索
 
LDR購読画面でエントリ概要が表示されるので、追いかけたいフィードを吟味して購読

という流れが割と手軽にできるかなと。 

ーーー以下余談ーーーー

例えば何か面白い記事を見かけたとして、その記事をはてなブックマークに投げて終わりじゃなくてそのブログを購読して、さらにその人の他のサイトも購読して、その人のはてブやTwitterも購読して、ってのが生まれれば良いなと思う。幸い1000件くらいは楽に追いかけられるLDRっていう道具があるんだし。はてブの "人気エントリー" を追いかけるよりは、自分が興味ある人のブックマークを追いかけた方が面白いし、またその方がはてブ全体の情報に多様性が生まれて健全だと思う。というか別にはてブに限定した話じゃなくて、Web全体として。

皆が一つの人気ランキングを見てその記事をブックマークすれば、ランキング上位の物はより上位に、下位の物はより下位に行くのは当然の話で、有名な情報はより有名に、無名な情報は無名なまま、という状況が起こりそうでまずい。じゃあそれを防ぐにはどうすれば良いか、と言われると答えは判らないけど、人の繋がりがキーになるんじゃないかなと考えている。人と人の繋がりが滑らかなクラスタリングを作って、上手く分散する。また逆に分散しすぎないよう多趣味な人間がブリッジになってクラスタを繋ぐ。じゃあどうやって実現するのが良いかと言うと難しいんだよな・・・

ーーーー追記ーーーーー

tako3.comで同じことをやる場合はこちら↓

tako3で検索して一発でLDR登録するブックマークレット

あと検索がどうも遅いなと思ったら、DBにインデックスを一つ貼り忘れていたので、修正しました。

http://fooo.name/accounts/search/http://twitter.com/otsune

修正前はやたら遅かったですが、普通になったかな。

fooo.name バグFIXと機能追加

29 Oct 2007
バグFIXと機能追加。
サイト情報も少しずつ溜まってきたので、サイト情報検索を作った。アカウント情報検索とほぼ同じ


サイト情報検索 http://fooo.name/sites/search/{検索したいURL}
アカウント情報検索 http://fooo.name/accounts/search/{検索したいURL}


複数件ヒットした場合は、複数結果として返る。
例えば、ffffound.comを検索してXML形式で取得するなら

GET http://fooo.name/sites/search/http://ffffound.com?format=xml

<?xml version="1.0" encoding="UTF-8"?>
<sites>
  <site>
    <created-on type="datetime">2007-10-14T12:19:22+00:00</created-on>
    <favicon-url>http://ffffound.com/favicon.ico</favicon-url>
    <id type="integer">110</id>
    <name>FFFFOUND!</name>
    <pattern>http://ffffound.com/home/[name]/found/</pattern>
    <updated-on type="datetime">2007-10-14T12:19:25+00:00</updated-on>
    <url>http://ffffound.com/</url>
  </site>
</sites>

がアカウント名のパターンなので

pattern = Regexp.new("http://ffffound.com/home/[name]/found/".gsub(/\[name\]/, "([^/]*)"))
acount = "http://ffffound.com/home/hogehoge/found/".scan(pattern)[0][0]

http://ffffound.com/home/hogehoge/found/ からアカウント名 hogehoge を取り出せる。

でこれを使って「OPMLのURLと探したいサービスのURLを入れれば、そのサービスを使ってる人のOPMLが出てくる」みたいな処理作れば、色々応用できないかな。例のOPMLからTwitter購読の応用として

OPML2OPMLみたいな

いつも読んでるRSSの持ち主をtako3.comとfooo.nameから探してTwitterでFollow

27 Oct 2007

ちょっと面白かった。

いつも読んでる人を follow - Elementary

まずLDRで購読してるRSSをOPMLでエクスポート、次に tako3.comfooo.name からURL全件取得。OPMLとJSONをパースしてtwitterのアカウントを探して一括Follow。
良いな〜、面白そうなので真似してみる。

まずLDRからOPMLエクスポート、全部ってのは多すぎるのでレート "5" のフィードのみに絞った。
この方法を使った http://d.hatena.ne.jp/margin/20070707/livedoor_reader_fastladder_rate

あとはRubyでパース、パース、パース
レート 5 のフィードが160個くらい、うちTwitterのアカウントが見つかったのが50人くらいだった。(むむ、もっとデータを充実する必要があるな>fooo.name)
でTwitterAPIから大量Follow :)すでにFollowしてる人はエラーになる、結果25人くらい。

こんな感じでやりました。

require "rexml/document"
require "open-uri"
require "net/http"

opml_name = "export.xml" #Download from LDR!!
tako3 = "http://tako3.com/json/all"
fooo = "http://fooo.name/tako3/json/all"

twitter_path = "/friendships/create"
twitter_account = "USERID" #twitter account
twitter_password = "PASSWORD" #twitter password

#opml to twitter url---------------------------------------------------------------
twitt_maps = {}
[tako3, fooo].each do |name|
  open(name) do |file|
    file.each_line do |line|
      urls = line.scan(/"(.*)"/)[0][0].split(" ")
      if twitter_url = urls.find {|url| url =~ Regexp.new("^http://twitter.com/") }
        urls.each do |url|
          twitt_maps[url] = twitter_url
        end
      end
    end
  end
end

opml_twitters = []
REXML::Document.new(open(opml_name)).elements["/opml/body/outline/outline[@title=&#39;5&#39;]"].each do |element|
  url = element.attributes["htmlUrl"] if element.class == REXML::Element
  opml_twitters << twitt_maps[url].chomp("/").scan(/twitter\.com\/(.*)/)[0][0] if twitt_maps[url]
end
opml_twitters.uniq!

#do post to twitter----------------------------------------------------------------
opml_twitters.each do |name|
  req = Net::HTTP::Post.new("#{twitter_path}/#{name}.xml")
  req.basic_auth twitter_account, twitter_password
  Net::HTTP.start("twitter.com", 80) do |http|
    res = http.request(req)
    p res.body
  end
end