2-1. 인가·권한 계열
관통 질문: 인증(누구인가)은 통과했는데, 인가(무엇을 할 수 있는가)를 별도로 검사했는가? 권한을 높였다면 다 쓰고 돌려줬는가? 4개 항목 모두 “로그인 성공”과 “권한 있음”을 같은 것으로 착각하는 데서 시작한다.
1. 부적절한 인가 CWE-285
신뢰 경계: 요청 파라미터(
username) → 로그인 사용자와 대조 없이 → LDAP 조회 대상 ⚠️ 인증(Authentication)을 통과했다고 인가(Authorization)가 끝난 것이 아니다. 기능별로 권한을 다시 검사하라.
공격 시나리오: LDAP 검색 시 로그인 정보와 인자로 넘어온 username 을 대조하지 않으면 일반 사용자가 타인의 개인정보를 조회·변조할 수 있다. (전형적인 IDOR)
❌ Bad
int searchDataLDAP(LDAP *ld, char *username) {
snprintf(filter, sizeof(filter), "(name=%s)", username);
rc = ldap_search_ext_s(ld, FIND_DN, LDAP_SCOPE_BASE,
filter, NULL, 0, NULL, NULL, ...);
return rc;
}✅ Good
if (ldap_simple_bind_s(ld, username, password) != LDAP_SUCCESS)
{ printf("인증 에러"); return FAIL; }
if (strcmp(username, getLoginName()) != 0) /* 요청값 == 로그인 사용자? */
{ printf("인증 에러"); return FAIL; }
snprintf(filter, sizeof(filter), "(name=%s)", username);
rc = ldap_search_ext_s(ld, FIND_DN, ..., &result);치환 규칙: 요청 파라미터의 식별자(user_id 등)는 매 요청마다 세션에 저장된 사용자와 일치하는지 대조. “요청에 담긴 식별자는 사용자의 주장일 뿐”이라고 취급.
2. 중요한 자원에 대한 잘못된 권한허용 CWE-732
신뢰 경계: 파일 생성 시
umask설정 → 최소 권한 검토 없이 → 전체 공개 권한 ⚠️ umask(0)은 “누구나 읽고 쓰라”는 선언이다. 파일 권한은 항상 최소 권한(077)으로 시작하라.
공격 시나리오: umask(0) 처럼 가장 넓은 권한으로 파일을 생성하면, 다른 일반 계정이나 침투한 저권한 프로세스가 민감 정보를 읽거나 설정·실행파일을 변조해 권한 상승으로 이어진다.
❌ Bad
umask(0); /* 모두 읽고 쓰기 허용 */
FILE *out = fopen("important_file", "w");✅ Good
umask(077); /* 소유자만 rw------- */
FILE *out = fopen("important_file", "w");
if (isProtected(filename) && !isAdminUserId(userId))
return NULL;치환 규칙: umask(0) 금지 → 최소 권한(077 등)으로 시작하고, 보호 대상 파일은 접근 전 별도 권한 검사 래핑.
3. 잘못된 권한 부여 CWE-266
신뢰 경계: 정상 로그인 여부 → 권한 등급 검증 없이 →
seteuid(0)root 권한 부여 ⚠️ “인증(누구인가)“과 “인가(무엇을 할 수 있는가)“는 별개다. 로그인 성공이 root 권한의 근거가 될 수 없다.
공격 시나리오: 정상 유저인지만 확인하고 무조건 root 권한을 부여하면, 접근 불가능해야 할 파일에 접근하고 일반 사용자가 관리자와 동일한 권한으로 동작하게 된다.
❌ Bad
if (isCorrectUser(userId, password))
{
seteuid(0); /* 정상 유저면 무조건 root */
...
}✅ Good
static const struct { const char *grp; uid_t uid; } TBL[] = {
{ "Admin", 0 }, { "Normal", 1000 }, { "Guest", 1001 } };
const char *g = getUserGroup(userId);
uid_t uid = (uid_t)-1;
for (size_t i = 0; i < sizeof(TBL)/sizeof(TBL[0]); i++)
if (strcmp(TBL[i].grp, g) == 0) uid = TBL[i].uid;
if (uid == (uid_t)-1) { denyAccess(); return; }
if (seteuid(uid) != 0) { denyAccess(); return; }
(uid_t)-1가 여기서 하는 일 — "테이블에 없음"을 표현하는 sentinel
uid_t는 unsigned 타입이라 원래-1을 담을 수 없다.(uid_t)-1로 캐스팅하면 C 표준의 unsigned 변환 규칙(모듈로 2^n, 정의된 동작이지 UB가 아니다)에 따라 그 타입의 최댓값(비트가 전부 1)이 된다.이 코드는 그 최댓값을 “그룹 테이블(
TBL)에서g와 일치하는 항목을 못 찾았다”는 sentinel로 쓴다.uid를0으로 초기화했다면 “못 찾음”과 “Admin(uid 0)“을 구분할 방법이 없다 —0은 root의 실제 uid라서 sentinel로 쓸 수 없다. 그래서 어떤 실제 계정도 가질 수 없는 값(unsigned 최댓값)을 예약해서 쓰는 것이다.
seteuid(uid)를 호출하기 전에if (uid == (uid_t)-1)검사를 반드시 통과해야 하므로, “모르는 그룹인데 루프에서 초기값이 그대로 남아 엉뚱한 uid로 승격되는” 사고를 애초에 막는다.chown()의 owner/group 인자에서(uid_t)-1/(gid_t)-1이 “이 값은 바꾸지 마라”를 뜻하는 것도 같은 계열의 관용구다 — unsigned 타입엔 “값 없음”을 표현할 자리가 따로 없으니, 최댓값을 빌려 쓰는 것.
치환 규칙: 인증 성공 여부와 권한 등급을 분리 — 그룹별 최소 유효 UID를 미리 테이블로 정의하고 그 결과로만 seteuid.
4. 최소 권한 적용 위배 CWE-272
신뢰 경계:
chroot/상위 권한 작업 완료 시점 → 즉시 반납 없이 → 이후 코드 전체가 상승된 권한으로 실행 ⚠️ 권한 상승은 “빌린 것”이다. 쓰자마자 즉시 반납하고, 반납 코드는 예외 경로에도 반드시 넣어라.
공격 시나리오: 상승된 권한이 유지된 채 이후 코드가 실행되므로, 그 구간의 어떤 결함도 곧바로 시스템 전체 권한 탈취로 이어진다.
❌ Bad
chroot(APP_HOME);
chdir("/");
fp = fopen("important_file", "r"); /* root 권한 유지된 채 계속 실행 */
fgets(buf, sizeof(buf), fp);
fclose(fp);✅ Good
uid_t uidBackup = geteuid(); /* 원래 권한 백업 */
seteuid(userGroupMinimumEffectUid[usrGroup]);
... /* 필요한 작업만 */
seteuid(uidBackup); /* 즉시 복귀 */치환 규칙: 권한 상승 직후 필요한 작업만 수행하고, 원래 UID를 백업했다가 작업 종료(정상·예외 경로 모두) 즉시 복귀.
이 계열 미니 체크리스트
- 요청 파라미터의 식별자(user_id 등)를 세션의 로그인 사용자와 대조하는가
-
umask(0)또는 그에 준하는 전체 공개 권한으로 파일을 생성하는 곳이 있는가 - “로그인 성공”만으로 상위 권한(
seteuid(0)등)을 부여하는 코드가 있는가 - 권한 상승 후 정상/예외 모든 경로에서 원래 권한으로 즉시 복귀하는가