
부트캠프에서 배운 내용을 정리한 글입니다.
점검을 위해 금융기관 등 민감한 환경을 방문했을 때, 단순 분석을 위해 별도의 소프트웨어(특히 Python과 같은 런타임 환경)을 설치하는 방식은 바람직하지 않다. 해당 환경의 무결성을 해칠 우려가 있으며, 외부 의존성을 남기는 것 자체가 감사·운영 측면에서 리스크로 받아들여진다. 이러한 이유로, 필요하다면 분석 기능을 바이너리 형태로 패키징하거나, 이미 시스템에 내장된 도구를 활용하는 접근이 권장된다.
특히 다양한 리눅스 배포판을 다루는 현업에서는 POSIX 호환 셸(SH/Bash) 기반의 스크립트를 우선적으로 고려하는 것이 가장 현실적이다.
네트워크는 본질적으로 “메모리” 기반의 처리 구조를 갖는다. 들어오는 패킷은 OS에 도달하기 전에 방화벽이나 NIC 레벨 큐에서 선처리되고, 이 과정에서 룰과 정책과 매칭되어 필터링된다.
만약 방화벽 이후 추가적인 보안 레이어(예: Zero Trust 검증 단계)를 삽입하고자 한다면, 기존 OS 구조 내에서 단순히 기능을 ‘얹는’ 방식으로는 어렵다. 패킷 처리 경로 자체를 다시 설계해야 가능하며, 이는 상당한 난이도를 가진 작업이다.
파일 분석 측면에서는, 파일의 정체성은 확장자가 아니라 헤더(Signature) 에 의해 결정된다. DLL, SYS, EXE는 공통적으로 PE(Portable Executable) 구조를 기반으로 하며, 운영체제는 내부 헤더를 기준으로 파일을 해석한다.
단, Windows는 사용자 편의성을 위해 확장자를 실행 프로그램 연결의 기준으로 삼는데, 이로 인해 확장자를 조작하는 방식의 사회공학 공격이나 오용이 발생하기도 한다.
실제로 포렌식이나 CTF 문제에서는 파일 확장자를 의도적으로 제거하거나 잘못된 확장자를 부여하여, 분석자가 헤더 기반 식별 능력을 갖추고 있는지를 평가한다.
리눅스 시스템은 모든 것을 파일로 다루는 철학을 가지고 있다. 하드디스크의 데이터, 디바이스 장치, 네트워크 소켓, 프로세스 간 통신까지 모두 파일 입출력의 형태로 처리된다.
파일 입출력은 리눅스 사용의 핵심이며, 이를 바탕으로 다양한 텍스트 처리 명령어를 활용하면 복잡한 데이터 가공과 분석을 단 몇 줄의 명령으로 자동화할 수 있다.
구분자를 무엇으로 설정할 것인가?
$ time cut -d " " -f1,3 data
time으로 명령어 처리시간을 보았을 때

cut 명령어는 데이터를 버퍼에 담지 않기 때문에 재가공하는 것이 어렵다. 또한 필드 구분자가 일정하지 않으면 사용이 매우 난감해진다. 다만 구분자가 의미없는 첫번째 필드를 볼 때는 cut 명령어의 속도와 편의성이 압도적이다. 두번째 세번째 부터는 조금 애매해진다.
스크립트에서는 쉘에서 처리하는 속도를 빠르게 하는 것이 최우선된다. 속도도 속도이지만, 시스템에 부하가 걸릴 수 있기 때문이다.



diff 명령어는 스크립트 명령어에 사용하기에는 적합하지 않다. 일상에서 사용하는건 괜찮다.


-i옵션은 대소문자를 구분하지 않는다.






공백을 제외한 구분자는 반드시 구분자를 명시해야 한다.






