7 분 소요

🛡️ 생성형 AI × 보안 4부작 (전체 4편)

  1. 공격 — 인가된 모의침투에 LLM 붙이기지금 글
  2. 방어 — 탐지·대응 자동화
  3. 포렌식과 데이터 복원
  4. LLM 그 자체의 보안 — 프롬프트 인젝션과 가드레일

Summary

생성형 AI가 보안 판을 바꾸고 있다는 말은 많은데, 정작 “그래서 내 워크플로우 어디에 붙이느냐” 는 잘 안 풀어줘요. 이 시리즈는 그걸 공격·방어·포렌식·LLM 보안 네 갈래로 나눠서, 실제 호출 코드와 출력까지 보여주며 정리합니다.

첫 글은 공격(레드팀) 관점이에요. 다만 전제부터 분명히 합니다. 여기 나오는 모든 기법은 본인이 권한을 가진 자산, 서면 동의를 받은 모의침투 계약, 또는 CTF·랩 환경 에서만 씁니다. 남의 시스템에 허락 없이 쓰면 그건 그냥 범죄예요.

🚨 이 글의 모든 예시는 인가된 보안 평가(authorized penetration test) · CTF · 본인 소유 랩을 전제로 합니다. 무허가 대상에 적용하지 마세요.

💡 이 글에서 다루는 것

  • 공격자도 방어자도 왜 지금 LLM을 쓰는가
  • 정찰(recon) 결과를 LLM으로 요약·우선순위화
  • 소스코드에서 취약 패턴 찾기 (SAST 보조)
  • 난독화된 페이로드·스크립트 해석
  • 인가된 피싱 시뮬레이션 템플릿 생성
  • LLM을 공격에 쓸 때의 한계와 함정



1. 왜 지금, 공격에도 LLM인가

모의침투는 늘 시간과의 싸움이에요. 계약 기간은 2주인데 대상 자산은 수백 개고, 그중 진짜 취약한 한두 개를 찾아야 합니다. 그래서 “지루하고 양 많은 읽기·요약·1차 분류” 작업을 얼마나 줄이느냐가 곧 실력이 돼요.

생성형 AI가 잘하는 게 정확히 그 지점이에요.

  • 자연어 요약 — 수천 줄짜리 스캔 결과를 “주목할 호스트 5개” 로 압축
  • 코드 이해 — 낯선 언어로 짠 펌웨어 스크립트도 한 번에 해설
  • 변환 — 한 포맷의 데이터를 다른 포맷으로(예: 로그 → 구조화 JSON)
  • 초안 생성 — 보고서·페이로드 후보·체크리스트의 1차 버전

반대로 못하는 것도 분명해요. 실제 익스플로잇을 안정적으로 만들어주지도 않고, 모르는 걸 그럴듯하게 지어내며(할루시네이션), 위험한 요청은 거부합니다. 그래서 LLM은 “판단하는 도구” 가 아니라 “1차 가공을 대신해주는 분석가 보조” 로 보는 게 맞아요. 최종 판단과 책임은 사람이 집니다.

이 관점을 코드로 끌고 가기 위해, 글 전체에서 쓸 작은 헬퍼 하나를 먼저 둘게요.

import os
from anthropic import Anthropic

client = Anthropic(api_key=os.environ["ANTHROPIC_API_KEY"])  # 키는 환경변수로만

def ask_llm(system: str, user: str, model: str = "claude-sonnet-4-6") -> str:
    """system 지시 + user 입력을 받아 모델의 텍스트 응답을 반환."""
    resp = client.messages.create(
        model=model,
        max_tokens=1024,
        system=system,
        messages=[{"role": "user", "content": user}],
    )
    return resp.content[0].text

호출은 이렇게 생겼어요. 입력은 system(역할 지시)과 user(분석 대상) 두 문자열, 반환은 모델이 만든 텍스트 한 덩어리입니다.

out = ask_llm(
    system="너는 보안 분석가다. 한국어로 간결하게 답한다.",
    user="포트 22, 80, 443 이 열린 호스트의 1차 점검 순서를 3줄로.",
)
print(out)
1. 443/80 의 웹 서비스 버전과 헤더부터 확인 (가장 표면적이 넓음)
2. 22(SSH) 는 배너로 버전 식별 후 약한 인증·구버전 여부만 점검
3. 세 포트 모두 기본 자격증명·알려진 CVE 매칭을 마지막에 일괄 확인

