CSS Tutor Study Hub 메인으로

Computersystemsicherheit 2025/26

실제 시험 문제 66

Code 감사 · Security Engineering / 4.2 Fehlerhafter Programmcode

Code 감사

문제

근거 신뢰도 높음실제 시험지 대조 완료Code 감사4점

독일어 원문

Identifizieren Sie den Fehler bei `system(cmd)` und erklären Sie Auswirkung und Behebung.

한국어 해석

`system(cmd)` 주변의 오류·영향·수정을 설명하시오.

실제 시험 Listing 1 보기
4  void build_message(char *name, char *dest) {
5      sprintf(dest, "echo Willkommen %s", name);
6  }
8  int main(void) {
9      char name[16];
10     char cmd[64];
11     unsigned int age;
14     fgets(name, 15, stdin);
17     scanf("%u", &age);
19     if (age - 21 < 0) {
20         printf("Zugriff verweigert\n");
21         return 1;
24     build_message(name, cmd);
25     system(cmd);
27     return 0;
28 }

출처: 실제 시험지 p17. 두 오류는 line 19의 unsigned underflow와 line 25의 command injection입니다.

실제 시험 코드 취약 줄 하이라이터

vulnerable lineattacker inputparser/contextimpactfix

실제 WiSe 25/26 시험 코드에서 취약한 줄을 선택하고 영향과 수정법을 말해 보세요.

단계별 힌트

막혔을 때만 한 단계씩 여세요. 정답을 바로 읽는 것보다 기억을 꺼내는 시간이 중요합니다.

0/8
채점 기준으로 내 답안 점검하기
  • line 14 입력 → line 24 호출 → line 5 cmd 구성 → line 25 system의 data flow를 연결한다.
  • process 권한의 command injection 영향을 쓴다.
  • system 제거 또는 shell-free API를 제시한다.

답안 슬롯 자가 점검 — 실제로 말하거나 쓴 항목만 체크하세요.

0/3 slots

정답과 핵심 해설 확인하기
summary_ko

Line 14에서 읽은 사용자 입력 name이 line 24 호출과 line 5의 cmd 구성을 거쳐 line 25의 shell로 전달되므로 `;`, `&&` 같은 metacharacter로 command injection이 가능하다. 이 listing에서는 local stdin 공격자가 process 권한으로 임의 OS command를 실행할 수 있다. `system`을 제거하고 직접 출력하거나 shell을 거치지 않는 고정 인자 API를 사용한다.

exam_answer_frame
  • line 14 입력 → line 24 호출 → line 5 cmd 구성 → line 25 system의 data flow를 연결한다.
  • process 권한의 command injection 영향을 쓴다.
  • system 제거 또는 shell-free API를 제시한다.
개념부터 다시 보는 상세 풀이

BEGINNER LESSON

Command Injection: 사용자 이름이 운영체제 명령으로 변하는 전체 경로

ZERO-BASE START

정말 아무것도 모른다고 가정하고 시작합니다

전문 용어를 알고 있다고 가정하지 않습니다. 먼저 일상적인 장면을 보고, 그 장면의 사람과 행동에 실제 보안 용어를 하나씩 붙인 뒤, 시스템에서 일어나는 순서를 따라갑니다.

기초 개념 01

system()과 shell command injection

1타 강사식 시작: 이름은 잠시 가리고 장면부터 봅시다

배달 주소 칸에 ‘서울; 창고 문도 열어라’라고 썼는데 프로그램이 세미콜론 뒤 문장까지 새 업무 지시로 처리하는 상황이다.

지금은 이 비유를 완벽히 외울 필요가 없습니다. 누가 무엇을 가지고 있고, 무엇을 하려 하며, 어느 지점에서 문제가 생기는지만 찾으면 됩니다.

이제 실제 용어를 하나씩 붙여 봅시다

system()은 문자열을 운영체제 shell에 명령으로 전달한다. 사용자 입력을 그 문자열에 직접 붙이면 세미콜론, 파이프, 명령 치환 같은 shell metacharacter가 데이터가 아니라 추가 명령으로 해석될 수 있다. 이것이 command injection이다. 가능하면 shell을 거치지 않고 허용된 프로그램과 argument를 분리해 실행해야 한다.

TERMS FROM ZERO

전문 용어를 한 단어씩 풀기

아래 단어는 이미 안다고 가정하지 않습니다. 먼저 쉬운 뜻을 읽고, 본문에서 같은 단어가 나오면 이 정의로 다시 바꾸어 읽으세요.

Shell

명령 문자열을 해석해 program을 실행하고 pipe·redirection 같은 문법을 처리하는 command interpreter입니다.

`system()`

문자열을 shell에 넘겨 command로 실행하는 C library 함수입니다.

Metacharacter

`;`, `|`, `&&`처럼 shell에서 단순 글자가 아니라 문법 역할을 하는 문자입니다.

Command injection

공격자 data가 shell syntax로 해석되어 의도하지 않은 command를 실행하게 되는 취약점입니다.

프로그램이나 프로토콜 안에서는 다음 순서로 움직입니다.

  1. 외부 입력 source를 찾는다.
  2. 입력이 system, popen, shell=True 같은 command sink에 도달하는지 본다.
  3. Shell 문법 문자가 명령 구조를 바꿀 수 있는지 확인한다.
  4. Shell 없는 process API와 고정 allowlist argument를 사용한다.
  5. 실행 계정 권한을 최소화한다.

왜 여기서 많이 틀릴까요?

일부 문자만 escape하는 방식은 shell 종류와 인코딩에 따라 우회될 수 있으므로 구조적으로 shell을 제거하는 편이 안전하다.

조건을 생략하거나 서로 다른 기능을 같은 것으로 취급했는지 확인하세요. 정답 문장을 외우는 것보다 틀린 이유를 말할 수 있어야 변형 문제를 풀 수 있습니다.

핵심부터 말하면 사용자 입력이 command string에 연결된 뒤 `system()`을 통해 shell에 전달되므로 shell metacharacter가 새 명령으로 해석될 수 있습니다. shell을 제거하고 직접 출력하거나 분리된 argument API를 사용해야 합니다.

이 글에서 익힐 것

  • source → string construction → shell sink의 data flow를 찾는다.

  • 정상 입력과 공격 입력의 최종 command string을 비교한다.

  • buffer overflow 방어와 command injection 방어를 구분한다.

먼저 알아야 할 개념

문자열은 그 자체로 선하거나 악하지 않습니다. 같은 `;`도 메모장에서는 글자지만 shell 앞에서는 명령 구분자입니다. 어느 parser가 읽는지가 핵심입니다.

C의 메모리와 문자열을 먼저 이해하기

  • C 변수는 type과 이름을 함께 선언합니다

    기본 모양은 `type variable_name;`입니다. 예를 들어 `int age;`는 정수 한 개를 저장할 `age`라는 memory 공간을 준비합니다. Type은 어떤 값을 어떻게 해석하고 몇 byte 정도를 사용할지 결정합니다.

  • C 문자열은 별도의 String 객체가 아니라 `char` 배열입니다

    `char name[16];`은 문자용 byte 16개를 연속해서 준비합니다. 문자열의 끝은 보이지 않는 null terminator `\0`로 표시하므로, 일반적인 최대 문자열 내용은 15byte이고 마지막 1byte는 `\0`에 필요합니다.

  • 배열 이름은 함수에 전달될 때 첫 칸의 주소처럼 사용됩니다

    `build_message(name, cmd)`에서 16byte와 64byte 전체가 복사되는 것이 아닙니다. 함수는 각 배열의 시작 memory address를 받아 같은 원본 buffer를 읽거나 씁니다. 그래서 함수 안의 `dest`에 쓴 결과가 `main`의 `cmd`에서 보입니다.

  • Pointer는 memory 위치를 저장하는 값입니다

    `char *name`의 `*`는 이 parameter가 `char`가 있는 memory 위치를 가리키는 pointer라는 뜻입니다. 여기서 `*`는 곱셈 기호가 아닙니다. 함수는 pointer를 통해 caller가 가진 문자열을 읽고 destination buffer에 씁니다.

  • C는 배열 크기를 pointer와 함께 자동 전달하지 않습니다

    `char *dest`만 받은 `build_message` 함수는 `dest` 뒤에 몇 byte가 준비되어 있는지 알 수 없습니다. 이런 이유로 destination 크기를 별도 parameter로 전달하지 않는 `sprintf` 같은 사용법이 위험해질 수 있습니다.

이 문제에 필요한 C 문법 도구상자

  • 문장(statement)과 세미콜론 `;`

    C에서 변수 선언, 함수 호출, `return` 같은 문장은 보통 `;`로 끝납니다. 예: `system(cmd);`. 세미콜론은 shell의 명령 구분자가 아니라 C source code에서 문장 끝을 표시합니다. 반대로 runtime에 만들어진 `cmd` 문자열 안의 `;`은 나중에 shell 문법이 됩니다.

  • 중괄호 `{ ... }`와 block

    중괄호는 여러 문장을 하나의 block으로 묶습니다. 함수 몸체와 `if` 조건이 참일 때 실행할 문장들이 block 안에 들어갑니다. 여는 `{`와 닫는 `}`의 범위를 따라가야 어느 문장이 조건부 실행인지 알 수 있습니다.

  • 함수 정의 `return_type name(parameters) { ... }`

    함수를 새로 만드는 문법입니다. `void build_message(char *name, char *dest) { ... }`에서 `void`는 반환값이 없다는 뜻, `build_message`는 함수 이름, 괄호 안은 함수가 받을 parameter, 중괄호 안은 실행할 코드입니다.

  • 함수 호출 `name(arguments);`

    이미 정의된 함수를 실행하는 문법입니다. `build_message(name, cmd);`는 현재 변수 `name`과 `cmd`를 argument로 넘겨 함수를 호출합니다. 함수 정의의 parameter는 받을 자리에 붙인 이름이고, 호출의 argument는 실제로 넘기는 값입니다.

  • 배열 선언 `char name[16];`

    대괄호 안 숫자는 index 최댓값이 아니라 원소 개수입니다. 16칸의 유효 index는 0부터 15입니다. `name[0]`은 첫 문자, `name[15]`는 마지막 byte입니다.

  • Pointer 선언 `char *name`

    `name`이 문자 하나가 아니라 문자가 저장된 위치를 가리킨다는 뜻입니다. 문자열 함수는 보통 첫 문자의 pointer를 받고 `\0`을 만날 때까지 다음 byte를 읽습니다.

  • 주소 연산자 `&`

    `&age`는 변수 `age`가 저장된 memory 주소를 구합니다. `scanf`는 읽은 숫자를 caller의 `age`에 써야 하므로 값 자체가 아니라 주소를 받습니다. 여기의 단항 `&`는 `&&` 논리 AND와 다른 문법입니다.

  • 조건문 `if (condition) { ... }`

    괄호 속 expression이 0이면 거짓, 0이 아니면 참으로 판단하고 참일 때 block을 실행합니다. `if (age - 21 < 0)`에서는 뺄셈과 비교의 type 규칙이 먼저 적용된 뒤 조건 결과가 결정됩니다.

  • 비교 연산자 `<`

    왼쪽 값이 오른쪽보다 작으면 참을 만드는 연산자입니다. HTML에서는 tag 시작 문자로 보일 수 있어 페이지 source에는 `&lt;`로 저장되지만, C source의 실제 문자는 `<`입니다.

  • 반환문 `return value;`

    현재 함수를 끝내고 caller 또는 operating system에 값을 돌려줍니다. `main`에서 관례적으로 `return 0;`은 정상 종료, `return 1;` 같은 0이 아닌 값은 오류 또는 거부 종료를 나타냅니다.

  • Escape sequence `\n`

    문자열 안에서 backslash로 특수 문자를 표현합니다. `\n`은 두 글자 `\`와 `n`을 출력하라는 뜻이 아니라 줄바꿈 newline 한 개를 뜻합니다.

  • Format specifier `%s`와 `%u`

    `%s`는 null-terminated C 문자열 자리, `%u`는 unsigned decimal integer 자리를 뜻합니다. 같은 `%` 문법처럼 보여도 `sprintf`에서는 값을 문자열에 써 넣고, `scanf`에서는 입력을 어떤 type으로 읽을지 지정합니다.

실제 시험 Listing을 한 줄씩 해석하기

  • 4행 `void build_message(char *name, char *dest) {`

    반환값이 없는 `build_message` 함수를 정의합니다. 첫 parameter `name`은 읽을 사용자 문자열의 시작 주소, 둘째 `dest`는 결과를 쓸 destination buffer의 시작 주소입니다. `{`부터 함수 몸체가 시작됩니다.

  • 5행 `sprintf(dest, "echo Willkommen %s", name);`

    Format string의 `%s`를 `name` 문자열로 바꾸어 결과를 `dest`가 가리키는 memory에 씁니다. `sprintf`에는 destination 크기 parameter가 없으므로 긴 결과에 대한 bounds check를 스스로 하지 못합니다.

  • 6행 `}`

    `build_message` 함수의 block이 끝납니다. Return type이 `void`이므로 값을 반환하는 `return value;`가 없어도 함수 끝에서 caller로 돌아갑니다.

  • 8행 `int main(void) {`

    Program 실행이 시작되는 `main` 함수를 정의합니다. `int`는 종료 status 정수를 반환한다는 뜻이고, parameter 위치의 `void`는 이 형태의 `main`이 argument를 받지 않는다는 뜻입니다.

  • 9행 `char name[16];`

    사용자 이름을 저장할 16byte character array를 만듭니다. C 문자열 끝의 `\0`까지 포함해야 하므로 내용 공간은 최대 15byte입니다.

  • 10행 `char cmd[64];`

    나중에 shell에 전달할 command 문자열을 저장하는 64byte array입니다. 아직 선언만 했으므로 의미 있는 command가 완성된 것은 아닙니다.

  • 11행 `unsigned int age;`

    음수를 표현하지 않는 정수 변수 `age`를 선언합니다. `unsigned`라는 type 성질 때문에 문제 65의 subtraction underflow가 발생합니다.

  • 14행 `fgets(name, 15, stdin);`

    표준 입력 `stdin`에서 문자열을 읽어 `name` buffer에 저장합니다. 첫 인자는 destination, 둘째 15는 null terminator까지 포함한 최대 저장 수라서 실제 입력 문자는 최대 14byte, 셋째는 입력 stream입니다. `fgets`는 공간이 있으면 사용자가 누른 Enter의 newline도 문자열에 포함하므로 실제 프로그램은 newline 제거와 반환값 검사도 고려해야 합니다.

  • 17행 `scanf("%u", &age);`

    표준 입력에서 unsigned integer를 읽어 `age`의 주소에 저장합니다. `%u`가 입력 형식을, `&age`가 결과를 쓸 위치를 알려 줍니다. 실제 안전한 코드는 반환값을 확인해 숫자 parsing 성공 여부도 검사해야 합니다.

  • 19행 `if (age - 21 < 0) {`

    계산 결과가 0보다 작은지 검사해 참이면 접근 거부 block으로 들어가려 합니다. 하지만 `age`가 unsigned라 21보다 작은 값도 음수가 아니라 큰 unsigned 값으로 wrap될 수 있습니다.

  • 20행 `printf("Zugriff verweigert\n");`

    고정 문자열 ‘접근 거부’를 표준 출력에 쓰고 `\n`으로 줄을 바꿉니다. 이 호출은 format string이 고정되어 있고 추가 사용자 값이 없으므로 현재 문제의 command injection 지점은 아닙니다.

  • 21행 `return 1;`

    `main`을 즉시 끝내고 operating system에 1을 반환합니다. 따라서 이 문장이 실행되면 아래의 `build_message`와 `system`에는 도달하지 않습니다.

  • 24행 `build_message(name, cmd);`

    앞에서 정의한 함수를 호출합니다. `name`의 시작 주소는 parameter `name`으로, `cmd`의 시작 주소는 parameter `dest`로 전달됩니다. 함수가 `dest`에 쓰는 것은 곧 `main`의 `cmd`에 쓰는 것입니다.

  • 25행 `system(cmd);`

    `cmd` 문자열을 command shell에 넘겨 해석하고 실행합니다. `name`에서 온 사용자가 제어하는 문자가 `cmd` 안에 섞여 있으므로 shell metacharacter가 새 command 문법이 될 수 있습니다.

  • 27행 `return 0;`

    Program이 정상 경로를 끝냈다는 status 0을 operating system에 반환합니다.

  • 28행 `}`

    `main` 함수의 block과 program source의 이 함수 부분이 끝납니다. 시험 listing에서 생략된 줄 번호에는 `if` block을 닫는 `}` 같은 문장이 있을 수 있으므로 줄 번호가 연속하지 않다는 사실과 C 문법을 혼동하지 않아야 합니다.

C 문법을 정말 처음부터 읽기

  • `sprintf(...)`는 함수 호출입니다

    C에서 `함수이름(인자1, 인자2, ...);`는 함수를 실행하라는 문장입니다. `sprintf`가 함수 이름이고, 둥근 괄호 안에는 함수에 전달할 값들이 들어갑니다. 쉼표 `,`는 인자를 구분하고 마지막 세미콜론 `;`은 C 문장 하나가 끝났음을 나타냅니다.

  • `cmd`와 `name`은 변수 이름입니다

    변수(variable)는 값을 보관하는 이름 붙은 memory 공간입니다. 여기서 `name`에는 사용자가 입력한 이름 문자열이 있고, `cmd`는 완성된 command 문자열을 저장할 character buffer입니다. 둘은 C 예약어가 아니라 이 프로그램의 개발자가 정한 이름입니다.

  • `"echo Willkommen %s"`는 문자열 literal입니다

    큰따옴표 안의 글자는 프로그램 source code에 직접 적힌 고정 문자열입니다. `echo`는 뒤의 문장을 출력하는 shell command이고, `Willkommen`은 독일어로 ‘환영합니다’라는 뜻입니다. 공백도 실제 결과 문자열에 포함됩니다.

  • `%s`는 format placeholder입니다

    `%s`는 ‘여기에 C 문자열(string)을 넣으라’는 format specifier입니다. 뒤에 오는 `name` 값이 `%s` 위치에 삽입됩니다. `%s` 자체가 결과에 그대로 남는 것이 아닙니다.

  • `system(cmd);`도 함수 호출입니다

    `system` 함수에 변수 `cmd`의 문자열 한 개를 전달합니다. 중요한 점은 `system`이 문자열을 화면에 단순 출력하는 함수가 아니라 operating system의 command shell에게 해석하고 실행하도록 넘기는 함수라는 것입니다.

각 인자(parameter)의 역할

  • 첫 번째 인자 `cmd`

    Destination buffer입니다. `sprintf`가 만든 최종 문자열이 저장될 memory 공간입니다. 예를 들어 함수 실행 뒤 `cmd` 안에는 `echo Willkommen Alice`가 들어갈 수 있습니다. Buffer의 실제 크기보다 긴 결과를 쓰면 별도의 buffer overflow 위험이 생깁니다.

  • 두 번째 인자 `"echo Willkommen %s"`

    Format string입니다. 최종 문자열의 고정 부분과 값이 들어갈 자리 `%s`를 정의합니다. 이 예제에서는 format string 자체는 개발자가 고정했으므로 사용자가 format string을 직접 조작하는 format-string vulnerability를 묻는 것이 아닙니다.

  • 세 번째 인자 `name`

    `%s` 자리에 넣을 실제 문자열입니다. `name`이 사용자 입력에서 왔으므로 attacker-controlled data입니다. `sprintf` 단계에서는 문자열의 일부로 복사되지만, 나중에 전체 `cmd`가 shell에 전달되면서 `name` 안의 shell 문법도 다시 해석될 수 있습니다.

  • `system`의 인자 `cmd`

    Shell이 실행할 완성된 command string입니다. `system`은 `cmd`를 data로만 보관하지 않고 shell parser에게 넘기므로 `;`, `&&`, `|` 같은 metacharacter가 command 문법 역할을 갖게 됩니다.

실제 시험 코드와 축약 예제 연결하기

상황 설정

실제 시험 listing에는 `sprintf(dest, "echo Willkommen %s", name);`, `build_message(name, cmd);`, `system(cmd);`가 나뉘어 있습니다.

풀이 순서

  1. `build_message(name, cmd)`가 호출될 때 `name`은 첫 번째 함수 인자로, `cmd`는 결과를 받을 두 번째 인자로 전달됩니다.

  2. `build_message` 함수 안에서 두 번째 parameter의 이름은 `dest`입니다. 즉 이 호출 동안 `dest`가 가리키는 실제 buffer는 caller의 `cmd`입니다.

  3. 따라서 함수 내부의 `sprintf(dest, "echo Willkommen %s", name)`를 호출 관계까지 펼쳐 쓰면 학습용 축약식 `sprintf(cmd, "echo Willkommen %s", name)`과 같은 data flow가 됩니다.

  4. 그 뒤 함수가 끝나도 완성된 문자열은 caller의 `cmd` buffer에 남고, line 25의 `system(cmd)`가 이를 shell에 전달합니다.

해설

축약 예제는 시험 코드를 다른 코드로 바꾼 것이 아니라 `dest`가 실제로 `cmd`를 가리킨다는 함수 parameter 연결을 한 줄로 펼쳐 보여 준 것입니다.

두 줄이 실행되는 과정을 한 단계씩 따라가기

  1. 시작 상태를 놓습니다. 정상 입력이면 `name = "Alice"`, 공격 입력이면 `name = "Alice; whoami"`입니다. `cmd`는 아직 완성되지 않은 destination buffer입니다.

  2. 정상 입력에서 `sprintf`는 format string의 `%s`를 `Alice`로 바꾸어 `cmd = "echo Willkommen Alice"`를 만듭니다.

  3. `system(cmd)`는 이 문자열을 shell에 넘깁니다. Shell은 `echo`를 command 이름, `Willkommen`과 `Alice`를 argument로 읽어 환영 문구를 출력합니다.

  4. 공격 입력에서 같은 `sprintf`는 글자를 검사하지 않고 그대로 삽입해 `cmd = "echo Willkommen Alice; whoami"`를 만듭니다.

  5. Shell은 세미콜론 `;`을 첫 command의 끝으로 해석합니다. 따라서 첫 command는 `echo Willkommen Alice`, 두 번째 command는 `whoami`가 됩니다.

  6. `whoami`는 현재 process가 어떤 OS 사용자 권한으로 실행 중인지 출력합니다. 여기서는 안전한 설명용 예시지만, 핵심은 공격자가 추가 command의 종류를 바꿀 수 있다는 점입니다.

  7. 추가 command는 공격자의 개인 shell 권한이 아니라 취약한 program process의 권한으로 실행됩니다. Service가 높은 권한이면 피해도 커집니다.

서로 다른 두 취약점 구분하기

  • 위험 1: `sprintf`의 길이 제한 부재

    `sprintf`는 첫 번째 인자 buffer의 크기를 전달받지 않습니다. `cmd`보다 최종 결과가 길면 경계 밖 write와 buffer overflow가 생길 수 있습니다. `snprintf`와 반환값 검사는 이 길이 문제를 줄이는 방법입니다.

  • 위험 2: `system`의 shell command injection

    결과 문자열 길이가 buffer 안에 완전히 들어가더라도 `name`에 shell metacharacter가 있으면 새 command가 실행될 수 있습니다. 즉 길이가 짧아도 command injection은 가능합니다.

  • 왜 `snprintf`만으로는 부족한가

    `snprintf`는 최대 write 길이를 제한하지만 `Alice; whoami` 안의 세미콜론을 data로 강제하지 않습니다. 잘리지 않고 들어간 문자열은 여전히 shell이 문법으로 해석합니다. 근본적인 command-injection 방어는 이 장면에서 shell을 사용하지 않는 것입니다.

  • 이 코드에서는 더 단순한 안전 설계가 가능하다

    목적이 환영 문구 출력뿐이라면 command를 조립하거나 shell을 부를 이유가 없습니다. Application 자체의 출력 함수로 고정 문구와 `name`을 data로 출력하면 shell parser라는 위험한 해석 단계를 제거할 수 있습니다.

문법을 직접 바꾸어 연습하기

  • `char city[8];`에서 원소 개수와 마지막 유효 index는 각각 얼마인가?

    원소는 8개이고 index는 0..7입니다. C 문자열로 사용할 때는 `\0` 공간 때문에 일반적인 내용은 최대 7byte입니다.

  • `void greet(char *user)`에서 `void`, `greet`, `char *user`는 각각 무엇인가?

    `void`는 반환값 없음, `greet`는 함수 이름, `char *user`는 문자 sequence의 시작 주소를 받는 parameter입니다.

  • `greet(name);`에서 parameter와 argument를 구분하면?

    함수 정의의 `user`가 parameter이고 호출 시 실제로 전달하는 `name`이 argument입니다.

  • `scanf("%u", &age)`에서 왜 `age`가 아니라 `&age`를 전달하는가?

    `scanf`가 읽은 값을 caller의 변수에 써야 하므로 변수 값이 아니라 그 memory 주소가 필요하기 때문입니다.

  • C source의 세미콜론과 `cmd` 문자열 안의 세미콜론은 어떻게 다른가?

    Source의 `;`은 compiler가 C statement 끝으로 해석합니다. 문자열 안의 `;`은 처음에는 문자 data지만 `system`이 shell에 넘긴 뒤 shell command 구분자로 다시 해석될 수 있습니다.

예제로 확인하기

상황 설정

`sprintf(cmd, "echo Willkommen %s", name); system(cmd);`에 두 입력을 차례로 넣습니다.

풀이 순서

  1. 정상 이름 `Alice`를 넣으면 `cmd`는 `echo Willkommen Alice`가 됩니다.

  2. 입력 `Alice; whoami`를 넣으면 `cmd`는 `echo Willkommen Alice; whoami`가 됩니다.

  3. `system()`은 이 문자열을 단순 출력하지 않고 shell parser에 전달합니다.

  4. shell은 세미콜론을 명령 경계로 읽어 환영 문구 출력 뒤 `whoami`를 별도 실행합니다.

  5. 추가 명령은 취약한 프로그램의 OS 권한으로 실행됩니다.

  6. 직접 환영 문구를 출력하면 shell이 필요 없습니다. 외부 프로그램 실행이 꼭 필요하면 고정 executable과 argument를 구조적으로 분리해 전달합니다.

해설

취약 sink는 `system(cmd)`, 공격자 source는 `name`, 영향은 arbitrary command execution, 근본 수정은 shell 제거입니다.

정답까지 사고 과정

  1. line 14의 `fgets`가 local stdin에서 사용자 입력을 `name`에 저장하는 것을 찾습니다.

  2. line 24 `build_message(name, cmd)` → line 5의 `cmd` 구성 → line 25 `system(cmd)` data flow를 화살표로 연결합니다.

  3. 공격 입력을 넣어 최종 command string을 적습니다.

  4. shell이 metacharacter를 어떻게 해석하는지 설명합니다.

  5. 영향은 이 listing에서 process 권한의 arbitrary OS-command execution이라고 쓰고, 원격 입력 경로가 없으므로 RCE라고 단정하지 않습니다.

  6. 현재 출력 길이 16+14+1=31 byte가 `cmd[64]`에 들어감을 별도 검산한 뒤 shell-free 수정 방법을 적습니다.

시험장에서는 이렇게 쓰기

최소 답안

Benutzereingabe gelangt in `cmd` und wird durch `system()` von einer Shell als Befehlsyntax interpretiert.

안전한 두 문장 답안

Metazeichen wie `;` können dadurch zusätzliche Befehle mit den Rechten des Programms ausführen. `system()` sollte entfernt und durch direkte Ausgabe bzw. eine Shell-freie API mit getrennten Argumenten ersetzt werden.

채점 포인트

  • attacker-controlled `name`을 지적했다.

  • shell parsing과 metacharacter를 설명했다.

  • command execution impact를 썼다.

  • shell 제거 또는 shell-free API를 제시했다.

자주 틀리는 지점

  • `snprintf`만 제시하면 command injection도 해결된다고 생각하기

  • 특수문자 몇 개의 blacklist를 완전한 해결책으로 쓰기

  • 취약한 줄만 말하고 input이 그 줄까지 오는 경로를 빼먹기

  • Line 5를 입력 source로 잘못 부르거나 현재 최대 31 byte가 들어가는 `cmd[64]`에서 overflow가 난다고 주장하기

  • Local stdin만 제시된 listing에 원격 도달성을 가정해 무조건 RCE라고 부르기

한 줄로 기억하기

Data가 shell에 닿으면 문법이 된다.

스스로 확인하기

  • `Alice; whoami`는 shell에서 어떤 두 명령이 되는가?

  • `snprintf`가 이 공격의 근본 방어가 아닌 이유는?

이 문제가 어려운 이유

짧은 문제 문장 ‘`system(cmd)` 주변의 오류·영향·수정을 설명하시오.’ 안에 정의, 조건, 처리 순서가 압축되어 있습니다. 아래 예시에서는 이를 한 단계씩 펼쳐 확인합니다.

AI 구두시험용 프롬프트

한 문항만 풀어라. 먼저 정답을 열지 말고 90초 안에 답안을 말한 뒤, css-ws2025-26-software-c-002의 채점 프레임으로 스스로 채점하라. 문제: `system(cmd)` 주변의 오류·영향·수정을 설명하시오.

학습 기록

이 문항을 얼마나 이해했나요?