hjl3066@HCR-Zenbook:/mnt/c/LAB$ awk '$2 = 40{print $1,$2"살 입니다."}' data | head -2
hong 40살 입니다.
park 40살 입니다.
비교가 아닌 대입을 한다. $2 필드의 값을 40으로 변경하고 조건문에서는 항상 참으로 간주한다. 따라서 모든 줄에 대해 $2 가 40으로 바뀌고 출력된다.
hjl3066@HCR-Zenbook:/mnt/c/LAB$ awk '$2 == 49{print $1,$2"살 입니다."}' data
son 49살 입니다.
숫자 비교 연산자로 $2 가 정수 49와 같을 때만 조건이 참이 된다.
hjl3066@HCR-Zenbook:/mnt/c/LAB$ awk '$2 ~ /49/{print $1,$2"살 입니다."}' data
son 49살 입니다.
~ 는 정규표현식 연산자로 $2 필드 안에 49라는 문자열이 포함되어 있으면 조건이 참이 된다. 부분 문자열로 49만 포함되면 모두 매칭된다.
이건 crontab과의 연관성이 좋다.

세 번째 필드($3)를 하이픈(-)을 기준으로 분리해서 첫 번째 부분 phone[1] 만 출력하는 명령이다. 동작 원리는 다음과 같다.
$3 필드를 -기호로 나누어 배열 phone에 저장한다.$1 필드와 분리된 전화번호의 앞자리를 출력한다.#1. 1000번 이상의 사용자를 사용자 이름과 UID를 출력하시오.




쉘 스크립트 작성 전 확인해야할 부분
#!/bin/bash → #!/bin/sh[user@lb ~]$ which lsalias ls=’ls —color=auto’/usr/bin/ls명령어와 언어에 익숙해지도록 많이 다루는 것이 중요하다.
UID기반으로 접속한 사용자를 추적할 수 있다. 하지만 더 좋은 방법은 터미널로 추적하는 것이다. 침해사고 분석 등에서 유용하게 사용되는 방법은 로그한 사용자의 계정이 접속한 터미널이다.
ps -ef | grep pts/1
[user@localhost ~]$ lastlog | grep -v "**Never logged in**"
Username Port From Latest
root tty1 Tue Sep 30 15:32:03 +0900 2025
user pts/0 10.0.2.1 Thu Oct 30 17:05:29 +0900 2025
testuser tty1 Thu Oct 30 17:14:30 +0900 2025
[user@localhost ~]$ last
user pts/0 10.0.2.1 Thu Oct 30 17:05 still logged in
user tty1 Thu Oct 30 17:05 still logged in
reboot system boot 5.14.0-570.39.1. Thu Oct 30 17:04 still running
user tty1 Thu Oct 30 16:08 - down (00:01)
reboot system boot 5.14.0-570.39.1. Thu Oct 30 15:03 - 16:10 (01:06)
user pts/1 10.0.2.1 Tue Oct 21 13:52 - 13:52 (00:00)
user pts/0 10.0.2.1 Tue Oct 21 13:52 - 17:06 (03:13)

실제로 testuser로 접속해 있지만 표시되지 않는다. 커널 취약점으로 자신의 터미널을 숨기는 작업을 하는 이상이 아니고서야 os 레벨에서 터미널을 숨길 수 있는 방법은 없다.

