이것저것

[SK shieldus Rookies 29기] 8일차 본문

SK Shieldus Rookies 29

[SK shieldus Rookies 29기] 8일차

atfield1988 2025. 12. 4. 10:59

8일차 강의
Shodan API, 영화 검색 API, MongoDB를 활용한 실전 프로젝트


📌 Part 1: API 활용 - OSINT & Shodan


1️⃣ OSINT란?

정의

OSINT (Open-Source Intelligence)는 '공개 출처 정보'의 약자로, 웹사이트, 소셜 미디어, 공공 데이터베이스 등 누구나 합법적으로 접근할 수 있는 공개된 정보를 수집하고 분석하는 활동입니다.

활용 분야

  • 사이버 위협 파악
  • 범죄 수사
  • 비즈니스 환경 분석
  • 네트워크 보안 점검

주요 OSINT 도구

  • Google - 검색 엔진
  • Archive.org - 웹 아카이빙
  • Shodan - 인터넷 연결 기기 검색
  • CriminalIP - IP 정보 분석

2️⃣ Shodan이란?

정의

Shodan은 전 세계적으로 인터넷에 연결된 IP 정보(서버, IoT 기기 등)를 크롤링하는 검색 엔진입니다.

특징

  • 포트 기반 검색 - 서비스와 연결된 포트 정보 수집
  • IoT 기기 검색 - 웹캠, 라우터, 서버 등
  • 보안 취약점 파악 - 노출된 서비스 탐지

🔗 공식 사이트

https://www.shodan.io/

3️⃣ Shodan API 사용하기

설치

pip install shodan

API 키 발급

  1. Shodan 웹사이트 접속
  2. 계정 생성 및 로그인
  3. 계정 페이지에서 API 키 확인

기본 사용 예제

import shodan
import json

# API 키 설정
SHODAN_API_KEY = "YOUR_API_KEY"
api = shodan.Shodan(SHODAN_API_KEY)

# 검색어 설정
query = 'webcam'

# 검색 실행
results = api.search(query)
print(f"총 결과: {results['total']}")

# 상위 2개 결과 출력
try:
    for match in results['matches'][:2]:
        print(f"IP 정보: {match['ip_str']}")
        print(f"포트 정보: {match['port']}")
        print(f"조직: {match.get('org', 'N/A')}")
        print(f"국가: {match['location']['country_name']}")
        print("=" * 40)

except shodan.APIError as e:
    print(f"오류: {e}")

출력 예시

총 결과: 3306
IP 정보: 8.221.139.222
포트 정보: 15673
조직: Alibaba Cloud (Singapore) Private Limited
국가: Japan
========================================
IP 정보: 163.172.152.168
포트 정보: 9000
조직: Scaleway Dedibox - Paris, France
국가: France
========================================


4️⃣ 영화 검색 API (OMDB)

OMDB API란?

OMDB (Open Movie Database)는 영화 정보를 제공하는 무료 API입니다.

🔗 공식 사이트

https://www.omdbapi.com/

API 키 발급

  1. OMDB 웹사이트 접속
  2. 이메일로 무료 API 키 요청
  3. 이메일에서 API 키 확인

기본 사용 예제

import requests

# API 설정
API_KEY = 'YOUR_API_KEY'
URL = 'https://www.omdbapi.com/'

# 검색어 입력
search = input("검색할 영화를 입력하세요: ")

# API 요청 (방법 1: params 사용)
params = {'apikey': API_KEY, 's': search}
response = requests.get(URL, params=params)

# API 요청 (방법 2: URL 직접 구성)
# url = f"http://www.omdbapi.com/?apikey={API_KEY}&s={search}"
# response = requests.get(url)

# JSON 파싱
data = response.json()

# 결과 출력
for movie in data['Search']:
    print(f"제목: {movie['Title']}")
    print(f"년도: {movie['Year']}")
    print(f"포스터: {movie['Poster']}")
    print("=" * 40)



