설명
비밀번호, 토큰, API 키 같은 민감 정보를 URL의 쿼리 문자열로 전달하면 브라우저 히스토리, 서버·프록시 로그, 공유 링크나 모니터링 시스템에 남을 수 있습니다. 정책과 요청 대상에 따라 Referer 헤더로 전달될 수도 있습니다. HTTPS를 사용해도 이러한 URL 저장·공유 경로까지 없어지는 것은 아닙니다. 공격자는 노출된 비밀 값을 재사용해 계정 탈취, 세션 하이재킹이나 API 남용을 시도할 수 있습니다.
잠재적 영향
- 민감정보 노출: URL이 여러 저장 지점(브라우저 히스토리, 서버/프록시 로그, 모니터링, 캐시, Referer 헤더)에 남아 비밀번호·토큰이 유출될 수 있음
- 계정/세션 탈취: 탈취한 비밀번호·액세스 토큰을 재사용하여 계정 접속 또는 세션 하이재킹 가능
- 무단 API 사용: 노출된 API Key/Access Token으로 대량 호출, 데이터 수집, 과금 피해 유발
해결 방법
- 민감 정보는 URL 쿼리에 넣지 말고 작업에 맞는 POST 본문(
req.body)이나 인증 헤더(Authorization)로 전달하세요. POST 요청의 URL에도 비밀 값을 넣지 마세요. - 해당 라우트에서 본문이나 헤더를 읽도록 변경하고, 폼에는
method="POST"를 사용하세요. - 항상 HTTPS를 사용하고 본문과 인증 헤더도 로그에서 제거·마스킹하세요.
- 애플리케이션뿐 아니라 리버스 프록시와 모니터링 시스템에서도 민감한 쿼리 값의 기록을 제한하세요.
- 민감한 GET 쿼리를 거부하되, 거부 전에 프록시 등이 URL을 기록할 수 있으므로 클라이언트도 함께 수정하세요.
- 리다이렉트 URL과 공유 링크에 비밀 값을 포함하지 마세요.
예시
전달 위치만 비교하는 발췌입니다. 실제 비밀번호 인증과 토큰 검증은 주석으로 생략되어 있으며, 성공한 경우에만 응답하도록 구현해야 합니다.
변경 전
javascript
const express = require("express");
const app = express();
// BAD: 비밀번호를 GET 쿼리에서 읽음
app.get("/signin", (req, res) => {
const user = req.query.user; // 비민감 예시
const password = req.query.password; // 취약: URL에 노출됨
// authenticate(user, password)
res.send("signed in");
});
app.listen(3000);
변경 후
javascript
const express = require("express");
const app = express();
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
// GOOD: POST 본문에서 읽기
app.post("/signin", (req, res) => {
const { user, password } = req.body; // 민감값은 본문으로 전달
// authenticate(user, password)
res.send("signed in");
});
// 또는 토큰은 Authorization 헤더 사용
app.post("/api/data", (req, res) => {
const auth = req.get("Authorization"); // 예: 'Bearer <token>'
// verifyBearer(auth)
res.json({ ok: true });
});
app.listen(3000);
설명:
- 변경 전: 비밀번호가 URL에 포함되어 히스토리나 로그에 남고, 정책에 따라
Referer로 전달될 수 있습니다. - 변경 후: 민감 정보는 GET 쿼리가 아닌 POST 본문(req.body) 또는 Authorization 헤더로 전달하여 URL에 남지 않게 합니다. HTTPS와 로그 보호도 함께 적용해야 합니다.