ask_llm 을 이 글 내내 재료로 씁니다.



2. 정찰(recon) 결과 요약·우선순위화

모의침투의 첫 단계는 정찰이에요. nmap, nuclei, httpx 같은 도구를 돌리면 출력이 수백~수천 줄 나옵니다. 사람이 다 읽기 전에, “먼저 볼 것” 을 골라주는 1차 분류를 LLM에 맡기면 시간이 확 줄어요.

아래는 가상의 스캔 요약(문서용 IP 대역 192.0.2.0/24 사용)을 LLM에 넣어 우선순위를 매기는 예시입니다.

scan_text = """\
192.0.2.11  22/tcp open  ssh    OpenSSH 7.4 (protocol 2.0)
192.0.2.11  80/tcp open  http   Apache httpd 2.4.6
192.0.2.23  443/tcp open https  nginx 1.14.0
192.0.2.23  3306/tcp open mysql MySQL 5.5.62
192.0.2.40  8080/tcp open http  Jenkins 2.150 (login disabled)
"""

system = (
    "너는 인가된 모의침투 팀의 분석가다. 아래 스캔 결과를 보고 "
    "공격 표면이 큰 순서로 우선순위를 매기고, 각 호스트별로 '왜 주목하는지'를 "
    "한 줄로 설명하라. 실제 익스플로잇 코드는 출력하지 말고 점검 방향만 제시하라."
)
print(ask_llm(system=system, user=scan_text))
[우선순위]
1. 192.0.2.40 (Jenkins 2.150) — 구버전 CI 서버. 인증 우회·RCE 계열 CVE가
   많은 제품군이라 가장 먼저 버전 대조 권장.
2. 192.0.2.23 (MySQL 5.5.62) — EOL 데이터베이스가 외부 노출. 약한 인증·
   원격 접근 정책부터 점검.
3. 192.0.2.11 (OpenSSH 7.4 / Apache 2.4.6) — 둘 다 구버전. 알려진 CVE 매칭과
   기본 자격증명 여부 확인.

여기서 핵심은 LLM이 “취약하다” 고 단정하게 두지 않는 것이에요. 버전이 낮다고 곧 취약은 아니거든요. 그래서 system 프롬프트에 “점검 방향만 제시” 라고 못 박았어요. LLM은 “어디부터 사람이 확인할지” 의 순서를 잡아주고, 실제 CVE 매칭·검증은 분석가가 searchsploit 이나 공식 어드바이저리로 직접 합니다.

⚠️ LLM의 버전→CVE 매핑은 자주 틀려요(할루시네이션). 모델이 댄 CVE 번호는 반드시 NVD/공식 어드바이저리에서 재확인하세요. 모델은 “어디를 볼지” 까지만 믿습니다.



3. 소스코드 취약점 리뷰 (SAST 보조)

소스코드를 받을 수 있는 화이트박스 평가에서는 LLM이 꽤 강력한 1차 리뷰어가 돼요. 정적분석 도구(SAST)가 잡는 패턴 외에, 맥락이 필요한 논리 취약점도 곧잘 짚습니다.

아래는 일부러 취약하게 짠 로그인 코드 조각을 리뷰시키는 예시예요.

vuln_code = '''
def login(request):
    user = request.GET["user"]
    pw   = request.GET["pw"]
    q = "SELECT * FROM users WHERE name='%s' AND pw='%s'" % (user, pw)
    row = db.execute(q).fetchone()
    if row:
        token = "session-" + user        # 예측 가능한 세션 토큰
        return {"ok": True, "token": token}
    return {"ok": False}
'''

system = (
    "너는 시큐어 코딩 리뷰어다. 아래 코드의 취약점을 "
    "심각도(높음/중간/낮음)·근거·수정 방향으로 정리하라. "
    "수정 코드는 안전한 패턴만 보여라."
)
print(ask_llm(system=system, user=vuln_code))
[높음] SQL 인젝션 — user/pw 를 문자열 포매팅으로 쿼리에 직접 삽입.
  근거: ' OR '1'='1 류 입력으로 인증 우회 가능.
  수정: 파라미터 바인딩 사용. db.execute("...WHERE name=? AND pw=?", (user, pw))

