컴퓨터/운영체제(주로 리눅스)

[리눅스] 코드로 알아보는 uid(real uid, effective uid, saved uid) 관계

REAKWON 2023. 6. 30. 13:15

아래 포스팅보다 더 많은 정보와 예제를 담은 리눅스 교재를 배포했습니다. 아래의 페이지에서 리눅스 교재를 받아가세요.

https://reakwon.tistory.com/233

 

리눅스 프로그래밍 note 배포

티스토리에 리눅스에 관한 내용을 두서없이 여지껏 포스팅했었데요. 저도 제 포스팅을 찾기가 어렵기도 하고 티스토리에서 코드삽입을 하게 되면 이게 일자로 쭉 쓰여져있는 x같은 현상이 생겨

reakwon.tistory.com

 

사용자 ID(Real User ID, Effective User ID,  Saved User ID) 그룹 ID(Real Group ID, Effective Group ID, Saved Group ID)

리눅스에서 사용자는 각 User ID라는 값을 갖습니다. 그리고 그 사용자는 어느 그룹의 일원이 되죠. 그래서 Group ID도 같이 갖습니다. 이렇게 id를 부여하는 이유는 각각 사람들마다 다른 권한을 갖고 있기 때문입니다. 각 사용자들은 자신들의 파일을 만들고 그 파일에 대해 권한을 부여합니다. 즉, 이 파일을 어디까지 허용할지 말이죠. 파일을 누구에게 까지 읽고 쓰는 것을 허락하느냐, 실행파일이라면 실행을 누구에게 까지 허락하느냐 이러한 허용 말입니다.

그런데 실행 파일이라면 특수한 권한 하나가 더 생깁니다. 이 파일을 실행할 경우에 갖는 권한, 즉 실행 할 때만 유효한 권한이요. 그게 뭐냐면 실행할 때만 소유주의 권한, 혹은 소유주 그룹의 권한으로 실행되는 권한입니다. 예를 들어서 파일을 읽는 어떤 프로그램이 있다고 칩시다. 그리고 이 프로그램의 소유주는 “기쁨”이라고 할게요. 어떤 사용자 “똘똘이”가 프로그램을 통해서 파일을 읽을 땐 똘똘이의 파일밖에 못 읽습니다. 기쁨이의 파일을 함부로 볼 수가 없죠. 근데 착한 기쁨이는 나의 프로그램을 쓸 때 나의 파일은 읽을 수 있게 특별한 권한을 이 프로그램에 준다면 똘똘이는 기쁨이의 파일도 읽게 될 수 있습니다. 이와 같은 상황을 권한이 확장되었다라고 합니다.

 

전문용어로 정리하면 아래와 같습니다. 그리고 여기서는 uid에만 포커스를 맞춰서 보겠습니다. 왜냐면 gid도 역시 uid와 같은 방식으로 동작되지 때문이죠.

UID 종류 설명
Real UID(User ID)
ruid
실제 사용자 ID를 의미합니다. 여러분들이 로그인할 때 접속하는 그 user의 ID입니다. 이 실제 id에 대한 정보는 /etc/passwd 파일에 기록이 되어있지요. 줄여서 ruid라고 하겠습니다.
Effective UID(User ID)
euid
프로그램 실행 시 갖는 권한을 의미하며 실행 파일의 소유주 권한입니다. 보통은 사용자 ruid와 같습니다. 실행파일의 user 실행 권한에 setuid 권한이 있을 경우에 달라집니다. 줄여서 euid라고 합니다.
Saved UID(User ID)
suid
저장된 사용자의 ID라고 합니다. 프로그램에 권한을 더 쎈 권한을 주어야할 때나 권한을 더 줄여야할 때에 유기적으로 쓰이게 됩니다. 줄여서 suid라고 합니다. suid의 쓰임새에는 조금 나중에(맨 아래에서) 코드로 설명을 드리겠습니다.

 

GID 종류 설명
Real GID(Group ID)
rgid
실제 사용자 그룹의 ID입니다. Ruid와 마찬가지로 로그인할때 부여되는 gid를 의미합니다. 줄여서 rgid라고 합니다.
Effective GID(Group ID)
egid
유효 사용자 그룹의 ID입니다. 역시 보통은 rgid와 같습니다. 실행파일에 setgid비트가 켜져있으면 rgid와 달라질 수 있습니다.
Saved GID(Group ID)
sgid
저장된 그룹의 ID입니다. 줄여서 sgid라고 합니다.

