[Linux] Lynis로 보안 취약점을 점검하고 Claude code로 Hardening guide를 작성
Lynis는 리눅스 서버의 보안 취약점을 점검하고 System Hardening 가이드라인을 제공해 주는 오픈소스 툴입니다. OFFLINE 서버에서도 사용할 수 있도록 무설치 방식의 사용법을 알아보겠습니다.
- https://cisofy.com/downloads/lynis/ 접속하여 최신 버전을 Binary Tarball을 다운로드합니다. 2026년5월12일 현재 최신 버전을 3.1.6입니다.
$ cd /usr/local/ $ wget https://downloads.cisofy.com/lynis/lynis-3.1.6.tar.gz $ tar xfvz lynis-3.1.6.tar.gz $ cd lynis
- Lynis 명령어를 실행합니다. 실무에 사용할 수 있도록 lynis 명령어의 옵션에 대해서도 알아보겠습니다.
$ ./lynis audit system
- --report-file: lynis-report.dat의 위치를 변경합니다.
- --log-file: lynis.log의 위치를 변경합니다.
- --profile: 기본으로 설정된 /usr/local/lynis/default.prf 대신에 사용할 사용자 프로파일을 지정합니다.
- --pentest: 대화형 확인 과정을 생략하고 전체 스캔을 수행합니다.
- --quick: 각 테스트 단계마다 엔터를 누를 필요 없이 바로 넘어갑니다.
- --tests: 점검하고 싶은 Test-ID를 쉼표(,)로 구분하여 입력합니다.
- --skip-test: 제외하고 싶은 Test-ID를 쉼표(,)로 구분하여 입력합니다.
- 점검 리포트는 디폴트로 /var/log 디렉토리에 생성되며, Lynis 감사 결과를 확인해 봅니다.
$ ls -l /var/log/lynis* -rw-r-----. 1 root root 398487 May 12 08:58 /var/log/lynis.log -rw-r-----. 1 root root 80752 May 12 08:58 /var/log/lynis-report.dat $ grep -iE "warning|suggestion" /var/log/lynis.log ....[test: Test-ID].... $ ./lynis show details Test-ID
- lynis.log : 프로그램이 각 테스트를 어떻게 수행했는지, 왜 특정 테스트를 건너뛰었는지 등 기술적인 과정이 기록됩니다. 감사 과정에서 발생한 모든 세부 디버깅 정보가 담기며 문제가 생겼을 때 확인합니다. 특정 보안 체크 항목이 왜 [ SKIPPED ] 되었는지 궁금하다면 이 파일을 뒤져봐야 합니다.
- lynis-report.dat : 일반적인 텍스트 문서라기보다 “키=값(Key=Value)” 형태의 데이터 파일로 Lynis의 모든 검사 결과를 요약한 ‘데이터베이스’와 같습니다. 실제 감사 결과(점수, 발견된 취약점, 제안 사항 등)가 기계가 읽기 좋은 형태로 저장됩니다. 나중에 이 데이터를 바탕으로 그래프를 그리거나, 다른 도구로 분석할 때 사용됩니다. 화면에 나왔던 Suggestions(제안)이나 Warnings(경고)만 다시 보고 싶을 때 이 파일을 열어보면 편리합니다.
- 조치 후 해당 테스트만 다시 수행해 봅니다.
$ ./lynis audit system --tests <Test-ID> --log-file=/var/log/lynis_<Test-ID>.log
- 본인의 서버 환경에 맞도록 테스트가 필요없는 항목(Test-ID)을 등록하고, 설정을 확인해 봅니다. 이 글을 참고하시면 기본적인 lynis 설정방법을 알 수 있습니다. Lynis security controls 페이지에서 Test-ID 정보를 확인할 수 있습니다.
$ ./lynis show profiles /usr/local/lynis/default.prf $ vi /usr/local/lynis/default.prf # Skip a test (one per line) # 인터넷이 차단되어 있고 내부 DNS서버도 사용하지 않는 경우, /etc/resolv.conf 파일과 관련된 테스트 제외 skip-test=NETW-2705 # 가상 브리지 인터페이스를 무차별 모드(promiscuous) 검사에서 제외 skip-test=NETW-3015:virbr0 skip-test=NETW-3015:virbr0-nic $ ./lynis show settings --brief
- Claud Code를 사용해서 lynis 점검 리포트를 분석하고 Hardening 가이드를 제공받습니다. 아래는 Claude에서 lynis 점검 리포트의 분석을 요청할 때 사용한 claude.md 파일입니다.
# 프로젝트 개요 - Lynis 툴로 Linux 서버의 보안 취약점 점검 결과를 분석해서 System Hardening 가이드를 제공하는 프로젝트 - 동일한 입력으로 다시 분석할 때 **항상 같은 결과**가 나오도록 모든 결정사항을 본 문서에 기록한다. # 입력 파일 규칙 - 작업 디렉토리에 호스트별로 다음 두 파일을 배치한다. - `lynis-report.<호스트네임>.
.dat` (필수, 파싱 대상) - `lynis.<호스트네임>. .log` (선택, FILE-7524 상세 보강용) - 구분자는 점(`.`)이며, 호스트네임에 점은 사용할 수 없다(필드 구분자와 충돌). 하이픈은 허용. - 두 파일의 `<호스트네임>. ` 부분이 동일해야 한 쌍으로 인식된다. - 다중 호스트 처리: 같은 디렉토리에 여러 쌍을 두면 파일명 사전순(`sorted`)으로 누적 처리한다. - Hardening 가이드 파일의 "분석 일자" 컬럼 값으로 yyyymmdd를 사용한다. # 분석 도구 - 실행 스크립트: `generate_hardening_guide.py` (이 프로젝트의 단일 소스) - 요구 환경: Python 3.10+, `openpyxl` (현재 검증: Python 3.12.6 / openpyxl 3.1.5) - 실행 명령 (PowerShell): ``` $env:PYTHONIOENCODING = "utf-8" python generate_hardening_guide.py --input-dir . --output @SystemHardeningGuide.xlsx ``` - 스크립트는 외부 네트워크/LLM 호출 없이 **정적 매핑 테이블**(`GUIDE`, `SSH_OPTION_KO`, `SYSCTL_KO`, `CATEGORY_BY_PREFIX`)만 사용한다. 따라서 같은 입력 + 같은 스크립트 버전이면 결과는 비트 단위로 동일하다. # Lynis 분류체계 (test ID prefix 기준) - 스크립트 상단의 `CATEGORY_BY_PREFIX` 가 단일 진실원본(SoT). - 매핑되지 않은 prefix 가 새로 등장하면 prefix 자체가 "분류" 컬럼에 그대로 출력되며, 이 표를 보강해야 한다. | prefix | 분류 | prefix | 분류 | |--------|-------------|--------|-----------------------| | BOOT | 부팅 서비스 | LOGG | 로깅/감사 | | KRNL | 커널 | BANN | 배너/경고문구 | | MEM | 메모리/프로세스 | ACCT | 계정 감사 | | AUTH | 사용자/인증 | TIME | 시간/NTP | | FILE | 파일시스템 | CRYP | 암호화 | | USB | 스토리지(USB) | MACF | 강제접근제어(SELinux) | | STRG | 스토리지 | FINT | 파일 무결성 | | NAME | 이름 서비스(DNS) | TOOL | 자동화 도구 | | PKGS | 패키지/소프트웨어 | HOME | 홈 디렉토리 | | NETW | 네트워크 | HRDN | 시스템 하드닝 | | MAIL | 메일 | SNMP | SNMP | | FIRE | 방화벽 | SSH | SSH | (전체 목록은 스크립트의 `CATEGORY_BY_PREFIX` 참고) # 출력 파일 (Excel) 구조 - 파일명: `@SystemHardeningGuide.xlsx` - 시트는 **2개**로 고정: 1. `Hardening 가이드` — 본문 (단일 시트, 평탄 구조) 2. `요약` — 호스트별 메타데이터/통계 ## 시트 1: Hardening 가이드 — 컬럼 정의 | # | 컬럼명 | 데이터 소스 | |---|------------------|----------------------------------------------------------| | 1 | 분석 일자 | 입력 파일명의 ` ` 접미사 | | 2 | 호스트네임 | `hostname` | | 3 | OS | `os_fullname` (예: RHEL 7.9 (Maipo)) | | 4 | 분류 | test ID prefix → `CATEGORY_BY_PREFIX` | | 5 | Lynis ID | 예: SSH-7408, KRNL-6000 | | 6 | 점검 항목 | 한글 설명 (`GUIDE[id].title_ko` 또는 suggestion 원문) | | 7 | 현재값 | `details[].value` 또는 warning 의 부가정보 | | 8 | 권장값 | `details[].prefval` | | 9 | 위험도 | warning → High / suggestion → Medium | | 10| 조치 명령어/설정 | `GUIDE[id].fix` (정적 매핑) | | 11| 비고 | warning / suggestion / sshd 옵션 / sysctl 키 / 권한 불일치 | | 12| 처리여부 | 수동 입력용 — 드롭다운(Y/N/NA), 기본값 공란 | - 헤더 1행 고정(`freeze_panes = "A2"`), 자동 필터 적용. - 위험도 셀: High(주황), Medium(노랑) 색상 강조. - 처리여부 셀: Excel `DataValidation(type="list", formula1='"Y,N,NA"', allow_blank=True)` 적용 → Y(조치 완료) / N(미조치) / NA(해당 없음) 중 드롭다운 선택. 잘못된 값 입력 시 경고. 가운데 정렬. ## 시트 2: 요약 — 컬럼 정의 | 호스트네임 | OS | 커널 | Lynis 버전 | Hardening Index | Warnings | Suggestions | 점검 행 수 | # 동적 항목 펼치기 규칙 같은 Lynis ID 가 여러 세부 항목으로 나오는 경우, 다음 규칙으로 행을 분리한다. - **SSH-7408**: `details[]=SSH-7408|sshd|field:...;prefval:...;value:...` 를 옵션 단위로 1행씩. - 한글 설명: `SSH_OPTION_KO[field]` (없으면 details 의 desc). - **KRNL-6000**: `details[]=KRNL-6000|sysctl|field:...;prefval:...;value:...` 를 sysctl 키 단위로 1행씩. - 한글 설명: `SYSCTL_KO[field]` (없으면 details 의 desc). - **FILE-7524**: `.log` 파일에서 `permissions of file are not matching expected value (NNN != MMM)` 패턴을 추출하여 파일 단위로 1행씩. - `.log` 가 없으면 suggestion 행 1개만 추가. - **NETW-3200, AUTH-9286 등** suggestion 안에서 동일 ID 가 부가정보(`extra`)만 다르게 여러 번 등장: `(test_id, extra)` 단위로 중복 제거하면서 모두 행으로 추가. # 네트워크 환경 분기 표기 규약 - 일부 점검 항목(외부 통신 전제)은 호스트의 네트워크 환경에 따라 적절한 조치가 달라진다. - 해당 항목의 `GUIDE[id].fix` 본문은 다음 라벨로 구간을 분리하여 작성한다: - `[폐쇄망/에어갭]` — 인터넷 단절, 내부망 전용 - `[내부 DNS 사용]` — 내부 DNS 서버만 사용 (선택, NETW-2705/NAME-4028 만 해당) - `[외부통신 가능]` — 인터넷/외부 서비스 접근 가능 - `공통:` — 환경 무관 적용 사항 - 폐쇄망 구간에는 가능한 한 Lynis 점검 예외처리 안내(`skip-test= `)를 포함한다. - 현재 분기 표기를 적용한 항목 (2026-05-13 기준): `NETW-2705`, `NAME-4028`, `PKGS-7420`, `LOGG-2154`, `CRYP-7902`, `HRDN-7230` - 신규 ID 추가 시, 외부 통신/서비스 의존성이 있다면 같은 라벨 체계로 작성한다. # 매핑 테이블 확장 정책 - 새 Lynis ID 가 결과에 등장하면 `GUIDE` 딕셔너리에 다음 형태로 추가한다: ```python "ABCD-1234": { "title_ko": "한글 점검 항목명", "fix": "권장 명령어/설정 (멀티라인 가능)", }, ``` - 매핑이 없으면 점검 항목은 Lynis 원문(영문)이 들어가고, 조치 컬럼은 `https://cisofy.com/lynis/controls/ /` 안내 링크가 자동으로 채워진다 — 이는 **누락 신호**이므로 발견 즉시 매핑을 보강한다. - prefix 가 새로우면 `CATEGORY_BY_PREFIX` 도 함께 보강한다. # 결과 재현 체크리스트 1. 입력 파일 (`lynis-report. . .dat`, `lynis. . .log`) 가 동일한가? 2. `generate_hardening_guide.py` 의 git/파일 해시가 동일한가? 3. Python / openpyxl 버전이 동일한가? (openpyxl 의 스타일 출력은 버전 간 미세 차이 가능) 4. 출력 파일을 비교: `Hardening 가이드` 시트의 모든 셀 값이 일치해야 한다 (스타일은 비교 제외). # 점검 결과 - 분석 대상 일자(파일명 yyyymmdd): **20260513** - 공통 환경: RHEL 7.9 (Maipo) / Kernel 3.10.0-1160.133.1.el7.x86_64 / Lynis 3.1.6 - 출력 파일: `@SystemHardeningGuide.xlsx` (12 컬럼 × 134 데이터 행) | 호스트 | Hardening Index | Warnings | Suggestions | 출력 행 | |------------|-----------------|----------|-------------|---------| | non-w-dev | 64 | 4 | 43 | 66 | | Non-W-PRD | 65 | 4 | 45 | 68 | | **합계** | - | 8 | 88 | **134** | - 전체 등장 Lynis ID: **35개** (모두 `GUIDE` 매핑 보유 — 누락 0) - 신규로 매핑 보강한 ID (2026-05-13): `NETW-2705`, `FILE-6354`, `HRDN-7230` ## 변경 이력 - 2026-05-13: 초기 가이드 생성. SSH-7408/KRNL-6000/FILE-7524 동적 펼치기 도입. 네트워크 환경 분기 표기(`[폐쇄망/에어갭]` / `[외부통신 가능]`) 6개 항목 적용. - 2026-05-14: 입력/출력 파일명 규약을 점(`.`) 구분자 기반으로 변경 (`lynis-report. . .dat`, `lynis. . .log`, 출력 `@SystemHardeningGuide.xlsx`). 통계 변동 없음.
- crontab을 사용하여 주기적으로 취약점 점검을 실행합니다.
$ crontab -e 0 3 1 3,6,9,12 * /usr/local/lynis/lynis audit system --cronjob --log-file /var/log/lynis.$(hostname).$(date +\%Y\%m\%d).log --report-file /var/log/lynis-report.$(hostname).$(date +\%Y\%m\%d).dat > /dev/null 2>&1 0 3 1 3,6,9,12 * find /var/log/ -type f -name "lynis-*" -mtime +90 -delete > /dev/null 2>&1