[높음] 예측 가능한 세션 토큰 — "session-" + user 는 추측 가능.
  수정: secrets.token_urlsafe(32) 같은 CSPRNG 기반 난수 토큰.

[중간] 평문 비밀번호 비교 추정 — pw 컬럼이 평문이면 유출 시 즉시 도용.
  수정: bcrypt/argon2 해시 저장 + 상수시간 비교.

[낮음] 입력 검증 부재 — user 길이/문자셋 화이트리스트 권장.

SAST 도구는 보통 첫 번째(SQLi)는 잘 잡지만, “세션 토큰이 예측 가능하다” 같은 건 놓치기 쉬워요. LLM은 코드의 의미를 읽으니까 이런 논리 결함을 같이 짚어줍니다. 대신 거짓 양성(false positive) 도 많아서, LLM 리뷰는 “후보 목록” 으로 받고 사람이 확정하는 흐름이 안전해요.

실무에서는 이걸 함수로 감싸 디렉토리 전체에 돌립니다.

from pathlib import Path

def review_dir(root: str, system: str) -> dict:
    """디렉토리의 .py 파일을 LLM 리뷰어에 한 개씩 넣어 결과 dict 반환."""
    results = {}
    for path in Path(root).rglob("*.py"):
        code = path.read_text(encoding="utf-8")
        results[str(path)] = ask_llm(system=system, user=code)
    return results

reviews = review_dir("./target_src", system)
print(f"리뷰한 파일 수: {len(reviews)}")
리뷰한 파일 수: 37

💡 파일이 크면 토큰 한도에 걸려요. 함수·클래스 단위로 잘라 넣고, 민감한 고객 소스를 외부 API에 올리는 게 계약상 금지면 온프렘/로컬 모델로 같은 파이프라인을 돌리세요. (로컬 모델 이야기는 2편에서 다시 나와요.)



4. 난독화된 페이로드·스크립트 해석

CTF나 멀웨어 1차 분석에서 자주 만나는 게 난독화(obfuscation) 예요. base64로 감싸고, 변수명을 뭉개고, 인코딩을 겹쳐놓죠. 손으로 풀면 시간이 오래 걸리는데, LLM은 “이게 무슨 동작을 하는지” 를 빠르게 설명해줍니다.

아래는 흔한 base64 + 동적 실행 패턴(분석용으로 단순화)이에요.

sample = '''
import base64
exec(base64.b64decode("cHJpbnQoJ2hlbGxvIGZyb20gcGF5bG9hZCcp"))
'''

system = (
    "너는 멀웨어 분석가다. 아래 스크립트가 '무엇을 하는지'만 설명하라. "
    "디코드된 내용과 의도를 한국어로 요약하되, 실제로 실행 가능한 "
    "공격 코드를 새로 작성하지는 마라."
)
print(ask_llm(system=system, user=sample))
이 스크립트는 base64 로 인코딩된 문자열을 디코드한 뒤 exec()로 실행합니다.
디코드 결과는 print('hello from payload') 입니다. 즉 실제 페이로드를 base64
뒤에 숨겨 정적 분석을 회피하려는 전형적인 '인코드 후 동적 실행' 패턴입니다.
실제 악성 표본에서는 이 자리에 C2 접속·파일 다운로드 코드가 들어갑니다.

[주의] exec(base64.b64decode(...)) 패턴 자체가 탐지 룰의 좋은 시그니처입니다.

여기서 중요한 디테일 두 가지예요. 첫째, 실제 디코드는 exec 없이 안전하게 합니다 — 분석 대상은 절대 그냥 실행하지 않아요.

import base64
decoded = base64.b64decode("cHJpbnQoJ2hlbGxvIGZyb20gcGF5bG9hZCcp").decode()
print(decoded)   # exec 하지 말고 출력만
print('hello from payload')

둘째, system 프롬프트에서 “공격 코드를 새로 작성하지 마라” 고 범위를 묶었어요. LLM에게 해설은 시키되 무기화는 시키지 않는 게 분석 윤리이자, 모델 거부를 피하는 현실적인 방법이기도 합니다.



