2
0

NFLabs. Cybersecurity Challenge for Students 2023 Writeup

Last updated at Posted at 2023-12-03

はじめに

11/22~11/27に開催されたNFLabs. Cybersecurity Challenge for Students 2023に参加してきました。こういったコンテストにガッツリ参加するのは半年ぶり以上でお試し半分で参加してみました。
結果としては9位ということで、思ったよりいい結果が得られて良かったです。

image.png

早速ですが、解けた問題に関してWriteupを書いていきます。

OSINT

[Easy] ntt.com

ntt.comのドメイン名を日本電信電話株式会社(関連企業を含む)が登録する以前に登録していた企業の名前を答えてください。

過去このドメイン名を利用していたサイトを調べるため、Wayback Machineが提供するインターネットアーカイブを利用します。

image.png

初めの記録が1997年にあるので見てみます。

image.png

答え:National TechTeam, Inc.

余談

インターネットアーカイブの記録では、1998年からNTTの関連会社がドメインを保持していたみたいでした。

image.png

[Easy] celebration

カタールW杯のベストゴールのゴールセレブレーションの輪の真後ろで、イギリスのあるプロサッカークラブの横断幕が掲げられていました。 そのクラブの名前にもなっている街の名前をアルファベットで答えてください。

まずカタールW杯のベストゴールが誰でどの試合かを調べます。
調査の結果、ブラジル対セルビア戦のブラジル代表FWリチャーリソン選手のゴールということが分かりました。この記事では73分にそのゴールの瞬間があると書いてあったので、YouTubeで調べてみたところ、ゴールの瞬間はハイライトなどで残っていましたが、ゴールセレブレーションの瞬間はカットされていました。他の動画サイトでも調べてみると、ニコニコ動画にゴールセレブレーションのシーンがあったので見てみましたが、画質が悪いため恐らく答えと思わしき横断幕に書かれている文字が読み取れませんでした。

image.png
リンク

次にブラジル対セルビアのフルサイズの映像がどこかに残っていないかを調べることにしました。
brazil vs serbia full matchと検索するといくつかのサイトでフルサイズの試合が見ることが出来ます。
問題のゴールセレブレーションの映像を見てみると、MIDDLESBROUGHと書かれた横断幕が見えます。

スクリーンショット 2023-11-23 185037.png

調べてみると、イングランド・ミドルズブラに本拠地を置くサッカークラブ、ミドルズブラFCだそうです。

答え:Middlesbrough

[Medium] stranger than fiction

ある著名な元俳優が2022年2月下旬、国会議員のDavid Braun氏を含む5名で撮った動画をSNSにアップロードしました。 その俳優の代表作の第一話冒頭に映っている記念碑の場所を答えてください。 回答は緯度経度を小数点下一桁(下二桁目以降は切り捨て)までお願いします。

まずDavid Braunという名前を調べてみると、Wikipediaには3人候補が上がりました。その中で国会議員はDavyd Arakhamiaという方で、David Braunという名はペンネームということが書かれていました。
Davyd Arakhamia氏のSNSを調査してみたところFacebookのアカウントがあったので、2022年2月下旬あたりの投稿を調べました。投稿を見たところ、この時期はロシアのウクライナ侵攻が始まった時だということが分かります。
つまり問題文に書かれているある著名な元俳優というのが、ウクライナ大統領のウォルディミナル・ゼレンスキーではないかと予想が付きます。
確認のため、ゼレンスキー大統領のFacebookのアカウントで、2022年2月下旬ごろの投稿を調べるとこの投稿が問題文で取り上げているものに一致していることが分かります。

ゼレンスキー大統領の代表作を調べると国民の僕(しもべ)という作品が該当するそうです。
国民の僕 第一話冒頭を見てみると問題文通りの記念碑が映っていました。

image.png
【1話無料公開!】みるアジアで配信中!『国民のしもべ~ウクライナ大統領~』

Google Lensで調べてみると、ウクライナ キーウにある独立記念碑だということが分かります。

答え:N50.4,E30.5

[Medium] Repair

この銅像の修復にかかった費用をユーロで答えてください。
[解答形式 : €**.***]
image.png

