Unix의 set-user-ID 메커니즘을 설명할 수 있는 사람이 있습니까? 이 디자인 결정의 근거는 무엇이었나요? 효과적인 사용자 ID 메커니즘과 어떻게 다른가요?
답변1
아마도 UNIX에서 파일에 대한 일반적인 읽기, 쓰기 및 실행 권한을 알고 있을 것입니다.
그러나 많은 애플리케이션에서 이러한 유형의 권한 구조(예: 특정 사용자에게 특정 파일을 읽을 수 있는 전체 권한을 부여하거나 파일을 전혀 읽을 수 있는 권한을 부여하지 않음)는 너무 조잡합니다. 따라서 Unix에는 또 다른 권한 비트인 set-user-ID
비트가 포함되어 있습니다. 실행 파일에 대해 이 비트가 설정되면 소유자가 아닌 사용자가 파일을 실행할 때마다 해당 사용자는 소유자의 다른 파일에 액세스할 때 소유자의 모든 파일 읽기/쓰기/실행 권한을 얻게 됩니다!
파일의 사용자 ID 비트 설정을 설정하려면 다음을 입력하십시오.
chmod u+s filename
group-other에 대한 실행 권한도 설정했는지 확인하세요. 다른 그룹에 대한 읽기 권한도 있으면 좋을 것입니다. 이 모든 것은 하나의 명령문으로 가능합니다
chmod 4755 filename
저장된 UID라고도 합니다. 시작된 파일에는 Set-UID 비트가 있으며 저장된 UID는 파일 소유자의 UID가 됩니다. 그렇지 않으면 저장된 UID가 실제 UID가 됩니다.
유효한 uid란 무엇입니까?
이 UID는 특정 작업을 수행하기 위한 프로세스의 권한을 평가하는 데 사용됩니다. EUID는 실제 UID로 변경될 수 있으며, EUID!=0인 경우 수퍼유저 UID로 변경될 수 있습니다. EUID=0이면 무엇이든 변경할 수 있습니다.
예
그러한 프로그램의 예가 입니다 passwd
. 전체 목록을 보면 Set-UID 비트가 있고 소유자가 "루트"임을 알 수 있습니다. 일반 사용자(예: "mtk")로 실행하면 다음 passwd
으로 시작됩니다.
Real-UID = mtk
Effective-UID = mtk
Saved-UID = root
답변2
man credentials
이 상황에서 좋은 정보 소스입니다. 이 질문도 참조하십시오그래서. 역사적 설명은 여기를 참조하세요보관된 게시물.
"UID 설정" 및 "유효한 UID" 메커니즘을 호출하는 대신 UID의 전체 개념을 메커니즘이라고 합니다. 다양한 UID가 존재하는 이유는 권한 분리로 인한 다양한 문제 때문이다. 일반(권한이 없는) 사용자라도 권한이 있는 사용자만 수행할 수 있는 작업(리소스에 액세스)을 수행해야 하는 경우가 있습니다. 이를 쉽게 달성하기 위해 프로그램은 UID를 변경할 수 있습니다. 3가지 유형이 있습니다.
실제 UID - 소유 프로세스의 UID
유효 UID - 프로세스가 현재 실행 중인 UID - 특정 순간에 프로세스의 실제 기능을 결정합니다. 이는
ps
사용자 필드에도 표시됩니다.저장된 세트 UID - 실제 UID와 유효 UID 간 전환을 위한 자리 표시자
일반 사용자가 할 수 있기 때문에 마지막 것이 필요합니다.오직이 세 가지 사이를 전환하세요.다른 사람은 없어setuid 프로그램은 일반적으로 자신을 로드한 사용자가 누구인지 알아야 합니다(그리고 실제 UID는 더 많은 혼란을 야기하므로 변경되어서는 안 됩니다).
답변3
mtk의 설명은 매우 좋습니다.
이 passwd
예는 권한 상승 중 하나입니다. passwd는 루트만 변경할 수 있는 파일을 변경해야 하기 때문에 항상 루트로 실행됩니다. 이는 passwd 실행 파일이 버퍼 오버플로와 같은 일이 발생하지 않도록 하여 영리한 일반 사용자가 의도하지 않은 목적으로 사용할 수 있도록 하는 것이 중요합니다.
또 다른 이유는 루트로 로그인한 경우와 동일한 방식으로 사용자를 보호하기 위한 것입니다 su
.줄이거나 제한하다에스컬레이션이 아닌 특정 작업에 대한 권한입니다. 예를 들어, 내 항목에 액세스할 필요가 없고 자체 항목이 있고 그것이 필요한 전부인 데몬 서비스(예: 로거)를 시작할 수 있는 권한이 있는 경우, suid를 실행하면 액세스만 가능하다는 의미입니다. 내 것 아니면 다른 사람의 것.
실행 파일에 suid 비트가 설정되지 않은 경우에도 프로그래밍 방식으로 uid를 설정할 수 있습니다.그러나 이는 업그레이드에는 작동하지 않습니다. 즉, 일반 사용자이고 특정 시점에 자체 uid를 설정하는 프로그램을 작성하는 경우 프로그램은 루트로 전환할 수 없습니다. 나는 이것이 Apache가 작동하는 방식이라고 믿습니다. 이는 일반적으로 하나의 프로세스로 루트에 의해 시작된 다음 하위 프로세스를 포크하여 uid를 권한이 없는 사용자(예: "httpd")로 전환합니다. 이러한 하위 프로세스는 실제 웹 서버가 수행하는 작업입니다.
답변4
위의 @mtk 답변에는 작은 오류가 있으며, @Nor.Z는 그의 의견에서 이를 지적했습니다.
자격 증명 매뉴얼 페이지에서는 실제 사용자 ID(RUID), 유효 사용자 ID(EUID) 및 저장된 사용자 ID(SUID)의 개념을 설명합니다. 바라보다여기.
잘 이해하면 기본적으로 프로세스가 시작될 때 프로세스의 EUID와 SUID는 프로세스의 RUID와 동일하게 설정됩니다. 그룹도 마찬가지다. 그러나 프로세스를 시작하는 바이너리의 권한에 set-user-ID 비트가 설정된 경우 EUID는그리고SUID는 루트와 같은 권한이 있는 사용자일 수 있는 바이너리 소유자의 SUID로 설정됩니다. RUID는 변경되지 않습니다. 나중에 프로그램이 소스 코드에서 EUID를 변경하면 루트 UID도 SUID에 저장되어 SUID에서 얻을 수 있으므로 필요한 경우 다시 루트의 UID로 재설정할 수 있습니다. 다음 예제 C 프로그램은 이를 보여줍니다(이것유튜브 영상):
// filename: uid.c
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
uid_t ruid, euid, suid;
// Get the real, effective, and saved user IDs
if (getresuid(&ruid, &euid, &suid) == -1) {
perror("getresuid");
return 1;
}
// Display the user IDs
printf("Real UID: %d\n", ruid);
printf("Effective UID: %d\n", euid);
printf("Saved UID: %d\n", suid);
// file /etc/hosts is owned by root, and only root can open it
// in write mode. Normal/unprovileged users can only open it in read mode.
/*See if we can open the /etc/hosts file for reading and writing, as the EUID*/
printf("open: %d\n", open("/etc/hosts", O_RDWR));
/* access() tests what the RUID can do. We check 'writing' in this case */
printf("access: %d\n", access("/etc/hosts", W_OK));
printf("--\n");
// drop the privileges in the EUID by setting it with the RUID, and re-try
seteuid(ruid);
if (getresuid(&ruid, &euid, &suid) == -1) {
perror("getresuid");
return 1;
}
printf("Real UID: %d\n", ruid);
printf("Effective UID: %d\n", euid);
printf("Saved UID: %d\n", suid);
/*See if we can open the /etc/hosts file for reading and writing, as the EUID*/
printf("open: %d\n", open("/etc/hosts", O_RDWR));
/* access() tests what the RUID can do. We check 'writing' in this case */
printf("access: %d\n", access("/etc/hosts", W_OK));
return 0;
}
를 사용하여 프로그램을 컴파일 gcc uid.c -o uid
하고 를 사용하여 실행합니다 ./uid
. 다음과 같이 출력됩니다.
Real UID: 1000
Effective UID: 1000
Saved UID: 1000
open: -1
access: -1
--
Real UID: 1000
Effective UID: 1000
Saved UID: 1000
open: -1
access: -1
열기 또는 액세스 반환 코드 -1은 작업이 실패했음을 나타냅니다. 그런 다음 프로그램의 소유권을 루트로 변경 sudo chown root ./uid
하고 set-user-ID 비트를 설정합니다 sudo chmod u+s ./uid
. ls -lA
바이너리 소유자 권한에서 수행할 작업을 확인하고 "s"를 기록하려면 다음을 수행하십시오 .uid
total 20
-rwsrwxr-x 1 root gamba 16272 dic 6 16:44 uid
-rw-rw-r-- 1 gamba gamba 1192 dic 6 16:17 uid.c
프로그램을 다시 실행하고 위의 내용과 비교하십시오.
Real UID: 1000
Effective UID: 0
Saved UID: 0
open: 3
access: -1
--
Real UID: 1000
Effective UID: 1000
Saved UID: 0
open: -1
access: -1
보시다시피 EUID는그리고SUID는 루트(바이너리 소유자)의 SUID로 설정됩니다 uid
! 그러면 EUID를 사용한 열기 작업은 성공하지만(반환 코드 3) 액세스 작업은 실패합니다(반환 코드 -1). 파일을 읽을 수만 있는(권한 없음) RUID를 계속 사용하고 있기 때문입니다 /etc/hosts
. "--" 플래그 뒤에 EUID를 RUID로 변경하면 두 작업이 다시 실패한다는 것을 알 수 있습니다.
마지막으로 바이너리의 소유권을 uid
일반/권한이 없는 사용자 "gamba"로 다시 설정하면 set-user-ID 비트를 다시 설정합니다.하지만러너를 사용하면 sudo
다음과 같은 출력을 얻을 수 있습니다.
Real UID: 0
Effective UID: 1000
Saved UID: 1000
open: -1
access: 0
--
Real UID: 0
Effective UID: 0
Saved UID: 1000
access: 0
access: 0
볼 수 있듯이 이 예에서 set-user-ID 비트는 권한이 없는 사용자에게 EUID를 설정하는 효과가 있어 열기 작업이 실패하게 됩니다. 실제 사용자(이 경우 루트)로 생성되었기 때문에 액세스 작업이 성공합니다(runner 를 사용하기 때문 sudo
). 이 동작은 위의 답변에서 @goldilocks가 지적한 것처럼 권한 있는 사용자(예: 루트)로 프로그램을 실행할 때 실수로 특정 파일을 변경하고 싶지 않을 때 유용합니다.