소개
시스템 관리자나 보안 분석가로 일하다 보면 Apache 나 Nginx 와 같은 웹 서버에서 생성된 방대한 로그 파일을 자주 접하게 됩니다. 수천 줄에 달하는 로그를 수동으로 읽으며 악의적인 활동을 찾아내는 것은 지속 불가능할 뿐만 아니라, 사람의 실수로 이어지기 쉽습니다.
이번 실습에서는 보안 대응 담당자가 되어 Apache 웹 서버 액세스 로그를 수집하는 Python 스크립트를 작성해 봅니다. Python 의 정규 표현식 (re) 라이브러리를 사용하여 IP 주소를 자동으로 추출하고, 알려진 SQL 인젝션 (SQLi) 공격 시그니처를 탐지할 것입니다. 마지막으로, 분석 결과를 구조화된 CSV 보고서로 내보내 추가 분석이 가능하도록 만듭니다.
이 모듈은 Python 이 단순한 프로그래밍 언어를 넘어 분석가의 업무 효율을 극대화하는 도구로 변모하는 과정을 보여줍니다. 로그 분석은 수동으로 하기에는 너무 반복적이지만, 자동화를 통해 즉각적인 효과를 볼 수 있는 가장 대표적인 업무 사례입니다.
Python 으로 로그 파일 읽기
이 단계에서는 Python 을 사용하여 로그 파일을 열고 읽는 방법을 배웁니다. 실습 환경의 작업 디렉토리에는 access.log라는 샘플 Apache 로그 파일이 이미 준비되어 있습니다.
파일을 한 줄씩 읽는 것은 자동화의 기초가 되는 패턴입니다. 이는 데이터를 수동으로 복사하는 것보다 훨씬 효율적이며, 기본적인 수집 방식을 변경하지 않고도 필터링, 파싱, 카운팅 및 보고 로직을 추가할 수 있는 기반이 됩니다.
먼저, 샘플 로그 파일의 내용을 확인하여 어떤 데이터를 다루는지 파악해 봅시다. 터미널에서 cat 명령어를 사용하여 파일 내용을 출력합니다.
cat access.log
표준 Apache 액세스 로그의 여러 줄이 보일 것입니다. 이 중 일부는 정상적인 웹 요청이며, 다른 일부는 의심스러운 SQL 인젝션 시도입니다.
이제 이 파일을 프로그래밍 방식으로 읽기 위한 Python 스크립트를 만들어 보겠습니다. nano 텍스트 편집기를 사용하여 log_analyzer.py라는 파일을 생성합니다.
nano log_analyzer.py
Python 에서 open() 함수는 파일을 여는 데 사용되며, with 문은 파일을 다 읽은 후 적절하게 닫히도록 보장합니다. 다음 Python 코드를 복사하여 nano 편집기에 붙여넣으세요.
def analyze_logs(file_path):
print(f"Opening log file: {file_path}")
## Open the file in read mode ('r')
with open(file_path, 'r') as file:
## Read the file line by line
for line in file:
## Print each line, stripping the extra newline character
print(line.strip())
if __name__ == "__main__":
log_file = "access.log"
analyze_logs(log_file)
Ctrl+O를 누른 후 Enter를 눌러 파일 이름을 확인하고 nano에서 파일을 저장합니다. 테스트하기 전에 스크립트를 계속 작성하려면 nano 상태를 유지하고 다음 단계로 넘어가면 됩니다. 터미널로 돌아갈 준비가 되었을 때만 Ctrl+X를 사용하세요.
python3 명령어를 사용하여 새로 만든 Python 스크립트를 실행합니다.
python3 log_analyzer.py
앞서 cat 명령어로 보았던 것과 동일한 로그 라인이 출력되는 것을 확인할 수 있습니다. 이는 Python 스크립트가 로그 데이터를 한 줄씩 성공적으로 수집할 수 있음을 의미합니다.
IP 추출을 위한 정규 표현식 적용
이 단계에서는 각 로그 라인에서 IP 주소를 자동으로 추출하도록 Python 스크립트를 개선합니다. 이를 위해 정규 표현식 (Regex) 을 사용합니다. 정규 표현식은 텍스트 내에서 패턴을 매칭하는 강력한 도구입니다.
로그는 반정형 (semi-structured) 데이터이기 때문에 정규 표현식이 유용합니다. 로그는 일정한 패턴을 따르지만, 깔끔한 Python 객체 형태는 아닙니다. 정규 표현식을 사용하면 원하는 텍스트 패턴을 정의하고 인터프리터가 반복적인 매칭 작업을 수행하도록 할 수 있습니다.
Python 은 정규 표현식을 다루기 위한 내장 모듈인 re를 제공합니다. 이 모듈을 가져와서 IPv4 주소와 일치하는 패턴을 정의해야 합니다. IPv4 주소는 점으로 구분된 4 개의 숫자 (0 에서 255 사이) 로 구성됩니다.
log_analyzer.py가 nano에서 열려 있다면 계속 편집하고, 그렇지 않다면 다시 엽니다.
nano log_analyzer.py
re 모듈을 포함하고 IP 주소를 검색하는 로직을 추가하도록 스크립트를 업데이트합니다. 파일의 전체 내용을 다음 코드로 교체하세요.
import re
def analyze_logs(file_path):
print(f"Analyzing log file: {file_path}\n")
## Define a Regex pattern for an IPv4 address
## \b ensures word boundaries, \d{1,3} matches 1 to 3 digits, \. matches a literal dot
ip_pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
with open(file_path, 'r') as file:
for line in file:
## re.findall returns a list of all matches in the string
ips_found = re.findall(ip_pattern, line)
if ips_found:
## We take the first IP found in the line
source_ip = ips_found[0]
print(f"Extracted IP: {source_ip}")
if __name__ == "__main__":
log_file = "access.log"
analyze_logs(log_file)
Ctrl+O와 Enter로 파일을 저장합니다. 이 중간 버전을 지금 테스트하려면 Ctrl+X로 종료하고 스크립트를 실행하세요. 그렇지 않다면 다음 단계로 바로 넘어가도 좋습니다.
python3 log_analyzer.py
다음과 유사한 출력이 나타날 것입니다.
Analyzing log file: access.log
Extracted IP: 192.168.1.10
Extracted IP: 10.0.0.15
Extracted IP: 172.16.0.5
Extracted IP: 192.168.1.22
Extracted IP: 10.0.0.25
이제 스크립트가 정규 표현식의 힘을 빌려 나머지 로그 텍스트를 무시하고 소스 IP 주소만 성공적으로 분리해 냅니다.
SQLi 시그니처 탐지
이 단계에서는 스크립트에 보안 탐지 기능을 추가합니다. SQL 인젝션 (SQLi) 은 악의적인 SQL 문을 입력 필드에 삽입하는 일반적인 웹 공격입니다.
이제 스크립트는 단순한 파서에서 탐지기로 진화합니다. 단순히 필드를 추출하는 것을 넘어, 어떤 라인이 주의를 요하는지 결정하는 보안 로직을 적용하는 것입니다. 수집에서 해석으로의 이러한 전환이 자동화를 운영적으로 가치 있게 만듭니다.
공격자들은 데이터를 탈취하기 위해 UNION SELECT와 같은 특정 키워드를 사용하거나, 인증을 우회하기 위해 ' OR '1'='1과 같은 구문을 사용합니다. 정규 표현식을 사용하여 로그 라인에서 이러한 알려진 공격 시그니처를 스캔할 수 있습니다.
nano에서 동일한 파일을 계속 편집 중이라면 그대로 진행하세요. 그렇지 않다면 다시 엽니다.
nano log_analyzer.py
일반적인 SQLi 구문을 포착하기 위해 새로운 정규 표현식 패턴을 정의합니다. (?i)를 사용하여 대소문자를 구분하지 않도록 설정하면 UNION, union, uNiOn 등을 모두 잡아낼 수 있습니다. 다음 코드로 스크립트를 업데이트하세요.
import re
def analyze_logs(file_path):
print(f"Scanning log file for SQLi attacks: {file_path}\n")
ip_pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
## Define a Regex pattern for SQL Injection signatures
## (?i) makes it case-insensitive
## It looks for 'union select' OR 'or 1=1' variations
sqli_pattern = r"(?i)(union.*select|or\s+1\s*=\s*1|'.*or.*')"
with open(file_path, 'r') as file:
for line in file:
ips_found = re.findall(ip_pattern, line)
source_ip = ips_found[0] if ips_found else "Unknown IP"
## re.search returns a match object if the pattern is found, otherwise None
if re.search(sqli_pattern, line):
print(f"[ALERT] SQL Injection detected from IP: {source_ip}")
print(f"Raw Log: {line.strip()}\n")
if __name__ == "__main__":
log_file = "access.log"
analyze_logs(log_file)
Ctrl+O와 Enter로 파일을 저장합니다. 터미널에서 스크립트를 실행할 준비가 되었을 때만 Ctrl+X로 nano를 종료하세요.
python3 log_analyzer.py
이제 출력 결과에서 악의적인 요청이 구체적으로 표시될 것입니다.
Scanning log file for SQLi attacks: access.log
[ALERT] SQL Injection detected from IP: 10.0.0.15
Raw Log: 10.0.0.15 - - [10/Oct/2026:13:55:45 -0700] "GET /login.php?user=admin' OR '1'='1 HTTP/1.1" 200 5120
[ALERT] SQL Injection detected from IP: 192.168.1.22
Raw Log: 192.168.1.22 - - [10/Oct/2026:13:56:12 -0700] "GET /products.php?id=1 UNION SELECT username, password FROM users HTTP/1.1" 200 4012
이제 스크립트가 기본적인 침입 탐지 시스템 (IDS) 역할을 수행하며, 로그 파일에서 의심스러운 요청을 자동으로 식별합니다.
CSV 보고서 출력
마지막 단계에서는 결과를 포맷팅하여 CSV(Comma-Separated Values) 파일로 저장합니다. 터미널에 출력하는 것도 테스트에는 좋지만, 보안 팀은 스프레드시트 소프트웨어에서 열거나 다른 도구에서 가져올 수 있는 구조화된 보고서가 필요합니다.
Python 에는 CSV 파일을 매우 쉽게 작성할 수 있는 내장 csv 모듈이 있습니다.
파일을 계속 열어두었다면 편집을 이어가고, 그렇지 않다면 다시 엽니다.
nano log_analyzer.py
csv 모듈을 가져오고, report.csv라는 새 파일을 쓰기 모드 ('w') 로 열어 분석 결과를 기록합니다. 코드를 다음 최종 버전으로 교체하세요.
import re
import csv
def analyze_logs(file_path, output_csv):
print(f"Analyzing logs and generating report: {output_csv}...")
ip_pattern = r'\b(?:\d{1,3}\.){3}\d{1,3}\b'
sqli_pattern = r"(?i)(union.*select|or\s+1\s*=\s*1|'.*or.*')"
## Open the output CSV file in write mode
with open(output_csv, 'w', newline='') as csv_file:
writer = csv.writer(csv_file)
## Write the header row
writer.writerow(['Source IP', 'SQLi Detected', 'Raw Log'])
with open(file_path, 'r') as file:
for line in file:
clean_line = line.strip()
ips_found = re.findall(ip_pattern, clean_line)
source_ip = ips_found[0] if ips_found else "Unknown IP"
## Check if SQLi is detected
sqli_detected = "Yes" if re.search(sqli_pattern, clean_line) else "No"
## Write the data row to the CSV
writer.writerow([source_ip, sqli_detected, clean_line])
print("Report generation finished.")
if __name__ == "__main__":
log_file = "access.log"
report_file = "report.csv"
analyze_logs(log_file, report_file)
파일을 저장하고 스크립트를 실행합니다.
python3 log_analyzer.py
보고서 생성이 완료되었다는 메시지가 표시됩니다. 이제 cat 명령어를 사용하여 CSV 파일이 생성되었는지 확인하고 내용을 확인해 봅시다.
cat report.csv
깔끔하게 포맷팅된 CSV 출력을 확인할 수 있습니다.
Source IP,SQLi Detected,Raw Log
192.168.1.10,No,192.168.1.10 - - [10/Oct/2026:13:55:36 -0700] "GET /index.html HTTP/1.1" 200 2326
10.0.0.15,Yes,10.0.0.15 - - [10/Oct/2026:13:55:45 -0700] "GET /login.php?user=admin' OR '1'='1 HTTP/1.1" 200 5120
172.16.0.5,No,172.16.0.5 - - [10/Oct/2026:13:56:01 -0700] "POST /api/data HTTP/1.1" 404 134
192.168.1.22,Yes,192.168.1.22 - - [10/Oct/2026:13:56:12 -0700] "GET /products.php?id=1 UNION SELECT username, password FROM users HTTP/1.1" 200 4012
10.0.0.25,No,10.0.0.25 - - [10/Oct/2026:13:57:00 -0700] "GET /about.html HTTP/1.1" 200 1024
요약
이번 실습에서는 수동 로그 분석에서 벗어나 Python 을 사용한 자동화된 로그 분석으로 성공적으로 전환했습니다. Python 의 내장 파일 처리 기능을 사용하여 텍스트 파일을 한 줄씩 읽고 처리하는 방법을 배웠습니다. 또한 re 모듈을 활용하여 IP 주소와 같은 특정 데이터 형식을 추출하기 위한 정규 표현식을 적용했습니다. 나아가 알려진 SQL 인젝션 공격 시그니처를 원시 로그 데이터와 대조하여 보안 탐지 로직을 구현했습니다. 마지막으로 csv 모듈을 사용하여 구조화된 데이터를 CSV 보고서로 내보내는 연습을 함으로써, 결과를 쉽게 공유하고 추가 분석할 수 있도록 만들었습니다.
이러한 기술들은 보안 자동화와 데이터 파싱의 기초가 되며, 방대한 데이터셋을 효율적으로 처리할 수 있게 해줍니다.