まずGoogle Lensで調べてみると、スペインにある教会がKarmacolorという団体に聖ジョージ像の清掃を依頼したところ、団体の代表である美術教師によって勝手に修復され、元の彫刻とは似ても似つかないものにされてしまった事件だということが分かります。
その後教会は改めて修復費を出して元通りにしたようです。

image.png
引用

この修復費が答えなわけですが、日本語で調べてもあまりいい記事が無かったので、スペイン語で検索してみました。el San Jorge restaurado(復元された聖ジョージ)

検索の結果、この記事が修復費について書かれていました。

image.png

答え:€30.759

余談

修復を勝手にやってしまった美術教師は、実は修復過程をSNS上に動画等で投稿しており、自身の作業の様子を記録に残していました。問題が発覚後、騒ぎが大きくなり現在ではアカウントごと無くなってしまいましたが、Facebook上では当時投稿された動画が残っており、聖ジョージ像に塗料を塗っているシーンが見ることが出来ます。

DFIR

[Easy] flower

会社で管理しているWebサイトにアクセスすると、「We hacked your Web page :)」という文章が表示されるというインシデントが発生しました。あなたはWebサーバを調査したが、不審なログを発見することはできませんでした。 しかし、調査の過程でWebサイトのドメイン名をDNS名前解決した際、本来のIPアドレスとは異なるIPアドレスが返っていることを発見しました。あなたはDNSに問題が発生していると考え、通信経路上で取得していたパケットデータを確認することにしました。 pcapngファイルを解析し、インシデントの原因となる攻撃を行ったと考えられる端末のIPアドレスを解答してください。
添付ファイル:flower.pcapng

Wiresharkでflower.pcapngを開き、統計>IPv4 Statics>All Addressesを見ます。

image.png

5つのIPアドレスがパケットに記録されていることが分かります。

パケットを見ていくと、問題文中にあったWe hacked your Web page :)というページが表示されたと思われるレスポンスのパケットがありました。つまり10.64.3.178は攻撃者が用意したWebサーバということですね。

image.png

次に統計にあるフローグラフを見てみると、パケットの始めのやり取りから以下のことが分かります。

  • Client:10.64.3.123
  • DNSサーバ:10.64.3.120
  • Webサーバ:10.64.3.64(flos.example.test)

また途中からDNSサーバへ大量のリクエストを送っているIP10.64.3.101があり、恐らく攻撃者によるものだと推測できます。

image.png

パケット最後のやり取りを見てみると、flos.example.testの名前解決でドメインと対応しているIPが10.64.3.178に変えられているのが分かります。

image.png

よって

  • 攻撃者:10.64.3.101
  • 攻撃者が用意したWebサーバ: 10.64.3.178

答え:10.64.3.101

[Easy]rockyou

あなたの運営しているwebアプリケーションにおいて、不正ログインが発生している可能性があるという報告がありました。それを知った同僚がパケットキャプチャを行ってくれていました。セキュリティ担当であるあなたは、手元にあるデータを用いて不正ログインに関する調査を行うこととなりました。Webサーバのアクセスログとpcapファイルを元に不正ログインされている可能性が高いと思われるユーザのユーザ名を解答してください。
添付ファイル:access.log、output.pcap

ログインが成功した=HTTPレスポンスステータスコードが200、だということに着目してhttp.response.code == 200でフィルタリングします。

image.png

答え:william

[Medium] invader (1)

【導入】
Windowsのサーバーに対して外部から不正アクセスを検知したため、この端末のイベントログを抽出した。
このログを分析し、invader (1) ~ (3)までの3つの問に答えよ。3つの問の解答順序は問わないものとする。
【Q1】
侵入元のIPアドレスを答えよ。
注:192.168.0.0/24以外のIPアドレスはすべて外部のIPアドレスとする。
回答方式:IPアドレスを答える
回答例:192.0.2.2
添付ファイル:eventlogs.zip

端末のログオンについてみたいため、Security.evtxを開き、イベントID4624でフィルターをかけます。
また今回は外部からのログオンについてなので、ログオンの種類は3と10で絞り込みます。