5️⃣ 실습 1️⃣: Flask 영화 검색 웹 서비스

요구사항

  1. 사용자로부터 영화 제목 입력받기
  2. OMDB API로 영화 정보 검색
  3. 결과를 테이블로 표시 (포스터 이미지 포함)

디렉토리 구조

project/
├── app.py
├── templates/
│   ├── index.html
│   └── result.html
└── static/

Flask 앱 (app.py)

from flask import Flask, render_template, request
import requests

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/search', methods=['GET', 'POST'])
def search():
    # 사용자 입력 받기
    query = request.form['query']

    # OMDB API 요청
    url = f'http://www.omdbapi.com/?s={query}&apikey=YOUR_API_KEY'
    response = requests.get(url)
    data = response.json()

    # 영화 정보 추출
    movies = []
    for movie in data['Search']:
        title = movie['Title']
        year = movie['Year']
        poster = movie['Poster']
        movies.append({'title': title, 'year': year, 'poster': poster})

    return render_template('result.html', movies=movies)

if __name__ == '__main__':
    app.run(debug=True)

검색 페이지 (templates/index.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>영화 검색</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            max-width: 600px;
            margin: 50px auto;
            text-align: center;
        }
        input[type="text"] {
            width: 300px;
            padding: 10px;
            font-size: 16px;
        }
        input[type="submit"] {
            padding: 10px 20px;
            font-size: 16px;
            background-color: #4CAF50;
            color: white;
            border: none;
            cursor: pointer;
        }
    </style>
</head>
<body>
    <h1> 영화 검색 서비스</h1>
    <form action="{{ url_for('search') }}" method="POST">
        <label><b>영화 검색:</b></label><br><br>
        <input type="text" name="query" placeholder="영화 제목을 입력하세요" required>
        <input type="submit" value="검색">
    </form>
</body>
</html>

결과 페이지 (templates/result.html)

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>영화 검색 결과</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css">
</head>
<body>
    <div class="container my-5">
        <h1 class="mb-4"> 영화 검색 결과</h1>
        <table class="table table-striped">
            <thead>
                <tr>
                    <th>제목</th>
                    <th>년도</th>
                    <th>포스터</th>
                </tr>
            </thead>
            <tbody>
                {% for movie in movies %}
                <tr>
                    <td>{{ movie.title }}</td>
                    <td>{{ movie.year }}</td>
                    <td><img src="{{ movie.poster }}" class="img-thumbnail" style="max-width: 100px;"></td>
                </tr>
                {% endfor %}
            </tbody>
        </table>
        <a href="{{ url_for('index') }}" class="btn btn-primary">다시 검색</a>
    </div>
</body>
</html>



Part 2: MongoDB 데이터베이스


6️⃣ MongoDB란?

정의

MongoDB는 테이블과 행 대신 문서(Document)를 사용하여 데이터를 저장하는 NoSQL 데이터베이스입니다.

🔄 관계형 DB vs NoSQL

구분 관계형 DB (RDBMS) MongoDB (NoSQL)
데이터 저장 테이블 (Table) 컬렉션 (Collection)
데이터 단위 행 (Row) 문서 (Document)
쿼리 언어 SQL MongoDB Query
스키마 고정적 유연함
예시 MySQL, Oracle MongoDB

🌐 웹 서버 구조

웹서버 (Apache, IIS)
    ↓ 정적 페이지
WAS (Tomcat, JBoss...)
    ↓ 동적 페이지
DB (MySQL, MongoDB...)
    ↓ 데이터

7️⃣ MongoDB 설치

다운로드

https://www.mongodb.com/try/download/community
  1. MongoDB Community Edition 다운로드
  2. 설치 진행
  3. 기본 포트: 27017

데이터 구조 비교

MySQL/MSSQL/Oracle
└── 데이터베이스 > 테이블 > 컬럼 > 데이터

MongoDB
└── 데이터베이스 > 컬렉션(Collection) > 데이터(Object)