확실한 테스트를 위해 외부에서 testuser로 접속했다.
pts/1 tty로 접속한 testuser가 보이고 위의 명령어를 통해 [ ]를 확인할 수 있다.
TTY는 원래는 물리 콘솔에 직접 연결된 입력 장치를 의미한다. Linux에서는 /dev/tty1, /dev/tty2 와 같은 로컬 콘솔이 바로 TTY다. 서버 현장에서 실제로 TTY로 접속하는 경우는 거의 없다. 직접 서버 앞에 앉아서 로그인해야 하기 때문이다.
TTY 세션이 등장한다는 건 “해당 시스템에 물리적으로 접근한 사람"이 존재한다는 뜻이다. 따라서 침해사고 포렌식에서는 내부자 행위 가능성, 또는 누군가 서버실 접근 권한을 악용했을 가능성까지 고려한다.
PTS (Pseudo Terminal Slave)는 원격 접속(SSH 등)에서 생성되는 가상 터미널이다. /dev/pts/0, /dev/pts/1 형태이며, 접속할 때마다 동적으로 번호가 증가한다. 대부분의 서버 운영 환경에서는 모든 관리 작업이 원격으로 이루어지므로 거의 모든 접속은 PTS라고 보면 된다.
SAN 스위치는 Fibre Channel 기반의 스토리지 네트워크 장비이며, 서버의 “콘솔 접속”을 의미하는 장치는 아니다.
물리 접근 또는 관리자용 원격 콘솔을 사용한 경우에만 TTY가 생성된다. 일반 사용자가 SAN 스위치를 통해 tty를 따는 일은 사실상 없다.
만약 침해사고 분석에서 tty 세션이 발견된다면?
운영 보안은 ansible, 클라우드 보안은 쿠버네티스를 공부하면 매우 좋다.

변수를 내부에 넣을거면 반드시 더블쿼테이션을 사용해야 한다.

계정을 추적하기 위해 터미널 넘버에 집중해야 사용자 트래킹에 유리하다.
su 접속과 로그의 시간을 일치여부를 매칭하여 이 두 요소를 시각화하기. 왜냐하면 지금 su 접속과 1시간전 su 접속 기록이 미스매칭되면 안되기 때문.
su를 하면 남는 흔적(로그)를 활용하여 탐지 스크립트 작성.

힌트.

#!/bin/bash
read -p "감시할 사용자 이름을 입력하세요: " USER_TO_WATCH
echo "사용자 '$USER_TO_WATCH' 로그인/로그아웃 감시를 시작합니다."
echo "중지하려면 [Ctrl + C]를 누르세요."
echo
IS_LOGGED_IN=0
while true; do
if who | grep -w "$USER_TO_WATCH" > /dev/null; then
if [ $IS_LOGGED_IN -eq 0 ]; then
LOGIN_TIME=$(date "+%Y-%m-%d %H:%M:%S")
echo "[+] $USER_TO_WATCH 로그인 감지: $LOGIN_TIME"
IS_LOGGED_IN=1
fi
else
if [ $IS_LOGGED_IN -eq 1 ]; then
LOGIN_TIME=$(date "+%Y-%m-%d %H:%M:%S")
echo "[+] $USER_TO_WATCH 로그아웃 감지: $LOGIN_TIME"
IS_LOGGED_IN=0
fi
fi
sleep 5
done

