hacktricks/linux-hardening/privilege-escalation/euid-ruid-suid.md
2023-07-07 23:42:27 +00:00

14 KiB
Raw Blame History

euid、ruid、suid

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥

この投稿は、https://0xdf.gitlab.io/2022/05/31/setuid-rabbithole.html#testing-on-jailからコピーされました

*uid

  • ruid: これはプロセスを開始したユーザーの実ユーザーIDです。
  • euid: これは有効なユーザーIDであり、システムはプロセスがどの特権を持つかを決定する際に参照します。ほとんどの場合、euidruidと同じになりますが、SetUIDバイナリはこの場合と異なる例です。SetUIDバイナリが起動すると、euidはファイルの所有者に設定され、これによりこれらのバイナリが機能することができます。
  • suid: これは保存されたユーザーIDであり、特権プロセスほとんどの場合はrootとして実行されるが一部の動作を行うために特権を降下する必要があるが、その後特権状態に戻る必要がある場合に使用されます。

{% hint style="info" %} 非ルートプロセスが**euidを変更したい場合、現在のruideuid、またはsuidの値に設定**することしかできません。 {% endhint %}

set*uid

最初に見ると、setuidシステムコールはruidを設定すると思われるかもしれません。実際には、特権プロセスの場合はそうです。しかし、一般的な場合では、実際にはeuidを設定します。manページから:

setuid()は、呼び出し元プロセスの有効なユーザーIDを設定します。呼び出し元プロセスが特権を持っている場合より正確には、プロセスがユーザーネームスペースのCAP_SETUID機能を持っている場合、実ユーザーIDと保存された設定ユーザーIDも設定されます。

したがって、rootとしてsetuid(0)を実行している場合、これはすべてのIDをrootに設定し、基本的にそれらをロックしますsuidが0であるため、以前のユーザーの情報は失われます - もちろん、rootプロセスは任意のユーザーに変更できます

2つの一般的でないシステムコール、setreuidreは実ユーザーIDと有効ユーザーIDを表すと**setresuid**resには保存されたユーザーIDも含まれるは、特定のIDを設定します。特権のないプロセスでは、これらの呼び出しは制限されますsetresuidmanページに記載されていますが、setreuidページにも似たような言語があります):

特権のないプロセスLinuxでは、CAP_SETUID機能を持つプロセスは、実ユーザーID、有効ユーザーID、保存された設定ユーザーIDを、現在の実ユーザーID、現在の有効ユーザーID、または現在の保存された設定ユーザーIDのいずれかに変更できます。

これらはセキュリティ機能としてではなく、意図したワークフローを反映するために存在していることを覚えておくことが重要です。プログラムが別のユーザーに変更する場合、有効なユーザーIDを変更してそのユーザーとして動作できるようにします。

攻撃者としては、最も一般的なケースがrootに移動することなので、setuidを呼び出すだけの悪い習慣に陥りやすいです。その場合、setuidは事実上setresuidと同じです。

実行

execveおよび他のexecs

execveシステムコールは、最初の引数で指定されたプログラムを実行します。2番目と3番目の引数は配列であり、引数argv)と環境(envp)です。execveに基づいていくつかの他のシステムコールがあり、execmanページ)と呼ばれます。これらはすべて、execveを呼び出すための略記法を提供するためのラッパーです。

manページには、その動作に関する詳細がたくさんあります。要するに、execveがプログラムを開始すると、呼び出し元プログラムと同じメモリスペースを使用し、その

system

systemは、新しいプロセスを開始するための完全に異なるアプローチです。execveが同じプロセス内でプロセスレベルで動作するのに対して、systemforkを使用して子プロセスを作成し、その子プロセスでexeclを使用して実行します

execl("/bin/sh", "sh", "-c", command, (char *) NULL);

execlは、文字列引数をargv配列に変換し、execveを呼び出すラッパーです。重要な点は、systemがコマンドを呼び出すためにshを使用するということです。

shとbashのSUID

bashには-pオプションがあり、manページでは次のように説明されています:

特権モードをオンにします。このモードでは、$ENVと**$BASH_ENVファイルは処理されず、シェル関数は環境から継承されず、環境にSHELLOPTS**、BASHOPTSCDPATHGLOBIGNORE変数が含まれている場合は無視されます。シェルが実効ユーザーグループIDが実ユーザーグループIDと等しくない場合、かつ**-pオプションが指定されていない場合、これらの操作が実行され、実効ユーザーIDが実ユーザーIDに設定されます。起動時に-pオプションが指定された場合、実効ユーザーIDはリセットされません**。このオプションをオフにすると、実効ユーザーIDとグループIDが実ユーザーIDとグループIDに設定されます。

要するに、-pを指定しない場合、Bashが実行されるとeuidruidに設定されます。-pはこれを防ぎます

**sh**シェルにはこのような機能はありません。manページでは、「ユーザーID」という言葉は言及されておらず、-iオプションのみが以下のように述べています:

-i シェルが対話的であることを指定します。以下を参照してください。呼び出し元のプロセスの実ユーザーIDが実効ユーザーIDと等しくない場合、または実グループIDが実効グループIDと等しくない場合、実装は-iオプションの指定をエラーとして扱う場合があります。

テスト

setuid / system

この背景を踏まえて、JailHTBでこのコードを実行し、何が起こるかを説明します

#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が99nobodyであり、euidが1000frankで開始されます。setuid呼び出しに到達すると、同じ値が設定されます。

その後、systemが呼び出され、uidが99であることを期待していますが、euidも1000であるはずです。なぜそうならないのでしょうかこのディストリビューションでは、shbashにシンボリックリンクされているため、そのような結果になります。

$ 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

この理論をテストするために、setuidsetreuidで置き換えてみます:

#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呼び出しは、ruideuidの両方を1000に設定します。したがって、systembashを呼び出したとき、それらは一致し、事は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では shbash です。そして、bashruid が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を呼び出すこともできます。