Elasticsearch
└── 인덱스 > 문서(Document) > 데이터

8️⃣ pymongo 설치 및 연결 🔗

설치

pip install pymongo

MongoDB 연결

from pymongo import MongoClient

# MongoDB 서버 연결
client = MongoClient('mongodb://localhost:27017/')

# 데이터베이스 선택
db = client['school_db']

# 컬렉션 선택
collection = db['students']

9️⃣ 데이터 삽입 (Create)

단일 문서 삽입

from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
db = client['school_db']
collection = db['students']

# 단일 문서 삽입
student = {
    "name": "atfield1988",
    "age": 100,
    "grade": "A",
    "subjects": ["Math", "English"]
}

result = collection.insert_one(student)
print(f"삽입된 문서 ID: {result.inserted_id}")


여러 문서 삽입

from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
db = client['school_db']
collection = db['students']

# 여러 문서 삽입
students = [
    {"name": "이영희", "age": 19, "grade": "B", "subjects": ["과학", "국어"]},
    {"name": "박민수", "age": 21, "grade": "A", "subjects": ["수학", "과학"]}
]

result = collection.insert_many(students)
print(f"삽입된 문서 ID들: {result.inserted_ids}")

🔟 데이터 조회 (Read)

모든 문서 조회

from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
db = client['school_db']
collection = db['students']

# 모든 문서 조회
print("모든 학생 데이터:")
for student in collection.find():
    print(student)

특정 문서 조회

# 단일 문서 조회
result = collection.find_one({"name": "김철수"})
print(result)

print("=" * 40)

# 조건에 맞는 문서 조회 (나이가 20 이상인 학생)
print("\n나이가 20 이상인 학생:")
for student in collection.find({"age": {"$gte": 20}}):
    print(student)

1️⃣1️⃣ 데이터 수정 (Update)

단일 문서 수정

from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
db = client['school_db']
collection = db['students']

# 김철수의 학년을 A+로 변경
result = collection.update_one(
    {"name": "김철수"},
    {"$set": {"grade": "A+"}}
)

print(f"수정된 문서 수: {result.modified_count}")

여러 문서 수정

# 수학 과목을 수강하는 학생들의 학년을 B로 변경
result = collection.update_many(
    {"subjects": "수학"},
    {"$set": {"grade": "B"}}
)

print(f"수정된 문서 수: {result.modified_count}")

1️⃣2️⃣ 데이터 삭제 (Delete)

단일 문서 삭제

from pymongo import MongoClient

client = MongoClient('mongodb://localhost:27017/')
db = client['school_db']
collection = db['students']

# 이름이 박민수인 학생 삭제
result = collection.delete_one({"name": "박민수"})
print(f"삭제된 문서 수: {result.deleted_count}")

여러 문서 삭제

# 나이가 19인 학생들 삭제
result = collection.delete_many({"age": 19})
print(f"삭제된 문서 수: {result.deleted_count}")

1️⃣3️⃣ Faker를 사용한 가짜 데이터 생성

설치

pip install faker

기본 사용법

from faker import Faker

# 한국어 Faker 객체 생성
fake = Faker("ko_KR")

# 가짜 데이터 생성
for _ in range(20):
    print(fake.name())
    print(fake.address())
    print(fake.email())
    print(fake.phone_number())
    print("=" * 40)

MongoDB에 가짜 데이터 삽입

from pymongo import MongoClient
from faker import Faker

# MongoDB 연결
client = MongoClient('mongodb://localhost:27017/')
db = client['mydatabase_faker']
collection = db['people']

# Faker 객체 생성
fake = Faker("ko_KR")

# 가짜 데이터 20개 생성 및 입력
for _ in range(20):
    person = {
        'name': fake.name(),
        'address': fake.address(),
        'email': fake.email(),
        'phone': fake.phone_number()
    }
    collection.insert_one(person)

print("가짜 데이터가 성공적으로 입력되었습니다.")




