~/VibeHandbook
무료 PDF

챕터 18 · 02

인젝션: 입력이 코드가 될 때

인젝션은 가장 오래됐으면서 여전히 가장 흔한 심각한 버그다. 사용자로부터 온 데이터가 데이터가 아니라 명령으로 취급될 때 발생한다. 대부분의 앱에서는 두 가지 종류가 중요하다.

injection — SQL(스트럭처드 쿼리 랭귀지)은 앱이 데이터베이스에 데이터를 요청할 때 쓰는 언어다 — 은 사용자 입력이 데이터베이스 쿼리 안으로 미끄러져 들어갈 때 일어난다. 전형적인 취약 패턴:

// VULNERABLE: 사용자의 입력이 쿼리에 그대로 붙어버린다
app.get("/user", (req, res) => {
  const name = req.query.name;
  db.query(`SELECT * FROM users WHERE name = '${name}'`);
});
// 누가  name = '; DROP TABLE users; --  를 넘기면
// 당신의 쿼리는 테이블을 삭제하라는 명령이 된다.

해결책은 parameterized query(prepared statement라고도 한다)다. 쿼리와 데이터를 따로 보내서 데이터베이스가 둘을 결코 혼동하지 않게 한다:

// SAFE: 값은 파라미터로 전달되고, 절대 코드로 취급되지 않는다
app.get("/user", (req, res) => {
  const name = req.query.name;
  db.query("SELECT * FROM users WHERE name = ?", [name]);
});

규칙: 문자열 연결로 쿼리를 만들지 말라. diff에서 변수 주위로 SQL을 조립하는 백틱이나 +가 보이면, 멈추고 parameterized 버전을 요청하라.

두 경로는 같은 사용자 입력을 데이터베이스에 넣지만, 데이터베이스가 그 입력을 명령으로 착각하지 않게 하는 것은 둘 중 하나뿐이다:

  사용자 입력: '; DROP TABLE users; --
        │
        ├─────────────────────────┬───────────────────────────┐
        ▼                         ▼                            
  ✗ 문자열 연결              ✓ 파라미터화                      
  "...WHERE name=             "...WHERE name = ?", [input]      
     '" + input + "'"          (쿼리와 데이터를 분리 전송)     
        │                         │
        ▼                         ▼
  ┌───────────────┐         ┌───────────────┐
  │ 데이터베이스  │         │ 데이터베이스  │
  │입력을 명령으로│         │입력을 데이터로│
  │읽어들임       │         │만 취급        │
  │ → 테이블 삭제 │         │ → 안전 조회   │ 
  └───────────────┘         └───────────────┘
       유출                      안전

**XSS (cross-site scripting)**는 브라우저에서 일어나는 같은 발상이다. 사용자 입력을 받아 페이지에 raw HTML(하이퍼텍스트 마크업 랭귀지, 브라우저에 무엇을 보여줄지 알려주는 코드)로 떨어뜨리면, 공격자는 당신의 다른 사용자들의 브라우저에서 실행되는 <script> 태그를 주입할 수 있다 — 예를 들어 그들의 세션을 훔쳐서. 해결책은 이스케이핑이다: 사용자 콘텐츠를 HTML이 아니라 텍스트로 렌더링하라. React 같은 현대 프레임워크는 기본적으로 이스케이프해주는데, 이건 훌륭하다 — AI가 "작동하게 만들려고" dangerouslySetInnerHTML이나 innerHTML 같은 탈출구에 손을 뻗기 전까지는. 그것들은 보호를 우회한다. diff에서 그런 호출이 보이면 의심해야 할 대상으로 취급하라.

모든 인젝션 버그 뒤에 있는 통합 원리: 데이터와 코드를 분리하라. 사용자 입력이 쿼리, 명령, 템플릿, 혹은 HTML로 넘어갈 때마다, 무언가가 그것을 이스케이프하거나 parameterize해야 한다.

오프라인으로 보고 싶으세요?

책 전체를 PDF나 EPUB으로 무료로 내려받으세요.