日々常々

ふつうのプログラマがあたりまえにしたいこと。

Mac mini M2が届いたのでセットアップのメモ

Mac miniが届いたのでセットアップのメモ2020-11-15 なので、 P2Y2M22D ぶり。2ならび。

前回は1週間で届いたけど、今回は3週間弱。発表直後だしね。M1出る直前にIntellのを買ってジリジリしてたのだけど、メモリとか使うソフト(主にDocker)の対応とか待ってた感じです。そろそろいいかなーと。できれば去年の経費にしたかったけれど仕方ない。確定申告が始まった?知らない子ですね……

やったこと

  • 箱開けて写真撮ってTwitterに投稿
  • 起動
    • WiFiのパスワード求められる。有線にすりゃよかった。
  • MBPから移行アシスタントで移行しようとしてやめる
    • 前回とほぼ同じ理由
  • OSのアップデート
    • 約3GBのダウンロード。有線にすりゃよかった。
    • 30分程度のインストール時間
    • アップデート後にまたWiFiのパスワード求められる。有線にすりゃよかった。
  • Apple IDでログイン
    • すんなり
  • Safari起動してChromeインストール
  • 1Passwordインストール
  • Chromeログインして同期
  • システム環境設定 > キーボード
    • ショートカットの入力ソースを ^スペース から ⌘スペース に変更
    • 音声入力のショートカットOFF
  • Homebrew インストール
  • GitHubにログインして鍵登録
  • dotfiles(private) を git clone して brew bundle
  • SDKMAN!! インストール
    • sdk i java 17.0.6-tem
    • sdk i gradle
    • 他はおいおい
  • IntelliJ IDEA インストール
    • あっぷるしりこーん
  • Docker Desktop インストール
    • あっぷるちっぷ
    • インストール後のgetting startedやって動作確認
      • ~/getting-started 作るのやめてほしい。せめてディレクトリ名に docker 入れるとかさぁ。
  • 最低限開発できる確認
    • 適当なプロジェクトを git clone ./gradlew bootBuildImage docker run してみる。OK。
  • IntelliJの設定同期
    • いままではGitHubのプライベートリポジトリ経由で行なっていたんだけど、 バージョン2022.3以降 は標準機能から外れていた。気づかなかった。
    • なのでJetBrainsアカウント経由で反映
      • なんか1回では同期されなかったけど、ON/OFF繰り返してたらいけた。多分ON/OFFせずとも待ってたらいけた気もする。
      • 認識したら自動的にPluginのインストールとかして再起動しろよって出てくる。再起動したら完了。
  • Slackログイン
    • メールアドレス入力したらメールで確認コード飛んできて、対象のWorkspaceをポチポチ。パスワードなんていらんかったんや。

おしまい。 あと鍵の登録とかしてかなきゃなとこあるけど、おいおいでよかろう。

追記: 他にインストールしたもの

  • OneDrive
  • Office
  • DeepL
  • Zoom
    • 背景画像はアカウントに紐づかないので端末ごとに設定しなきゃいけない。
  • ToyViewer
    • 画像のちょっとした加工に使ってる
  • タブレットドライバ
    • 入れなくてもおっそいマウスとしては使えるんだけど流石に使い物にならないので。
    • 再起動しないとクリックをクリックと認識してくれなかった。筆圧は感知してるけど。そいやMacの設定許可してるときに「再起動いるよ」とかダイアログ出てた気がする。
  • CLIP STUDIO

追記: 画面共有

必須の情報はだいたいリモートにあるとはいえ、端末に残ってる情報もあるので、Mac標準機能の画面共有でVNC接続して、2枚目のディスプレイに全画面表示しておく。スムーズ。 VNC接続がFinderにあるのわかりづらくない?

物買うと物欲が連鎖するよね

Mac mini2つになったからTrackpad買わないとかなぁ。マウスはあるけどMacはTrackpadで使いたい。

なんか黒いMulti Touch対応とか出てる……何これ? 初心に帰って(?)MagicMouse買ってみるかな……初心でも使ったことないけど。Mac買ったのはAirからだし。昔研究室ではワンボタンマウスのを使った記憶が薄らあるけど。

あとモニター買わないといけない気がする。今のメインはEIZOの31.5インチの4K。

