서명된 URL을 깨뜨리는 트래킹 링크 버그

버그는 코드 속에 오랫동안 숨어 있을 수 있습니다. 대부분의 링크에서는 잘 작동합니다. 마케팅 링크에서도 아무런 문제를 일으키지 않죠. 하지만 가장 중요한 링크들을 망가뜨립니다.

저는 제 도구인 mail-history에서 이 버그를 발견했습니다. 이 버그는 서명된 URL(signed URLs)을 깨뜨립니다. 서명된 URL은 이메일 인증이나 보안 다운로드에 사용되는 링크입니다.

어떻게 이런 일이 발생했는지 설명하겠습니다.

제 도구는 클릭을 추적하기 위해 이메일 HTML을 재작성합니다. 원본 URL을 가져와 암호화한 뒤 트래킹 링크로 변환합니다. 사용자가 클릭하면, 도구는 URL을 복호화하여 실제 목적지로 보냅니다.

문제는 렌더링된 HTML에서 URL을 추출할 때 시작되었습니다.

Laravel과 Blade는 HTML 속성을 이스케이프(escape) 처리합니다. URL에 포함된 앰퍼샌드(&)는 HTML 엔티티가 됩니다. 즉, &가 &로 변하는 것입니다.

일반적인 링크의 경우, 브라우저가 이를 자동으로 수정합니다. 그래서 차이를 전혀 느끼지 못합니다.

하지만 제 도구는 링크를 브라우저로 보내는 것이 아니라, 복호화 함수로 문자열을 보냅니다.

만약 서명된 URL이 다음과 같다면: https://example.com/verify?expires=123&signature=abc

HTML은 다음과 같이 보입니다: https://example.com/verify?expires=123&signature=abc

제 도구는 &가 포함된 버전을 암호화합니다. 사용자가 클릭하면, 도구는 이를 복호화하여 &가 포함된 버전으로 보냅니다.

Laravel은 &를 보고 서명이 잘못되었다고 판단합니다. 결국 인증에 실패하게 됩니다.

해결 방법은 코드 한 줄이면 충분합니다. 암호화가 수행되기 전에 html_entity_decode를 사용하여 &를 다시 &로 되돌리는 것입니다.

이렇게 하면 복호화된 URL이 원본 서명된 URL과 바이트 단위로 정확히 일치하게 됩니다.

업무를 위한 교훈:

  • 함수에서 사용하기 위해 렌더링된 HTML에서 데이터를 추출한다면, 해당 데이터가 이스케이프 처리되어 있다고 가정하십시오.
  • 브라우저는 실수를 용납하지만, 암호화와 서명 검증은 그렇지 않습니다.
  • 한 줄짜리 수정 코드를 작성했다면, 반드시 그에 대한 테스트를 작성하십시오.

저는 특히 &의 존재 여부를 확인하는 테스트를 작성했습니다. 나중에 다른 개발자가 코드를 정리하다가 제 수정 사항을 삭제하더라도, 테스트가 즉시 실패하여 문제를 알 수 있게 됩니다.

Source: https://dev.to/nasrulhazim/the-tracking-link-bug-that-only-breaks-signed-urls-38c