teppay’s log

*について書きます

サイボウズ サマーインターンシップ2018いってきた

はじめに

https://cybozu.co.jp/company/job/recruitment/intern/images/interntop/banner_summer2018.png

動機

サイボウズインターンに申し込んだ動機は以下の4つです

セキュリティ・キャンプで気になった企業だった

セキュリティ・キャンプに参加した際に,企業プレゼン?的なものでサイボウズのプレゼンを見て,プレゼン中の雰囲気が良くて気になっていました

セキュリティで有名

サイボウズの脆弱性報奨金制度Twitterなどで良くランキングが流れてきたり,「バグハンター合宿」のような独特の企画を開催したりしていて,その裏側に興味がありました

報酬が良かった

ぶっちゃけインターン先としてサイボウズに目が行った最初の理由は報酬の高さです(笑 調べれば出るのであえて金額には言及しませんがトップクラスの良さだと思います.
また報酬がもらえるのはもちろん嬉しいことですが,それだけでなく「その良い報酬をインターンに支払うことのできる景気のいい?企業」であるということ自体も選択の大きい理由になりました.

インターンの内容が詳しめに公開されていた

サイボウズインターンは応募の時点でどんなことをやるかが詳しめに公開されていて,過去のレポートもあったので,インターンに参加したらなにができるかが明確だったのが良かったです.

インターンまでの流れ

あくまで今回のインターンの流れです

書類選考

Webの応募フォームからエントリーシートを提出すればOKでした.
内容も動機と自己PRとインターンに必要なスキルの確認程度のもので,研究なんかで時間がなかった自分にはとてもありがたかったです.

面接

東京オフィスでの面接でした.私服で行きました.
内容としては,書類選考の際に書いた内容をベースに,自分が今やってることなんかを聞かれたり逆に質問したりでした. 「面接」という固い感じではなく,おしゃれなオフィスで二人の社員さんとおしゃべりって感じだったので緊張せず セキュリティが面白い,セキュリティはかっこいいみたいな話で一人で勝手に盛り上がって途中でどんな質問に答えてたかわからなくなった記憶があります(笑

インターンの概要

品質保証・セキュリティコースはそのコース名の通り,サイボウズ品質保証セキュリティの2つのプロセスを体験することのできるコースでした.
1日目の午後から3日目の午前中までが品質保証で3日目の午後から5日目までがセキュリティでした.

0日目

自分は本社(日本橋)と自宅の距離が遠くはないという判定でホテルが支給されなかったのですが,9時出社のためには6時ごろには起きなければならず,普段から遅寝遅起きを徹底している自分には厳しいと思ったので近くのカプセルホテルをとって,前泊から5泊をそこで過ごしました.

↓カプセルホテルの一室

f:id:teppay:20180921174239j:plain

1日目

9時集合だったので8:45くらいに着く感じで行って,例によってオフィスの写真をとりました

f:id:teppay:20180921174306j:plain

午前中はオリエンテーションと,インターン生全員に向けてセキュリティの講義がありました.この講義のあとの簡単な小テストに合格しなければ補習を受けなければならなかったたためびびりましたが無事全員合格できました.

午後からは早速品質保証のインターンで,kintoneのある機能に対する試験設計が始まりました.

↓歓迎会

2日目

1日目に引き続き試験設計を実施して,設計した試験をお互いにレビューしたり,モブ形式でレビューしたりしました. その後試験仕様書を基に試験を行い,また探索的にソフトウェアを試験するアドホック試験を体験しました.

またこの日は社長の青野さんとインターン生でランチをしました.最初は若干緊張したけどすごくフランクな方で「会社に来る意味がわからなかったので,会社を人が集まる(人が来たくなる)場所にしたかった」という話がなるほどとなりました.

3日目

3日目は2日目に発見された不具合の改修確認を体験して,品質保証のインターンが終了しました.

この日のお昼は社員さんと鶏料理のお店に行って唐揚げを食べました.

午後からはセキュリティのインターンが始まりました.セキュリティのインターンではPSIRTの業務を体験させてもらうことになりました.
最初は脆弱性のハンズオンで,XSSやSQLiなどのメジャーな脆弱性について古い製品で体験しました.

その後,用意された脆弱性に対して,サイボウズ脆弱性報奨金制度で報奨金の金額を決める際などのも使われるCVSS v3の値を算出して,それをメンターさんにレビューしてもらう体験をしました.

4日目

この日も引き続き脆弱性の評価とそのレビューを受けるところから始まりました.

この日もお昼は社員さんと外食で,確かスペイン料理のお店に行きました

↓揚げたニクに葉っぱが乗ったモノ f:id:teppay:20180921174332j:plain

午後からは,PSIRTの人が行う脆弱性の検証を体験しました.こちらも脆弱性が存在する古いバージョンに対してテストしました。その際、メンターの方が把握していなかったSQLi(※)を発見することができてゾクゾクしました(笑 ※最新バージョンではすでに改修済です。

5日目

この日の午前は検証を引き続き行って,午後からは社員さんが行っているのと同様に脆弱性の報告をして,それを評価しました.

最終日は成果報告会があって,その発表資料を2時間で作らねばならず,3人で分担しましたが作り終わったのは報告会の3分前でした

その他

インターン中にいろんなイベントに参加させてもらったのでそのことについて

勉強会(任意参加)

3日目の夜に社員さんがインターン生向けに勉強会を開いてくださったものに参加しました. 内容はアクセシビリティについてで,「アクセシビリティとはなにか」という話から,iPhoneのVoiceOverという機能を使って実際にアクセシビリティのための機能について解説を聞くことができました.

QA勉強会 -BlackHat 報告会-

QAとPSIRTが所属している品質保証部という部門の勉強会にも参加しました. これはBlackHat USAに参加された社員さんの報告を聞くもので,東京オフィスだけでなく他のオフィスからリモートで参加している人が居たのが印象的でした.

話スシ🍣(任意参加)

話スシとはサイボウズの社内LT大会のようなもので,技術以外のことについて社員さんが熱く語っていました.
内容としては,総務さんがSUGEEEっていう話と,ディズニーの過ごし方についての話でした.とりあえずディズニーにとても行きたくなりました.

またここでは(普段はなかなかでないらしいんですが),リアル寿司がでたので美味しくLTを聞くことができました.

f:id:teppay:20180921180825j:plain

Cybozu Meetup(任意参加)

話スシのあとに,オフィスで開催されていたCybozu Meetupにも特別に参加させていただきました.
内容はアジャイルとかチームについてだったんですが,正直ほぼ知らない分野だったので単語をググりながら理解するのがやっとで疲れました笑

インターンの感想

楽しかった

メンターさんや人事さんの努力のおかげでしょうが,とても楽しくインターンすることができました.
インターンの内容は知らなかったことや興味深いことばかりで,「明日も早く行きたい」となりましたし,盛りだくさんすぎてこんなにいろいろしてもらって大丈夫か?となりました笑

美味しかった

インターン中にいろいろ食べさせてもらいました.昼食は毎日支給されましたし,夜も歓迎会やら寿司やら懇親会やらでたくさん食べました.

帰りたくなくなった

最終日の報告会のあと,「じゃあまた来週」的な冗談を言われて「はい!ぜひ!」って感じでした笑

サイボウズすごい

  • 出勤時間・場所が超フレキシブル
  • 良い意味で超自由
  • 社員さんどうしの仲がいい
  • 全体的に堅苦しい「会社感」がない

おわりに

こんなこと言ってたのに1週間以上おそくになってごめんなさい笑

Google Apps ScriptとSlack Event APIで体重を記録してくれるBotを作ってみた

はじめに

  • 最近顕著に太ってきた
  • お盆に帰って会ったすべての人に太ったと言われた
  • 腹がたった
  • 体重計を買った
  • ランニングを始めた
  • 効果が見えたほうが続けやすいだろうし記録したい
  • SlackBotにやってもらおう
  • はじめて作るので備忘録も兼ねて記録します
  • 改善点があれば教えてください
  • コードはGitHubにあげてあります

  • 初めてSlackBotをつくってテンション上がってたんですが後でよく考えたら,(体重の記録という部分が)車輪の再々々発明的なことをしていて若干恥ずかしい感じですが「SlackBotの作り方の例」として読んであげてくださいw

ながれ

  • Slack側: 自作Appを新規作成する
  • Slack側: 登録したAppにBot Userを追加する
  • GAS側: 最低限のEvent Handlerを実装
  • Slack側: Slackで起こったEventを外部サーバに通知してもらう設定
  • Slack側: 自作Appのインストール
  • 体重の記録機能
  • グラフ

やったこと

Slack側: 自作Appを新規作成する

まずはSlack側での操作です
Slack API: Applications | SlackここからSlack Appsを新規作成します
以下のようなダイアログでAppの名前と開発に使用する(公開しない場合は実際に使用する)Workspaceを指定します.

f:id:teppay:20180923021553p:plain

Slack側: 登録したAppにBot Userを追加する

次にBot UsersというメニューからAppにBot Userを追加する 以下のように表示名とDefault usernameというものを指定します.

f:id:teppay:20180923021632p:plain

ちなみに自分は良いのが思い浮かばなかったのでそのまま体重管理さんって名前にしました笑
今回のブログでは体重管理さんですが今後は改良を加えて健康管理さん秘書さんみたいにグレードアップしていく予定です

GAS側: 最低限のEvent Handlerを実装

今回作ったSlackのBotはバックエンドにGoogleGoogle Apps Script(以降GAS)を使用しました.
GASを使うことの最大の理由はタダであることです.WebのIDEを使用してブラウザで開発して,そのままWebサイトから実行ができます.よってサーバを自前で用意する必要があるません.素晴らしい!!

今回のSlackのBotではEvent APIと呼ばれる種類のAPIを使用します.このAPIを使用するためには最低限実装する必要のある処理があるのでまずはそれを実装します.Slack Botの種類は以下の記事にわかりやすくまとまっています

qiita.com

Event APIを使うためにはアクセス先の検証のためにurl_verificationというtypeのアクセスに適切に答える必要があります.
challangeパラメータをそのままレスポンスとして返すだけの処理を書きます. 詳しくはこのページに書いてありますが,以下のコードのコピペで動きます url_verification event | Slack

function doPost(e) {
  var postData = JSON.parse(e.postData.getDataAsString());
  var res = {};
  
  if(postData.type == 'url_verification') {
    res = {'challenge':postData.challenge}
  } 
  
  return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}

doPostはGASのいわゆる組み込み関数のようなもので,Webアプリケーションとして動作させている場合にPOSTメソッドでのアクセスで実行される関数です.なのでdoGetもあります. ちなみにGAS自体の説明はあまりしません.
Apps Script – Google Apps Scriptにアクセスして,左側のメニューから新規スクリプトを作成するとIDEに入れます.コードをコピペして保存してください.
ここからが重要で,Scriptを保存しただけではBotのバックエンドとして動かすことはできません.
ファイル -> 版を管理を選択し,適当な名前を付けて保存します.バージョン管理になるのでGitHubのCommitメッセージ的な感じでつけると良いんだと思います.
次に,公開 -> ウェブアプリケーションとして導入を選択して,プロジェクトバージョンを最新の版にして,アプリケーションにアクセスできるユーザを「全員(匿名ユーザを含む)」にして更新します.この一通りの操作は面倒ですが,コードを変更したらその都度行ってください,
そこで表示されたURLが作ったWebアプリケーションのURLになるので,適当にメモっといてください. 下記の記事に画像つきでわかりやすくウェブアプリケーションとして導入の手順がまとまっています. Google Apps Scriptを使って簡易APIをサクッと作る

Slack側: Slackで起こったEventを通知してもらう(Event API)設定

次はSlack側のEvent APIを有効にします
左側のメニューからEvent Subscriptionsを選択し,トグルスイッチを操作してONにします.
ONになると,Request URLという欄があるのでそこに先程メモったGASのWebアプリケーションのURLを入力します. スクショのようにVerifiedとなれば1つ前で実装したurl_verificationがうまく行ったということです

f:id:teppay:20180923021643p:plain

あとは,どのEventを通知してほしいか選べば良いだけです.今回のBotはひとまずDMで送られてきたメッセージが読めればいいのでこのようにSuvscribe to Bot EventsAdd Bot User Eventからmessage.imを追加して,画面下部のSave Changesをクリックして保存します.

f:id:teppay:20180923021722p:plain

Slack側: 自作Appのインストール

いくら設定やバックエンドの処理を書いたってインストールしなければ使えないので,Workspaceにインストールします.
左側のメニューでInstall Appを選択し,Install App to Workspaceをクリックして許可すれば,最初にしていしたWorkspaceにAppがインストールされます.

f:id:teppay:20180923021734p:plain

これで,体重管理さんにDMを送ると,それがGASのWebサーバに通知される様になりました.
1枚目はSlackのDMで体重管理さんに送ったメッセージで,2枚目はGASでconsole.logして表示したPOSTリクエストのペイロードの一部です. f:id:teppay:20180923021743p:plain f:id:teppay:20180923021749p:plain

これでほぼ完成です.あとは受け取ったEventに対してSlack側にメッセージを送るなり,SpreadSheetに記録するなりの処理を書くだけです.
この記事は以下の2つの機能を実装してひとまず終わりにしようと思います.

  • 体重を送信すると,SpreadSheetに記録する
  • ぐらふと送信すると,グラフを返信してくれる

体重の記録機能

体重を記録してほしいというのが当初の目的ですので,この機能を実装しないわけには行きません.
この機能に必要なのは

  • 体重をメッセージでうけとって,SpreadSheetに書き込む
  • 返事をする

の2つの処理です.

体重をメッセージで受け取って,SpreadSheetに書き込む

今回のBotはなるべく簡単に体重を入力したいので,小数を入力したら体重とみなすことにします. 文字列を小数に直したものと,unix timeを引数としてうけとってSpreadSheetに書き込む関数recodeWeightは以下のようになります.

function getSpreadsheet(){
  var spreadsheetId = '<SpreadSheetID>';
  var spreadsheet = SpreadsheetApp.openById(spreadsheetId).getSheetByName('シート1');
  return spreadsheet;
}

function recodeWeight(w, utime){
  //console.log('recodeWeight');
  var spreadsheet = getSpreadsheet();
  var d = new Date(utime*1000);
  spreadsheet.appendRow([d.getFullYear(), d.getMonth()+1, d.getDate(), d.getHours(), d.getMinutes(), w])
}

下記の記事にGASからSpreadSheetにアクセスする方法がわかりやすくまとまっています. Google Apps Script で Spreadsheet にアクセスする方法まとめ

返事をする

体重を送って返事がなかったらBot感がないし,ほんとに記録できてるのかもよくわからないので返事をしてほしいので返事をする処理も実装します.
今回Eventを通知してもらうためにEvent APIを使用していますが,これだけでは返事ができません.Botからメッセージを送りたい場合Web APIというものを使用します.
詳しいことは下記のサイトに書いてあるので説明は端折ります. eventオブジェクトと文字列message返事を返すreplyDM関数は以下のようになります.

function replyDM(e, message){
  var url = 'https://slack.com/api/chat.postMessage'
  var token = '<Bot User OAuth Access Token>';
  
  var data = {
    'channel' : e.channel,
    'text' : message,
    'as_user' : true
  };
  
  var options = {
    'method' : 'post',
    'contentType' : 'application/json; charset=UTF-8',
    'headers' : {'Authorization': 'Bearer '+token},
    'payload' : JSON.stringify(data)
  };
  
  var response = UrlFetchApp.fetch(url, options)
  console.log(response.getContentText());

f:id:teppay:20180923021809p:plain

ぐらふと送信すると,グラフを返信してくれる

記録してるだけではなんかつまらないのでそれをグラフにして表示してもらうことにします.
この機能に必要な処理は,

  • SpreadSheetのデータからグラフを作る
  • グラフを画像として出力する
  • 画像をSlackに送信する

の3つです

SpreadSheetのデータからグラフを作る

まずは1つ目の機能で記録したデータからグラフを作ります. Spreadsheetの機能を使ってグラフを作ることも出来るんですが,今回はChartsクラスを使ってみました.これはなんかSpreadsheetを使う場合のグラフ自体のカスタマイズ方法がわからなかったためですw
意外とこっちを使っている記事がすくなくて大変でした.
Class LineChartBuilder  |  Apps Script  |  Google Developers

function buildLineChart(){
  var sheet = getSpreadsheet();
  var dates = sheet.getRange(1, 1, sheet.getLastRow(), 3).getValues();
  var weights = sheet.getRange(1, 6, sheet.getLastRow(), 1).getValues();
  var dataTable = Charts.newDataTable()
                        .addColumn(Charts.ColumnType.DATE, 'date')
                        .addColumn(Charts.ColumnType.NUMBER, 'weight');
  
  for(var i=0; i<sheet.getLastRow(); i++){
    var date = new Date(dates[i][0], dates[i][1], dates[i][2])
    dataTable.addRow([date, weights[i][0]])
  }
  
  var chart = Charts.newLineChart()
                    .setDataTable(dataTable)
                    .setTitle('My Weight')
                    .setTitleTextStyle(Charts.newTextStyle().setFontSize(40))
                    .setDimensions(800, 600)
                    .setColors(['#4aa0f7'])
                    .setPointStyle(Charts.PointStyle.MEDIUM)
                    .setOption('vAxis.minValue', 50)
                    .setOption('vAxis.maxValue', 70)
                    .setXAxisTextStyle(Charts.newTextStyle().setFontSize(13))
                    .setXAxisTitle('Date').setXAxisTitleTextStyle(Charts.newTextStyle().setFontSize(20))
                    .setYAxisTextStyle(Charts.newTextStyle().setFontSize(13))
                    .setYAxisTitle('Weight').setYAxisTitleTextStyle(Charts.newTextStyle().setFontSize(20))
                    .build()

  return chart
}
グラフを画像として保存する グラフから画像を生成する

保存してから送る必要があるのかと思ってたら生成して,そのまま送信できるみたいです
しかも画像の生成は chart.getBlob() だけ

画像をSlackに送信する

これはSlackのAPIを使うだけです files.upload method | Slack

function uploadImage(channel, imageBlob){
  var url = 'https://slack.com/api/files.upload';
  var token = credentials['slackToken'];
  
  var data = {
    channels: channel,
    file: imageBlob,
    filetype: 'png',
    title: 'Chart',
  };
  
  var options = {
    'method': 'POST',
    'headers': {'Authorization': 'Bearer '+token},
    'payload': data
  };
  
  var response = UrlFetchApp.fetch(url, options)
}

これですべての部品が完成しました コードはGitHubに置いてあるので参考にしていただけたら喜びます

GitHub - teppay/health_manager: This is Slack Bot that manage our health

f:id:teppay:20180923021824p:plain

補足:不具合

これまでのコードをコピペしただけでは1回の「ぐらふ」に対して4回くらい画像が送られてくるという不具合が発生します.
これはおそらくdoPostの中で重たい処理(グラフ・画像の生成)を行うことでEventAPIに対するレスポンスが遅くなって,(届いてないと勘違いされることで)Slack側からのWebHookが何回も送られてきてしまうことが原因だと思っています.
GASでは非同期処理が基本的にできなそうなので,暫定的に以下の方法で乗り切っています.なにかいい方法を知っている方はぜひ教えてください!!!

Slack Event APIでは,それぞれのEventにevent_idという値が割り振られているのでそれが重複しているWebhookを捨てるという方法をとりました. CacheServiceというkey/value方式で値を保存できる仕組みが用意されているので,そこにevent_idを保存しています

function doPost(e) {
  var postData = JSON.parse(e.postData.getDataAsString());
  
  var res = {};
  if(postData.type === 'url_verification') {
    res = {'challenge':postData.challenge}
  } else if(postData.type === 'event_callback'){
    console.log(postData);
    if(!eventIdProceeded(postData.event_id)){
       eventHandler(postData.event);
    }
  }
  
  return ContentService.createTextOutput(JSON.stringify(res)).setMimeType(ContentService.MimeType.JSON);
}

function eventIdProceeded(eventId){
  var prevEventId = CacheService.getScriptCache().get(eventId);
  if(prevEventId){
    return true;
  }else{
    CacheService.getScriptCache().put(eventId,'proceeded', 60*5);
    return false;
  }
}

参考Webサイト

TokyoWesterns CTF 4th 2018 Writeup

はじめに

  • Tokyo Westerns CTF 4th 2018にぼっち参戦した
  • 今回もwarmupしか解けなかった
  • お勉強が必要..
  • 複数のパケットに渡ってメッセージが隠されている系のやつは苦手だったので解けて嬉しかった
  • dec_dec_decは途中までで詰まって競技終了を迎えましたが,終了後writeupからヒントを貰ってとき直して追記しました(追記: 2018/9/3 18:45)

Writeup

Welcome[warmup]

Welcome TWCTF{Welcome_TokyoWesterns_CTF_2018!!}

SimpleAuth[warmup, web]

与えられたURLにアクセスすると以下のようなソースコードが表示される

<?php

require_once 'flag.php';

if (!empty($_SERVER['QUERY_STRING'])) {
    $query = $_SERVER['QUERY_STRING'];
    $res = parse_str($query);
    if (!empty($res['action'])){
        $action = $res['action'];
    }
}

if ($action === 'auth') {
    if (!empty($res['user'])) {
        $user = $res['user'];
    }
    if (!empty($res['pass'])) {
        $pass = $res['pass'];
    }

    if (!empty($user) && !empty($pass)) {
        $hashed_password = hash('md5', $user.$pass);
    }
    if (!empty($hashed_password) && $hashed_password === 'c019f6e5cd8aa0bbbcc6e994a54c757e') {
        echo $flag;
    }
    else {
        echo 'fail :(';
    }
}
else {
    highlight_file(__FILE__);
}

ソースを読むとURLのクエリ文字列がない時は実行されているファイル(このソースコード)が表示される
それ以外の部分を読むと一見ユーザ名とパスワードをくっつけてMD5ハッシュと比較するだけの弱い雑な認証にみえる
?action=auth&user=admin&pass=passwordというクエリ文字列をつけることで,ユーザ名admin,パスワードpasswordを投げる. ただし大きなミス?がある.

クエリ文字列をパースしてその結果を受け取っているな部分で使用されているparse_str関数について調べてみると,

void parse_str ( string $encoded_string [, array &$result ] ) URL 経由で渡されるクエリ文字列と同様に encoded_string を処理し、現在のスコープに変数をセットします。 (or in the array if result is provided)

この関数に戻り値はない.しかもパースの結果は現在のスコープの変数に入る.
つまり,?aaa=bbb&ccc=dddのようにクエリ文字列を指定すると

$aaa = "bbb";
$ccc = "ddd";

と書いたのと同じになるというわけなのでこれを利用すれば,変数$hashed_passwordに入る文字列をはじめから指定することが可能となる. またuserpassを指定してしまうとMD5ハッシュで上書きされてしまうので指定しない. よってクエリ文字列は,?action=auth&hashed_password=c019f6e5cd8aa0bbbcc6e994a54c757e

これを付けてアクセスするとフラグがきた TWCTF{d0_n0t_use_parse_str_without_result_param}

mondai.zip[warmup, misc]

zipファイルが与えられた以下の順序でといた

$ unzip mondai.zip
$ ls 
y0k0s0.zip

y0k0s0.zipにはパスワードがかかっていたのでJohnTheRipperを使ってみると

$ zip2john y0k0s0.zip > hash_y0k0s0.txt
$ john --show hash_y0k0s0.txt
y0k0s0.zip:y0k0s0:::::y0k0s0.zip

1 password hash cracked, 0 left

パスワードがy0k0s0とわかった

$ unzip -P y0k0s0 y0k0s0.zip
Archive:  y0k0s0.zip
  inflating: capture.pcapng
replace mondai.zip? [y]es, [n]o, [A]ll, [N]one, [r]ename: r
new name: mondai_2.zip
  inflating: mondai_2.zip

capture.pcapngと新しいmondai.zipがでてきたのでmondai_2.zipとrenameした mondai_2.zipにもパスワードがかかっているが,JohnTheRipperでは無理っぽい
そこでcapture.pcapngの方にヒントが隠されてるということだと思い,そっちをwiresharkで開いてみた.
中身はすべてICMPなパケットでこのどこかにzipのパスワードが隠されているはず. 正直良くわからなかったのでWiresharkでだらだら見ていたら,ICMPパケットのdataフィールドのLengthが全体的にasciiの範囲内にある気がしたので1番多いip.src == 192.168.11.3 and ip.dst == 192.168.11.5なパケットのdata.lenフィールドを取り出してasciiに変換してみた

$ tshark -r capture.pcapng -T fields -e data.len 'ip.src == 192.168.11.3 and ip.dst == 192.168.11.5' | awk '{printf "%c", $1}'
We1come

We1comeという文字列がでてきたのでパスワードに使ってみると

$ unzip -P We1come mondai_2.zip
Archive:  mondai_2.zip
  inflating: list.txt
replace mondai.zip? [y]es, [n]o, [A]ll, [N]one, [r]ename: r
new name: mondai_3.zip
  inflating: mondai_3.zip

list.txtmondai.zip(mondai_3.zipにrename)がでてきた.

$ head list.txt
nb`_hlPYgZ
dhuYP\pUks
uSXWbQPtiY
cnbs_Yeahn
ui\R_ReZ[m
gZW^b]Sjqg
^RT\nmZ`^`
mnXgPr\^[T
erRQSov[uY
lkWdn[_oTj

$ wc -l list.txt
999

このようにlist.txtにはパスワードらしき文字列が999こ入っている. この中に本物があると仮定して,これを一つずつ試していくことにした.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

import zipfile

def unzip_passwd(filename, passwd):
    with zipfile.ZipFile(filename, 'r') as zip_f:
        try:
            zip_f.extractall(path='.', pwd=passwd)
            print(f'Password is {passwd}!!!')
            return True
        
        except:
            return False

def challange_all_passwd():
    pass_file = 'list.txt'
    zip_file = 'mondai_3.zip'

    with open(pass_file, 'r') as pass_f:
        for p in pass_f:
            p = p.replace('\n', '')
            p = p.replace('\r', '')
            p = p.encode('utf-8')
            ret = unzip_passwd(zip_file, p)
            if ret:
                break


if __name__ == '__main__':
    challange_all_passwd()

この実行の結果,パスワードはeVjbtTpvkUとわかった.

$ unzip -P eVjbtTpvkU mondai_3.zip
Archive:  mondai_3.zip
  inflating: 1c9ed78bab3f2d33140cbce7ea223894

1c9ed78bab3f2d33140cbce7ea223894というファイルがでてきた.
長い...Flagはまだか...

$ file 1c9ed78bab3f2d33140cbce7ea223894
1c9ed78bab3f2d33140cbce7ea223894: Zip archive data, at least v2.0 to extract

fileコマンドによるとこれもzipらしい
ファイル名がハッシュ値っぽいのでとりあえずググってみる. その結果happyhappyhappyという文字列のMD5ハッシュであることがわかった.

$ unzip -P happyhappyhappy 1c9ed78bab3f2d33140cbce7ea223894
Archive:  1c9ed78bab3f2d33140cbce7ea223894
replace mondai.zip? [y]es, [n]o, [A]ll, [N]one, [r]ename: r
new name: mondai_5.zip
  inflating: mondai_5.zip
 extracting: README.txt

はい.まだ終わりません.mondai.zipREADME.txtがでてきた

$ cat README.txt
password is too short

パスワードが短いということで最悪ブルートフォースすればいいかと思いながらJohnTheRipperを使ったらパスワードtoが求まった

$ unzip -P to mondai_5.zip
Archive:  mondai_5.zip
  inflating: secret.txt
  
$ cat secret.txt
Congratulation!
You got my secret!

Please replace as follows:
(1) = first password
(2) = second password
(3) = third password
...

TWCTF{(2)_(5)_(1)_(4)_(3)}

よって,やっっっっっと,
TWCTF{We1come_to_y0k0s0_happyhappyhappy_eVjbtTpvkU}

dec dec dec[warmup, reversing]

$ file dec_dec_dec
dec_dec_dec: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=0c8f77007398e1cb7857e481cff521122c1b2cb9, stripped

この通り64bitのELFを与えられた. IDAで開いて見てみると,コマンドライン引数でFlagを与えて実行すると,correctincorrectが表示されるよくあるタイプのやつだった 大まかには以下のような処理

  • mallocコマンドライン引数で与えられた文字列と同じ長さの領域(dest)を確保
  • strncpyで確保された領域にコマンドライン引数で与えられた文字列をdestにコピー
  • destにコピーされた文字列を関数sub1にわたし,戻り値をdestにもどす
  • destの文字列を関数sub2にわたし,戻り値をdestにもどす
  • destの文字列を関数sub3にわたし,戻り値をdestにもどす
  • 最後にdestの文字列とある文字列を比較して同じならcorrect

よってこの3つの関数を読み解けば良いわけでひとつずつ見ていく

sub1

関数の最初で'A~Za~z0~9+/'の文字列を読み込んでいる.これで頭に浮かんだのはBase64.そこでこの関数はBase64エンコードを行うものと判断.
デバッガで確かめた結果やはりBase64を求めるものだった.

sub2

これはただのRot13

sub3

ここがわからなかった... 競技終了後Writeupを確認してuuencodeだということがわかったので以降とき直し よく考えれば問題名dec_dec_decだし,上の2つエンコードだし,有名なエンコード手法を確認すれば解けたのでは??(怒)

3つの関数のエンコード手法がわかったのであとは順番にデコードすればいいだけ. 最後に比較される文字列を持ってくると@25-Q44E233=,>E-M34=,,$LS5VEQ45)M2S-),7-$/3Tのようなものだったので,(最後にスペースがあるので注意)
以下のようなファイルをuuencoded.txtという名前で保存

begin 777 out.txt
@25-Q44E233=,>E-M34=,,$LS5VEQ45)M2S-),7-$/3T 
 
end

あとは

$ uudecode uuencoded.txt
$ cat out.txt | tr A-Za-z N-ZA-Mn-za-m | base64 -D
TWCTF{base64_rot13_uu}