各ログオンの種類
ログオンの種類 ログオンタイトル 説明
2 対話型 ユーザーがこのコンピューターにログオンしました。
3 ネットワーク ネットワークからこのコンピューターにログオンしたユーザーまたはコンピューター。
4 パッチ バッチ ログオンの種類はバッチ サーバーによって使用され、そこではプロセスが直接介入せずにユーザーの代わりに実行される可能性があります。
5 サービス サービス コントロール マネージャーによってサービスが開始されました。
7 ロックを解除する このワークステーションのロックが解除されました。
8 NetworkCleartext ユーザーがネットワークからこのコンピューターにログオンしました。 ユーザーのパスワードは、非ハッシュ化形式で認証パッケージに渡されました。 組み込みの認証では、ネットワーク経由で送信する前に、すべてのハッシュ資格情報がパッケージ化されます。 資格情報は、プレーンテキスト (クリア テキストとも呼ばれます) でネットワークを通過しません。
9 NewCredentials 送信元が現在のトークンを複製し、送信接続用に新しい資格情報を指定しました。 新しいログオン セッションのローカル ID は同じですが、他のネットワーク接続には異なる資格情報を使用します。
10 RemoteInteractive ターミナル サービスまたはリモート デスクトップを使用してリモートでこのコンピューターにログオンしたユーザー。
11 CachedInteractive コンピューターにローカルに保存されたネットワーク資格情報を使用してこのコンピューターにログオンしたユーザー。 資格情報を確認するために、ドメイン コントローラーに接続できませんでした。

引用

  • Logon Type:3 2023/10/10 16:40:01 :192.168.81.128
  • Logon Type:10 2023/10/10 16:40:03 :192.168.81.128
  • Logon Type:3 2023/10/10 16:44:54 :192.168.81.128
  • Logon Type:10 2023/10/10 16:44:55 :192.168.81.128

答え:192.168.81.128

[Medium] invader(2)

invader (1) の続き
【Q2】
攻撃者は、侵入後にWindows Defenderの設定を変更し特定のフォルダーを検知の対象外とした。
そのフォルダーを答えよ。
回答方式:フォルダーのフルパスを回答(case insensitive)
回答例:C:\Windows\Tasks\

Windows Desfenderの設定に関してなので、Microsoft-Windows-Windows Defender%4Operational.evtxを開きます。以下、関連度が高いイベントです。

  • イベントID 5001 2023/10/10 16:46:38 リアルタイム保護の無効化
  • イベントID 5007 2023/10/10 16:46:39
    • Old value: HKLM\SOFTWARE\Microsoft\Windows Defender\SpyNet\SpyNetReporting = 0x2
    • New value: HKLM\SOFTWARE\Microsoft\Windows Defender\SpyNet\SpyNetReporting = 0x0
  • イベントID 5007 2023/10/10 16:46:40
    • Old value: HKLM\SOFTWARE\Microsoft\Windows Defender\SpyNet\SubmitSamplesConsent = 0x1
    • New value: HKLM\SOFTWARE\Microsoft\Windows Defender\SpyNet\SubmitSamplesConsent = 0x0
  • イベントID 5007 2023/10/10 16:46:54
    • Old value:
    • New value: HKLM\SOFTWARE\Microsoft\Windows Defender\Exclusions\Paths\C:\ = 0x0

答え:C:\

[Medium] invader(3)

invader (2)の続き
【Q3】
攻撃者は、永続化のためにとあるファイル(実行ファイルまたはスクリプトファイル)をOS起動時に自動的に実行するように設定した。
攻撃者が用意したOS起動時に自動的に実行されるファイルを答えよ。
回答方式:ファイルのフルパスを回答(case insensitive)
回答例:C:\Windows\Tasks\test.exe

Windowsのスケジュールタスクが作成されたイベントを見たいためSecurity.evtxを開き、イベントID4698でフィルターします。

  • イベントID: 4698 2023/10/10 16:50:14
Security.evtx(一部抜粋)
 <Actions Context="Author">
    <Exec>
      <Command>C:\Windows\System32\cmd.exe</Command>
      <Arguments>/c C:\Users\Administrator\rtcp.exe</Arguments>
    </Exec>
  </Actions>

答え:C:\Users\Administrator\rtcp.exe

[Medium] exfildb

あなたはシステム&セキュリティ担当の一員として、Webサイトのインシデント対応におけるログ分析を依頼されました。システム構成および調査対象のログは、exfildb.zipに格納されています。攻撃者にDBの情報が持ち出されたと推測される時間を調査し、日本時間(JST)で mm:hh:ss の形式で解答してください。
例) 持ち出された時間が日本時間で2023年9月6日11時02分05秒の場合、11:02:05と解答してください。
添付ファイル:exfildb.zip

