Python ๋ณด์ ์๋ํ ์๋ฆฌ์ฆ
- IPยท๋๋ฉ์ธ ํํ ์กฐํ ์๋ํ โ VirusTotal API ์ค์ ํ์ฉ
- ๋ณด์ ๊ถ๊ณ ๋ฌธ ๋ชจ๋ํฐ๋ง ์๋ํ โ ์์กด์ฑ 0์ผ๋ก ๋ง๋๋ RSS ์์ง๊ธฐ โ ํ์ฌ ๊ธ
๋ณด์ ์ ๋ฌด์ ํ๋ฃจ๋ ๋๊ฐ "๋ฐค์ฌ์ด ์๋ก ๋์จ ๊ถ๊ณ ๋ฌธ ํ์ธ"์ผ๋ก ์์ํ๋ค. KISA ๋ณดํธ๋๋ผ ๋ณด์๊ณต์ง, ์ฌ์ฉํ๋ ์ฅ๋น ๋ฒค๋์ PSIRT(Product Security Incident Response Team) ๊ณต์ง๋ฅผ ๋ธ๋ผ์ฐ์ ๋ก ํ๋์ฉ ์ด์ด๋ณด๋ ์ผ์ด ๋งค์ผ ๋ฐ๋ณต๋๋ค.
๋ฌธ์ ๋ ์ด ์์ ์ด ํ๋ฃจ๋ ๋น ์ง๋ฉด ์ ๋๋๋ฐ, ์ฌ๋์ด ํ๋ฉด ๋ฐ๋์ ๋น ์ง๋ค๋ ์ ์ด๋ค. ํด๊ฐ, ๋ฐ์ ์์นจ, ๋จ์ ๊น๋นก์ โ ์ด์ ๋ ๋ง๋ค. ๊ทธ๋ฆฌ๊ณ ๋์น ๊ถ๊ณ ๋ฌธ ํ๋๊ฐ ํจ์น ์ง์ฐ์ผ๋ก ์ด์ด์ง๋ค.
์ด ๊ธ์์๋ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ง์ผ๋ก(์์กด์ฑ 0) ๋ณด์ ๊ถ๊ณ ๋ฌธ RSS๋ฅผ ์์งํ๊ณ , ์ด๋ฏธ ๋ณธ ๊ฒ์ ๊ฑธ๋ฌ์ ์ ๊ถ๊ณ ๋ฌธ๋ง ์๋ ค์ฃผ๋ ์์ง๊ธฐ๋ฅผ ์ฒ์๋ถํฐ ๋ง๋ค์ด๋ณธ๋ค. ์ฝ๋ ์์ฒด๋ณด๋ค ๊ฐ์ง ๊ฑด ์ด์์์ ๊ฒช๋ ํจ์ ๋ค์ด๋ค โ ์ฒซ ์คํ ์๋ฆผ ํญํ, ์ฌ์๋ ์๋ fetch์ ์คํ, RSS ๋ ธ์ถ ๊ฐ์ ํ๊ณ ๊ฐ์ ๊ฒ๋ค์ ํจ๊ป ๋ค๋ฃฌ๋ค.
์ ์คํฌ๋ ์ดํ์ด ์๋๋ผ RSS์ธ๊ฐ
๊ถ๊ณ ๋ฌธ ํ์ด์ง๋ฅผ HTML ์คํฌ๋ ์ดํ์ผ๋ก ๊ธ์ ์๋ ์๋ค. ํ์ง๋ง ๊ณต์ RSS๊ฐ ์๋ค๋ฉด RSS๊ฐ ํญ์ ๋จผ์ ๋ค.
| ๋น๊ต | HTML ์คํฌ๋ ์ดํ | RSS |
|---|---|---|
| ์์ ์ฑ | ํ์ด์ง ๊ฐํธ ๋๋ง๋ค ํ์ ๊นจ์ง | RSS 2.0 ํ์ค ๊ตฌ์กฐ ๊ณ ์ 1 |
| ํ์ฑ ๋์ด๋ | DOM ๊ตฌ์กฐ ๋ถ์ ํ์ | <item> ๋ฐ๋ณต๋ง ์ฝ์ผ๋ฉด ๋ |
| ์๋ฒ ๋ถ๋ด | ํ์ด์ง ์ ์ฒด ๋ก๋ | ๊ฒฝ๋ XML ํ๋ |
| ๋ฒ์ /๋งค๋ | robots.txtยท์ด์ฉ์ฝ๊ด ํ์ธ ํ์ | ๊ตฌ๋ ํ๋ผ๊ณ ๊ณต๊ฐํ ์ฑ๋ |
๋ณด์ ๊ถ๊ณ ๋ฌธ์ ๋คํํ ์ฃผ์ ๊ธฐ๊ดยท๋ฒค๋๊ฐ RSS๋ฅผ ๊ณต์ ์ ๊ณตํ๋ค. ์ด ๊ธ์์ ์ฐ๋ ๋ ์์ค:
| ์์ค | RSS | ํญ๋ชฉ ๊ณ ์ ID |
|---|---|---|
| KISA ๋ณดํธ๋๋ผ ๋ณด์๊ณต์ง2 | https://www.boho.or.kr/kr/rss.do?bbsId=B0000133 |
๋งํฌ์ nttId ํ๋ผ๋ฏธํฐ |
| Fortinet PSIRT (IR Advisories)3 | https://filestore.fortinet.com/fortiguard/rss/ir.xml |
๋งํฌ ๋์ FG-IR-YY-NNN |
์ฌ์ฉํ๋ ๋ฒค๋๊ฐ ๋ค๋ฅด๋ค๋ฉด ํด๋น ๋ฒค๋์ PSIRT/Security Advisory ํ์ด์ง์์ RSS ๋งํฌ๋ฅผ ์ฐพ์ผ๋ฉด ๋๋ค. Cisco, Palo Alto, Juniper ๋ฑ ๋๋ถ๋ถ์ ๋คํธ์ํฌ ์ฅ๋น ๋ฒค๋๊ฐ ์ ๊ณตํ๋ค.
์ ์ฒด ์ค๊ณ โ ํ์ดํ๋ผ์ธ ํ ์ฅ
ํต์ฌ ์ค๊ณ ๊ฒฐ์ ์ ์ธ ๊ฐ์ง๋ค.
โ ์์กด์ฑ 0. urllib(HTTP), xml.etree(RSS ํ์ฑ), sqlite3(์ํ ์ ์ฅ), smtplib(๋ฉ์ผ) โ ์ ๋ถ ํ์ค ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ค. pip install ์์ด Python๋ง ์์ผ๋ฉด ์ด๋์๋ ๋๋ค. ์๋ฒ, ์ฌ๋ด๋ง, ์ ๋
ธํธ๋ถ ์ด๋์ ์ฎ๊ฒจ๋ "ํ๊ฒฝ ์ธํ
"์ด๋ผ๋ ๋จ๊ณ ์์ฒด๊ฐ ์๋ค.
โก "์ด๋ฏธ ๋ดค๋์ง"๋ DB UNIQUE ์ ์ฝ์ด ํ๋จํ๋ค. "์ง๋๋ฒ ๊ฒฐ๊ณผ์ ๋น๊ต" ๋ก์ง์ ์ง์ ์ง๋ ๋์ , UNIQUE(source, entry_id) ์ ์ฝ์ ๊ฑธ๊ณ INSERT๋ฅผ ์๋ํ๋ค. ์ด๋ฏธ ์์ผ๋ฉด IntegrityError๊ฐ ๋๊ณ , ๊ทธ๊ฒ ๊ณง "๋ณธ ์ ์์"์ด๋ค. ๋น๊ต ์ฝ๋๊ฐ 0์ค์ด ๋๋ค.
โข ์ฒซ ์คํ์ seed ๋ชจ๋. ์ฒ์ ๋๋ฆฌ๋ฉด ํผ๋์ ๊ธฐ์กด ํญ๋ชฉ ์์ญ ๊ฑด์ด ์ ๋ถ "์ ๊ท"๋ก ์กํ๋ค. ์ด๊ฑธ ๊ทธ๋๋ก ๋ฉ์ผ๋ก ์๋ฉด ์ฒซ๋ ๋ถํฐ ์๋ฆผ ํญํ์ด๋ค. DB๊ฐ ๋น์ด ์์ผ๋ฉด(=์ฒซ ์คํ) ์๋ฆผ ์์ด ์ ์ฌ๋ง ํ๊ณ ๋๋ธ๋ค.
๊ตฌํ 1 โ fetch: ์ฌ์๋ ์๋ ์์ง๊ธฐ๋ ์คํ ์ ์กฐ๊ธฐ
๊ฐ์ฅ ํํ ์ค์๋ถํฐ ๋ณด์.
# โ ์ทจ์ฝํ ์ฝ๋ โ ์ผ์ ์ฅ์ ํ ๋ฒ์ "์ ๊ท 0๊ฑด"์ผ๋ก ์กฐ์ฉํ ์คํจ
def fetch(url):
return urllib.request.urlopen(url, timeout=20).read().decode()RSS ์๋ฒ๋ ๊ฐ๋ ์ฃฝ๋๋ค. ์ผ์์ ์ธ 500 ์๋ต์ด๋ ํ์์์ ํ ๋ฒ์ ๊ทธ๋ ์์ง์ด ํต์งธ๋ก ๋น ์ง๊ณ , ์ฌํ๋ฉด "์ค๋์ ์ ๊ถ๊ณ ๋ฌธ์ด ์๋ค"๋ผ๋ ์กฐ์ฉํ ๊ฑฐ์ง ๊ฒฐ๋ก ์ด ๋๋ค. ์ค๋ฅ์ ์ข ๋ฅ์ ๋ฐ๋ผ ๋ค๋ฅด๊ฒ ๋ฐ์ํด์ผ ํ๋ค.
| ์๋ต | ์๋ฏธ | ๋์ |
|---|---|---|
| 404 | ํผ๋ ์์น๊ฐ ๋ฐ๋ | ์ฌ์๋ ๋ฌด์๋ฏธ โ ์ค์ ์ ๊ฒ ์ ํธ๋ก ํ๋ฉดํ |
| ๊ทธ ์ธ 4xx | ์์ฒญ ์์ฒด๊ฐ ์๋ชป๋จ | ์ฆ์ ์์ธ โ ์ฝ๋/์ค์ ๋ฒ๊ทธ |
| 5xx ยท ํ์์์ ยท ์ฐ๊ฒฐ ์ค๋ฅ | ์๋ฒ/๋คํธ์ํฌ ์ผ์ ์ฅ์ | ์ฌ์๋ (๋ฐฑ์คํ) |
# โ
์์ ํ ์ฝ๋ โ ์ค๋ฅ๋ฅผ ๋ถ๋ฅํด์ ์ฌ์๋ํ ๊ฒ๋ง ์ฌ์๋
HTTP_TIMEOUT, HTTP_RETRIES, HTTP_BACKOFF = 20, 3, 5
def fetch(url: str) -> str | None:
last = None
for attempt in range(1, HTTP_RETRIES + 1):
try:
req = urllib.request.Request(url, headers={"User-Agent": UA})
with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT) as res:
return res.read().decode("utf-8", errors="replace")
except urllib.error.HTTPError as e:
if e.code == 404:
return None # ํผ๋ ์ด๋ โ ํธ์ถ๋ถ์์ ํ๋ฉดํ
if not (500 <= e.code < 600):
raise # 4xx๋ ์ฆ์ ๋๋ฌ๋ธ๋ค
last = e
except (urllib.error.URLError, TimeoutError, OSError) as e:
last = e # ๋คํธ์ํฌ์ฑ โ ์ฌ์๋
if attempt < HTTP_RETRIES:
time.sleep(HTTP_BACKOFF * attempt) # 5s, 10s ์ ํ ๋ฐฑ์คํ
raise lastโ ๏ธ User-Agent๋ฅผ ๊ผญ ๋ฃ์. ์ผ๋ถ ๊ณต๊ณต๊ธฐ๊ด ์๋ฒ๋ ๊ธฐ๋ณธ
Python-urllibUA๋ฅผ ์ฐจ๋จํ๋ค. ์๋ณ ๊ฐ๋ฅํ UA(๋๊ตฌ๋ช ๋ฑ)๋ฅผ ๋ช ์ํ๋ ๊ฒ์ด ์์ ์ธก์ ๋ํ ๋งค๋์ด๊ธฐ๋ ํ๋ค.
๊ตฌํ 2 โ ํ์ฑ: RSS 2.0์ xml.etree๋ก ์ถฉ๋ถํ๋ค
feedparser ๊ฐ์ ์ข์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์์ง๋ง, RSS 2.0์ <item> ๊ตฌ์กฐ๋ ํ์ค xml.etree๋ก ๋ช ์ค์ด๋ฉด ๋๋๋ค. ์์กด์ฑ 0 ์์น์ ์งํฌ ์ ์๋ค.
import xml.etree.ElementTree as ET
def parse_rss(xml_text: str) -> list[dict]:
channel = ET.fromstring(xml_text).find("channel")
if channel is None:
return []
entries = []
for item in channel.findall("item"):
entries.append({
"title": (item.findtext("title") or "").strip(),
"link": (item.findtext("link") or "").strip(),
"pubDate": (item.findtext("pubDate") or "").strip(),
"description": (item.findtext("description") or "").strip(),
})
return entries.strip()์ ๋นผ๋์ง ๋ง์. ์ค์ ํผ๋์๋ <link> https://... </link>์ฒ๋ผ ์๋ค ๊ณต๋ฐฑ์ด ์์ฌ ์ค๋ ๊ฒฝ์ฐ๊ฐ ํํ๊ณ , ์ด ๊ณต๋ฐฑ์ด ๊ทธ๋๋ก DB์ ๋ค์ด๊ฐ๋ฉด ๊ฐ์ ํญ๋ชฉ์ด ๋ค๋ฅธ ํญ๋ชฉ์ผ๋ก ์ค๋ณต ์ ์ฌ๋๋ค.
์์ค๋ง๋ค "ํญ๋ชฉ์ ๊ณ ์ ID"๋ฅผ ๋ฝ๋ ๋ฐฉ๋ฒ์ด ๋ค๋ฅด๋ค๋ ์ ์ด ์ ์ผํ ์์ค๋ณ ์ฐจ์ด๋ค. ์ด ์ฐจ์ด๋ฅผ ์์ค ์ ์์ ๋๋ค๋ก ๋ฃ์ด๋๋ฉด, ์ ์์ค ์ถ๊ฐ๊ฐ dict ํญ๋ชฉ ํ๋๋ก ๋๋๋ค:
SOURCES = {
"KISA": {
"rss": "https://www.boho.or.kr/kr/rss.do?bbsId=B0000133",
# ๊ฒ์๊ธ ๋งํฌ์ nttId ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๊ฐ ๊ณ ์ ID
"entry_id": lambda item: _query_param(item["link"], "nttId"),
},
"FORTINET": {
"rss": "https://filestore.fortinet.com/fortiguard/rss/ir.xml",
# ๋งํฌ ๋ FG-IR-YY-NNN์ด ๊ณ ์ ID
"entry_id": lambda item: item["link"].rstrip("/").rsplit("/", 1)[-1],
},
}๊ตฌํ 3 โ ์ค๋ณต ์ ๊ฑฐ: ๋น๊ต ์ฝ๋ ๋์ UNIQUE ์ ์ฝ
CREATE TABLE IF NOT EXISTS advisories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source TEXT NOT NULL,
entry_id TEXT NOT NULL,
title TEXT NOT NULL,
url TEXT NOT NULL,
published TEXT,
cves TEXT,
detected_at TEXT NOT NULL DEFAULT (datetime('now','localtime')),
notified INTEGER NOT NULL DEFAULT 0,
UNIQUE(source, entry_id) -- ์ด ํ ์ค์ด "์ด๋ฏธ ๋ดค๋์ง" ํ๋จ ์ ๋ถ
);์์ง ๋ฃจํ๋ INSERT๋ฅผ ์๋ํ๊ณ , IntegrityError๋ฉด "๋ณธ ์ ์์"์ผ๋ก ๋์ด๊ฐ๋ค:
for entry in parse_rss(xml_text):
entry_id = src["entry_id"](entry)
try:
conn.execute(
"INSERT INTO advisories (source, entry_id, title, url, published, cves)"
" VALUES (?,?,?,?,?,?)",
(key, entry_id, entry["title"], entry["link"],
entry["pubDate"], extract_cves(entry)))
new_count += 1
except sqlite3.IntegrityError:
pass # ์ด๋ฏธ ๋ณธ ํญ๋ชฉ โ ์กฐ์ฉํ ์คํต๊ถ๊ณ ๋ฌธ์์ CVE ๋ฒํธ๋ ๋ฝ์๋๋ฉด ๋์ค์ ์์ฐ ๋งค์นญยท๊ฒ์์ ์ธ ์ ์๋ค. ์ ๊ท์ ํ๋๋ก ๋๋๋ค:
CVE_RE = re.compile(r"CVE-\d{4}-\d{4,7}")
def extract_cves(entry: dict) -> str:
found = CVE_RE.findall(entry["title"] + " " + entry["description"])
return ",".join(sorted(set(found)))๊ตฌํ 4 โ ์ด์ ์์ ์ฅ์น ์ธ ๊ฐ์ง
์ฒซ ์คํ seed ๋ชจ๋. DB๊ฐ ๋น์ด ์์ผ๋ฉด ์ ์ฌ๋ง ํ๊ณ ์๋ฆผ์ ์๋ตํ๋ค.
first_run = conn.execute("SELECT COUNT(*) FROM advisories").fetchone()[0] == 0
new_rows = scan(conn)
if first_run:
mark_notified(conn, new_rows)
print(f"์ฒซ ์คํ seed ์๋ฃ โ {len(new_rows)}๊ฑด ์ ์ฌ (์๋ฆผ ์๋ต)")
return์์ค ๊ฒฉ๋ฆฌ. ์์ค ํ๋์ ์ฅ์ ๊ฐ ์ ์ฒด ์์ง์ ์ฃฝ์ด๋ฉด ์ ๋๋ค. ์์ค๋ณ try/except๋ก ๊ฐ์ธ๊ณ , ์ค๋ฅ๋ scan_log ํ
์ด๋ธ์ ๋จ๊ฒจ์ "์ ์ค๋ ์ด ์์ค๋ง 0๊ฑด์ธ์ง" ์ถ์ ๊ฐ๋ฅํ๊ฒ ํ๋ค.
์๋ฆผ ์คํจ ์ ๋์ฒด ๊ฒฝ๋ก. ๋ฉ์ผ ์ค์ ์ด ์๊ฑฐ๋ SMTP๊ฐ ์คํจํด๋ ์์ง ์์ฒด๋ ์ฑ๊ณต์ด์ด์ผ ํ๋ค. ๋ฐ์ก ํจ์๊ฐ False๋ฅผ ๋ฐํํ๋ฉด ์ฝ์ ์ถ๋ ฅ์ผ๋ก ๋์ฒดํ๋ค. ๋ฉ์ผ์ smtplib + Gmail ์ฑ ๋น๋ฐ๋ฒํธ(STARTTLS 587)๋ฉด ์ถฉ๋ถํ๊ณ , ์๊ฒฉ์ฆ๋ช
์ ๋ฐ๋์ .env๋ก ๋ถ๋ฆฌํด .gitignore์ ๋ฃ๋๋ค.
โ ๏ธ
.env.example์ ์ค์ ๋น๋ฐ๋ฒํธ๋ฅผ ๋ฃ๋ ์ฌ๊ณ ๊ฐ ์์ธ๋ก ํํ๋ค. ์์ ํ์ผ์๋your-16char-app-password๊ฐ์ placeholder๋ง. ์์ ํ์ผ์ ์ปค๋ฐ๋๋ ํ์ผ์ด๋ผ๋ ๊ฑธ ์์ง ๋ง์.
์ ๊ธฐ ์คํ โ ๊ทธ๋ฆฌ๊ณ RSS์ ์จ์ ํจ์
# Linux cron โ ๋งค์ผ 09:00
0 9 * * * cd /path/to/watcher && python watcher.py >> data/cron.log 2>&1# Windows ์์
์ค์ผ์ค๋ฌ
schtasks /Create /TN advisory-watcher /SC DAILY /ST 09:00 `
/TR "python C:\path\to\watcher.py"์ฌ๊ธฐ์ ๋ง์ง๋ง ํจ์ ํ๋. RSS๋ ์ต๊ทผ N๊ฑด๋ง ๋ ธ์ถํ๋ค. ์ด๋ฒ์ ํ์ธํ ํผ๋๋ KISA๊ฐ 10๊ฑด, Fortinet์ด 50๊ฑด์ด์๋ค. KISA ๋ณด์๊ณต์ง๋ ํ๋ฃจ์ ์ฌ๋ฌ ๊ฑด ์ฌ๋ผ์ค๋ ๋ ์ด ๋ง์ผ๋ฏ๋ก, ์คํ ์ฃผ๊ธฐ๊ฐ ํผ๋ ๋ ธ์ถ ๊ฐ์๋ฅผ ๋๊ฒจ๋ฒ๋ฆฌ๋ฉด ๊ทธ ์ฌ์ด ํญ๋ชฉ์ ์์ ๋์น๋ค. ์ต์ ํ๋ฃจ 1ํ, ๊ณต์ง๊ฐ ๋ชฐ๋ฆฌ๋ ์๊ธฐ์๋ ๋ ์์ฃผ ๋๋ฆฌ๋ ๊ฒ์ด ์์ ํ๋ค.
๋ง์น๋ฉฐ
| ์์ | ์๋ํ ์ | ์๋ํ ํ |
|---|---|---|
| ์์นจ ๊ถ๊ณ ๋ฌธ ํ์ธ | ์ฌ์ดํธ 2๊ณณ ์๋ ์ํ (๋งค์ผ ~10๋ถ) | ๋ฉ์ผํจ ํ์ธ (์ ๊ท ์์ ๋๋ง) |
| ๋์นจ ์ํ | ํด๊ฐยท๋ฐ์ ๋ = ๊ณต๋ฐฑ | ์ค์ผ์ค๋ฌ๊ฐ ๋งค์ผ ์คํ |
| ์ด๋ ฅ ๊ด๋ฆฌ | ์์ (๊ธฐ์ต์ ์์กด) | SQLite์ ์์งยท์๋ฆผ ์ด๋ ฅ ๋์ |
| CVE ์ถ์ถ | ์๋ ๋ณต์ฌ | ์ ๊ท์ ์๋ ์ถ์ถยท์ ์ฅ |
์ด ์์ง๊ธฐ์ ๋ณธ์ง์ "RSS ์ฝ๊ธฐ"๊ฐ ์๋๋ผ ์ํ ๊ด๋ฆฌ๋ค. ๋ฌด์์ ๋ดค๊ณ (UNIQUE ์ ์ฝ), ๋ฌด์์ ์๋ ธ๊ณ (notified ํ๋๊ทธ), ์ธ์ ๋ญ๊ฐ ์คํจํ๋์ง(scan_log)๋ฅผ ๊ธฐ๋กํ๋ ์๊ฐ, ์ผํ์ฑ ์คํฌ๋ฆฝํธ๊ฐ ๋งค์ผ ๋ฏฟ๊ณ ๋งก๊ธฐ๋ ์ด์ ๋๊ตฌ๊ฐ ๋๋ค.
์ค๋ฌด ์ฒดํฌ๋ฆฌ์คํธ
- ์์ง ์์ค์ ๊ณต์ RSS ํ์ธ (์คํฌ๋ ์ดํ์ ์ตํ ์๋จ)
- fetch์ ์ค๋ฅ ๋ถ๋ฅ ์ฌ์๋ (404/4xx/5xxยทํ์์์์ ๋ค๋ฅด๊ฒ)
- User-Agent ๋ช ์ (๊ธฐ๋ณธ UA ์ฐจ๋จ ๋๋น)
-
UNIQUE(source, entry_id)โ ๊ณ ์ ID๋ ์์ค๋ณ๋ก ์ถ์ถ ๊ท์น ์ ์ - ์ฒซ ์คํ seed ๋ชจ๋ (์๋ฆผ ํญํ ๋ฐฉ์ง)
- ์์ค ๊ฒฉ๋ฆฌ โ ํ ์์ค ์ฅ์ ๊ฐ ์ ์ฒด๋ฅผ ์ฃฝ์ด์ง ์๊ฒ
- ์๊ฒฉ์ฆ๋ช
์
.env๋ถ๋ฆฌ +.gitignore(์์ ํ์ผ์ placeholder๋ง) - ์คํ ์ฃผ๊ธฐ < RSS ๋ ธ์ถ ๊ฐ์ ์์ง ์๋ (์ต์ ํ๋ฃจ 1ํ)
๋ค์ ๊ธ์์๋ ์ด๋ ๊ฒ ์์งํ ๊ถ๊ณ ๋ฌธ ๋ฐ์ดํฐ๋ฅผ JSONยทCSV๋ก ์ ์ ยทํ์คํํ๊ณ , ๋ณด์ ์์ฐ ๋ชฉ๋ก๊ณผ ๋งค์นญํด "์ฐ๋ฆฌ์ ๊ด๋ จ ์๋ ๊ถ๊ณ ๋ฌธ"๋ง ๊ณจ๋ผ๋ด๋ ๋ฐฉ๋ฒ์ ๋ค๋ค๋ณผ ์์ ์ด๋ค.
๊ด๋ จ ๊ธ
- ๐ Python์ผ๋ก IPยท๋๋ฉ์ธ ํํ ์กฐํ ์๋ํํ๊ธฐ โ VirusTotal API ์ค์ ํ์ฉ โ ์๋ฆฌ์ฆ 1ํธ: ์ํ ์ธํ ๋ฆฌ์ ์ค API ์๋ํ
- ๐ก๏ธ SOAR๋ ๋ฌด์์ธ๊ฐ? ๋ณด์ ์๋ํ๊ฐ ํ์ํ ์ด์ ์ FortiSOAR ํต์ฌ ๊ฐ๋ ์ ๋ฆฌ โ ์์งํ ๊ถ๊ณ ๋ฌธยทIOC๊ฐ ํ๋ฌ๊ฐ๋ ์๋ํ ๋์ ์ฒด๊ณ
- GitHub Actions ์๋ ๋ฐฐํฌ ์ค๋ฅ ํด๊ฒฐ: SSH ํค, PAT, ์์กด์ฑ, Git ์ค์ ๋ฌธ์ โ ์์ง๊ธฐ๋ฅผ ๋ก์ปฌ ์ค์ผ์ค๋ฌ ๋์ CI๋ก ์ฎ๊ธธ ๋ ๋ง๋๋ ์ค์ ๋ฌธ์ ๋ค
- RSS Advisory Board. "RSS 2.0 Specification." https://www.rssboard.org/rss-specificationโฉ
- Fortinet. "FortiGuard PSIRT Advisories." https://www.fortiguard.com/psirtโฉ
- KISA ๋ณดํธ๋๋ผ&KrCERT/CC. "๋ณด์๊ณต์ง." https://www.boho.or.kr/kr/bbs/list.do?bbsId=B0000133&menuNo=205020โฉ
