Linux system() 문제점
Linux system() 문제점
system 함수
#include <stdlib.h>
int system(const char *command);
system()
은 command를 입력으로 받아 fork()
를 통해 자식을 생성하고, 다음과 같은 execl
함수로 shell command를 실행시키는 함수다.
execl("/bin/sh", "sh", "-c", command, (char *) NULL);
command가 실행되는 동안 SIGCHLD가 blocked 되고, SIGINT와 SIGQUIT가 무시된다.
command가 NULL인 경우, system()은 shell이 사용가능한지 상태를 반환한다.
system()
은 fork
, execl
, waitpid
호출의 세부사항을 처리해주며, 필요한 신호들을 조정해줌으로써, 단순성과 편리성을 제공한다.
system 함수의 문제점
-
쉘을 실행하는 프로세스를 생성하고 쉘을 실행하려면 추가적인 시스템 콜이 필요하기 때문에 비효율적이다.
-
command 실행 중에 SIGINT와 SIGQUIT가 무시됨으로써, loop 에서
system
을 호출하는 경우, 프로그램이 중단되지 않을 수 있다는 문제점이 있다.while(something){ int ret = system("foo"); //If Child Process Terminated by a Signal if(WIFSIGNALED(ret) && \ //The Signal is SIGINT or SIGQUIT TERMSIG(ret) == SIGINT || WTERMSIG(ret) == SIGQUIT) break; }
- 예시 코드 : loop 내에서 system을 호출하고 signal 발생으로 loop를 빠져나가는 경우
-
pthread_atfork
는fork()
를 호출할 때, 실행할 fork handler를 선언하는 함수로, 이때 지정된 fork handler는system()
실행 중에 호출되는지의 여부는 지정되지 않았으며, glibc 구현에서는 handler는 호출되지 않는다.#include <pthread.h> int pthread_atfork(void (*prepare)(void) , void (*parent)(void) , void (*child)(void));
-
쉘 명령이 127로 종료되는 경우(“command not found”), 자식 프로세스에서 쉘을 실행할 수 없는 경우와 구별이 불가하다.
-
system
함수 사용 시, 일부 환경 변수에 대한 비정상적인 값이 시스템 무결성을 파괴하는 데 사용될 수 있기 때문에setuid
,setgid
기능이 있는 프로그램과 같이 권한이 있는 프로그램에서는 사용을 금한다.
환경 변수를 조작하는 예시는 아래와 같다.
-
sh 및 bash 쉘은 IFS (Internal Field Separator) 변수를 사용하여 명령 행 인수를 구분하는 문자를 판별하는데, IFS를 비정상적인 값으로 설정하여
system()
을 호출하는 경우 안전한 호출이 파괴될 수 있다. -
임의의 프로그램이 권한이 있는 프로그램으로 실행되도록 PATH를 조작 할 수 있다. 이에 대해, /bin/sh bash version 2는
system()
시작 시 권한을 삭제하기 때문에,system()
은 bash 2인 시스템에서 권한이 있는 프로그램을 실행할 경우 제대로 동작하지 않는다.
popen()
, system()
은 명령 쉘을 호출함으로써 구현되며, execlp()
와 execvp()
도 filename에 ‘/’가 붙지 않은 경우에 쉘의 동작을 복제한다. 이때 쉘 메타 문자에 영향을 받게 되는데, 이로 인해 예기치 않은 저수준 루틴을 호출할 수 있어, 많은 가이드라인에서는 popen()
, system()
, execlp()
, execvp()
사용을 전부 피하고, 프로세스를 생성하려고 할 때는 C에서 직접적으로 execve()
을 사용하도록 제안하고 있다. [2].
또한 리눅스와 유닉스를 위한 Secure Programming 가이드 라인 [3]에서는 system()
은 쉘을 사용하여 명령을 계속 주입할 수 있어, Command Injection 등 비정상적인 사용 시도가 발생할 가능성이 있기 때문에, 최소한 execve()
를 사용할 수 있는 경우에 system()
을 사용하지 말라고 언급하고 있다.
권한이 있는 프로그램에서 system() 을 사용할 때 예기치 않은 쉘 명령, 명령 옵션이 실행되지 않도록, command 실행의 일부로 사용되는 사용자 입력은 신중히 처리해야 한다.
system()
의 대안 : execve()
execve()는 쉘을 실행하는 프로세스를 생성하고, command를 실행하는 system()과는 달리, 현재 프로세스에서 실행 중인 프로그램을 새 프로그램으로 대체하는 함수다.
기존 프로세스가 새로운 프로그램을 실행할 수 있도록 정렬한다.
#include <unistd.h>
int execve(const char *pathname, char *const argv[],
char *const envp[]);
execve()
는 system()
과 달리 전체 쉘 인터프리터를 사용하지 않으므로 Command Injection 공격에 취약하지 않다. 또한 입력이 args
배열에 통합되어 execve ()
에 인수로 전달되므로 의도하지 않은 시스템 실행을 방지할 수 있다.
#include <stdlib.h>
int main(void)
{
char *command = "/bin/ls -a";
system(command);
return 0;
}
system()을 이용한 ls -a 커맨드 실행
#include <unistd.h>
int main(void)
{
char *command[] = {"ls", "-a", 0};
char *envp[] = {0};
execve("/bin/ls", command, envp);
return 0;
}
execve()을 이용한 ls -a 커맨드 실행
system()
의 대안 : exec 함수 계열
exec 함수 계열은 현재 프로세스 이미지를 새로운 프로세스 이미지로 대체한다.
#include <unistd.h>
extern char **environ;
int execl(const char *pathname, const char *arg, ...
/* (char *) NULL */);
int execlp(const char *file, const char *arg, ...
/* (char *) NULL */);
int execle(const char *pathname, const char *arg, ...
/*, (char *) NULL, char *const envp[] */);
int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],
char *const envp[]);
하지만 exec
함수 계열 중 execlp()
, execvp()
, execvpe()
함수는 지정된 파일 이름에 ‘/’ 가 포함되어 있지 않으면 실행 파일을 검색할 때 쉘의 동작을 복제하기 때문에 PATH 환경 변수 값이 신뢰할 수 있을 경우에만 파일 이름에 ‘/’ 문자 없이 사용해야 한다. The Unix Secure Programming FAQ [2] 에 의하면 쉘을 호출할 위험이 있는 execlp()
, execvp()
의 사용을 피하고 가급적 execve()
를 직접적으로 사용하라고 제안하고 있다.
참고 문헌
[1] system(3)-Linux manual page
[2] Peter Galvin (August 1998), The Unix Secure Programming FAQ, SunWorld
[3] David A. Wheeler(2003), Secure Programming for Linux and Unix HOWTO
[4] execve()-Linux manual page
[5] Injection Prevention-Embedded Application Security Best Practices