1-2. 외부제어·우회 계열
관통 질문: 외부 값이 시스템 동작이나 보안 판단을 직접 결정하고 있는가? 삽입 계열이 “질의의 구조”를 노린다면, 이 계열은 “판단 그 자체”를 노린다. 클라이언트나 명령줄에서 온 값을 그대로 신뢰해 권한·설정·실행 대상을 정하면, 공격자는 그 값만 바꿔서 프로그램의 판단을 조작한다.
8. 보호 메커니즘을 우회할 수 있는 입력값 변조 CWE-807
신뢰 경계: 쿠키(
role필드) → 검증 없이 → 관리자 권한 판단 ⚠️ 클라이언트에서 받는 것은 “누구인지 가리키는 표(세션 ID)“까지다. “무엇을 할 수 있는지(권한)“는 서버가 가진 값으로 판단하라.
공격 시나리오: 권한 정보를 쿠키에서 그대로 읽어 판단하면, 공격자가 프록시로 role 값을 admin 으로 바꾸는 것만으로 관리자 기능을 실행할 수 있다.
❌ Bad
char role[32];
cgiFormString("role", role, sizeof(role));
if (strcmp(role, "admin") == 0) /* 조작 가능한 값으로 보안 결정 */
doAdminJob();✅ Good
char sid[64];
cgiFormString("SESSIONID", sid, sizeof(sid)); /* 세션 식별자만 받는다 */
Session *s = lookupSession(sid);
if (s == NULL || isExpired(s)) { denyAccess(); return; }
if (s->role == ROLE_ADMIN) /* 판단 근거는 서버 안에 있다 */
doAdminJob();치환 규칙: 클라이언트 값으로 직접 보안 판단 금지 → 세션 식별자만 받고 권한은 서버 저장소에서 조회.
9. 시스템 또는 구성 설정의 외부 제어 CWE-15
신뢰 경계: 명령줄 인자 → 검증 없이 → 시스템 설정 함수 인자 ⚠️ 외부에서는 “값”이 아니라 “선택지 번호”만 받게 하는 간접 참조(indirection)가 표준 해법이다.
공격 시나리오: 공격자가 명령줄 인자로 호스트 ID나 FTP 서비스 포트를 바꾸면 시스템이 다르게 식별되거나 기존 서비스와 충돌해 서비스가 중단된다.
❌ Bad
main(int argc, char *argv[])
{
sethostid(atol(argv[1])); /* 외부 입력으로 변경 */
}
const char *servicePort = GetParameter(queryStr, PORT_PARM);
ChangeServicePort(FTP_SERIVCE, servicePort);✅ Good
main(int argc, char *argv[])
{
sethostid(0xC0A80101); /* 고정 상수 사용 */
}
if (servicePortIndex == DEFAULT)
ChangeServicePort(FTP_SERVICE, DEFAULT_FTP_PORT);
else if (servicePortIndex == ALTERNATIVE)
ChangeServicePort(FTP_SERVICE, ALTERNATIVE_FTP_PORT);치환 규칙: 설정 함수 인자에 외부 값 직접 전달 금지 → 내부 상수 또는 “인덱스 → 상수” 매핑 테이블 경유.
10. 프로세스 제어 CWE-114
신뢰 경계: 환경변수(
SHAREDFILE)·요청 파라미터 → 검증 없이 → 라이브러리 경로·OS 명령어 ⚠️ 실행 대상(라이브러리 경로, 명령어)은 코드에 고정하거나 화이트리스트 맵을 거치게 하라.
공격 시나리오: SHAREDFILE 환경변수를 악성 .so 로 바꾸면 해당 프로세스 권한으로 임의 코드가 실행된다.
❌ Bad
char *filename = getenv("SHAREDFILE");
handle = dlopen(filename, RTLD_LAZY);
string op = GetRequestParameter(queryStr, CONVERT_PARAM);
op = op + " " + documentName;
system(op); /* 외부 입력을 그대로 OS 명령으로 */✅ Good
char *filename = "/usr/lib/hello.so"; /* 절대경로 */
handle = dlopen(filename, RTLD_LAZY);
additionalOperations.insert("DocToHtml", "convertDocToHtml"); /* 허용 명령 매핑 */
string osCommand = additionalOperations[op] + " " + documentName;
system(osCommand); /* 매핑을 통과한 값만 실행 */치환 규칙: 라이브러리 경로는 상대경로·환경변수 대신 신뢰 디렉터리 절대경로. 실행 명령은 화이트리스트 맵 결과값만 사용.
보충 — dlopen() 이 왜 위험한 진입점인가
dlopen(filename, flag)은 지정한 공유 라이브러리(.so)를 프로세스 주소공간에 적재하고 초기화 코드를 실행한다. 위험은 경로 탐색 방식과 실행 시점 두 가지에서 온다.
- 경로 탐색:
filename에 슬래시(/)가 없으면LD_LIBRARY_PATH→/etc/ld.so.cache→ 기본 경로(/lib,/usr/lib) 순으로 탐색한다. 공격자가LD_LIBRARY_PATH를 조작하거나 탐색 경로 앞쪽에 동명의 악성.so를 심으면, 의도한 라이브러리보다 먼저 그 파일이 로드된다 — 이것이 절대경로를 써야 하는 이유다.- 실행 시점: 적재와 동시에 라이브러리의 생성자 함수(
__attribute__((constructor)))가 실행된다.dlsym()으로 원하는 심볼을 찾기 전에도 코드가 이미 실행되므로, “필요한 함수만 나중에 호출한다”는 것이 안전장치가 되지 않는다.- RTLD_LAZY vs RTLD_NOW:
RTLD_LAZY는 심볼을 처음 호출하는 순간 지연 바인딩하고,RTLD_NOW는 적재 시점에 모든 심볼을 즉시 해석한다. 보안 관점에서는RTLD_NOW가 유리하다 — 존재하지 않는 심볼이나 손상된 라이브러리를 적재 즉시 실패시켜, 지연 바인딩 구간에서 벌어질 수 있는 변조 여지를 줄인다.- 결론: 경로를 절대경로로 고정해도, 그 디렉터리에 대한 쓰기 권한까지 통제해야 한다 — 경로만 절대경로면 안전하다고 착각하기 쉽지만, 그 경로에 공격자가 파일을 쓸 수 있으면 결과는 상대경로일 때와 같다.
이 계열 미니 체크리스트
- 쿠키·히든필드·요청 파라미터 값으로 직접 권한/역할을 판단하는 코드가 있는가
-
sethostid()·dlopen()· 서비스 포트 변경처럼 시스템 설정 함수에 외부 입력이 그대로 들어가는가 - 클라이언트가 “값”을 보내는가, 아니면 “선택지 인덱스”만 보내는가 — 값이라면 간접 참조로 바꿀 수 있는가
- 권한 판단 근거가 서버 세션(내부 확정값)에 있는가, 요청 데이터(외부 입력)에 있는가