1️⃣4️⃣ 실습 2️⃣: Flask + MongoDB CRUD 시스템

요구사항

  1. MongoDB에서 people 데이터 조회
  2. 웹 페이지에 목록 표시
  3. 데이터 삭제 기능
  4. 데이터 수정 기능

Flask 앱 (app.py) - 기본 버전

from flask import Flask, redirect, render_template
from pymongo import MongoClient

app = Flask(__name__)

# MongoDB 연결
client = MongoClient('mongodb://localhost:27017/')
db = client['db_faker']
collection = db['people']

@app.route('/')
def index():
    people = collection.find()
    return render_template('list.html', people=people)

if __name__ == '__main__':
    app.run(debug=True)

목록 페이지 (templates/list.html) - 기본 버전

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
    <title>사용자 목록</title>
</head>
<body>
    <h1> 사용자 목록</h1>
    <table>
        <tr>
            <th>이름</th>
            <th>주소</th>
            <th>이메일</th>
            <th>전화번호</th>
        </tr>
        {% for person in people %}
        <tr>
            <td>{{ person.name }}</td>
            <td>{{ person.address }}</td>
            <td>{{ person.email }}</td>
            <td>{{ person.phone }}</td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

1️⃣5️⃣ 삭제 기능 추가

Flask 앱 (app.py) - 삭제 기능 추가

from flask import Flask, redirect, render_template
from pymongo import MongoClient
from bson import ObjectId

app = Flask(__name__)

# MongoDB 연결
client = MongoClient('mongodb://localhost:27017/')
db = client['db_faker']
collection = db['people']

@app.route('/')
def index():
    people = collection.find()
    return render_template('list.html', people=people)

@app.route('/delete_user/<user_id>')
def delete_user(user_id):
    # ObjectId로 변환하여 삭제
    collection.delete_one({'_id': ObjectId(user_id)})
    return redirect('/')

if __name__ == '__main__':
    app.run(debug=True)

목록 페이지 (templates/list.html) - 삭제 버튼 추가

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
    <title>사용자 목록</title>
</head>
<body>
    <h1> 사용자 목록</h1>
    <table>
        <tr>
            <th>이름</th>
            <th>주소</th>
            <th>이메일</th>
            <th>전화번호</th>
            <th>삭제</th>
        </tr>
        {% for person in people %}
        <tr>
            <td>{{ person.name }}</td>
            <td>{{ person.address }}</td>
            <td>{{ person.email }}</td>
            <td>{{ person.phone }}</td>
            <td><a href="{{ url_for('delete_user', user_id=person._id) }}"> 삭제</a></td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

1️⃣6️⃣ 수정 기능 추가

Flask 앱 (app.py) - 수정 기능 추가

from flask import Flask, redirect, render_template, request
from pymongo import MongoClient
from bson import ObjectId

app = Flask(__name__)

# MongoDB 연결
client = MongoClient('mongodb://localhost:27017/')
db = client['db_faker']
collection = db['people']

@app.route('/')
def index():
    people = collection.find()
    return render_template('list.html', people=people)

@app.route('/delete_user/<user_id>')
def delete_user(user_id):
    collection.delete_one({'_id': ObjectId(user_id)})
    return redirect('/')

@app.route('/edit_user/<user_id>', methods=['GET', 'POST'])
def edit_user(user_id):
    if request.method == 'POST':
        # 수정된 데이터 저장
        updated_data = {
            'name': request.form['name'],
            'address': request.form['address'],
            'email': request.form['email'],
            'phone': request.form['phone']
        }
        collection.update_one({'_id': ObjectId(user_id)}, {'$set': updated_data})
        return redirect('/')

    else:
        # 수정 페이지 표시
        person = collection.find_one({'_id': ObjectId(user_id)})
        return render_template('edit.html', person=person)

if __name__ == '__main__':
    app.run(debug=True)

목록 페이지 (templates/list.html) - 수정 버튼 추가

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
    <title>사용자 목록</title>
