리눅스 시스템 기초 · 입문편 — 본문의 "N편"은 입문 과정 번호다. 번호별 글과 전체 250편 구성은 통합 로드맵에서 확인할 수 있다.
리눅스 시스템 기초 26 / 50 · Part 3. Linux 명령어 활용
실습 환경: 21편 실습용 샘플 로그 (secure,access_log)
이전 글: 25. cut과 awk
sed(stream editor)는 입력을 한 줄씩 읽어 치환·삭제·추출 한 결과를 출력하는 도구다. 설정 파일 수정 도구로 많이 알려져 있지만, 관제·분석 업무에서는 다음 네 가지 용도가 특히 유용하다.
그리고 반드시 기억해야 할 금지 사항이 하나 있다. 분석 대상 원본 파일에 sed -i를 쓰지 않는다.
sed [옵션] '[주소]명령' 파일
| 명령 | 의미 | 예 |
|---|---|---|
p | 출력 (-n과 함께) | sed -n '10,20p' |
d | 삭제 | sed '/^#/d' (주석 줄 제거) |
s/A/B/ | A를 B로 치환 (첫 번째만) | s/root/****/ |
s/A/B/g | 줄 안의 모든 A 치환 | |
= | 줄 번호 출력 | sed -n '/Accepted/=' |
| 주소 | 의미 |
|---|---|
10 | 10번째 줄 |
10,20 | 10~20번째 줄 |
/패턴/ | 패턴과 일치하는 줄 |
/A/,/B/ | A가 나온 줄부터 B가 나온 줄까지 |
$ | 마지막 줄 |
| 옵션 | 의미 |
|---|---|
-n | 자동 출력 끄기 (p로 지정한 줄만 출력) |
-E | 확장 정규식 ((), +, {n}) |
-i | 원본 파일을 직접 수정 — 증거 파일에는 사용 금지 |
-e | 여러 명령 지정 (;로도 연결 가능) |
치환의 \1, \2는 -E의 괄호로 묶은 부분(캡처 그룹)을 다시 쓰는 것이다.

