Step 103. Natas 6~10 — 서버 코드 읽기와 명령어 인젝션
Level 2 — 보안 입문과 공격 스킬 기초 | 난이도 ★★★☆☆ | 예상 소요 시간 3시간
전제: Step 102의 웹 정찰 루틴, Step 41~46의 파이썬, Step 94의 서버 지식을 사용합니다.
- 준비물: 브라우저와 개발자 도구, 파이썬 대화형 모드(인코딩 역산용), Step 102의 정찰 루틴.
- ⚠️ 이 챕터의 실습은 내 랩·합법 플랫폼 전용입니다. 허가 없는 시스템에 적용하면 범죄입니다.
- 주의: Natas는 OverTheWire가 공식 운영하는 합법 학습 플랫폼입니다. 특히 오늘 배우는 명령어 인젝션 입력을 연습장이 아닌 어떤 검색창에도 넣지 마세요 — 그 순간 실험이 아니라 공격이 됩니다.
Step 102까지 우리는 문서의 뒤를 봤습니다. 이제부터는 로직의 뒤를 봅니다. Natas 6부터는 페이지에 "소스 코드 보기" 버튼이 달립니다 — 서버의 PHP 코드를 읽는 것이 곧 풀이입니다. 서버는 요청을 받아 코드를 실행하고 결과를 돌려줍니다(Step 94에서 직접 만들어 봤죠). 그 코드를 읽을 수 있다면 정답을 "추측"이 아니라 "추론"할 수 있습니다. PHP를 배운 적 없어도 괜찮습니다 — 읽기에 필요한 것은 코드 감각뿐이고, 여러분은 이미 파이썬과 C를 압니다.
1. 학습 목표
이 챕터를 끝내면 다음을 할 수 있습니다:
- PHP 소스에서 include 경로와 비교 로직을 찾아 읽는다
../경로 탐색(Path Traversal)으로 의도 밖의 파일을 여는 원리를 설명한다- 서버의 변환(인코딩) 로직을 파이썬으로 역순 계산해 정답을 복원한다
- 명령어 인젝션을 성공시키고 성립 조건 두 가지를 설명한다
- 금지 목록(블랙리스트) 방어의 한계와 hidden input의 실체를 설명한다
2. 배경 지식 — 오늘의 도구와 개념
오늘의 도구 한눈에 보기
| 구분 | 내용 |
|---|---|
| 언어·환경 | PHP는 "읽기"만(설치 불필요), 실험은 파이썬 + 리눅스 쉘(WSL) |
| 오늘의 명령어 | grep -i 패턴 파일, 쉘의 #(주석), 파이썬 base64, bytes.fromhex, [::-1] |
| 필요한 개념 | PHP 최소 문법($변수, include, $_POST, system()), 경로 탐색(../), 인코딩 역산, 명령어 인젝션, 블랙리스트의 한계 |
| 오늘의 산출물 | decode_lab.py — 인코딩 역산기, 인젝션 실험 기록("내 입력 / 최종 명령" 한 쌍) |
2-1. PHP 읽기의 최소 문법
PHP는 웹 서버에서 오래 사랑받은 언어입니다. 읽기용으로 네 가지만 알면 됩니다.
$secret = "abcd"; // 변수는 $로 시작
include "includes/secret.inc"; // 다른 파일을 이 자리에 끼워 넣기
if ($_POST["key"] == $secret) {...} // POST로 온 값과 비교
system("grep $word dictionary.txt"); // 운영체제 명령 실행 — 위험 표지판!
$_GET, $_POST는 요청으로 온 입력, include는 파일 끼워 넣기, system()·passthru()는 서버에서 명령어를 실행하는 함수입니다. 마지막 것이 보이면 공격자의 눈이 반짝입니다 — 입력이 저 안으로 들어가면 명령어가 되니까요.
2-2. 경로 탐색 — ../의 사다리
../../etc/passwd 같은 형태를 경로 탐색(Path Traversal)이라고 합니다. ..은 "한 단계 위 폴더". 서버가 include "pages/" + 입력처럼 파일을 끼워 넣을 때, 입력으로 ../../../../를 쌓으면 서버의 파일 구조를 거슬러 올라가 원래 보여 주려던 곳 밖의 파일에 닿습니다. 루트(최상위) 위로는 못 올라가므로, ../는 넉넉하게 많이 써도 해가 없습니다.
2-3. 명령어 인젝션 — 검색창이 터미널이 되는 순간
서버 코드가 이렇게 생겼다고 합시다.
system("grep " + 입력 + " dictionary.txt")
입력이 hello면 grep hello dictionary.txt — 정상 검색. 그런데 입력이 hello /etc/passwd #라면? 명령은 grep hello /etc/passwd # dictionary.txt가 되어 전혀 다른 파일을 뒤집니다. 입력이 데이터가 아니라 명령의 일부가 되었습니다. SQL 인젝션과 정확히 같은 뿌리 — 코드와 데이터의 혼합입니다.
PHP의 system()은 문자열을 쉘(/bin/sh -c)에 통째로 넘깁니다. 그래서 공백은 인자 구분이 되고, #은 "이 뒤는 주석"이 되고, ;는 "명령 하나 끝, 다음 명령 시작"이 됩니다. 이 쉘 문법이 우리의 무기입니다.
2-4. 인코딩 역산 — 변환은 되돌릴 수 있다
서버 코드에 "입력을 이렇게 변환해 비교한다"는 로직이 있으면, 그 변환을 역순으로 돌리면 정답이 나옵니다. 변환은 암호가 아닙니다(Step 50의 Base64 교훈). 파이썬 대화형 모드에서 한 단계씩 되감으면 됩니다.
3. 따라 하기
3-1. Natas 6 → 7: include 경로 따라가기
이번 페이지에는 "View sourcecode" 링크가 있습니다. 눌러서 PHP를 읽습니다.
읽는 법: 코드에 이런 줄이 있습니다 — include "includes/secret.inc";. 비밀이 이 파일에 있다는 선언입니다.
입력: 주소창에 그 경로를 직접 칩니다.
http://natas6.natas.labs.overthewire.org/includes/secret.inc
출력 예시: 비밀번호가 담긴 파일 내용.
왜 하는가: include의 대상은 서버의 파일일 뿐이고, 접근 제한이 없으면 누구든 직접 열 수 있습니다. 코드를 읽고, 코드가 가리키는 곳으로 간다 — 오늘의 기본 동작입니다.
3-2. Natas 7 → 8: 경로 탐색 사다리
힌트(소스의 주석)를 읽으면 비밀번호가 /etc/natas_webpass/natas8에 있다고 합니다. 페이지의 링크들이 index.php?page=home 같은 형태인 것에 주목하세요.
입력: page 파라미터를 사다리로 바꿉니다.
http://natas7.natas.labs.overthewire.org/index.php?page=../../../../etc/natas_webpass/natas8
출력 예시: 다음 비밀번호가 페이지에 표시됩니다.
읽는 법: 서버가 "pages 폴더 안의 파일"을 기대했지만, ../ 사다리가 서버의 파일 구조 밖으로 데려갔습니다.
왜 하는가: 파일을 끼워 넣는 파라미터를 보면 사다리를 떠올리는 것 — 이것이 경로 탐색의 조건 반사입니다.
3-3. Natas 8 → 9: 인코딩 로직 역산 (로컬 실측)
소스를 보면 서버의 검증이 이렇게 생겼습니다.
$encodedSecret = "...(16진 문자열)...";
function encodeSecret($secret) {
return bin2hex(strrev(base64_encode($secret)));
}
if ($_POST["secret"] == $encodedSecret) { ... }
읽는 법: 입력을 ①base64 인코딩 → ②뒤집기(strrev) → ③16진 변환(bin2hex) 한 것과 비교합니다. 그런데 기대값($encodedSecret)이 소스에 적혀 있습니다!
이 로직은 PHP 없이 파이썬으로 완전히 재현됩니다. 서버 역할과 공격자 역할을 둘 다 해 봅시다 (decode_lab.py):
"""Natas 8 스타일: 인코딩 로직 역산 실험."""
import base64
# 서버 쪽 로직(PHP)과 동일: base64_encode -> strrev -> bin2hex
def encode_secret(secret: str) -> str:
step1 = base64.b64encode(secret.encode()) # ① base64
step2 = step1[::-1] # ② 뒤집기
step3 = step2.hex() # ③ 16진수
return step3
# 1) 우리가 '서버' 역할로 정답을 인코딩해 본다
answer = "natas8_style_secret_abc123"
encoded = encode_secret(answer)
print("서버 소스에 적힌 $encodedSecret:", encoded)
# 2) 공격자: 소스에서 훔친 문자열을 역순으로 되감는다
step1 = bytes.fromhex(encoded) # ③의 역: hex -> bytes
step2 = step1[::-1] # ②의 역: 다시 뒤집기
step3 = base64.b64decode(step2) # ①의 역: base64 해독
print("역산 결과:", step3.decode())
출력 (2026-09-09 실측):
서버 소스에 적힌 $encodedSecret: 3d4d6a4d784d6d59683946646c4a33596c4e33586c785765304e3358344d585930466d62
역산 결과: natas8_style_secret_abc123
읽는 법: 변환 A→B→C의 역산은 반드시 C→B→A 역순입니다. 실제 레벨에서는 소스의 16진 문자열을 복사해 같은 세 줄에 넣으면 끝입니다.
왜 하는가: 변환은 열쇠 없이 되돌아갑니다. "난독화"는 느리게 할 뿐 막지 못합니다 — 정답이 소스에 있으니까요.
3-4. Natas 9 → 10: 명령어 인젝션 첫 성공
이번 페이지는 "단어를 검색해 주는" 폼이고, 소스에는 그 위험 표지판이 있습니다 — 입력이 grep 명령 안으로 들어갑니다.
입력: 폼의 글상자에:
.* /etc/natas_webpass/natas10 #
출력 예시: 대상 파일의 내용 — 다음 비밀번호가 목록처럼 출력됩니다.
읽는 법: 서버가 만든 최종 명령은 grep -i .* /etc/natas_webpass/natas10 # dictionary.txt입니다. .*는 "모든 줄에 매칭"하는 정규식, #은 뒤를 주석 처리해 원래 검색 대상(dictionary.txt)을 무효화합니다. 검색창이 터미널이 되었습니다.
왜 하는가: 여러분은 방금 서버에서 원래 의도에 없던 명령을 실행했습니다. 이것이 명령어 인젝션이며, 웹 취약점 왕좌의 단골입니다.
3-5. 내 랩에서 재현 — grep 인젝션 실험 (로컬 실측)
이 공격의 원리는 리눅스 쉘만 있으면 재현됩니다. WSL이나 리눅스 터미널에서 미니 환경을 만듭니다.
입력:
mkdir -p /tmp/natas_lab && cd /tmp/natas_lab
printf 'apple\nbanana\ncat\n' > dictionary.txt
printf 'natas10_fake_password_XYZ987\n' > webpass.txt
먼저 정상 검색 (2026-09-09 실측):
$ grep -i apple dictionary.txt
apple
이제 서버의 system()이 하듯, 조립된 문자열을 쉘에 통째로 넘겨 봅니다. (sh -c "문자열"이 PHP system()의 동작 방식입니다.)
$ key=".* /tmp/natas_lab/webpass.txt #"
$ cmd="grep -i $key dictionary.txt"
$ echo "$cmd"
grep -i .* /tmp/natas_lab/webpass.txt # dictionary.txt
$ sh -c "$cmd"
grep: ..: Is a directory
/tmp/natas_lab/webpass.txt:natas10_fake_password_XYZ987
(2026-09-09 실측, Ubuntu 24.04 WSL.)
읽는 법: #가 주석으로 처리되어 맨 뒤의 dictionary.txt는 결과에서 사라졌고, 끼워 넣은 webpass.txt의 내용이 출력됐습니다. 앞의 오류 줄(..: Is a directory)은 쉘이 .*를 파일명 패턴으로도 해석해 .과 .. 폴더까지 인자로 넣었기 때문입니다 — 오류가 섞여도 비밀 줄은 정확히 출력됩니다. 실제 서버에서는 작업 디렉터리에 따라 이 오류가 없을 수 있습니다.
왜 하는가: "내 입력이 들어간 최종 명령"을 눈으로 보는 것이 인젝션 이해의 절반입니다. write-up에는 입력만 적지 말고 이 한 쌍을 적으세요 — 내 입력 / 최종 명령.
3-6. Natas 10 → 11: 필터 뚫기
같은 문제인데 이번엔 필터가 있습니다 — ;, |, & 같은 문자를 막습니다.
입력: 3-4와 같은 입력을 넣어 봅니다.
출력 예시: 그대로 통과 — 비밀번호 획득.
읽는 법: 우리의 입력에는 ;|&가 없습니다. .*와 공백, #만으로 구성되니까요. 필터는 공격자가 쓸 것이라고 개발자가 상상한 문자만 막습니다.
왜 하는가: "금지 목록"(블랙리스트) 방어의 구조적 한계입니다. 막을 것을 열거하는 방어는 항상 열거 밖의 공격에 열립니다. 필터 목록을 볼수록 공격 후보가 늘어난다고 생각하세요 — 목록에 없는 것이 답입니다.
3-7. hidden input — 보이지 않을 뿐, 보내진다
Natas의 다른 문제들(및 수많은 실제 사이트)에는 이런 폼이 있습니다.
<input type="hidden" name="admin" value="0">
이 "숨겨진" 값은 안전할까요? hidden은 "화면에 안 그릴 뿐" 요청에는 그대로 실립니다 — 브라우저의 렌더링 규칙일 뿐, 전송 규칙이 아닙니다. 개발자 도구 Elements에서 이 태그를 찾아 value를 1로 고치고 폼을 전송하거나, curl로 -d "admin=1" 옵션을 쓰면 서버는 고쳐진 값을 받습니다. Step 102의 제1원리가 여기서도 그대로입니다 — 클라이언트에 맡겨진 값은 클라이언트의 것.
3-8. 따라 하기 정리 — 오늘의 다섯 수법
획득한 비밀번호를 정리하기 전에, 수법 자체를 이름으로 고정합니다.
- include 추적 — 코드가 끼워 넣는 파일을 직접 열어 본다 (Natas 6)
- 경로 탐색 — 파일 파라미터에
../사다리를 태운다 (Natas 7) - 인코딩 역산 — 변환 로직을 역순으로 되감는다 (Natas 8)
- 명령어 인젝션 — 입력을 명령의 문법으로 승격시킨다 (Natas 9)
- 필터 우회 — 금지 목록에 없는 구조로 같은 목적을 이룬다 (Natas 10)
이 다섯 가지는 각각 독립 기술이 아니라 하나의 습관 — "서버 코드를 먼저 읽는다" — 의 다섯 표정입니다. 페이지가 열리면 제일 먼저 소스 버튼을 찾는 손가락, 그것이 이 구간을 지난 사람의 첫 반응입니다.
4. 미션과 연습문제
미션 — "클라이언트 불신" 사례집과 인젝션 재현
- natas6~10 전 레벨을 통과하고, 각 레벨의 서버 코드에서 "구멍이 된 한 줄"을 발췌해 write-up에 정리합니다
- 3-3의
decode_lab.py를 직접 실행해 인코딩→역산이 맞물리는 것을 확인하고, 위키에 재사용 가능한 형태로 보관합니다 - 3-5의 grep 인젝션 실험을 재현하고, "내 입력 / 최종 명령" 한 쌍을 write-up에 적습니다
- 위키에
클라이언트불신.md를 작성합니다 — 사례 4개(헤더 조작, 쿠키 변조, hidden input, JS 검증 우회)를 "서버가 믿은 것 / 공격자가 한 일" 표로 정리합니다
연습문제
문제 1. include "includes/secret.inc"; 한 줄이 공격자에게 주는 정보 두 가지를 말해 보세요.
문제 2. 경로 탐색에서 ../를 "넉넉하게 많이" 써도 되는 이유를 설명해 보세요.
문제 3. 변환 순서가 base64 → 뒤집기 → hex일 때, 역산의 순서와 각 단계에 해당하는 파이썬 함수를 말해 보세요.
문제 4. 명령어 인젝션이 성립하는 조건 두 가지(입력 측 / 서버 측)를 말하고, .* /etc/natas_webpass/natas10 #에서 #가 하는 일을 설명해 보세요.
5. 모범 답안과 완료 기준
미션 모범 답안
레벨별 "구멍이 된 한 줄" (서버 풀이는 출력 예시 기준):
natas6: include "includes/secret.inc"; → 그 경로를 직접 열람
natas7: page 파라미터가 파일 경로로 쓰임 → ?page=../../../../etc/natas_webpass/natas8
natas8: $encodedSecret가 소스에 공개됨 → 역산(3-3)으로 원문 복원
natas9: system/passthru 안으로 입력 결합 → .* /etc/natas_webpass/natas10 #
natas10: ; | & 만 필터링 → 같은 입력이 그대로 통과
인젝션 write-up의 모범 형식 (3-5 실측 기준):
내 입력: .* /tmp/natas_lab/webpass.txt #
최종 명령: grep -i .* /tmp/natas_lab/webpass.txt # dictionary.txt
결과: 대상 파일의 비밀 줄 출력 (dictionary.txt는 # 뒤라 무시됨)
검증하는 법: ① decode_lab.py의 "역산 결과"가 원래 문자열과 일치하는가. ② 로컬 grep 실험에서 dictionary.txt의 줄(apple 등)이 결과에 없고 webpass.txt의 줄만 나오는가 — 그렇다면 # 주석이 먹은 것. ③ 사례집 표의 네 행이 전부 채워졌는가. 전부 ‘예’이면 완성입니다.
연습문제 해답
문제 1 해답. ① 비밀이 이 파일에 들어 있다는 사실, ② 그 파일의 서버 상 경로. 접근 제한이 없으면 주소창에 그 경로를 쳐서 직접 열 수 있습니다. 코드 한 줄이 곧 지도입니다.
문제 2 해답. ..는 "한 단계 위"이고, 루트(/)에서는 더 위로 올라가도 계속 루트에 머물기 때문입니다. 그래서 깊이를 모를 때 ../를 6~8개 넉넉히 써도 부작용이 없습니다.
문제 3 해답. 역산은 반드시 C→B→A 역순입니다: ③의 역 bytes.fromhex()(hex→바이트) → ②의 역 [::-1](다시 뒤집기) → ①의 역 base64.b64decode(). 순서가 틀리면 깨진 바이트가 나오거나 binascii.Error: Incorrect padding 같은 오류가 납니다 (3-3 실험에서 순서를 틀리면 정확히 이 오류가 나는 것을 확인했습니다 — 2026-09-09 실측).
문제 4 해답. 조건은 ① 입력이 명령 문자열에 그대로 결합되고, ② 그 문자열을 서버가 쉘로 실행하는 것 — 둘 다여야 합니다. #는 쉘에서 "이 뒤는 주석"이라는 뜻이라, 원래 명령의 나머지(dictionary.txt)를 무효화해 우리가 지정한 파일만 검색 대상으로 남깁니다. 3-5 실측에서 dictionary.txt의 줄이 결과에서 사라진 것이 그 증거입니다.
완료 기준 체크리스트
- [ ] PHP 소스에서 include 경로와 비교 로직을 찾아 읽을 수 있다
- [ ]
../경로 탐색의 원리와 "넉넉히 쓰는" 이유를 설명할 수 있다 - [ ] 인코딩 로직을 파이썬으로 역순 계산해 정답을 복원했다
- [ ] 명령어 인젝션을 서버(예시)와 로컬(실측) 양쪽에서 이해했다
- [ ] "내 입력 / 최종 명령" 한 쌍을 write-up에 적는 습관이 생겼다
- [ ] 필터(금지 목록)의 한계를 Natas 10 사례로 설명할 수 있다
- [ ] hidden input이 왜 클라이언트의 것인지 시연할 수 있다
6. 흔한 실수와 해결
벽 1. ../ 사다리가 안 먹힌다
증상: 경로 탐색 입력에 오류 또는 빈 페이지 (서버에서, 출력 예시).
원인: 개수가 부족하거나(깊이를 못 올라감), 서버가 ../를 걸러 내거나, 최종 파일명이 틀렸습니다.
해결: ../를 넉넉히(6~8개) 쓰세요 — 루트 위로는 못 올라가므로 많아도 안전합니다. 걸러낸다면 ....// 같은 변형을 실험해 봅니다.
벽 2. 인코딩 역산 결과가 깨진다
증상 (2026-09-09 실측, 순서를 틀렸을 때):
binascii.Error: Incorrect padding
원인: 역순의 순서가 틀렸습니다. 변환 A→B→C의 역산은 반드시 C→B→A입니다.
해결: 파이썬 대화형 모드에서 한 단계씩 중간 결과를 출력하며 검증하세요. 어느 단계에서 읽히기 시작하는지 보면 순서 오류를 찾을 수 있습니다.
벽 3. 인젝션 입력이 그냥 검색된다
증상: 명령 주입 문자가 결과 없이 끝납니다.
원인: 따옴표나 공백이 서버의 명령 조립과 어긋났습니다. 서버 코드의 정확한 형태(따옴표 위치, 옵션)에 맞춰야 합니다.
해결: 소스의 system/passthru 줄을 글자 단위로 읽고, "내 입력이 들어간 최종 명령"을 종이에 적어 보세요. 명령어 전체를 먼저 상상하는 것이 인젝션의 절반입니다.
벽 4. .* 옆에 이상한 오류 줄이 섞여 나온다
증상 (2026-09-09 실측):
grep: ..: Is a directory
원인: 쉘이 .*를 정규식이 아니라 파일명 패턴(글롭)으로 먼저 해석해, 현재 폴더의 .·..까지 인자로 넣은 경우입니다.
해결: 고칠 필요가 없는 경우가 많습니다 — 오류 줄이 섞여도 대상 파일의 내용은 정상 출력됩니다 (3-5 실측에서도 오류 줄 아래에 비밀이 나왔습니다). 결과에서 파일명: 접두사가 붙은 줄을 읽으면 됩니다.
벽 5. 필터 우회 문자가 다 막혀 있다
증상: ;, |, &가 전부 거부됩니다.
원인: 필터는 문자만 막습니다. 문자가 아닌 구조(정규식의 .*, 주석의 #)는 안 막힐 수 있습니다.
해결: "연결 문자 없이 목적을 이루는 입력"을 설계하세요. 3-6의 답이 바로 그것입니다 — 우리의 입력에는 막힌 문자가 하나도 없었습니다.
7. 정리
오늘의 개념
| 개념 | 한 줄 설명 |
|---|---|
| include | PHP가 다른 파일을 끼워 넣는 문법 — 그 경로가 곧 지도 |
| 경로 탐색 | ../ 사다리로 의도 밖의 파일에 닿는 공격 |
| 인코딩 역산 | 변환 A→B→C를 C→B→A로 되감기 — 난독화는 방어가 아님 |
| 명령어 인젝션 | 입력이 명령의 문법으로 승격 — system() + 입력 결합이 조건 |
#(쉘 주석) |
"이 뒤는 무시" — 원래 명령의 꼬리를 끊는 무기 |
| 블랙리스트 | 막을 문자를 열거하는 방어 — 열거 밖의 공격에 항상 열림 |
| hidden input | 화면에 안 그릴 뿐 요청엔 실림 — 클라이언트의 값 |
오늘의 명령어와 코드
| 명령어·코드 | 하는 일 |
|---|---|
grep -i 패턴 파일 |
대소문자 무시 검색 — 인젝션의 무대 |
.* (정규식) |
"모든 줄에 매칭" — 검색을 전체 출력으로 바꿈 |
sh -c "명령 문자열" |
문자열을 쉘로 실행 — PHP system()의 동작 방식 |
bytes.fromhex() |
③ hex 되돌리기 (역산 첫째) |
[::-1] |
② 뒤집기 되돌리기 (역산 둘째) |
base64.b64decode() |
① base64 되돌리기 (역산 셋째) |
명령어보다 중요한 감각
코드를 읽는 공격자는 추측하지 않습니다. 로직의 구멍을 읽어 냅니다. 페이지가 열리면 소스 버튼부터 누르고, include·비교문·system() 세 종류의 줄을 찾습니다 — 파일을 끼우는 곳, 정답을 비교하는 곳, 명령을 실행하는 곳. 그 세 곳이 웹 서버 코드의 약한 부위입니다.
소스를 보여 주지 않는 실제 대상에서도 이 훈련은 그대로 작동합니다 — 코드 대신 반응을 읽는 것입니다. 입력을 바꿨을 때 출력이 달라지는 방식이 곧 코드의 윤곽이고, 오류 메시지에 새어 나오는 경로와 함수명이 지도가 됩니다. 그래서 운영 서버에서 상세 오류를 끄는 것이 방어의 기본 상식입니다. 오늘 만난 취약점들 — 인젝션, 경로 탐색, 깨진 인증 — 은 OWASP Top 10(국제 웹 보안 커뮤니티의 위험 취약점 명부)의 단골 손님입니다. 워게임에서 배운 것은 변종이 아니라 현역입니다. 그리고 방어는 이미 배웠습니다 — 명령 실행 함수에 입력을 넣지 않고, 걸러낼 것이 아니라 허용할 것만 열거하고, 경로를 사용자 입력에서 떼어 놓는 것. 공격을 본 뒤의 이 문장들은 더 이상 추상이 아닙니다.
전부 체크되면 Step 103 완료입니다. 사이드바의 체크박스를 눌러 진도를 저장하세요.