이제 uid와 관련한 함수들을 보면서 어떤 특징을 갖는지 확인해보도록 합시다. 현재 접속한 사용자가 누군지 쉽게 알아보게 하기 위해서 프롬프트 앞에 사용자 계정명을 같이 표시하겠습니다.

1. 사용자 uid 읽기 - getuid, geteuid, getresuid

#include <unistd.h>
uid_t getuid(void);
uid_t geteuid(void);
int getresuid(uid_t *ruid, uid_t *euid, uid_t *suid);

getuid는 사용자의 진짜 레알 아이디인 ruid를 가져옵니다.
geteuid는 프로그램의 실행 중 권한 euid를 가져옵니다.
getresuid는 프로그램의 ruid, euid뿐만 아니라 suid까지 가져옵니다. 실패시 -1을 가져옵니다.

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <pwd.h>

void pr_resuid(){
        uid_t ruid, euid, suid;
       
        if(getresuid(&ruid, &euid, &suid) < 0){
                printf("getresuid error\n");
                exit(1);
        }

        printf("ruid : %d, euid :%d, suid :%d\n",
                        ruid, euid, suid);

}
int main(){
        pr_resuid();
}

보통의 상황이라면 ruid와 euid, suid는 같게 될 겁니다. 아래와 같이 u+s를 주어 setuid를 설정합니다. 그리고 실행하면 ruid와 euid는 0입니다. 여기까지는 다들 예상 하실 겁니다.

root# ls -l a.out 
-rwxr-xr-x 1 root root 8968 Jun 19 11:27 a.out
root# chmod u+s a.out
root# ./a.out 
ruid : 0, euid :0, suid :0

만약 다른 사용자로 로그인하게 되면 아래와 같이 실제 ruid는 바뀌지 않으나 euid는 바뀌죠. 여기까지 다 아는 내용입니다. 단순히 getresuid를 통해서 세가지 uid(ruid, euid, suid)를 가져오는 예제일 뿐입니다. 이 함수는 3개의 uid인 ruid, euid, suid를 모두 가져올 수 있기 때문에 지금부터 아래의 예제들은 이 함수로 uid들을 출력하도록 하겠습니다. 아, suid는 맨 처음 euid와 같은 점은 눈여겨 보시기 바랍니다.

root# su ubuntu
ubuntu$ ./a.out 
ruid : 1000, euid :0, suid :0

 

2. uid 설정 함수들

setuid, setgid

#include <unistd.h>
int setuid(uid_t uid);
int setgid(uid_t gid);

setuid : 사용자가 루트일 경우 ruid, euid, suid를 전달된 인자인 uid로 변경할 수 있는 함수입니다(ruid = euid = suid = uid). 다만, 일반 조건에 맞는 유저일 경우만 제한적으로 euid만 자신의 uid로 변경됩니다.

자, 여기서 잘 생각해보세요. 아무나 ruid, 혹은 euid 혹은 suid를 바꾸면 될까요? 안될까요? 그러니까 아무나 본인의 학번이나 사번을 바꾼다고 생각해보세요. 있을 수가 없는 일이죠. 결론부터 말씀드리자면 setuid는 프로그램이 루트 권한으로 실행되면 루트사용자일 경우에 ruid, euid, suid 모두 변경합니다. 루트는 킹 중의 킹, 전설의 레전드입니다. 루트가 바꾸면 토 달지말고 그냥 바꾸는 겁니다.

다시 말하면 프로그램의 euid가 루트일때, setuid는 ruid, euid, suid 모두 바꿔버립니다. 즉, “유효 사용자 id가 root이면(현재 프로그램의 euid가 root이면) 모든 id(ruid, euid, suid)를 setuid를 통해서 바꿀 수 있다.” 입니다. 그런데 루트 사용자의 권한이 아닌 프로그램이고 그 안에서 setuid를 통해서 인자인 uid를 바꾸려하면, 사용자의 ruid 혹은 suid가 setuid의 인자로 전달되는 uid와 같을 때 euid만 변경이 됩니다. 그러니까 루트 사용자가 아닌 경우에는 실제 uid, 즉 ruid는 죽었다 깨어나도 변경할 수 없다는 의미입니다. euid만 바꿀 수 있습니다. 자신의 ruid 혹은 suid로만 말이죠.