sed는 한 줄을 패턴 공간(pattern space) 이라는 버퍼에 읽어 명령을 적용하고, 결과를 표준 출력으로 내보낸다. 원본 파일은 읽기만 한다.
-i 옵션을 쓰면 동작이 달라진다. GNU sed는 결과를 임시 파일에 쓴 뒤 원본 이름으로 교체 한다. 즉 11편에서 본 mv와 같은 효과가 생겨서 inode가 바뀌고, mtime·ctime이 현재 시각으로 갱신 된다. 원본 로그에 sed -i를 한 번이라도 실행하면 "분석가가 증거를 수정했다"는 문제가 생기고, 그 파일을 쓰고 있던 프로세스(rsyslog)는 옛 inode에 계속 기록하게 된다.
cd ~/lab
# 1) 구간 추출: 로그인 성공부터 su 실패까지
sed -n '/Accepted password for devuser/,/FAILED SU/p' secure
# 2) 줄 번호로 자르기 (grep -n / sed -n '=' 로 위치 확인)
sed -n '/Accepted password/=' secure
sed -n '127,131p' secure
# 3) 마스킹 — IP 마지막 자리, 계정명
grep "Accepted password" secure \
| sed -E 's/([0-9]+\.[0-9]+\.[0-9]+)\.[0-9]+/\1.xxx/g; s/for [a-z]+/for ****/'
# 4) 설정 파일의 비밀번호 마스킹 (출력만, 원본 유지)
printf 'db_host=10.0.0.200\ndb_user=webapp\ndb_pass=S3cr3t!2026\n' > app.conf
sed -E 's/^(db_pass=).*/\1********/' app.conf
# 5) URL 디코딩 (%XX → 문자)
grep -E '%2f|%27|%20' access_log | awk '{print $7}' \
| while read -r u; do printf '%b\n' "$(echo "$u" | sed 's/%/\\x/g')"; done
# 6) 형식 정규화: 호스트명·PID 제거, sshd-session 통일
sed -E 's/sshd-session\[/sshd[/; s/^([A-Z][a-z]{2} +[0-9]+ [0-9:]+) [^ ]+ ([a-z]+)\[[0-9]+\]: /\1 \2: /' secure | head -3
# 7) 주석·빈 줄 제거로 설정 파일 핵심만 보기
sed -E '/^\s*(#|$)/d' /etc/ssh/sshd_config
샘플 로그에 실행한 실제 결과다.
① 구간 추출
Sep 23 02:19:21 rocky sshd[5224]: Accepted password for devuser from 10.0.0.128 port 51514 ssh2
Sep 23 02:19:21 rocky sshd[5224]: pam_unix(sshd:session): session opened for user devuser(uid=1001) by (uid=0)
Sep 23 02:21:21 rocky sudo[5233]: devuser : user NOT in sudoers ; TTY=pts/1 ; PWD=/home/devuser ; USER=root ; COMMAND=/bin/bash
Sep 23 02:22:21 rocky su[5270]: pam_unix(su-l:auth): authentication failure; logname=devuser uid=1001 euid=0 tty=pts/1 ruser=devuser rhost= user=root
Sep 23 02:22:21 rocky su[5270]: FAILED SU (to root) devuser on pts/1
시작 줄과 끝 줄을 이벤트 이름 으로 지정할 수 있어서, 줄 번호를 몰라도 사건 구간을 보고서에 그대로 옮길 수 있다.
② 마스킹
Sep 23 02:19:21 rocky sshd[5224]: Accepted password for **** from 10.0.0.xxx port 51514 ssh2
db_pass=********
③ URL 디코딩
/view.php?file=../../../etc/shadow
/board.php?id=1' OR '1'='1
/board.php?id=1 UNION SELECT user,pass FROM members--
인코딩된 원문(..%2f..%2f, 1%27%20OR%20%271%27=%271)만 보면 공격 의도가 잘 보이지 않는다. 디코딩하면 경로 탐색 과 SQL Injection(항상 참 조건, UNION 기반 조회) 시도가 명확해진다. 보고서에는 원문과 디코딩 결과를 함께 적는다.
④ 정규화
Sep 23 01:47:45 sshd: Failed password for root from 203.0.113.150 port 51662 ssh2
Sep 23 02:10:09 sshd: Failed password for root from 10.0.0.128 port 59219 ssh2
Sep 23 02:10:18 sshd: Failed password for root from 10.0.0.128 port 40083 ssh2
| 상황 | 위험 | 올바른 방법 |
|---|---|---|
원본 로그에 sed -i | inode·시각 변경, 증거 훼손, 기록 중단 | 사본에서 작업, 결과는 > 새파일 |
| 설정 파일 일괄 수정 | 문법 오류로 서비스 중단 (sshd 등) | -i.bak로 백업, 수정 후 문법 검사 (sshd -t) |
| 보고서에 원문 그대로 | 계정·내부 IP·비밀번호 노출 | 마스킹 규칙 적용 |
| 디코딩 결과만 기록 | 원래 요청을 재현·검색할 수 없음 | 원문 + 디코딩 결과 병기 |
| 탐욕적 정규식 | .*가 의도보다 많이 치환 | [^ ]+처럼 범위 제한 |
설정 파일을 sed로 바꾸는 것은 운영에서 흔하지만, 보안 설정(예: PermitRootLogin)을 바꿀 때는 백업 → 수정 → 문법 검사 → 재시작 → 접속 확인 순서를 지킨다.
sudo sed -i.bak -E 's/^#?PermitRootLogin .*/PermitRootLogin no/' /etc/ssh/sshd_config
sudo sshd -t && sudo systemctl reload sshd
# 사건 구간 추출 → 호스트·PID 정리 → 내부 IP 마스킹 → 새 파일로 저장
sed -n '/02:10:0/,/FAILED SU/p' secure \
| sed -E 's/ rocky ([a-z]+)\[[0-9]+\]:/ \1:/' \
| sed -E 's/10\.0\.0\.([0-9]+)/10.0.0.x/g' \
> incident_20260923_timeline.txt
sha256sum incident_20260923_timeline.txt
[입력] 원본 secure / access_log (읽기 전용, 사본)
↓
[sed 추출] 사건 구간 /Accepted/,/FAILED SU/
↓
[sed 디코딩] 웹 공격 URL → 경로 탐색, SQLi 확인
↓
[sed 정규화] 서버별 형식 차이 제거 → 여러 서버 로그를 한 타임라인으로
↓
[sed 마스킹] 계정·내부 IP·비밀번호 가림
↓
[출력] 보고서 첨부 파일 + 해시
s)·삭제(d)·출력(p) 하며, 기본적으로 원본을 바꾸지 않는다./A/,/B/p로 이벤트 기준 구간 추출 이 가능하다.-E와 캡처 그룹(\1)으로 마스킹·정규화 규칙을 만든다.sed 's/%/\\x/g' + printf '%b'로 디코딩해 공격 의도를 확인한다.sed -i는 inode·시각을 바꾼다. 증거 원본에는 절대 쓰지 않고, 설정 파일에는 -i.bak + 문법 검사를 함께 쓴다.다음 글 「27. 파이프라인과 리다이렉션」 에서는 지금까지 당연하게 써 온 |, >, 2>/dev/null의 원리를 정리한다. 표준 입력·출력·오류(파일 디스크립터 0·1·2)의 흐름을 이해하고, 분석 결과를 안전하게 저장하고 기록하는 방법(tee, set -o pipefail) 을 다룬다.