오늘은 Zest 스크립트에서 Headless Browser와 일반 ZAP 요청간 Cookie를 처리하는 방법에 대해 이야기하려고 합니다. Headless Browser와 ZAP 내부의 Req/Res 간에 쿠키 교환으로 여러 상황에서 웹 요청을 쉽게 처리하여 원하는 보안 테스팅, 자동화 루틴을 이어갈 수 있습니다.
아래와 같은 문제가 있는 경우엔 도움이 되실 수 있을 것 같네요.
- Headless Browser를 통해 웹 요청 처리 중 헤더를 추가/수정하고 싶은 경우
- ZAP의 기본적인 기능이나 Script를 통해 인증 로직 등을 통과하기 어려운 경우
- 로그인 시 FE단 암호화 존재
- 다중인증(MFA, Multi-Factor Authentication) 적용 페이지 등
- SPA(Single Page Application)
Background knowledge
Node
Node는 ZAP 전체에서 사용되는 HTTP Request/Response를 담고있는 Object입니다. 우리는 이러한 Node들을 Zest 스크립트로 추가시켜 쉽게 원하는 웹 요청을 순차적으로 전송시킬 수 있습니다.
- Node의 데이터 전송 주체는 ZAP(Java)입니다.
- 전송 헤더 등을 임의로 수정하기 쉽습니다.
- DOM을 처리하지 않기 때문에 복잡한 JS 로직이 있는 페이지를 처리하기 어렵습니다.
Client
Zest Client는 Headless Browser를 의미하며 ZAP에서 제공하는 타입(Firefox, Chrome, Safari, PhantomJS 등) 안에서 여러가지 브라우저를 구동하여 테스트할 수 있습니다.
Ruby, Python 에서 Headless Browser를 사용하듯이 Key, Elements, Click, Submit 등 여러 DOM Object에 대해 여러가지 Event를 발생시켜 테스트를 자동화할 수 있습니다.
- Client의 전송 주체는 Browser입니다.
- 전송 헤더를 통제하기 어렵습니다. (Capabilities를 조정해서 가능하긴 하지만 부드럽진 않아요)
- DOM과 브라우저 내 이벤트를 사용할 수 있기 때문에 사용자와 동일한 동선으로 움직일 수 있습니다.
Problem
위에서 이야기했듯이 Zest 사용 시 Node와 Client는 각각의 장점과 단점을 가집니다. 그리고 보통 이는 상호보완 관계라서 종종 Node와 Client를 혼용한 스크립트를 작성할 때가 많습니다.
이 때 문제는 Request의 전송 주체입니다. Node는 ZAP, Client는 Browser이기 때문에 기본적으로 서로의 세션 정보를 공유하지 않습니다. 예를들어 아래와 같은 순서의 스크립트를 실행했다면 결과적으로 Node에서 전송된 요청을 기존 Client의 정보를 가지지 못한채 전송합니다.
- Client로 웹 페이지 로그인 시도
- Response로 Set-Cookie로 인증 쿠키를 할당받음
- Client로 /page1 접근
- 쿠키는 포함되어 전송됨
- Node로 /page1 접근
- Node에 추가한 데이터만 전송함, 쿠키가 포함되지 않음
참고로 인증 처리를 위한 Session Management 타입의 스크립트에서는 기본 로직과 다르게 동작합니다. 해당 스크립트에선 자동으로 인증정보를 처리합니다.
Assign Cookie
Zest에서 제공하는 Client Assign Cookie를 이용하면 Client(Headless Browser)에서 특정 쿠키 값을 얻어 Zest 내 변수로 저장할 수 있습니다. 그리고 이를 Node 등에서도 사용이 가능합니다. 이를 이용하면 Client와 Node간의 쿠키 정보를 교환할 수 있습니다.
Step by Step
Get cookie from Client
제 도메인을 예시로 들어보겠습니다. 먼저 Client로 Headless Browser를 열어 접근하게 되면 _ga 쿠키를 할당받습니다. 그리고 이는 모든 요청에 포함되어 전송됩니다.
GET https://www.hahwul.com/ HTTP/1.1
Host: www.hahwul.com
Cookie: _ga=GA1.2.2040430370.1675666125; _gid=GA1.2.1364411377.1675666125;
초기 Browser 로드 이후에 Client Assign Cookie를 이용하여 원하는 쿠키 이름을 원하는 변수로 받아줍시다.
해당 부분을 지나게 되면 _ga 쿠키 값을 읽어 Test라는 변수에 저장합니다.
Use variable
Set Req/Res
Zest에서 변수를 호출하는 방식인 {{}}
를 통해 값을 대치할 수 있습니다. Req/Res의 Header, Body 등 원하는 부분에 {{Test}}
로 Test 변수의 값을 넣어서 전송할 수 있습니다.
위에선 IAMGACOOKIE란 헤더를 추가하고 필드 부분에 넣어줬습니다.
Set Cookie
Node의 Cookies 탭에는 임의로 쿠키를 설정할 수 있도록 제공하고 있습니다. 이는 한번 세팅하면 뒤에 후속 요청에도 영향을 주는 부분이기 때문에 쿠키를 달고 요청해야할 부분이 많다면 이를 활용하는 것도 좋은 방법입니다.
위 Req/Res 부분과 동일하게 {{Test}}
로 값을 불러와 사용할 수 있습니다.
Printing
스크립트 작성 중 빠른 디버깅을 위해선 Print로 값을 찍어서 보는게 편리합니다 :D
Run
자 이제 만든 Zest 스크립트를 실행해보면 Headless Browser로 쿠키를 가져오는 로직 등을 실행하고, Print로 인해 _ga 쿠키값을 스크립트 콘솔에 한번 찍고 마지막 /about 페이지로 웹요청을 전송하는 것을 볼 수 있습니다.
History에서 전송된 요청을 보면 우리가 위에서 설정한 IAMGACOOKIE 란 헤더와 Test란 이름의 쿠키에 Client(Headless)에서 받아온 _ga Cookie 값이 들어있는 것을 볼 수 있습니다.
Conclusion
개인적으로 Zest를 여러가지 보안테스팅 뿐만 아니라 개인적인 자동화 작업에도 많이 사용합니다. 제 자동화 시스템은 Ruby code와 Zest로만 이루어졌다고 봐도 될 정도인데요(물론 몇몇 Go 앱들도 있지만요 😁). 그만큼 웹 기반의 자동화 작업을 수행하는데 있어서는 Zest의 편리함이 정말 큰 이점으로 작용합니다.
두가지 타입에서 서로 쿠키를 공유할 수 있는 방법은 단순하지만 충분히 쓸모있기 떄문에 이런 기능이 있구나 하고 알아두시면 언젠간 도움되지 않을까 싶습니다. 글은 여기까지 마무리하고, 혹시나 궁금한점 있다면 편하게 댓글주세요!