본문 바로가기
소프트웨어(SW) 보안약점 진단원/구현단계 보안약점 제거 기준

구현단계 보안약점 기준 - 운영체제 명령어 삽입

by 브루노W 2025. 5. 21.
유형 입력데이터 검증 및 표현
보안약점 운영체제 명령어 삽입
개요 적절한 검증절차를 거치지 않은 사용자 입력값이 운영체제 명령어의 일부 또는 전부로 구성되어 실행되는 경우, 의도하지 않은 시스템 명령어가 실행되어 부적절하게 권한이 변경되거나 시스템 동작 및 운영에 악영향을 미칠 수 있다.
보안대책 웹 인터페이스로 서버 내부로 시스템 명령어를 전달시키지 않도록 응용프로그램을 구성하고, 외부에서 전달되는 값을 검증 없이 시스템 내부 명령어로 사용하지 않는다.

외부 입력에 따라 명령어를 생성하거나 선택이 필요한 경우에는 명령어 생성에 필요한 값 들을 미리 지정해 놓고 외부 입력에 따라 선택하여 사용한다.
진단방법 운영체제 명령어(exec(), system(), Runtime.getRuntime().exec 등)를 실행할 수 있는 함수가 호출되는지 확인하고 외부에서 전달되는 값이 시스템 내부 명령어의 일부 또는 전부로 사용되는지 확인한다.

정해진 후보군에서 선택된 값(White List)이거나 적절하게 검증하면 안전하고 그 외에는 취약하다.
연관된 설계단계 기준 시스템 자원 접근 및 명령어 수행 입력값 검증

 

코드예제

 

● 안전하지 않은 코드 예 (Java)

