리눅스 태스크의 uid에 대해 정리해 볼 것이다.
먼저 각각에 대해 알아보자.
이들에 대해 자세히 알아보기 이전에, SUID binary는 무엇인지 먼저 알아보도록 하자.
SUID는 "Set User ID"로, 리눅스에서의 특별한 권한에 해당한다.
기존에 존재하던 rwx와는 다른 권한으로, 파일을 실행했을 경우 실제 파일의 소유자의 권한으로 실행되도록 하는 역할을 한다.
예시를 한 번 보자.
/* test1.c */
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc, char *argv[], char *envp[]) {
uid_t ruid, euid, suid;
getresuid(&ruid, &euid, &suid);
printf("ruid: %d, euid: %d, suid: %d\n", ruid, euid, suid);
int fd = open("/etc/shadow", O_RDONLY);
if (fd < 0) {
perror("open");
exit(1);
}
char buf[0x100];
read(fd, buf, 0x100);
write(1, buf, 0x100);
return 0;
}
코드를 간단히 살펴보면, 우선 ruid, euid, suid를 가져와서 출력해 준다.
그 후 root 권한에서만 read가 가능한 /etc/shadow
에서 값을 읽어와 출력해 준다.
파일 소유자를 root로 설정해 주기 위해 sudo를 통해 컴파일 하였다.
실행 결과는 다음과 같다.
ruid, euid, suid 셋 다 모두 1000으로 기존 user의 id값과 동일하다.
그리고 euid가 1000이므로 권한이 없어서 파일 open에 실패한 것을 알 수 있다.
이번에는 chmod를 통해서 SUID binary로 만들어준 후 다시 실행해 보자.
ls -l a.out
을 했을 때, 권한이 rwx -> rws로 바뀌었는데, 여기서 s가 바로 SUID bit에 해당한다.
출력 결과를 보면 ruid는 1000으로 그대로지만, euid와 suid는 0(root)으로 설정된 것을 확인할 수 있다.
즉, 이 바이너리를 실행할 경우에는 root 권한으로 실행한다는 의미이고, 실제로 실행 결과를 봐도 성공적으로 파일을 읽어와서 출력해 준다.
그러면 ruid, euid는 대충 알겠는데, suid는 뭘까?
suid는 uid값을 저장해 놓는데, setuid()
호출 시 도움을 주는 역할을 한다.
몇 가지 예시를 통해 알아볼 건데, 우선 wow
라는 새로운 user를 만들어서 a.out
파일의 권한을 아래와 같이 바꾼다.
a.out
의 소유자가 wow
로 바뀌었다. 이대로 한 번 실행해 보자.
보면 root의 소유였을 때와는 달리, euid, suid 모두 1001(wow
)의 것으로 바뀐 것을 확인할 수 있다.
물론 root권한은 아니므로 open은 실패하였다.
새로운 테스트 코드를 통해 suid에 대해 자세히 알아보자.
/* test2.c */
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
void print_uid() {
uid_t ruid, euid, suid;
getresuid(&ruid, &euid, &suid);
printf("ruid: %d, euid: %d, suid: %d\n", ruid, euid, suid);
}
int main(int argc, char *argv[], char *envp[]) {
print_uid();
setuid(1000);
puts("after setuid(1000):");
print_uid();
setuid(1001);
puts("after setuid(1001):");
print_uid();
setuid(0);
puts("after setuid(0):");
print_uid();
setuid(1002);
puts("after setuid(1002):");
print_uid();
return 0;
}
1000(jdoh
), 1001(wow
), 0(root
), 1002의 순서대로 uid값을 바꾸어 주고 있다.
마찬가지로 이번에도 파일의 소유자를 wow
로 설정하여 jdoh
계정에서 실행해 볼 것이다.
결과를 보면, 앞의 두 setuid(1000);
, setuid(1001);
함수 호출만 성공하는 것을 확인할 수 있다.
root는 당연히 막혀있는 것일테고, setuid(1002);
는 어떤 기준으로 성공여부를 판별하는 것일까?
setuid()
호출 시 바꿀 수 있는 값에 대해 권한의 측면에서 생각해 보면, 다른 user로 바꾸는 것은 금지해야 한다. 대신, SUID binary인 경우에는 파일 소유자의 권한으로 실행되는 것을 허용하므로 실행하는 사용자의 권한, 파일 소유자의 권한 이렇게 2가지가 가능할 것으로 생각된다.
하지만 이 두 권한에 속하는지 확인할 수 있는 정보가 바로 ruid, suid이다.
SUID binary의 경우, 위에서도 봤지만 소유자의 권한이 suid, 실행자의 권한이 ruid로 저장된 상태로 바이너리가 실행된다.
즉, 이렇게 저장되어있는 ruid, suid 값으로 setuid()
를 통해 바꿀 수 있는 것이다.
단, 여기서 하나의 예외가 존재하는데 바로 root권한이다.
root의 경우 ruid, suid가 무엇인지와 관계없이 ruid, euid, suid 모두 원하는 값으로 바꿔버린다.
/* test3.c */
#define _GNU_SOURCE
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
void print_uid() {
uid_t ruid, euid, suid;
getresuid(&ruid, &euid, &suid);
printf("ruid: %d, euid: %d, suid: %d\n", ruid, euid, suid);
}
int main(int argc, char *argv[], char *envp[]) {
print_uid();
setuid(0);
puts("after setuid(0):");
print_uid();
setuid(1001);
puts("after setuid(1001):");
print_uid();
return 0;
}
결과를 통해 간단하게 확인해 볼 수 있다.
uid를 바꾸는 함수에는 setuid()
외에 여러 가지가 있는데, 아래의 표로 대체한다.
(출처 - https://reakwon.tistory.com/228)
root
일반 user