ZAP의 fuzz-script를 이용해 Fuzzing 스킬 올리기

여러분들은 Fuzzing 많이 하시나요? 웹해킹.. 아니 대다수 보안 테스팅에서 Fuzzing은 많은 시간을 차지 하기도 하고, 반대로 시간을 줄여주기도 합니다. 오늘은 웹 테스팅에서 ZAP을 이용해 Fuzzing할 때 Script를 이용해서 조금 더 나은 테스팅을 하는 방법에 대해 이야기하려고 합니다.

ZAP Fuzzer

제가 몇 년 전부터 주력 도구로 OWASP ZAP으로 사용하고 있어서 또 ZAP 이야기를 하나보다 하시겠지만, 사실 Fuzzing 관련 기능은 제가 Burp를 사용 했었던 시기에도 ZAP이 가장 강력하다고 생각했습니다 😍

이는 Fuzzing 자체가 세밀한 컨트롤이 필요한데, ZAP은 전체적으로 굉장히 세밀한 컨트롤을 할 수 있고, 당연히 Fuzzing에는 굉장한 이점이 되었죠.

또한 오늘 이야기드릴 Scripting을 통한 보조가 정말 유용하기 때문에 아직도 Fuzzing에 대해선 ZAP이 좋은건 사실입니다. 물론 Burpsuite도 제작년부터 Turbo intruder가 나온 이후 스크립팅이 가능하기 때문에 굉장히 좋아졌어요 :D

Message Processors

ZAP Fuzzer에는 Message Processors라는 기능이 있습니다.

Fuzzer로 진입해서 세번째 탭을 보면 있습니다

여기엔 Fuzzing을 위한 Processor들을 등록할 수 있어요

이건 Fuzzing 시 발생하는 history 로그에 표기할 데이터를 처리할 수 있는 기능인데요, 기본적으로 Request의 Content-Length를 자동으로 업데이트하는 기능과 Reflected를 체크해주는 기능이 Enabled 되어 있습니다. 이외에도 Tag creator라고 해서 history 탭에서 보는 tags 정보와 동일한 기능을 수행시킬 수도 있죠.

Fuzzer HTTP Processor

새로운 Processor를 하나 생성해보면 Fuzzer HTTP Processor (script)라는 항목이 있습니다. 요게 바로 script에서 processor를 읽어오는 기능인데요. 현재는 사용 설정한 processor가 없어서 아무것도 나타나지 않습니다.

그럼 하나 만들어보면서 어떤 기능인지 알아보죠!

Fuzz script

Fuzz script도 일반 ZAP의 다른 Script와 동일한 문법을 사용합니다. Javascript죠. 물론 확장 설치에 따라서 Ruby, Python, Kotlin 등 여러가지 언어로 사용 가능하지만 개인적으론 JS가 가장 깔끔합니다.

자 그럼 먼저 Fuzz script의 구조를 보겠습니다. 크게 2가지만 알고 있으면 구현에 문제는 없습니다.

processMessage

실제 message 작업을 처리할 함수입니다. 메시지를 전송하기 전에 호출한다고 생각하면 됩니다. 인자값으론 utils와 message를 받는데, utils는 scripting에서 사용할 기능들, message는 전송 전에 HTTP Request의 정보를 의미합니다. 그래서 예를들어 아래와 같은 구문으로 임의로 헤더 세팅을 할 수도 있죠.

function processMessage(utils, message) {
	message.getRequestHeader().setHeader("X-API-KEY", "APIKEYALSKJDLAKSJFLKAJS");
}

processResult

processResult는 요청 후 결과를 처리하기 전 잡히는 함수입니다. 여기에 추가 검증 과정을 따로 집어 넣을 수도 있습니다. 그러면 fuzz의 모든 로그를 보지 않고 코드를 통해 원하는 결과만 걸러낼 수도 있겠죠.

function processResult(utils, fuzzResult){
	var msg = fuzzResult.getHttpMessage();
	if (msg.getRequestHeader() != ""){
		return false;
	}

  // addCustomState를 통해서 Fuzzer의 history에 Column을 추가할 수 있습니다.
  fuzzResult.addCustomState("Return", "True!!!")
	return true;
}

Make fuzz script

Random IP in X-Forwarded-For