最高解像度では使ってない。老眼とかじゃないはず。31.5インチに4Kは要らない……でもフルHDじゃ物足りなく感じる今日この頃。間は……すなおに4K買って解像度落とせって話か。 不満はないんだけど、横長の湾曲したのを「イイヨイイヨー」とTLで見た気がして惹かれてるんだよね。ううむ。

内部構造から学ぶPostgreSQL設計・運用計画の鉄則

改訂3版。定番なのかな?少し前にTwitterのTimeLineに流れてきたのをポチって忘れてたのが届きました。やはり物理本は良い……。

「基本編」「設計/計画編」「運用編」「チューニング編」の4パート、18章で構成されています。

はじめにに書かれているようにPostgreSQL自体が手軽に使えるようになっている昨今は、内部構造や運用ノウハウを得る機会自体が減っています。 だからと言って全く知らない状態で使っていて良いかと言われると、良いわけもなく。でも使えてしまう以上、各々の立場で「どこまで詳しくなる必要があるのか?」という悩みがあると思います。

何か設定しようとかトラブルに遭遇したとかで調べようと思えばネットに玉石混交ながらも情報はありますし、ドキュメントも豊富にあります。 とは言えFAQは一問一答だったりと知りたいことに対して表面的な対処で終わることも多く、短期で対処するには適切なのですが、「浅い知識と対応になっていないか?」などと不安になる人も多いんじゃないかなと。

知識がない状態だとドキュメントで使われている用語も一般用語かPostgreSQL特有の言葉なのかの区別もつきづらいです。 この本をざっと読むだけでもPostgreSQL関連で使われる言葉にアタリがつくようになると思います。 「自分はRDS for PostgreSQLを使うから要らない知識だ」と思っているなら早計で、私はRDSのドキュメントを読んで「よくわからないなー」とか「そんなもんかなー」と思っていた内容がある程度理解できるようになりました。

言葉だけでなく、データ型やバックアップ方式など、選択肢あるものの選び方とかも書かれています。固定長文字列は性能上のメリットがあるとかたまに聞いたりするけど、無いのねぇ。 単に違いやメリデメ並べるだけじゃなく、選択におけるフローチャートとか図も多くてわかりやすい。 バックアップの話は「最初にリカバリ要件を明確にします」と書いてたのも良いところ。バックアップしてたつもりがリストアできないとか、いざ対応しなきゃいけないときに(慌てないのは無理にしても)パニックにならないためにもこの辺りは抑えておかなきゃですよねと。

最近また物理本を買うようになって、置き場どうしたものかなと悩んでおります。

3月末のSpringFrameworkのRCEを紐解いてみる

https://spring.io/blog/2022/03/31/spring-framework-rce-early-announcement

Spring Shell がかわいそうなのでSpring4Shellって呼び方はしてほしくないなーと。でもCVEの番号なんて覚えれないので、通称は欲しい。この名前で広まっちゃったから検索に使ってるけど、、、

起こりうる問題とか実務ですべき対応とかはSpringの公式アナウンスを読んでください。 なんかおかわりもあるっぽいので、SpringBoot使ってるなら 2.6.6 -> 2.6.7 と更新してくことになりそ。

中身の話します。発生直後とかは攻撃の助けになっちゃうので言及は控えめにした方がいい気もするけど、そろそろいいかなって……攻撃者はもう情報持ってるだろうし。。

Springの対応内容

コミット で「なるほどねー」と言えるなら、以降は読む必要ありません。

この CachedIntrospectionResultsHistory を見ると、ひとつ前の変更は 2020-08-29 で2年弱触られておらず、過去10年で30コミット程度。そんな変更が活発なクラスでもないです。

動かして確認してみよう

CachedIntrospectionResults の変更箇所は private なコンストラクタで、使用箇所はパッケージプライベートな static メソッド forClass(Class) だけ。このメソッドは BeanUtilsBeanWrapperImpl で使用されます。 使いやすい BeanUtils を使用して試してみましょう。

BeanUtils と言われると「ああ、getter/setter呼んだりBeanのプロパティをがさっとコピーしたりするあれね」とか「 BeanUtils とか BeanUtil とかいろんなパッケージにあるよなー」とか思い当たる方も多いでしょう。

動作確認コード

今回問題になっていたリクエストは class.module.classLoader.resources.context... というものでした。

