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