root権限を取り返したい - イントラサーバー奮闘記録

# 0日目

私は自宅用に外部公開していないUbuntu Serverを運用しています。
時折SSHでログインして`sudo apt update && sudo apt upgrade`とかWinSCPでファイル操作しているのですが、ある日こう思いました。

> root所有のファイルに触れないのがめんどくさい

そこでなんとなく自分がrootグループに所属してれば解決する気がしたので、少し検索してこのコマンドを入力しました。

```bash
$ sudo usermod -G root kurema
```

ここで私は3つ間違いを犯しています。

1. rootグループに所属したところでroot権限は得られない。得られるのも望ましくない。
1. rootグループに所属したところでパーミッションが`rwxrwx---`みたいなファイルしか触れない。大抵は`rwx------`とか。
1. **`usermod`の`-G`オプションは副グループが指定したものに書き換わる。追加したいなら`-aG`。**

やらかしました。
この時は気付いていません。

(ただしsambashareグループは残っていたので何か別の操作をしたかもしれません。)

# 1日目
しばらくしてログインした時、`sudo`で以下のエラーが出ることに気づきました。

```bash
$ sudo apt update
kurema is not in the sudoers file. This incident will be reported.
```

ここで`usermod`コマンドを間違えたくさいと思い当たりました。
Ubuntu Serverではsudoグループに所属しているユーザーがsudoできます。
`/etc/sudoers`に書いてあります。adminでも良いようです。

しまった。現時点でsudo権限を持ったユーザーが居ない。どうしよう？
なんとかして誤魔化せないか。いや一般ユーザーがroot権限を普通に奪取出来たら脆弱性だ。
権限昇格系の脆弱性が公開されるのを待つか？いや待ってられないし実証コードとか絶対めんどくさいに決まってる。
考えろ。考えるんだ…。

まぁこの時点で解決策は分かっていました。
サーバーには物理アクセスできるわけですから、キーボードとモニターを刺してレスキューモードに入る、最悪ハードディスクを抜いて直接書き換えればどうにでもなる(ファイルシステムは暗号化していません)。
しかしそれは嫌でした。
なぜならめんどくさいからです。

そこで思い浮かびました。
確か`crontab`で`root`が毎日シェルスクリプトを回してる。
もう`sudo`できないので記憶頼みだけど確かそうです。
しかも`kurema`が書き換え可能な権限持ってる！
やった。勝った！

というわけでシェルスクリプト末尾に以下の行を追加しました。

```bash
cd `dirname $0`
#...

usermod -aG sudo kurema
```

そして安心して寝ました。
cronは夜働くからです。


# 2日目
再びログインして、`sudo`を試しました。

```bash
$ sudo apt update
kurema is not in the sudoers file. This incident will be reported.
```

何も変わりません。`groups`の結果も同じです。
スクリプト自体は動いた形跡があります。
とりあえずエラーか何かで途中で終了したのかもと考え、`usermod`行を末尾から先頭に移動しました。

ちなみにシェルスクリプトでは途中エラーが出ても普通に下まで実行するはずなのでこれは間違いです。
途中で終了させたい場合は`set -e`などとしますが、今回してないので推測がおかしいです。
その辺はなんとなく分かってましたがダメもとです。

```bash
usermod -aG sudo kurema

cd `dirname $0`
#...
```

