ZAP에서 Zest Script로 Headless 기반의 인증 자동화 처리하기

최근에 Headless 기반의 Authentication script를 만들고 있었는데 약간의 어려움이 있었습니다. 실제로 headless browser를 통해 인증 처리는 쉬우나 그 뒤에 ZAP이 이를 인지하게 하는 것이 쉽지가 않았었는데요. 문뜩 제가 예전에 Zest script를 작성할 때 Client 관련 항목을 봤던게 갑자기 기억이 나서 GUI로 작성해보니 역시나 Client(Headless browser)를 지원하는게 맞았네요.

그래서 오늘은 이 Zest script를 이용해서 Authentication script를 작성하는 이야기를 하려고 합니다.

Why headless?

단순히 Form 또는 JSON으로 로그인 처리하는 서비스는 그냥 ZAP의 기능으로도 쉽게 만들 수 있습니다. 다만 몇몇 서비스들은 JS단 암호화로 ID와 Password를 추가로 암호화하기 때문에 이런 경우 암호화 로직을 파악해서 매번 암호화 처리하기에는 코스트가 좀 높습니다. 이 때 headless를 이용하면 실제 사용자의 플로우와 동일하게 접근할 수 있기 때문에 쉽게 만들고 사용할 수 있습니다.

Zest

Zest는 Mozilla 보안팀에서 만든 JSON 기반의 스크립팅 언어입니다. ZAP에서는 GUI로 코드를 작성할 수 있어 빠르게 코드 구성을 진행할 수 있습니다. 자세한 내용은 아래 글을 참고해주세요.

Zest와 ZAP! 강력한 보안 테스트 루틴을 만들어봐요 ⚡️

Write code

Sample target

매번 인증 스크립트 테스트할 때 Starbucks로 많이 했었어서 오늘도 스벅으로 해봅니다. (매번 대상으로 선정해서 약간 미안한 마음도 드네요 😊)

Make Zest Script

먼저 Scripts에서 New script로 하나 만들어줍시다. Type은 Authentication, Script Engine은 Zest로 지정합시다.

Script 생성 시 Parameters를 보면 기본적으로 LoginURL, Password, Username 3가지 변수를 미리 세팅해줍니다. 서비스에 맞게 변경하고 추가하셔서 사용하시면 됩니다.

Add Zest Client

Scripts에서 우클릭 시 Zest를 GUI로 작성할 수 있는 메뉴가 나타납니다. 먼저 Headless client를 만들고, 페이지를 접근해야 하기 때문에 Add Zest Client > Launch 를 통해 Client를 만들어줍시다.

Client에서 Window handle, Browser type 등을 지정해야하는데, 저는 Firefox로 진행하거라 동일하게 맞춰주었습니다. 그리고 URL은 우리가 고정 값이 아닌 변수를 사용할거라면 아까봤던 변수 이름인 {{LoginURL}}을 입력해줍니다.

Headless 체크는 개발 단계에선 체크를 풀고하는게 좋습니다. 눈에 동작이 직접 보이니깐요 :D

Find Elements

자 이제 Headless browser에서 URL을 연 이후에 ID/PW의 Elements를 찾아 값을 입력하고 Submit 버튼을 통해서 전달해야합니다. 브라우저 하나 열어서 개발자 도구로 id나 class 등 식별할 수 있는 값들을 찾아봅니다.

스타벅스에선 ID/PW 모두 id 속성을 사용하고 있습니다.

  • ID: txt_user_id (id)
  • PW: txt_user_pwd (id)
  • Button: btn_login (class)

Add Element Send Key

Add Zest Client > Element Send Keys 를 통해 해당 Element에 값을 보낼 수 있습니다. 이는 보통 Selenium으로 직접 통제하는 것과 비슷하죠.

Add Element Submit or Click

Submit 버튼도 등록해줍시다.

Form 영역에 속하지 않는 경우 Click 으로 해주시는 것도 괜찮습니다.

Add Sleep

참고로 중간에 LoginURL을 연 이후 2초 정도 딜레이를 주었습니다. 페이지가 정상 로드를 위해서죠.

Endgame

다 끝났습니다. GUI 상에선 5줄이고 Script console에서 보면 JSON 기반의 코드를 확인하실 수 있습니다. 이제 이 코드는 저장해두고 두고두고 로그인 처리를 할 떄 사용하실 수 있어요.

Test

그럼 실제로 동작하는 것을 봐야겠죠?

Load Authentication Script

먼저 각 Context에서 Authentication을 설정해줍시다. 여기서 Method는 Script로 지정하고 우리가 방금 만든 스크립트를 로드합시다.

그러면 바로 아래 LoginURL 변수 받는 공간이 생깁니다. 값을 넣어주세요.

Add User

이제 User를 추가해줍시다. 아까 변수로 사용된 Username과 Password 값을 넣어주는 단계에요.

Fire!!!

자 이제 Spidering이나 ActiveScan 시 User를 지정해주면 우리가 만든 스크립트를 사용하여 로그인하고 해당 세션을 기반으로 테스트하게 됩니다.

저는 Spidering으로 테스트했고 아래와 같이 로그인 이후에 확인할 수 있는 페이지들이 수집된 것을 볼 수 있습니다. 직접 Raw Request를 보면 쿠키도 잘 추가되어 있구요 :D

Code

{
  "about": "This is a Zest script. For more details about Zest visit https://github.com/zaproxy/zest/",
  "zestVersion": "0.3",
  "title": "StarbucksLogin",
  "description": "",
  "prefix": "",
  "type": "StandAlone",
  "parameters": {
    "tokenStart": "{{",
    "tokenEnd": "}}",
    "tokens": {
      "Username": "",
      "LoginURL": "",
      "Password": ""
    },
    "elementType": "ZestVariables"
  },
  "statements": [
    {
      "windowHandle": "firefox",
      "browserType": "firefox",
      "url": "{{LoginURL}}",
      "capabilities": "",
      "headless": true,
      "index": 1,
      "enabled": true,
      "elementType": "ZestClientLaunch"
    },
    {
      "milliseconds": 2000,
      "index": 2,
      "enabled": true,
      "elementType": "ZestActionSleep"
    },
    {
      "value": "{{Username}}",
      "windowHandle": "firefox",
      "type": "id",
      "element": "txt_user_id",
      "index": 3,
      "enabled": true,
      "elementType": "ZestClientElementSendKeys"
    },
    {
      "value": "{{Password}}",
      "windowHandle": "firefox",
      "type": "id",
      "element": "txt_user_pwd",
      "index": 4,
      "enabled": true,
      "elementType": "ZestClientElementSendKeys"
    },
    {
      "windowHandle": "firefox",
      "type": "classname",
      "element": "btn_login",
      "index": 5,
      "enabled": true,
      "elementType": "ZestClientElementClick"
    }
  ],
  "authentication": [],
  "index": 0,
  "enabled": true,
  "elementType": "ZestScript"
}

Conclusion

최근에는 JS Script를 많이 사용했는데 다시금 Zest의 강력함을 느꼈습니다. 혹시나 Headless 기반의 테스팅이나 자동화가 필요하시다면 Zest는 정말 탁월한 선택이 될겁니다.