5. 인가된 피싱 시뮬레이션

조직의 보안 인식 훈련(security awareness)에서, 동의된 직원 대상으로 피싱 시뮬레이션을 돌리는 건 합법적인 레드팀 업무예요. LLM은 자연스러운 한국어 시나리오 초안을 빠르게 만들어줍니다 — 단, 훈련 플랫폼·랜딩 페이지가 “이건 훈련입니다” 를 고지하는 구조 안에서요.

system = (
    "너는 사내 보안 인식 훈련을 설계하는 레드팀이다. "
    "동의된 직원 대상 피싱 '시뮬레이션' 메일 시나리오 초안을 만든다. "
    "실제 악성 링크/첨부는 넣지 말고, 훈련 플랫폼이 치환할 자리표시자 "
    " 만 사용하라. 마지막에 '식별 단서' 도 함께 적어라."
)
user = "주제: 사내 복지포털 비밀번호 만료 안내. 톤: 업무적, 약간의 긴급함."
print(ask_llm(system=system, user=user))
[제목] [복지포털] 비밀번호 만료 7일 전 — 갱신 안내

안녕하세요. 사내 복지포털 운영팀입니다.
회원님의 비밀번호가 7일 후 만료될 예정입니다. 서비스 중단 없이 이용하시려면
아래 링크에서 비밀번호를 갱신해 주세요.

▶ 비밀번호 갱신: 

기한 내 갱신하지 않으면 로그인이 일시 제한될 수 있습니다.

[훈련용 식별 단서]
- 발신 도메인이 사내 정식 도메인과 미묘하게 다름
- '7일 후 만료' 식의 시간 압박
- 갱신을 메일 링크로 유도 (정상 절차는 포털 직접 접속)

훈련의 핵심은 메일 자체가 아니라 “식별 단서” 를 직원이 학습하는 것이에요. 그래서 시나리오에 “이런 신호를 보면 의심하라” 를 같이 생성하게 했습니다. 이게 공격 편이지만 사실상 방어로 이어지는 다리예요 — 좋은 레드팀은 항상 블루팀이 쓸 교훈을 같이 만듭니다.

🚨 이 기능은 반드시 사전 동의·법무 검토·훈련 플랫폼 위에서만. 실제 자격증명을 수집하는 진짜 피싱 페이지를 만드는 데 쓰면 불법이고, 모델도 그 방향은 거부합니다.



6. 한계와 함정 — 이건 꼭 기억하세요

LLM을 공격 워크플로우에 붙일 때 사람이 끝까지 책임져야 하는 지점들이에요.

함정 무슨 일이 생기나 대응
할루시네이션 없는 CVE·존재하지 않는 함수·틀린 포트를 자신있게 말함 모든 사실 주장은 1차 출처로 재확인
과신 “이 코드는 안전합니다” 를 믿어버림 LLM은 후보만, 확정은 사람·도구
데이터 유출 고객 소스/스캔 결과를 외부 API에 업로드 계약 확인, 필요시 로컬 모델·마스킹
모델 거부 무기화 요청을 거부해 흐름이 끊김 해설·분석으로 범위를 묶어 요청
로그 잔존 API 제공자 측에 프롬프트가 남을 수 있음 민감정보는 마스킹 후 입력

요약하면, LLM은 “빠른 1차 분석가” 지 “판단 주체” 가 아니에요. 정찰을 요약하고, 코드를 읽고, 난독화를 풀고, 훈련 시나리오를 초안 잡는 데까지가 강점이고 — 무엇이 진짜 취약한지, 무엇을 실제로 칠지, 그 책임은 사람에게 있습니다.

다음 편에서는 같은 도구를 방어 쪽에서 봅니다. SOC의 알람 더미를 LLM으로 트리아지하고, 로그에서 IOC를 뽑고, 탐지 룰(Sigma/YARA)을 생성하는 흐름을 실제 코드로 정리할게요.

일단 오늘은 여기까지…..
다음 글에서는 방어자 입장에서 LLM을 탐지·대응에 붙이는 법을 정리해볼게요.


다음 글 →: (2/4) 방어 — 탐지·대응 자동화