Node.js 개발자가 운영 환경에 배포하는 보안 버그들

작년에 한 스타트업의 코드를 리뷰했습니다. 코드는 깔끔해 보였고, 테스트도 모두 통과했습니다.

그러다 이 코드를 발견했습니다: const query = SELECT * FROM users WHERE email = '${req.body.email}'

이것은 SQL 인젝션 버그입니다. 이 스타트업은 이 코드를 8개월 동안 운영 환경에서 실행했습니다. 어떤 개발자나 CTO도 이를 잡아내지 못했습니다.

이런 버그들은 코드가 정상적으로 작동하기 때문에 눈에 보이지 않습니다. 사용자가 입력창에 악의적인 명령어를 입력하기 전까지는 아무 문제 없이 작동합니다.

다음 5가지 흔한 실수를 방지하세요:

  1. 사용자 입력이 포함된 Raw SQL 쿼리에 템플릿 리터럴을 사용하지 마세요. 이는 공격자가 데이터베이스에 접근할 수 있게 합니다.
  • 나쁨: const query = SELECT * FROM users WHERE email = '${email}'
  • 좋음: 매개변수화된 쿼리(parameterized queries)를 사용하세요. const query = 'SELECT * FROM users WHERE email = $1' db.query(query, [email])
  1. Git에 비밀 정보 유출 개발자들은 종종 .env 파일을 저장소에 커밋합니다. 이는 데이터베이스 URL과 API 키를 노출시킵니다. 항상 .gitignore 파일에 .env를 추가하세요.

  2. jwt.verify 대신 jwt.decode 사용 jwt.decode는 토큰을 읽기만 할 뿐, 토큰이 진짜인지 확인하지 않습니다. 누구나 디코딩된 토큰을 위조할 수 있습니다.

  • 나쁨: const user = jwt.decode(token)
  • 좋음: 항상 서명을 검증하세요. const user = jwt.verify(token, process.env.JWT_SECRET)
  1. 인증 엔드포인트의 속도 제한(rate limiting) 누락 속도 제한이 없으면 공격자가 무차별 대입 공격(brute force)을 통해 수백만 개의 비밀번호를 시도할 수 있습니다. 라이브러리를 사용하여 로그인 시도 횟수를 제한하세요.
  • 좋음: 15분당 10회로 제한을 설정하세요.
  1. 너무 상세한 에러 메시지 클라이언트에게 가공되지 않은 에러 메시지를 보내는 것은 공격자가 시스템 구조를 파악하는 데 도움을 줍니다. 공격자는 테이블 이름과 데이터베이스 유형을 알 수 있게 됩니다.
  • 나쁨: res.status(500).json({ error: error.message })
  • 좋음: 에러를 내부적으로 로그로 남기고, 사용자에게는 일반적인 메시지를 보내세요. res.status(500).json({ error: 'Something went wrong' })

엔드포인트를 배포하기 전에 한 가지 질문을 던져보세요: 사용자가 예상치 못한 데이터를 보내면 어떻게 될까?

보안 버그는 복잡한 경우가 드뭅니다. 악의적인 사용자를 고려하는 것을 잊었을 때 발생합니다.

운영 환경에서 발견한 보안 버그는 무엇인가요?

Source: https://dev.to/manolito99/the-security-bug-every-nodejs-developer-ships-to-production-49e6