public static void main(String args[]) throws IOException {
	// 해당 프로그램에서 실행할 프로그램을 제한하고 있지 않아 파라미터로 전달되는 모든 프로그램이 실행될 수 있다.
	String cmd = args[0];
	Process ps = null;
	try {
		ps = Runtime.getRuntime().exec(cmd);
		...

 

 

● 안전한 코드 예 (Java)

public static void main(String args[]) throws IOException {
    // 해당 어플리케이션에서 실행할 수 있는 프로그램을 노트패드와 계산기로 제한하고 있다.
    List<String> allowedCommands = new ArrayList<String>();
    allowedCommands.add("notepad");
    allowedCommands.add("calc");
    String cmd = args[0];
    if (!allowedCommands.contains(cmd)) {
    	System.err.println("허용되지 않은 명령어입니다.");
    	return;
    }
    Process ps = null;
    try {
    	ps = Runtime.getRuntime().exec(cmd);
    	......

 

 

● 안전하지 않은 코드 예 (Java)

//외부로 부터 입력 받은 값을 검증 없이 사용할 경우 안전하지 않다.
String date = request.getParameter("date");
String command = new String("cmd.exe /c backuplog.bat");
Runtime.getRuntime().exec(command + date);

 

 

● 안전한 코드 예 (Java)

String date = request.getParameter("date");
String command = new String("cmd.exe /c backuplog.bat");
//외부로부터 입력 받은 값을 필터링으로 우회문자를 제거하여 사용한다.
date = date.replaceAll("|","");
date = date.replaceAll(";","");
date = date.replaceAll("&","");
date = date.replaceAll(":","");
date = date.replaceAll(">","");
Runtime.getRuntime().exec(command + date);

 

 

● 안전하지 않은 코드 예 (C#)

//외부 입력값이 프로세스가 실행할 파일 이름을 지정하고 있습니다.
string fileName = PgmTextBox.Text;
ProcessStartInfo proStartInfo = new ProcessStartInfo();
proStartInfo.FileName = fileName;
Process.Start(proStartInfo);

 

 

● 안전한 코드 예 (C#)

string fileName = PgmTextBox.Text;
//외부 입력값에 대해 정규식 등을 이용하여 검증을 해줘야 합니다.
if (Regex.IsMatch(fileName, "properRegexHere"))
{
    ProcessStartInfo proStartInfo = new ProcessStartInfo();
    proStartInfo.FileName = fileName;
    Process.Start(proStartInfo);
}

 

 

● 안전하지 않은 코드 예 (C)

int main(int argc, char* argv[]) {
    char cmd[CMD_LENGTH];
    if (argc < 1 ) {
        // error
    }
    // 외부 입력값으로 커맨드를 직접 수행
    cmd_data = argv[1];
    snprintf(cmd, CMD_LENGTH, "cat %s", cmd_data);
    system(cmd);
    ……
}

 

 

● 안전한 코드 예 (C)

int main(int argc, char* argv[]) {
    char cmd[CMD_LENGTH];
    int len = 0;
    if (argc < 1 ) {
   		// error
    }
    // 외부 입력값으로 커맨드를 직접 수행
    cmd_data = argv[1];
    len = strlen(cmd_data);
    for (int i = 0; I < len; i++) {
    	if (cmd_data[i] == ‘|’ || cmd_data[i] == ‘&’ || cmd_data[i] == ‘;’ || cmd_data[i] == ‘:’ || cmd_data[i] == ‘>’) {
            // 멀티라인을 지원하는 특수문자나 파일 리다이렉트 특수문자가 존재하여
            // 안전하지 않음
    		return -1;
    	}
    }
    snprintf(cmd, CMD_LENGTH, "cat %s", cmd_data);
    system(cmd);
    ……
}

 

 

진단방법

 
 
 

운영체제 명령어(exec(), system(), Runtime.getRuntime().exec 등)를 실행할 수 있는 함수가 호출되는지 확인하고(①) 외부에서 전달되는 값이 시스템 내부명령어의 일부 또는 전부로 사용되는지 확인한다(②).
정해진 후보군에서 선택된 값(White List)이거나 적절하게 검증하면 안전하고 그 외에는 취약하다.

 

● 일반적인 진단의 예

public void f() throws IOException {
    Properties props = new Properties();
    String fileName = "file_list";
    FileInputStream in = new FileInputStream(fileName);
    props.load(in);

    // 외부에서 전달된 값이 시스템 내부 명령어의 일부로 사용됨.
    String version = props.getProperty("dir_type") ·································②
    String cmd = new String("cmd.exe /c rmanDB.bat ");

    // 입력값 검증 없이 내부 명령어의 일부로 사용됨.
    Runtime.getRuntime().exec(cmd + "c:\\prog_cmd\\" + version); ···················①
}

 

 

● 정탐코드

// < EgovProcessMonController.java >
@RequestMapping("/utl/sys/prm/selectProcessSttus.do")
public String selectProcessSttus(@ModelAttribute("processMonVO") ProcessMonVO processMonVO, ModelMap model) throws Exception {
	…
}

// < ProcessMonChecker.java >
public staticString getProcessId(String processNm) throws Exception {
    Process p = null;
    String procsSttus = null;
    BufferedReader buf = null;
    String result = null;
    String execStr = "tasklist /fo table /nh /fi \"imagename eq "+processNm+"\"";
    int cnt = 0;
    String str = null;
    try {
    	if (Globals.OS_TYPE == null) {
   		throw new RuntimeException("Globals.OS_TYPE property value is needed");
    }
    if ("WINDOWS".equals(Globals.OS_TYPE)) {
   		cnt = -1;
    	p = Runtime.getRuntime().exec(execStr);
    }
}

외부 입력값인 processMon의 값을 파라미터로 getProcessId를 호출하고, execStr 문자열을 만들며 그 문자열이 28라인에서 명령어로 쓰이고 있어 취약한 것으로 판정한다.

 

 

● 오탐코드

public static floatgetMoryFreeCpcty() throws Exception {
    float cpcty = 0;
    Process p = null;
    try {
    	String cmdStr = EgovProperties.getPathProperty(Globals.SERVER_CONF_PATH,
    	"SHELL."+Globals.OS_TYPE+".getMoryInfo");
    	String[] command = {cmdStr.replace(‘\\’, FILE_SEPARATOR).replace('/', FILE_SEPARATOR),"FREE"}
    }
    p = Runtime.getRuntime().exec(command);

cmdStr 변수는 Property에서 값을 가져 온 이후 문자열을 만드는데 사용되고, 해당 문자열이 명령어로 사용되므로 안전한 것으로 판정한다.

 

 

● 오탐코드

public static String getOwner(String file) throws Exception{
    String owner = "";
    String src = file.replace('\\', FILE_SEPARATOR).replace('/', FILE_SEPARATOR);
    BufferedReader b_err=null;
    BufferedReader b_out=null;
    try {
        File srcFile = newFile(src);
        if (srcFile.exists()) {
            String parentPath = srcFile.getParent();
            String fname = srcFile.getName();
            Process p = null;
            String cmdStr = EgovProperties.getProperty(Globals.SHELL_FILE_PATH, "SHELL."+Globals.OS_TYPE+".getDrctryOwner");
            String[] command = {cmdStr.replace('\\', FILE_SEPARATOR).replace('/', FILE_SEP- ARATOR), parentPath.replace('\\', FILE_SEPARATOR).replace('/',
            FILE_SEPARATOR), fname};
            p = Runtime.getRuntime().exec(command);
        }
    }
}

fname은 srcFile.getName()의 결과값인 파일의 이름이므로 명령어에 삽입되어 공격할 수 있는 file separator나 ".." 등의 문자열이 없으므로 취약하지 않은 것으로 판정한다.