ディレクトリ構成
exfildb
├── IDSlog
│   ├── 2021-04-20_1645_nids_log.pcap (Server LAN内のフルパケットキャプチャ 16:45 ~ 18:58)
│   ├── 2021-04-20_nids_et_eve.json (簡易アラート情報)
│   └── 2021-04-20_nids_et_fast.log (アラートの詳細情報)
├── Jumpserver
│   └── 2021-04-20_jump_secure.log (Jumpサーバの認証ログ)
├── Webserver
│   └── 2021-04-20_web_access.log (Webサーバのアクセスログ)
└── systeminfo.pdf (説明資料)


システム構成
・Webサイトの訪問者はhttp://172.16.193.103にアクセスする
・FWが172.16.193.103:80宛の通信をWebサーバの内部IPアドレスにNAT

                                              .11
                                             ├── Web Server
                                             |.12
                       WAN         LAN       ├── DB Server
                      .103          .1       |
Client ──── Internet ──── Firewall ──────────
      (172.16.193.0/24)                :     |.101
                                      NIDS   ├── Jump Server
                                             |.201
                                             ├── Local Maintenance PC
                                             |
                                        Server LAN
                                    (172.16.10.0/24)

各システムの説明
ホスト名 IPアドレス OS 説明
web 172.16.10.11 CentOS 7 会社のコーポレートサイトを運用しているWebサーバ
db 172.16.10.12 CentOS 7 コーポレートサイトの情報を管理しているDBサーバ
jump 172.16.10.101 CentOS 7 Server LAN内のサーバにSSHするための踏み台サーバ
Local Maintenance 172.16.10.201 - Server LAN内のサーバを操作するためのPCサーバのメンテナンスはここから行っている
firewall (LAN)172.16.10.1 (WAN)172.16.192.103 OPNSense Server LAN内への通信を制御するためのファイアウォール
nids - Ubuntu 20.04 Server LANを監視しているIDS(Suricata)
各種サーバについて
  • Webサーバ(172.16.10.11)
    • Apache 2.4.6 + PHP 7.4.16 + WordPress 5.7.1で構築
    • WordPressには以下二つのプラグインをインストール
      • Contact Form 7(version 6.4)
      • WP File Manager (version 6.0)
  • Jumpサーバ(172.16.10.101)
    • SSH でリモートから各サーバのメンテナンスを行うために構築中
    • 構築中のため、現在は誰も使用していない
  • DBサーバ(172.16.10.12)
    • Web サーバのみデータベースへアクセスする
    • 以下のデータを格納
      • WordPressのサイトデータ
      • 問い合わせフォーム(Contact Form 7)で送信されたデータ(個人情報)
前提条件(1)

IDSのアラートを確認したところ大量のアラートが発生していた。
大量のアラートからScan行為などのアラートを取り除いたところ、 16:16:33から16:53:27の間に、Webサーバ、Jumpサーバに対して次に示すようなアラートが上がっていた。
アラートの情報をもとにサーバのイベントログ、パケットログを調査し、攻撃の流れを推測したい。

前提条件(2)

Evebosでアラート分析した結果

image.png

2021-04-20_1645_nids_log.pcapをざっくりと見てみると、使われていないはずのJumpサーバとやり取りをしているIPアドレス172.16.193.101が見つかります。前提条件(2)から、SSH BruteForce Attackでログインに成功した攻撃者と思われます。
攻撃者の活動を見るため、Wiresharkでip.addr == 172.16.193.101でフィルターをかけてパケットを見ていきます。

WordPressの脆弱性悪用
  • 16:47:00 cat /etc/passwd

image.png

  • 16:48:06 cat whoami

image.png

  • 16:48:13 cat hostname

image.png

  • 16:48:19 ip addr

image.png

  • 16:48:43 ls

image.png

  • 16:48:47 ls ../

image.png

~中略~

  • 16:49:02 ls ../../../../../

image.png

  • 16:49:32 wp-config.phpの閲覧でDBの認証情報が洩れた

image.png

その後攻撃者はJump ServerへSSH接続をし、ncの実行ファイルをJump Serverへ配送しています。

image.png