* [シェルスクリプト内のエラーが発生した場所で異常終了したい](https://qiita.com/rtakasuke/items/fd35ef60370e3d8c225d)
* [bashスクリプトのエラー処理のベストプラクティス](https://www.marketechlabo.com/bash-batch-best-practice/)

# 3日目
再び試しました。案の定ダメです。
以前、cronで`shutdown`を実行するときrootでも`sudo shutdown -r now`のように書かなければならなかったことを思い出し、`sudo usermod -aG sudo kurema`のようにしました。
ないだろうなぁ。

```bash
sudo usermod -aG sudo kurema

cd `dirname $0`
#...
```

# 4日目
ダメです！
意味が分かりません。エラー文が見たいのでファイル出力を設定しました。
明日解決する可能性はないですが、原因は分かるはずです。
これは3日目にまとめてやったかも。

```bash
sudo usermod -aG sudo kurema > /home/.../usermod.log 2>&1

cd `dirname $0`
#...
```

# 5日目
ファイルを確認してみよう。
あれ、ファイルが存在しません。
シェルスクリプト自体が実行された形跡はあります。
これはおかしい…。

ここでいくつか推測。

1. ほぼ同一のシェルスクリプトが他の場所にあるのでそっちを実行してた…？←一行目が``cd `dirname $0` ``(シェルスクリプトの場所にcd)なのでそれはないはず
1. セキュリティ上の理由で、シェルスクリプトは`crontab -e`実行時のものがキャッシュされる仕組みがある？←そんな非直感的な仕様はないはず。調べてもなさそうだった。

違う。
そして思い出しました。
「そういやこれ、フォルダをdockerコンテナにvolumeでマウントして、docker内からシェルスクリプトを実行してる。」
忘れてた。

詰んだ。
これはもう素直にモニターを運んでつなげるしかないのでしょうか。
嫌だなぁ。

ただ`docker`はroot権限でファイルは作れるんだよな。
何かできないか？
ファイル作れても仕方ないしなぁ。

そうだ！
`/etc/sudoers.d/`をボリュームに追加でファイル作れるじゃん！
これで解決だよ。良かった。

というわけで`docker-compose.yml`(これもkuremaが変更可能)を書き換え、シェルスクリプトも書き換え、寝ました。

```yaml
version: '3'
services:
  nicochcgi:
#...
    volumes:
      - ./config:/etc/nicochcgi
      - /home/share/niconico:/media/niconico
      - /etc/sudoers.d:/media/sudoers.d #ここを追加
```

```bash
echo `kurema ALL=(ALL:ALL) ALL` > /media/sudoers.d/kurema
#...
```

* [sudoを/etc/sudoers.d/fileで許可する](https://qiita.com/kkoito/items/f5242991c43b719ef2db)

# 6日目
確認。変化なし。
考えてみると`docker-compose.yml`を実行中書き換えても反映されないだろうし、コンテナ再起動が必要か。忘れてた。
サーバーごと再起動っと。

```bash
$ shutdown -r now
Failed to set wall message, ignoring: Interactive authentication required.
Failed to reboot system via logind: Interactive authentication required.
Failed to open initctl fifo: Permission denied
Failed to talk to init daemon.
```

そりゃそうだ。
再起動は確か日曜日にスケジュールしてたはずだ。
電源ボタンを押せば手動でできるけど、dockerが起動しなくなったりすると厄介なので待とう。

# 7日目
(再起動までの数日除く)

ダメ。慣れてきた。
そういえば、ボリューム追加にはあらかじめマウント先のフォルダを作ってないといけないかったような。
とりあえずフォルダを作るか。

```bash
mkdir /media/sudoers.d
echo `kurema ALL=(ALL:ALL) ALL` > /media/sudoers.d/kurema
#...
```

さすがにもう一週間は待てないので電源ボタンを押す。まぁ大丈夫だろう。

# 8日目
またもダメ。変化なし。
しかしなぜ動かないんだろう。
dockerはroot権限で動いて、作るファイルもroot所有…。
あ！

Ubuntu ServerのdockerはSnappyで動いてる！
Snappyだと`/etc`とかには直接触れないんだ！
思い出した…。[そんなことあったわ](https://github.com/docker/for-linux/issues/564#issuecomment-716197556)。

うーん。何か手はないか。
…まぁないだろ。
サンドボックス下からsudo権限取れたら話にならんし。
dockerをclassicオプション付きで再インストールできれば良いが、当然sudoが必須だし。

* [Snap(Snappy) コマンドの使い方](https://mekou.com/linux-magazine/snapsnappy-%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AE%E4%BD%BF%E3%81%84%E6%96%B9/)

…諦めよう。

# 9日目
仕方ないのでキーボードとモニターを持って行ってサーバーに繋げました。
`shutdown -r now`はなぜか`sudo`なしで動きました。直接繋ぐとOKなのかな？
まぁ目の前に電源ボタンがあるから`sudo`必須にする意味もないか。
(ちなみに下の作業前にインストールメディアから色々やろうとしたが、LVMのマウントが面倒だったので検索しなおした。)

1. 電源投入時<kbd>Esc</kbd>連打。
1. GRUBメニューが出たらnormal<kbd>Enter</kbd>すぐ<kbd>Esc</kbd>。
1. `Advanced options for Ubuntu`選択。
1. `Ubuntu, with Linux *.*.*(recovery mode)`選択。2つあったがどっちでも良い。
1. `⁠root:Drop to root shell prompt`選択。
1. `ls /home`とかでサーバーと同じ環境で普通にマウントされてることを確認。念の為。
1. ```usermod -aG sudo kurema```。`groups kurema`で確認。

* [第639回　Ubuntuに「トラブル時に」ログインするいろいろな方法](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0639)

終わり。
長かった。

毎日やったのは数行程度の作業だから「最初からこうすれば」ということはなかった。
数日`sudo`使えなくても実害はないし。
むしろモニターとキーボードをつないで作業するってのはホントめんどくさい。

というわけで奮闘むなしくシングルユーザーモードでログインして解決しました。
一番避けたかった(再インストールやHDD抜き差しという最悪の事態は回避できたとはいえ)結果に終わったのは残念ではあるけれど。

改めて攻撃者の立場で見てみると、結構やらかしてる点があるというのに気づきました。
最終的にroot奪取はできなかったけど、sudo権限を奪われてもrootでファイル操作は多少できていたし、docker-composeも書き換えられるし。
自分はsudo可能だから大して気にしてなかったけど本来はよろしくないね。
あと念のためレスキュー用ユーザーでも作っておくのも良いかなとも思いました。散らかるのでやらないけど。