URL Manipulation
- 사용자가 웹 애플리케이션의 URL을 의도적으로 변경하여 서버와 상호작용하는 방식
목적
- 정보 노출 : 다른 사용자가 접근해서는 안 되는 정보에 접근하려는 시도
- 권한 상승 : 낮은 권한의 사용자가 높은 권한의 데이터를 열람하거나 조작하려는 시도
- 비즈니스 논리 취약점 : 애플리케이션의 비즈니스 로직을 우회하거나 악용하려는 시도
- 기타 보안 취약점 악용 : SSRF(서버 사이드 요청 위조) 등 보안 취약점을 악용하려는 시도
예시
- 정보 노출 : user가 타인의 프로필에 접근하는 경우
- 원래 URL : http://example.com/profile?id=1
- 변경 URL : http://example.com/profile?id=2
- 권한 상승 : 낮은 권한의 사용자가 관리자 페이지에 접근하는 경우
- 원래 URL: http://example.com/admin
- 변경된 URL: http://example.com/admin?user=guest
- 비즈니스 논리 취약점 : 상품의 가격을 임의로 변경해 결제하는 경우
- 원래 URL: http://example.com/checkout?item=123&price=100
- 변경된 URL: http://example.com/checkout?item=123&price=1
방지법
- 인증 및 권한 확인
- 각 요청마다 사용자의 인증 상태와 권한을 확인
- 사용자 프로필을 요청할 때 현재 로그인한 사용자의 정보만 볼 수 있도록 함
- 입력 검증
- URL 매개변수를 포함한 모든 입력값을 verify
- 타입과 범위 확인
- URL 서명
- 중요한 데이터가 포함된 URL에는 서명을 추가하여 URL이 변경되지 않았음을 검증
- http://example.com/profile?id=123&sig=abc123 과 같이 서명을 추가하여 서버에서 sig 값을 검증
- redirection 제한
- URL을 제한하여 사용자가 의도된 경로 내에서만 이동할 수 있도록 함
- 로그아웃 후 redirection 할 때 신뢰할 수 있는 도메인만 허용
- 로깅 및 모니터링
- URL 조작 시도를 로깅하고 주기적으로 모니터링하여 이상 징후를 조기에 발견
프로젝트에 적용
현재 로직은 카드 id값을 requestParam값으로 넘겨주어 post요청을 보내며 url 단축도 같은 방식으로 진행하는 식으로 되어있다. 그래서 url이 http://3.36.97.132:9090/~/completedCard/104 과 같은 형식으로 되어있는데, 이 url에는 다른 사용자가 맨 끝 숫자를 사용하여 타인의 url에 접근할 수 있는 문제점이 있었다.
가장 간단한 URL Manipulation 방지법은 user를 검증하는 것인데, 우리의 서비스는 사용자가 카드를 만든 후 결제하면 생성되는 url로 로그인하지 않은 불특정 다수가 접근할 수 있는 서비스이기 때문에 user를 검증할 수 없었다.
또한, 카드를 공개/비공개로 바꾸어 비공개의 경우에는 카드를 만든 사람 말고는 조회하지 않게 할 수도 있었다. 하지만, 그러면 원하는 사용자(지인)에게도 공유하지 못한다는 UX적 문제가 발생하게 되었다.
따라서, url의 requestParam값을 암호화하여 컨트롤러에 보여주고, 이를 사용할 때에는 서버 측에서 복호화 하는 식으로 로직을 수정하려고 하였다.
기존에 작성되어 있는 코드는 cardID 값을 매개변수로 받아 모든 로직을 실행한다. 그러므로 기존 작성 코드를 최소한으로 수정하는 방법은 단 하나뿐이었다. 기존 url로 get 요청이 들어오면 암호화시킨 후 암호화 url로 서버 측에서 바로 리다이렉트 시켜주는 방법이다. 그러면 리다이렉트된 url로도 get요청이 갈텐데, 이 때 기존 로직을 수행하면 되는 것이다.
한마디로, 다음과 같다.
~completedCard/104로 get 요청 발생 -> URL 암호화 후 redirect -> redirect URL로 get 요청 발생 -> URL 복호화 후 cardID 값을 추출하여 카드 정보를 가져옴
@GetMapping("/completedCard/{cardID}")
public String getCompletedCardPage(@PathVariable int cardID) {
String originUrl = "completedCard/" + cardID;
String encodeUrl = Base64Util.encode(originUrl);
return "redirect:/card/" + encodeUrl;
}
@GetMapping("/{encodedUrl}")
public String decodeCompletedCardPage(@PathVariable String encodedUrl, Model model) {
try {
// URL 디코딩
String decodedUrl = Base64Util.decode(encodedUrl);
String[] parts = decodedUrl.split("/");
int cardID = Integer.parseInt(parts[parts.length - 1]);
System.out.println("카드 ID : " + cardID);
// 카드 정보 가져오기
CardVO cardVO = editService.getCompletedCardPage(cardID);
model.addAttribute("cardVO", cardVO);
// 카드의 CSS 정보 추출
String css = cardVO.getTemplateThumbnail().substring(25, cardVO.getTemplateThumbnail().length() - 4);
model.addAttribute("css", css);
// 방명록 정보 가져오기
List<GuestBookVO> guestBooks = editService.selectGuestBooks(cardVO.getCardID());
model.addAttribute("guestBooks", guestBooks);
return "card/completedCard";
} catch (IllegalArgumentException e) {
model.addAttribute("error", "Invalid URL encoding.");
return "error";
} catch (Exception e) {
model.addAttribute("error", "An error occurred while processing your request.");
return "error";
}
}
그럼 기존 url이 변경되어 ~/card/Y29tcGxldGVkQ2FyZC8xMA== 와 같은 형식으로 암호화되어 브라우저에 표시되는 것을 볼 수 있다. 또한, 위의 암호화된 url로 접속되면 잘 접속되는 것을 알 수 있다.
'커스텀 축하 카드 쇼핑몰 [축하해요]' 카테고리의 다른 글
Amazon EC2(AWS EC2)를 이용한 서버 배포 (0) | 2025.01.06 |
---|---|
Amazon S3(AWS S3) (1) | 2025.01.06 |
네이버 단축 url api 사용하기 (0) | 2025.01.06 |
아임포트 API 사용 (1) | 2025.01.06 |
주제 회의 및 문서 작성 (1) | 2025.01.06 |