BeanUtils はJavaBeansを扱うもので、 java.beans.PropertyDescriptor が使われています。 PropertyDescriptor はリフレクションで少し踏み込んだことをしたことがなければ見たこともないかもしれませんが、別に知らなくても問題ありません。「あーJavaBeansのパッケージにプロパティをいじるクラスがあるんだなぁ」くらいで十分すぎると思います。興味あったら触ってみるのもいいと思いますが。

JavaBeansについては以下のエントリを見ておくと心穏やかに生きられると思います。

irof.hateblo.jp

さて、やってみましょう。

  1. BeanUtils を(介して対象の CachedIntrospectionResults を)使ってプロパティ名に対する PropertyDescriptor を取得
  2. PropertyDescriptor#getReadMethod() で getterを取得
  3. getterを使ってインスタンスを取得
  4. インスタンスに対して1に戻って繰り返し
import org.springframework.beans.BeanUtils;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;

public class CVE202222965Test {

    public static void main(String... args) throws Exception {
        Object instance = new CVE202222965Test();

        for (String propertyName : "class.module.classLoader.definedPackages".split("\\.")) {
            System.out.println("-- " + propertyName);
            PropertyDescriptor propertyDescriptor = BeanUtils.getPropertyDescriptor(instance.getClass(), propertyName);
            System.out.printf("%s に対する %s のPropertyDescriptor: %s%n", instance.getClass(), propertyName, propertyDescriptor);
            if (propertyDescriptor == null) {
                System.out.println("とれなかった(セーフ)");
                return;
            }

            Method readMethod = propertyDescriptor.getReadMethod();
            instance = readMethod.invoke(instance);
            System.out.println("PropertyDescriptorで取れた値: <<" + instance + ">>");
            if (instance == null) {
                System.out.println("とれなかった(セーフ)");
                return;
            }
        }
        System.out.println("ClassLoaderから取れちゃった: " + instance);
    }
}

Java9以降で該当するバージョンの spring-beans を使うと最後まで行きます。こんな感じ。

