
파일 하나. 게시판에 올라온 그 파일은 서버에서 실행되는 코드였습니다. 2023년 12월 13일, 개인정보보호위원회(개인정보위)는 웹셸이 업로드돼 데이터베이스(DB)까지 접근을 허용한 ㈜스피드옥션 등 두 곳에 과징금과 과태료를 의결했습니다. 1년 반 전인 2022년 6월 8일에도 같은 방식의 사고로 두 곳이 제재를 받았습니다.
사고 원인: 파일 업로드의 확장자와 실행 권한을 제한하지 않았고, DB 접근을 IP 주소로 막지 않았습니다. 잘 알려진 공격에 기본 조치가 없었습니다.
이 글은 개인정보위 의결을 다룬 보도 두 건을 바탕으로 합니다. 회사별 유출 인원과 과징금 액수는 보도에 나온 것만 적었습니다.
| 항목 | 2023년 12월 의결 | 2022년 6월 의결 |
|---|---|---|
| 회의 | 개인정보위 제20회 전체회의(2023-12-13) | 개인정보위 제10회 전체회의(2022-06-08) |
| 대상 | 2개 사업자 | 3개 사업자 |
| 웹셸 사고 | ㈜스피드옥션 | 위더스교육(원격평생교육원), 뉴지스탁(주식 데이터분석 서비스) |
| 같은 회의의 다른 사고 | ㈜코다스디자인: SQL 인젝션, 이용자 38,209명 유출 | 창비(온라인 도서 사이트): SQL 주입 |
| 처분 합계 | 과징금 2억 2,343만 원, 과태료 1,440만 원 | 과징금 3,700만 원, 과태료 2,140만 원 |
| 위반 | 안전조치 의무, 유출 통지·신고 의무 | 안전조치 의무 등 |
웹셸(web shell)은 웹 서버에 올려 실행시키는 명령 실행 코드입니다. 한 번 실행되면 공격자가 브라우저로 서버를 원격 조종할 수 있습니다. SQL 인젝션은 입력값을 조작해 DB 질의를 바꾸는 공격입니다.

원리 수준으로 그린 경로입니다. 어떤 취약점으로 파일이 올라갔는지는 보도에 나오지 않습니다.
스피드옥션은 파일 업로드 확장자와 실행 권한을 제한하는 등 웹셸 공격을 막는 조치를 하지 않았습니다. 확장자를 제한하지 않으면 실행 코드가 그대로 올라가고, 실행 권한을 막지 않으면 올라간 코드가 서버에서 동작합니다.
DB 접근 권한을 IP 주소로 제한하지 않은 점도 위반으로 확인됐습니다. 업로드에서 한 번 놓친 공격을 DB 앞에서 한 번 더 막을 장치가 없었습니다.
정보통신신문 기사는 이 기업들이 적절한 사이버보안 조치를 했다면 피해를 막을 수 있었다고 전했습니다. 기사 제목도 "언제적 해킹 기법인데"였습니다.
사고 회사의 코드가 아니라 같은 실수를 보여 주는 일반 예시입니다. Spring Boot 모양의 Java 코드이고 값은 모두 가짜입니다.
위험한 모양
// 게시판 첨부 업로드 (일반 예시)
@PostMapping("/board/upload")
public String upload(@RequestParam MultipartFile f)
throws IOException {
String name = f.getOriginalFilename(); // 이름 그대로
Path dst = Path.of("webapp/upload", name); // 웹 루트 안
f.transferTo(dst);
return "/upload/" + name; // 주소로 바로 열림
}
// 확장자 검사 없음, 실행 권한 그대로
// DB: 어느 서버에서든 접속 가능
사용자가 정한 이름과 확장자 그대로, 웹 서버가 내려주고 실행하는 폴더에 저장합니다. 실행 코드가 올라오면 그 주소를 여는 순간 서버에서 실행됩니다.
고친 모양
private static final Set<String> ALLOWED =
Set.of("jpg", "png", "pdf"); // 허용 목록
private static final Path STORE =
Path.of("/srv/app-files"); // 웹 루트 밖
@PostMapping("/board/upload")
public FileRef upload(@RequestParam MultipartFile f)
throws IOException {
String ext = extOf(f.getOriginalFilename());
if (!ALLOWED.contains(ext) || !magic.matches(f, ext))
throw new BadRequestException("허용하지 않는 파일");
String id = UUID.randomUUID().toString(); // 무작위 이름
Path dst = STORE.resolve(id + "." + ext);
f.transferTo(dst);
Files.setPosixFilePermissions(dst,
PosixFilePermissions.fromString("rw-r-----")); // 실행 권한 없음
return files.save(id, ext, f.getOriginalFilename());
}
@GetMapping("/files/{id}") // 내려받기만, 실행하지 않음
public ResponseEntity<Resource> download(@PathVariable UUID id) {
FileRef r = files.find(id);
return ResponseEntity.ok()
.header("Content-Disposition", "attachment")
.header("X-Content-Type-Options", "nosniff")
.body(new FileSystemResource(STORE.resolve(r.stored())));
}
/srv/app-files)에 둡니다. 파일은 컨트롤러를 거쳐 내려받기로만 나갑니다.DB 쪽은 접속을 허용하는 출발지를 애플리케이션 서버 주소(예: 192.0.2.10)로만 좁힙니다. 웹셸이 실행된 서버가 다른 곳이라면 DB에 닿지 않습니다. 같은 서버라면 그 서버의 DB 계정 권한을 필요한 테이블로 줄여 둡니다.
2023년 의결을 전한 보도는 SQL 인젝션과 웹셸 공격이 잘 알려진 기법이지만 피해가 매우 크다고 전했습니다(데일리시큐). 같은 회의에서 SQL 인젝션 사고를 함께 다룬 것도 같은 맥락입니다. 입력을 믿지 않는 것은 글자 입력이든 파일 입력이든 같습니다.
| 의결 | 과징금 합계 | 과태료 합계 | 위반 사항 |
|---|---|---|---|
| 2023-12-13(2개 사업자) | 2억 2,343만 원 | 1,440만 원 | 안전조치 의무, 유출 통지·신고 의무 |
| 2022-06-08(3개 사업자) | 3,700만 원 | 2,140만 원 | 안전조치 의무, 접속기록 미보관, 미이용자 정보 미파기·미분리 등 |
두 보도 모두 사업자별 과징금 액수는 밝히지 않았습니다. 개인정보위는 2022년 의결 때 비대면 활동이 늘면서 해킹으로 인한 유출이 계속되고 있다며, 보안 취약점을 주기적으로 확인하고 안전조치 의무를 상시 점검하라고 당부했습니다. 정보보호산업계는 웹방화벽(WAF) 운영, 서버와 웹사이트의 정기 취약점 점검, 소스코드 단계의 시큐어 코딩을 권고했습니다(정보통신신문).
이 글은 법률 자문이 아닙니다.
오늘 본 사고의 원인은 대부분 서버와 코드의 기본기에서 막을 수 있는 것들이었습니다. 직접 손으로 막아 보고 싶다면 이런 강의도 있습니다.
백엔드 개발자를 위한 서버 보안 실무: 해킹 방어부터 정보유출 방지까지
수치는 개인정보위 의결(2022년 6월 8일, 2023년 12월 13일)을 다룬 보도 기준입니다. 이 글은 법률 자문이 아닙니다.
이전 글 침해 신고 8,565건, 10건 중 8건은 중소기업: 두 번 이상 신고한 782곳
다음 글 지우지 않은 관리자 계정 하나: 발란 162만 건 유출
시리즈 전체 목록 해킹 사고 해부 전체 목록