</head>
<body>
    <h1> 사용자 목록</h1>
    <table>
        <tr>
            <th>이름</th>
            <th>주소</th>
            <th>이메일</th>
            <th>전화번호</th>
            <th>삭제</th>
            <th>수정</th>
        </tr>
        {% for person in people %}
        <tr>
            <td>{{ person.name }}</td>
            <td>{{ person.address }}</td>
            <td>{{ person.email }}</td>
            <td>{{ person.phone }}</td>
            <td><a href="{{ url_for('delete_user', user_id=person._id) }}"> 삭제</a></td>
            <td><a href="{{ url_for('edit_user', user_id=person._id) }}">수정</a></td>
        </tr>
        {% endfor %}
    </table>
</body>
</html>

수정 페이지 (templates/edit.html)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
    <title>사용자 수정</title>
</head>
<body>
    <h1>✏️ 사용자 정보 수정</h1>
    <form method="post" action="{{ url_for('edit_user', user_id=person._id) }}">
        <label>이름:</label><br>
        <input type="text" name="name" value="{{ person.name }}" required><br><br>

        <label>주소:</label><br>
        <input type="text" name="address" value="{{ person.address }}" required><br><br>

        <label>이메일:</label><br>
        <input type="text" name="email" value="{{ person.email }}" required><br><br>

        <label>전화번호:</label><br>
        <input type="text" name="phone" value="{{ person.phone }}" required><br><br>

        <input type="submit" value="수정 완료">
        <a href="{{ url_for('index') }}">취소</a>
    </form>
</body>
</html>

스타일시트 (static/style.css)

body {
    font-family: Arial, sans-serif;
    margin: 20px;
}

h1 {
    color: #333;
}

table {
    border-collapse: collapse;
    width: 100%;
    margin-top: 20px;
}

th, td {
    border: 1px solid #ddd;
    padding: 12px;
    text-align: left;
}

th {
    background-color: #f2f2f2;
    font-weight: bold;
}

tr:nth-child(even) {
    background-color: #f9f9f9;
}

tr:hover {
    background-color: #e8e8e8;
}

a {
    color: #4CAF50;
    text-decoration: none;
}

a:hover {
    text-decoration: underline;
}

form {
    max-width: 500px;
    margin: 20px 0;
}

input[type="text"] {
    width: 100%;
    padding: 8px;
    margin: 5px 0;
    box-sizing: border-box;
}

input[type="submit"] {
    background-color: #4CAF50;
    color: white;
    padding: 10px 20px;
    border: none;
    cursor: pointer;
    margin-top: 10px;
}

input[type="submit"]:hover {
    background-color: #45a049;
}

양도현 삭제 클릭!


📋 핵심 개념 정리

✅ API 활용

항목 설명
OSINT 공개 출처 정보 수집
Shodan 인터넷 연결 기기 검색
OMDB 영화 정보 API

✅ MongoDB CRUD

작업 메서드 예시
Create insert_one(), insert_many() 데이터 추가
Read find(), find_one() 데이터 조회
Update update_one(), update_many() 데이터 수정
Delete delete_one(), delete_many() 데이터 삭제

🎯 프로젝트 흐름도

Flask 웹 애플리케이션
    ↓
사용자 입력 (검색, 수정, 삭제)
    ↓
API 요청 / MongoDB 쿼리
    ↓
데이터 처리
    ↓
템플릿 렌더링
    ↓
결과 표시

💡 실무 활용 사례

📌 보안 모니터링

  • Shodan API로 자사 IP 노출 확인
  • 취약점 스캔 자동화
  • IoT 기기 보안 점검

📌 데이터 관리

  • MongoDB를 사용한 유연한 데이터 저장
  • 가짜 데이터로 테스트 환경 구축
  • CRUD 기반 사용자 관리 시스템

인프라 활용을 위한 파이썬도 얼마 남지 않았다...
왜 이렇게 빨리 가지???