2-2. 자격증명 관리 계열
관통 질문: 이 비밀(패스워드·계정·키)이 소스 코드 안에 있는가? 4개 항목 모두 “비밀을 코드 안에 둔다 — 바꿀 수도, 숨길 수도 없다”는 한 문장으로 요약된다. 컴파일된 바이너리에도 문자열 상수는 그대로 남는다.
strings명령 하나면 목록이 나온다.
5. 하드코드된 패스워드 CWE-259
신뢰 경계: 소스 코드 상수 → 그대로 → DB 접속 인증 ⚠️ 소스에 박힌 비밀번호는 바이너리에도 남는다. 패스워드는 코드가 아니라 설정·저장소에서 암호화된 상태로 로드하라.
공격 시나리오: strings 명령 한 번으로 DB 계정·관리자 패스워드가 드러나고, 배포된 모든 설치본이 동일한 비밀번호를 공유한다. 변경하려면 재컴파일·재배포가 필요하다.
❌ Bad
SQLConnect(hdbc, (SQLCHAR*)server, strlen(server),
user, strlen(user), "asdf", 4); /* 패스워드가 하드코딩 */✅ Good
int dbaccess(char *server, char *user, char *passwd) /* 외부에서 전달받는다 */
{
SQLConnect(hdbc, (SQLCHAR*)server, strlen(server),
user, strlen(user), passwd, strlen(passwd));
}
char *adminPassword = loadPassword(ADMIN); /* 저장소에서 로드 */치환 규칙: 소스 상수 패스워드 삭제 → 함수 인자로 전달받거나 설정파일/저장소에서 암호화된 값을 로드.
보충 — Vault 같은 시크릿 매니저는 정확히 무엇을 대체하는가
아래 “중간 점검” 2번이 말한 최종 목표(접근 권한이 제한된 저장소에서 필요한 시점에만 읽어오는 구조)를 실제로 구현한 것이 HashiCorp Vault 같은 3rd party 시크릿 매니저다.
loadPassword(ADMIN)이 실제로 하는 일을 Vault로 채우면 다음과 같다.
- 저장 위치가 코드/설정파일에서 완전히 빠진다: 패스워드는 Vault 서버(또는 클러스터)에만 존재하고, 애플리케이션은 기동 시점에 “인증 후 조회”만 한다 — 소스에도, 배포 설정에도, 환경변수에도 값 자체는 남지 않는다.
- 애플리케이션 인증 → 비밀 조회의 2단계: 앱은 AppRole(
role_id/secret_id)이나 쿠버네티스 서비스 어카운트 같은 방식으로 먼저 Vault에 자신을 인증하고, 발급받은 토큰으로secret/db-password같은 경로를 조회한다. 즉 “DB 패스워드”가 아니라 “Vault에 접근할 자격”만 배포 시점에 필요하고, 그 자격조차 짧은 TTL을 가진다.- 동적 자격증명(dynamic secrets): 정적 패스워드를 그대로 보관하는 것을 넘어, Vault가 요청 시점에 DB 계정을 즉석에서 만들고 TTL이 지나면 자동 폐기하게 할 수 있다 — 하드코딩 문제(변경 불가)의 반대 극단, “아예 고정된 값이 없는” 상태.
- 회전(rotation)과 감사: 패스워드·키 교체가 재컴파일·재배포 없이 Vault 쪽 정책만으로 이루어지고, 누가 언제 어떤 비밀을 조회했는지 감사 로그가 남는다 — 7번(
CWE-321) 하드코드된 암호화 키의 “교체 불가능” 문제도 같은 구조로 해결된다.- 주의할 점은 하나 남는다: Vault에 접근할 자격 자체(초기 토큰, unseal key)는 여전히 어딘가에 있어야 한다. 이건 코드가 아니라 배포 인프라(K8s Secret, 클라우드 IAM 역할, HSM)로 넘기는 문제이지 완전히 없앨 수 있는 문제는 아니다 — Vault의 실제 역할은 “비밀을 저장하는 책임”을 코드에서 인프라로 옮기는 것이다.
6. 하드코드된 사용자 계정 CWE-255
신뢰 경계: 소스 코드 상수(
"root","DBAdmin") → 그대로 → 인증 계정 ⚠️ 하드코드된 계정은 “고칠 수 없는 백도어”다. 바꾸려면 재컴파일과 전체 정지가 필요하다.
공격 시나리오: 프로그램만 실행할 수 있으면 누구나 인증 없이 DB에 접근할 수 있고, 바이너리 문자열 추출로 계정과 패스워드가 노출된다.
❌ Bad
SQLConnect(hdbc, (SQLCHAR*)server, strlen(server),
"root", 4, passwd, strlen(passwd));✅ Good
char * dbUserId, * dbUserPassword;
loadDbUserInfo(&dbUserId, &dbUserPassword); /* 검증 과정을 거쳐 획득 */
SQLConnect(hdbc, (SQLCHAR*)"173.234.56.78", SQL_NTS,
(SQLCHAR*)dbUserId, strlen(dbUserId),
dbUserPassword, strlen(dbUserPassword));치환 규칙: 계정명 상수 삭제 → 외부(설정·자격증명 저장소)에서 로딩·검증하는 별도 루틴으로 획득.
7. 하드코드된 암호화 키 CWE-321
신뢰 경계: 소스 코드 상수 배열(AES 키) → 그대로 → 암호화 연산 키 인자 ⚠️ 키를 코드에 박으면 키 교체가 불가능해진다. 키는 코드 밖(키 저장소·설정파일)에서 주입받아야 한다.
공격 시나리오: 키가 바이너리에 박혀 있어 리버싱으로 추출되면 그 키로 암호화된 모든 데이터가 한 번에 복호화되고, 키 교체(rotation)도 불가능하다.
❌ Bad
static const unsigned char KEY[16] = { 0x13,0x2a,0x7f, /* ... */ };
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, KEY, iv);✅ Good
unsigned char key[16];
if (loadKeyFromKeystore("db-enc-key", key, sizeof(key)) != 0) {
denyAccess(); return -1;
}
EVP_EncryptInit_ex(ctx, EVP_aes_128_cbc(), NULL, key, iv);
OPENSSL_cleanse(key, sizeof(key)); /* 사용 후 메모리에서 지운다 */치환 규칙: 키 상수 배열 삭제 → 키 저장소·HSM·권한 제한 키 파일에서 런타임 주입, 사용 후 메모리 클리어.
8. 주석문안에 포함된 패스워드 등 시스템 주요정보 CWE-615
신뢰 경계: 개발 편의로 남긴 주석 → 제거 없이 → 배포 소스/저장소 ⚠️ 주석은 컴파일되지 않을 뿐 사라지지 않는다. 배포 전 “주석 청소”를 릴리스 체크리스트에 넣어라.
공격 시나리오: 소스코드나 배포물에 한 번만 접근하면 인증 절차 없이 관리자 패스워드와 시스템 내부 구조를 획득할 수 있다.
❌ Bad
/* default password is "abracadabra" <- 주석에 패스워드 */
int verifyAuth(char *ipasswd, char *orgpasswd) { ... }✅ Good
/* 주석에 패스워드를 기입하지 않는다 */
int verifyAuth(char *ipasswd, char *orgpasswd) { ... }치환 규칙: 배포 전 grep/정적분석으로 password/key/id 키워드 주석 전수 검사 후 제거.
중간 점검 — 인가와 자격증명
- 로그인에 성공한 사용자가
GET /report?user_id=1234로 리포트를 조회한다. 서버는 세션이 유효한지만 확인하고user_id로 조회한다. 무엇이 문제일까? - DB 접속 비밀번호를 소스에서 빼서 환경변수로 옮겼다. 이제 안전한가?
- “이미 배포된 바이너리에는 비밀번호가 안 보인다”는 주장은 맞을까?
정답 보기
- 부적절한 인가(2-1의 1번·CWE-285) — 전형적인 IDOR. 인증은 되었지만 인가가 없다. 로그인한 아무 사용자나
user_id값만 바꾸면 남의 리포트를 본다. “요청에 담겨 온 식별자는 사용자의 주장일 뿐”이라는 원칙이 여기에도 적용된다.- 개선이지만 충분하지 않다(5번·CWE-259). 환경변수는 같은 사용자 권한이면
/proc/PID/environ으로 읽히고, 프로세스 목록이나 코어 덤프에도 남으며, 자식 프로세스에 자동 상속된다. 최종 목표는 접근 권한이 제한된 저장소(0600 설정파일 + 암호화, OS 키링, 시크릿 매니저)에서 필요한 시점에만 읽어오는 구조.- 틀렸다(5·7번·CWE-259/321). 컴파일된 바이너리에도 문자열 상수는 그대로 남는다.
strings명령 하나면 목록이 나온다. 난독화나 XOR 인코딩도 리버싱 앞에서는 시간 벌기일 뿐이다. “코드 안에 비밀을 둘 수 있다”는 전제 자체가 성립하지 않는다.
이 계열 미니 체크리스트
- 소스 코드에 패스워드·계정명·암호화 키가 상수로 박혀 있는가
- 자격증명이 환경변수에만 의존하고 있지는 않은가(파일·프로세스 노출 경로 점검)
- 주석에
password/key/id/내부 URL 이 남아 있는가 (배포 전 grep 검사 여부) - 암호화 키를 사용 후 메모리에서 지우는가(
OPENSSL_cleanse등)