-- class
class cve.CVE202222965Test に対する class のPropertyDescriptor: org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=class]
PropertyDescriptorで取れた値: <<class cve.CVE202222965Test>>
-- module
class java.lang.Class に対する module のPropertyDescriptor: org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=module]
PropertyDescriptorで取れた値: <<unnamed module @30cb5b99>>
-- classLoader
class java.lang.Module に対する classLoader のPropertyDescriptor: org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=classLoader]
PropertyDescriptorで取れた値: <<jdk.internal.loader.ClassLoaders$AppClassLoader@42110406>>
-- definedPackages
class jdk.internal.loader.ClassLoaders$AppClassLoader に対する definedPackages のPropertyDescriptor: org.springframework.beans.GenericTypeAwarePropertyDescriptor[name=definedPackages]
PropertyDescriptorで取れた値: <<[Ljava.lang.Package;@548e7350>>
ClassLoaderが取れちゃった: [Ljava.lang.Package;@548e7350

Java8以前はgetModuleメソッドがないので取れないし、対応後のspring-beansはmoduleが取れないようになってます。もちろんclassLoaderも取れない。

"class.module.classLoader.definedPackages" の最後の definedPackagesClassLoader の持っている public なgetterならなんでも良いです。 PropertyDescriptor 経由のアクセスなので乱暴なメソッド呼び出しはできませんが、今回の脆弱性DataBinder なのでsetterを呼び出しちゃう。で、setter経由でTomcatのあいつを狙ったらいけちゃう、と言うのが示されちゃった内容。 探し回ったらTomcat以外でもなんやかんや見つかるでしょうね。

こんな感じでTomcatにwar作ってデプロイしたりDataBinderを使ったりしなくても、事象の原因部分だけは動かせます。

直感的にはとにかく ClassLoader に手が届くのが気持ち悪いのですが、これ "class.module.classLoaderclass.classLoader だと だいぶ昔(2010-03)から取れない んですよね。 今回の対応前から ("classLoader".equals(pd.getName())continue してるので、元々 CachedIntrospectionResultsClassLoader を扱わせるつもりはなかったわけで。

動きを変えて遊んでみる

  • プロパティのたどりかたをクラスを読みながら変える
    • "class.module.classLoader.definedPackages" をいじってみてどこまで手が届くんだっけ?とやってみる。
  • 起点のクラスを変える
    • たとえば new CVE202222965Test() としてるのを new Object() とか、クラスローダーが違うクラスを使ってみる。
  • setterを呼んでみる
    • getReadMethod してるとこで getWriteMethod 呼べばsetterがあれば取れます。値をねじ込めます。
    • インスタンスメソッドなら値を突っ込む対象のインスタンスがなかったらあまり役に立たないんですが、手に入ったインスタンス経由で static なsetter風メソッドとかに手が届いちゃったら、ね。

……攻撃者がやってることなんですが、こう言うことしてるとJSLとかフレームワークとかの構造が少し見えるようになってくるんじゃないかなと。遊びで得られる知見ってどこで役に立つかわかりませんが、なんらかの糧にはなります。

思うところ

別段 ClassLoader に手が届いたからと言って、 ClassLoader の機能を使ってるわけではないです。初期情報を見た時は「 ClassLoader に無理矢理クラスを読み込ませてるのかな?」と思ったりしましたが、濡れ衣でした。「任意のコードを実行できるプロパティに手が届いてしまうケースが見つかった」ですかね。

今回の方法はgetterを辿ってsetterを呼び出せるなら何にでも適用できるもので、 ClassLoader は踏み台にされただけ。 ClassLoader は多くのインスタンスに手が届きやすいのでよく狙われるんですけども。

じゃぁ問題はどこにあるんだって言うと、意識的に実装してないプロパティにアクセスできるところなんじゃないかなぁ。 具体的には getClass() メソッドで Classクラスのインスタンスが取れる点。要するにすべてのインスタンスObject を実装するがゆえに Object のメソッドをすべて持っている(けれどそれが意識から漏れがち)というところが。 Object のメソッドって業務コードではだいたい直接は使わないんですよね。 toString equals hashCode あたりは意識するものの、別段 Object のメソッドである必要はないと言うか。他のメソッドはもっと意識されてないでしょうし。そういえば Java9でdeprecatedになっていた finalize がJava18の JEP 421: Deprecate Finalization for Removal で削除に一歩進みましたね。さすがに finalize くらいになると削除に対してもすごく慎重な進め方をしている模様。

話を戻して、 BeanUtils を使う場面で Object クラスのメソッドを呼び出したいと思うことはない、と言うかシンプルに考えれば対象クラスに実装していないsetter/getterを呼び出したいとは思っていなさそうなんですが、現実問題としてBeanと呼ばれるものは共通プロパティを基底Beanと呼ばれるものに定義してたりするので、ユーティリティとしては親クラスも辿って呼び出す必要はあるんですよねぇ。

対応内容を見てると「なんで getClass() の呼び出し自体止めないんだろ」とか思うところですが、 Class クラス自体を使用されたら大穴は空いたままだし、 class プロパティはなんだかんだで使われてるんだろうなぁ。。

そういえば InitBinderのフィールド指定

@InitBinder でdisallowFieldを指定する」がバージョンアップするまでの対応として挙げられています。「そんな機能知らなかった」と言う話を聞いたりしたので、haljikさんのこちらを紹介。

意図しない改竄に対して安全

「動けばいい」なら面倒なだけなんですが、こういうこと起こるとホワイトリスト方式って強いなぁと……

まとめると

と言うことで、変更内容は以下。

  • 変更前
    • Class クラスのプロパティ名 classLoaderprotectionDomain は扱わせない。(ブラックリスト
  • 変更後
    • Class クラスで扱えるプロパティは name で終わるものだけ。 (ホワイトリストに近いルール)
    • すべてのクラスで ClassLoaderProtectionDomainインスタンスは扱わせない。

だいぶ厳しくなってます。 Java9で生えた getModule メソッドが public でなかったり get から始まっていなければ、と言う感じでしょうか。私がメンテナなら「まじかー……」とか言ってると思います。 普段よく触っているコードならともかく、ほとんど更新しないコードですしね。

ともかく、今後は BeanUtilsClassLoader なプロパティと Class クラスのほとんどのプロパティは無視されるようになります。 万一使っていたら「バージョンアップしたら動かなくなった」とかなります。 そんなもの BeanUtils でやるんじゃないよ、って思うけど。踏まないこと祈ってる。