DBの情報を含むであろうwpdb.dumpをftpを用いて持ちだそうと2回試行しているのが分かります。

image.png

1回目は失敗しており、2回目で以下のようにwpdb.dumpを持ち出していました。

image.png

答え:16:56:28

Web

[Easy] ASCII

友人が授業で作成したWebサイトに脆弱性があるという話になりました。Webアプリケーションを診断して脆弱性を特定し、フラグを入手して解答してください。

指定の問題サーバにアクセスすると以下のような画面が表示されており、フォームに値を入力するとアスキーアートとして表示されるみたいです。

image.png

如何にも脆弱性がありそうなフォームなので試しに色々入力してみると、特殊文字(;や&)以降の文字はアスキーアートとして表示されませんでした。
試しにaaa;idをリクエストしてみると

image.png

OSコマンドインジェクションがありましたね。

;ls
image.png

カレントディレクトリにflag.txtがあるみたいなので見てみます。

;cat flag.txt
image.png

答え:NFLABS{1nj3ct_n_unv3il_th3_s3cr3tz}

セキュリティ上の問題とその対策

server.pyのコードを見てみます。

server.py
import subprocess

from flask import Flask, render_template, request

app = Flask(__name__)


@app.route("/", methods=["GET", "POST"])
def index():
    msg = request.form.get("message", "ASCII Art Generator")
    if msg == "":
        msg = "ASCII Art Generator"
    app.logger.debug("msg: {}".format(msg))
    p = subprocess.run(
        "figlet -w100 -f big {}".format(msg), shell=True, stdout=subprocess.PIPE
    )
    return render_template(
        "index.html", msg=p.stdout.decode().replace("\n", "
").replace(" ", " ")
    )


if __name__ == "__main__":
    app.run(host="0.0.0.0", port=80, threaded=True, debug=True)

subprocess.runの引数にshell=Trueを渡してしまうことで、明示的にシェルを呼び出してしまいOSコマンドインジェクションが出来てしまうので、もし明示的に呼び出す場合は必ずエスケープ処理を自分でするべきみたいです。(基本はshell=Trueを渡さないことが一番ですね)

参考:subprocess - セキュリティで考慮すべき点

[Easy] UniDine

私たちの管理するWebサイトに対し、外部から脆弱性の報告がありました。Webアプリケーションを診断して脆弱性を特定し、フラグを入手して解答してください。

指定されたサーバへアクセスする。

image.png

ログインフォームがあったので、挨拶がてらSQLインジェクションしてみる。

image.png

すると以下のエラー文が。

Fatal error: Uncaught mysqli_sql_exception: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'a'' at line 1 in /var/www/html/login.php:20 Stack trace: #0 /var/www/html/login.php(20): mysqli_query() #1 {main} thrown in /var/www/html/login.php on line 20

PostgreSQLの書式だったので怒られました。再度MySQLの書式でSQLインジェクションしてみます。

image.png

ログインが成功するとメールアドレスにフラグがありました。

image.png

答え:NFLABS{l0g1n_h4x_sql_1nj3ct1on}

セキュリティ上の問題とその対策

ログインフォームでSQLインジェクションが成功したことから、login.phpでプレースホルダやエスケープ処理が行われていないことになります。対策としてはプレースホルダやエスケープ処理の実装を行う必要があります。詳しいことはIPAさんが出している 安全なSQLの呼び出し方を参考に。

Dev

[Easy] stegano

ステガノプログラムmain.goを用いてflagを画像の中に隠した。画像からflagを見つけよ。
添付ファイル:main.go、stegano.png

main.go
main.go
package main

import (
	"bufio"
	"fmt"
	"image"
	"image/color"
	"image/png"
	"log"
	"os"
)

func main() {
	reader, err := os.Open("original.png")
	if err != nil {
		log.Fatal(err)
	}
	defer reader.Close()
	m, _, err := image.Decode(reader)
	if err != nil {
		log.Fatal(err)
	}
	bounds := m.Bounds()
	im := image.NewRGBA64(image.Rect(0, 0, bounds.Dx()*2, bounds.Dy()*2))
	for i := 0; i < bounds.Dx(); i++ {
		for j := 0; j < bounds.Dy(); j++ {
			c := m.At(i, j)
			im.Set(i*2, j*2, c)
			im.Set(i*2+1, j*2, c)
			im.Set(i*2, j*2+1, c)
			im.Set(i*2+1, j*2+1, c)
		}
	}
	file, err := os.Open("flag.txt")
	if err != nil {
		log.Fatal(err)
	}
	defer file.Close()
	s := bufio.NewScanner(file)
	s.Scan()
	flag := s.Text()
	x := 100
	y := 100
	for i := 0; i < len(flag); i++ {
		r1, g1, b1, a1 := im.At(x*2, y*2).RGBA()
		r2, g2, b2, a2 := im.At(x*2+1, y*2).RGBA()
		r3, g3, b3, a3 := im.At(x*2, y*2+1).RGBA()
		box := []uint8{uint8(r1), uint8(g1), uint8(b1), uint8(r2), uint8(g2), uint8(b2), uint8(r3), uint8(g3)}
		for j := 0; j < 8; j++ {
			if (flag[i] & byte(1<<j)) != 0 {
				if box[j] == 0 {
					box[j] = box[j] + 1
				} else {
					box[j] = box[j] - 1
				}
			}
		}

		im.Set(x*2, y*2, color.RGBA{R: box[0], G: box[1], B: box[2], A: uint8(a1)})
		im.Set(x*2+1, y*2, color.RGBA{R: box[3], G: box[4], B: box[5], A: uint8(a2)})
		im.Set(x*2, y*2+1, color.RGBA{R: box[6], G: box[7], B: uint8(b3), A: uint8(a3)})
		x = x + 1
		y = (y + int(uint8(b3))) % bounds.Dy()
	}
	fmt.Println(im)
	result, err := os.Create("stegano.png")
	if err != nil {
		log.Fatal(err)
	}
	defer result.Close()
	if err := png.Encode(result, im); err != nil {
		log.Fatal(err)
	}
}

ざっくりプログラムを説明すると、まずoriginal.pngの2倍大きい画像を生成します。

(例えば、original.pngの大きさが300x300の時)

image.png

その次にoriginal.pngの一つのピクセルを、2倍大きい画像に2x2で複製し、それを画像すべてに適応します。

(例えば、original.pngの左上のピクセルをコピーする時

image.png

最後にFlagが書かれたflag.txtを読み込み、文字を2進数として扱い、右下以外のピクセルにFlagの情報を組み込む、という流れです。
Flagの内容を復元するには、右下とそれ以外のピクセルの値の変化を確認することが必要になります。

answer.py
from PIL import Image

def main():
    
    original_image = Image.open("stegano.png")
    width, height = original_image.size

    flag = [0,0,0,0,0,0,0,0]
    x, y = 100, 100
    for i in range(50):
        r1, g1, b1= original_image.getpixel((x * 2, y * 2))
        r2, g2, b2= original_image.getpixel((x * 2 + 1, y * 2))
        r3, g3, b3 = original_image.getpixel((x * 2, y * 2 + 1))
        r4, g4, b4 = original_image.getpixel((x * 2 + 1, y * 2 + 1))

        box = [r1, g1, b1, r2, g2, b2, r3, g3]
        box2 = [r4, g4, b4,r4, g4, b4,r4, g4]

        for j in range(8):
            if box2[j] - box[j] == 1:
                flag[7-j] = 1
            elif box2[j] - box[j] == -1:
                flag[7-j] = 1
            else:
                flag[7-j] = 0
        
        if 1 in flag:
            pass
        else:
            break

        print(*flag, sep='', end=' ')

        x += 1
        y = (y + b3) % (height // 2)

if __name__ == "__main__":
    main()
出力結果
01001110 01000110 01001100 01000001 01000010 01010011 01111011 01001001 01001110 01010000 01110101 01100010 00110110 00110011 01001100 01010110 01000101 01101000 00110100 00110010 01010110 01001110 01100100 01100111 00110100 01001101 01101111 01111101

CyberChef

image.png

(プログラムで完結させれば良かったですね...)

答え:NFLABS{INPub63LVEh42VNdg4Mo}

おわりに

Malwareジャンル、PenTestジャンルに関しては1問も解けなかったので他の人のWriteupを参考に勉強させていただこうかと思っています。
6日間とかなりキツかったですが、同時にとても楽しく様々な知識が得られたコンテストでした!

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0