#!/bin/bash
read -p "감시할 사용자 이름을 입력하세요: " USER_TO_WATCH
echo "사용자 '$USER_TO_WATCH'의 로그인/로그아웃 및 su 감시를 시작합니다."
echo "중지하려면 [Ctrl + C]를 누르세요."
echo
IS_LOGGED_IN=0
LOG_FILE="/var/log/secure"
if [ ! -f "$LOG_FILE" ]; then
LOG_FILE="/var/log/messages"
fi
echo "[DEBUG] 감시 중인 로그 파일: $LOG_FILE"
# 로그 감시 백그라운드
tail -n 0 -F "$LOG_FILE" | while read LINE; do
if echo "$LINE" | grep -q "pam_unix(su"; then
# su로 세션이 열린 경우
if echo "$LINE" | grep -q "session opened for user $USER_TO_WATCH"; then
SU_TIME=$(date "+%Y-%m-%d %H:%M:%S")
echo "[!] su 명령을 통한 '$USER_TO_WATCH' 로그인 감지: $SU_TIME"
fi
# su로 세션이 닫힌 경우
if echo "$LINE" | grep -q "session closed for user $USER_TO_WATCH"; then
SU_TIME=$(date "+%Y-%m-%d %H:%M:%S")
echo "[!] su 명령을 통한 '$USER_TO_WATCH' 로그아웃 감지: $SU_TIME"
fi
fi
done &
# 로그인/로그아웃 감시 루프
while true; do
if who | grep -w "$USER_TO_WATCH" > /dev/null; then
if [ $IS_LOGGED_IN -eq 0 ]; then
LOGIN_TIME=$(date "+%Y-%m-%d %H:%M:%S")
echo "[+] $USER_TO_WATCH 로그인 감지: $LOGIN_TIME"
IS_LOGGED_IN=1
fi
else
if [ $IS_LOGGED_IN -eq 1 ]; then
LOGOUT_TIME=$(date "+%Y-%m-%d %H:%M:%S")
echo "[-] $USER_TO_WATCH 로그아웃 감지: $LOGOUT_TIME"
IS_LOGGED_IN=0
fi
fi
sleep 5
done
문제 해결 과정은 다음과 같다.
/var/log/secure 와 /var/log/messages 를 통한 로그 확인
Nov 3 02:25:37 localhost su[5903]: (to testuser) user on pts/1
초기 코드 작성:
# 로그 파일 결정
LOG_FILE="/var/log/secure"
if [ ! -f "$LOG_FILE" ]; then
LOG_FILE="/var/log/messages"
fi
echo "[DEBUG] 감시 중인 로그 파일: $LOG_FILE"
# 로그 감시 백그라운드
tail -n 0 -F "$LOG_FILE" | while read LINE; do
echo "[DEBUG] 새 로그 감지: $LINE"
if echo "$LINE" | grep -q "su"; then
echo "[DEBUG] su 관련 로그 발견"
if echo "$LINE" | grep -q "(to $USER_TO_WATCH)"; then
echo "[DEBUG] 대상 사용자 감지됨"
SU_TIME=$(date "+%Y-%m-%d %H:%M:%S")
TTY=$(echo "$LINE" | awk '{print $NF}')
echo "[!] su 명령을 통한 '$USER_TO_WATCH' 전환 감지 ($TTY): $SU_TIME"
fi
fi
done &
su와 to USERNAME이 동시에 포함된 로그 탐지
작동되지 않음을 확인하고 다시 로그 확인
[user@localhost LAB]$ sudo tail -F /var/log/secure | grep "su"
Nov 3 02:30:12 localhost su[6287]: pam_unix(su-l:session): session opened for user testuser(uid=1001) by user(uid=1000)
Nov 3 02:30:32 localhost su[6287]: pam_unix(su-l:session): session closed for user testuser
Nov 3 02:30:35 localhost sudo[6012]: pam_unix(sudo:session): session closed for user root
Nov 3 02:31:54 localhost sudo[6947]: user : TTY=pts/0 ; PWD=/LAB ; USER=root ; COMMAND=/bin/tail -F /var/log/secure
Nov 3 02:31:54 localhost sudo[6947]: pam_unix(sudo:session): session opened for user root(uid=0) by user(uid=1000)
Nov 3 02:32:02 localhost su[7001]: pam_unix(su-l:session): session opened for user testuser(uid=1001) by user(uid=1000)
Nov 3 02:32:08 localhost su[7001]: pam_unix(su-l:session): session closed for user testuser
로그 포맷에서 다음을 확인
pam_unix(su-l:session): session opened for user testuser(uid=1001) by user(uid=1000)
pam_unix(su-l:session): session closed for user testuser
to testuser 패턴이 아닌 session opened/closed for user testuser 형식의 기록을 확인.
감시 결과를 파일로 저장하고 /var/log/secure의 로그 파일 형식이 아닌 /var/log/messages 로그 파일 형식에도 해당 코드가 적용되는지 안되면 코드를 어떻게 수정해야할지 고민해야 함.

※ 본 글은 비영리적 목적에 한해 자유롭게 이용 가능합니다. 단, 동일한 라이선스를 적용해야 하며, 상업적 이용은 금지됩니다.