얘기가 길었죠. 간단히 정리하면, 현재 유효 사용자 ID가 루트인 경우 ruid, euid, suid 가 원하는 uid로 바꿀 수 있습니다. 현재 유효 사용자 ID가 일반 유저인 경우 현재의 실 사용자 id(ruid) 혹은 현재의 저장된 사용자 id(suid)로 euid만 바꿀 수 있습니다.

다음은 setuid의 특징을 알아보는 코드입니다.

 

//setuid.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

void pr_resuid(){
        uid_t ruid, euid, suid;
       
        if(getresuid(&ruid, &euid, &suid) < 0){
                printf("getresuid error\n");
                exit(1);
        }

        printf("ruid : %d, euid :%d, suid :%d\n",
                        ruid, euid, suid);

}

int main(int argc, char *argv[]){
        uid_t uid;
        if(argc < 2){
                printf("%s uid(>=0)\n", argv[0]);
                return 0;
        }

        pr_resuid();

        uid = atoi(argv[1]);
        printf("setuid(%d)\n", uid);
        if(setuid(uid) == -1){
                printf("setuid error : %s\n", strerror(errno));
                exit(0);
        }

        pr_resuid();
}

ubuntu의 계정으로 컴파일 합니다. 이때 ruid는 1000번입니다.

ubuntu$ gcc setuids.c 
ubuntu$ chmod u+s a.out
ubuntu$ id
uid=1000(ubuntu) gid=1000(ubuntu) groups=1000(ubuntu),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd)

이 프로그램을 다른 계정으로 실행해보면 어떻게 될까요? euid 1000인 프로그램에서 실제 ruid가 다른 1001로 바꾸려고 할 때는 아래와 같이 에러가 발생합니다. 당연하겠죠. euid도 누군가가 함부로 남의 euid를 쓸 수는 없는 겁니다.

ubuntu$ ./a.out 1000
ruid : 1000, euid :1000, suid :1000
setuid(1000)
ruid : 1000, euid :1000, suid :1000

ubuntu$ ./a.out 1001
ruid : 1000, euid :1000, suid :1000
setuid(1001)
setuid error : Operation not permitted

 

아까 조건에서도 봤듯이 ruid 혹은 suid가 변경하려는 uid와 같아야 euid만 변경이 됩니다. 여기 1001인 유저 hello로 로그인해서 확인해보겠습니다.

ubuntu$ su hello
Password: 
hello$ id
uid=1001(hello) gid=1001(hello) groups=1001(hello) 

hello$ ./a.out 1000    <- ruid:1001, suid: 1000이므로 euid를 1000으로 변경 가능
ruid : 1001, euid :1000, suid :1000
setuid(1000)
ruid : 1001, euid :1000, suid :1000

hello$ ./a.out 1001  <- ruid:1001, suid: 1001이므로 euid를 1001으로 변경 가능
ruid : 1001, euid :1000, suid :1000
setuid(1001)
ruid : 1001, euid :1001, suid :1000

hello$ ./a.out 1       <- ruid:1001, suid: 1000이므로 1로 변경 불가 
ruid : 1001, euid :1000, suid :1000
setuid(1)
setuid error : Operation not permitted

그렇다면 만약 프로그램의 주인이 루트라면 위 상황은 어떻게 될까요? 아래 명령을 통해서 owner를 바꾸고 setuid도 같이 바꿔줍니다.

root# chown root:root a.out     <- 절대무적 루트가 나타나 owner를 자신으로
root# chmod u+s a.out               <- 실행시 root의 권한을 갖는다.

그리고 계정을 root로 바꿔서 실행해보면 ruid, euid, suid를 전부 바꾼다는 것을 알 수 있습니다. 이는 프로그램이 실행될 때 root의 권한으로 실행(초기 euid가 root) 되어지기 때문에 이라는 것을 설명드렸어요. 루트는 절대무적이니까요.