간단하게 예시를 들어서 Fuzzing 시 X-Forwarded-For 헤더를 랜덤한 IP로 지정해보죠. 이는 Hop-by-Hop 구조에서 IP 기반 차단 정책이 뒤쪽 App 서버에 있는 경우 차단을 우회하면서 테스트하기에 용이합니다.

function processMessage(utils, message) {
  // 랜덤으로 IP 포맷의 값을 생성합니다.
	var random_ip = Math.floor(Math.random() * 254)+ "." + Math.floor(Math.random() * 254) + "." + Math.floor(Math.random() * 254) + "." + Math.floor(Math.random() * 254);
	// Fuzzing의 Request가 전송되기 전 X-Forwarded-For 헤더에 추가합니다.
	message.getRequestHeader().setHeader("X-Forwarded-For", random_ip);
}

function processResult(utils, fuzzResult){
	return true;
}

function getRequiredParamsNames(){
	return [];
}

function getOptionalParamsNames(){
	return [];
}

이후 Scripts에서 생성한 script를 enable 해줍시다.

이후 다시 Fuzz에 들어가서 확인해보면 Fuzzer HTTP Processor를 확인해보면 방금 만든 script가 추가되어 있습니다.

추가한 후 Fuzzing을 진행해보면 아래와 같이 X-Forwarded-For 헤더가 랜덤한 IP로 추가되어 진행됩니다.

비슷한 방법으로 X-Remote-IP X-Originating-IP X-Remote-Addr X-Client-IP 헤더도 가능하겠죠 😊

Fuzz with Cache busting

자 그럼 위 내용을 이용해서 하나 더 만들어볼까요? Fuzzing 을 통한 테스팅에선 실제 서비스 페이지에 접근하는 사용자의 영향을 줄이거나, 오탐할 수 있는 여지를 줄이기 위해서 Cache busting을 많이 사용하는데요. 이 또한 매번 귀찮은 과정인데, Fuzz script를 통하면 쉽게 구현할 수 있습니다.

Cache busting에 대한 내용은 Cache busting과 보안 테스팅 글을 읽어주세요 😎

Cache busting 방법은 각 요청마다 겹치지 않는 또는 다른 사용자와 중첩될 가능성이 적은 파라미터 등을 붙여 광역적인 캐싱을 예방합니다. 결국 매번 요청 마다 아까 헤더를 붙여줬다면 이번엔 파라미터를 붙여주면 됩니다.

function processMessage(utils, message) {
	var cbValue = Math.floor(Math.random() * 10000)
        setCacheBusting(message,cbValue);
	message.getRequestHeader().setHeader("X-Cache-Busting", cbValue);
}

function setCacheBusting(message,cbValue) {
    var URI = Java.type("org.apache.commons.httpclient.URI");
    var HtmlParameter = Java.type('org.parosproxy.paros.network.HtmlParameter')
    var URL_TYPE   = org.parosproxy.paros.network.HtmlParameter.Type.url;
    var params = message.getUrlParams()
    var newParam = new HtmlParameter(URL_TYPE, "x_cache_busting_"+cbValue, cbValue);
    params.add(newParam)
    message.getRequestHeader().setGetParams(params)
}

function processResult(utils, fuzzResult){
	return true;
}

function getRequiredParamsNames(){
	return [];
}

function getOptionalParamsNames(){
	return [];
}

잘 되네요 😍

Conclusion

몇가지 케이스로 간단하게 써봤는데, 어떤 느낌으로 사용하면 될지 감이 오시죠? ZAP의 Fuzzer가 오늘 했던 Message Processor 이외에도 기본 Processor도 잘 갖추고 있습니다. (예를들면 여러가지 인코딩 등)

이를 잘 활용하면 ffuf 같은 다른 web fuzzer를 사용하지 않고도 더 깔끔하고 정밀하게 테스트 할 수 있기 때문에 만약 ZAP을 자주 사용하신다면 꼭 알아두시면 좋을 기능이라고 생각드네요.

(Burpsuite 유저분들은 Turbo Intruder를 통해서 비슷한 형태로 구현할 수 있긴한데.. 그냥 파이썬 잘 하시면 됩니다 😁)

References

  • https://www.javadoc.io/doc/org.zaproxy/zap/latest/org/parosproxy/paros/network/HtmlParameter.Type.html