ubuntu$ ./a.out 0
ruid : 1000, euid :0, suid :0.     <- 현재 유효사용자 id(euid)는 root
setuid(0)
ruid : 0, euid :0, suid :0   <- euid가 root이기 때문에 모든id를 바꿀 수 있다.

ubuntu$ ./a.out 1000
ruid : 1000, euid :0, suid :0
setuid(1000)
ruid : 1000, euid :1000, suid :1000

ubuntu$ ./a.out 1001
ruid : 1000, euid :0, suid :0
setuid(1001)
ruid : 1001, euid :1001, suid :1001

ubuntu$ ./a.out 1
ruid : 1000, euid :0, suid :0
setuid(1)
ruid : 1, euid :1, suid :1

 

seteuid, setegid

#include <unistd.h>
int seteuid(uid_t euid);
int setegid(gid_t egid);

현재의 euid를 바꿉니다. 유효사용자가 root라면 euid를 원하는 값으로 바꿀 수 있습니다. 그런데 일반 사용자라면 euid는 현재의 ruid 혹은 현재의 suid로만 바꿀 수 있습니다. 실패시 -1을 반환합니다.

setreuid, setregid

#include <unistd.h>
int setreuid(uid_t ruid, uid_t euid);
int setregid(gid_t rgid, gid_t egid);

위 함수들은 ruid, euid를 바꾸는 것 처럼 보이죠? 맞습니다. 그런데 추가로 suid도 바꿉니다. suid는 euid와 같이 바꿉니다. 이전에 보았던 setuid는 일반 유저가 ruid를 바꾸는게 불가능했었습니다. 그런데 이 함수는 가능합니다. 일반유저인 경우 setreuid 함수는 현재 ruid, euid(현재 suid와는 상관없습니다.) 중에서만 변경할 수 있습니다. 변경을 원치 않는 uid인 경우 -1을 넣어주면 됩니다.

ex) setreuid(-1, 1000);

실패시 -1을 반환합니다.

setresuid, setresgid

#include <unistd.h>
int setresuid(uid_t ruid, uid_t euid, uid_t suid);
int setresgid(gid_t rgid, gid_t egid, gid_t sgid);

만약 ruid, euid, suid를 모두 개별적으로 바꾸고 싶다면 이 함수를 사용하면 됩니다. 단, 이 함수도 아무 id나 바꿀 수는 없겠죠? root를 제외한 나머지 유저들은 ruid, euid, suid 각각의 값을 현재 ruid, euid, suid 중의 값으로만 바꿀 수 있습니다. 실패 시 -1을 반환합니다. 만약 현 uid를 그대로 유지하고 싶다면 해당하는 인자에 -1을 넣어주세요.

그럼 여기까지 uid 설정 함수들을 정리합니다.

현재 ruid, euid, suid = ruid`, euid`, suid`

Ø  유효 사용자 ID = Root

함수 ruid euid suid
setuid ruid`, euid`, suid와 상관없이 원하는 uid로 변경
seteuid 변경 불가 원하는 값으로 설정 가능 변경 불가
setreuid ruid`, euid`, suid와 상관없이 원하는 uid로 변경 euid와 같음
setresuid ruid`, euid`, suid와 상관없이 원하는 uid로 변경

 

Ø  유효 사용자 ID = 일반 유저

함수 ruid euid suid
setuid 변경 불가 ruid` 혹은 suid`로 변경 가능 변경 불가
seteuid 변경 불가 ruid` 혹은 suid`로 변경 가능 변경 불가
setreuid ruid`, euid` 중 하나로 변경 가능 ruid`, euid` 중 하나로 변경 가능 euid와 같음
setresuid ruid`, euid`, suid`  중 하나로 변경 가능 ruid`, euid`, suid`  중 하나로 변경 가능 ruid`, euid`, suid`  중 하나로 변경 가능

 

아래 setuid, setreuid, setresuid를 차례차례 알아보는 코드와 위의 표 내용이 맞는 지 확인하는 실행 결과를 보여줍니다.

//setuids_test.c

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

void pr_resuid(){
        uid_t ruid, euid, suid;

        if(getresuid(&ruid,&euid,&suid) < 0){
                printf("getresuid error\n");
                exit(1);
        }

        printf("ruid : %d, euid : %d, suid : %d\n", 
                        ruid, euid, suid);

}
void setuid_test(){
        uid_t uid;
        printf("uid :");
        scanf("%d", &uid);

        if(setuid(uid) < 0){
                printf("setuid %d로 변경 불가 \n", uid);
        }
        pr_resuid();
}
void seteuid_test(){
        uid_t euid;
        printf("euid :");
        scanf("%d", &euid);
        if(seteuid(euid) < 0){
                printf("seteuid %d로 변경 불가 \n", euid);
        }
        pr_resuid();
}
void setreuid_test(){
        uid_t ruid, euid;
        printf("ruid euid :");
        scanf("%d %d", &ruid, &euid);
        if(setreuid(ruid, euid) < 0){
                printf("setreuid %d %d로 변경 불가\n", ruid, euid);
        }
        pr_resuid();
}
void setresuid_test(){
        uid_t ruid, euid, suid;
        printf("ruid euid suid :");
        scanf("%d %d %d", &ruid, &euid, &suid);
        if(setresuid(ruid, euid, suid) < 0){
                printf("setresuid %d %d %d로 변경 불가\n", ruid, euid, suid);
        }
        pr_resuid();
}
int main(){
        printf("현재 uid\n");
        pr_resuid();

        while(1){
                char c;
                printf("(1)setuid-test\n");
                printf("(2)seteuid-test\n");
                printf("(3)setreuid-test\n");
                printf("(4)setresuid-test\n");
                printf("(other)exit\n");

                scanf(" %c", &c);
                switch(c){
                        case '1':
                                setuid_test();
                                break;
                        case '2':
                                seteuid_test();
                                break;
                        case '3':
                                setreuid_test();
                                break;
                        case '4':
                                setresuid_test();
                                break;
                        default:
                                return 0;
                }

        }

}

 

아래의 상황은 ubuntu(1000)의 유효사용자 id를 갖는 위 프로그램을 hello(1001)라는 유저가 실행했을 때의 상황입니다.

hello$ ls -l a.out
-rwsr-xr-x 1 ubuntu ubuntu 13616 Jun 23 08:43 a.out
hello$ ./a.out 
현재 uid
ruid : 1001, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
1
uid :1001
ruid : 1001, euid : 1001, suid : 1000   <- euid만 변경됨
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
2
euid :1000
ruid : 1001, euid : 1000, suid : 1000  <- 현재 ruid, suid로만 euid만 변경됨
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
3
ruid euid :1000 1001
ruid : 1000, euid : 1001, suid : 1001   <- euid와 suid는 같은 값이 됨
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
3
ruid euid :1000 1000       <- setresuid(1000, 1000, 1000)과 같다.
ruid : 1000, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
2
euid :1001   <- 3개의 uid가 전부 같은 1000이므로 euid를 바꿀 어떤 방법이 없다
seteuid 1001로 변경 불가 
ruid : 1000, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
4
ruid euid suid :1000 1000 1001  <- 위와 같은 이유로 setresuid를 통해서도 바꿀 수없음
setresuid 1000 1000 1001로 변경 불가
ruid : 1000, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
4
ruid euid suid :1000 1000 1000  <- 이게 무슨 의미가 있을까
ruid : 1000, euid : 1000, suid : 1000
(1)setuid-test
(2)seteuid-test
(3)setreuid-test
(4)setresuid-test
(other)exit
q         <- 분노의 종료

 

실제 사용자 ID(Real UID), 그리고 유효 사용자 ID(Effective UID)는 대충 알겠는데, saved UID는 어떤 경우에 사용이 되는 걸까요? 포스팅이 너무 길어지니 아래의 포스팅에서 설명하도록 하겠습니다.

https://reakwon.tistory.com/234

 

리눅스 - 코드로 이해하는 저장된 사용자 ID(Saved UID)가 있는 이유

저장된 사용자 ID - Saved UID Saved UID를 이해하기 위해서는 실제 사용자 ID(Real UID)와 유효 사용자 ID(Effective UID)에 대한 이해가 깔려있어야합니다. 아직 개념이 안잡혀있다면 아래의 포스팅을 먼저

reakwon.tistory.com

 

반응형