ZAP Scripting으로 Code Generator 구현하기

ZAProxy와 Burp를 혼용해 쓰면서 불편한 점들을 찾고, 고쳐가고 있습니다. 오늘은 그 중 Code genertor에 대한 이야기를 할까 합니다.

Code Generator?

이름 그래도 코드를 생성해주는 기능입니다. 자주 애용하던 기능이라 불편함이 바로 찾아왔는데요, 바로 Code generator 입니다. Burp suite에선 reissue request scripter 확장 기능으로 Request를 Python, Ruby, HTML 등 여러가지 언어 형태로 바로 변환할 수 있습니다.

  • https://portswigger.net/bappstore/6e0b53d8c801471c9dc614a016d8a20d

Burp suite에서도 확장 기능을 통해 해결된 부분이라 ZAProxy에서 찾아봤지만 확장 기능으로 따로 제공되진 않네요. 그래서 만들까 생각을 하던 중 ZAProxy에서 제공하는 Scripts 엔 없을까 하고 뒤져봤는데…. 이럴수가 유사한 기능을 찾았습니다.

CSRF Poc Genertor 인데요, 이 이야기인 즉슨 Request 데이터를 읽어와서 원하는 대로 파싱하고 문자열을 붙여서 기능을 만들 수 있다는 이야기가 되겠지요. 그래서 Scripts 작성을 통해 해결해봅니다.

(Request => HTML의 경우는 CSRF Poc 사용하면 될 듯 합니다. 전 Reuqest => Ruby로 작성해봅니다)

Write Code for generator

우선 Script 종류는 여러가지를 사용할 수 있습니다. 우선 ECMAScript(Javascript), Mozila Zest Script가 기본으로 탑재되어 있고 확장 기능 설치를 통해 Ruby, Python, Groovy 등 여러가지 언어에 대해 스크립팅을 할 수 있도록 추가가 가능합니다.

Ruby로 짤까 고민했지만 JRuby는 그닥 땡기지 않기 떄문에 많이 사용하는 JS로 작성합니다. (사실 보안분석 때문에 JS가 더 익숙할지도…)

Request/Response, Requester 등 요청 정보가 있는 곳에서 우클릭(Context Menu) > 선택으로 코드를 생성하는 것을 생각했기 떄문에 Tageted type API인 invokeWith를 사용하면 조금 편합니다.

작성전에 몇가지만 체크합시다.

  • invokeWith를 통해 우클릭 시 해당 위치에 타겟팅(포커싱)된 요청 값을 msg 인자값으로 넘겨줍니다.
  • 우리는 msg 값을 이용해서 Method, URL 등을 가져올 수 있습니다.
  • 이 과정은…
    • Method : msg.getRequestHeader().getMethod()
    • Url : msg.getRequestHeader().getURI()
    • clipboard 제어 : java.awt.Toolkit.getDefaultToolkit().getSystemClipboard();
    • jruby 처럼 java 코드에 접근해서 가져오면 됩니다.

Add script 해서 하나 만들면 되요. 결과는 Clipboard 복사 및 Log에 찍힙니다.

Request to Ruby Generator Code

작성하는 과정을 하나하나 작성할 순 없으니.. 기본 동작만 하는 풀 코드로 올립니다. GET/POST 구별 정도만 하고있고 JSON, Multipart는 코드 추가해야할 것 같네요. 손봐야할 부분이 많긴한데, 조만간 정리해서 git에 올려두도록 하겠습니다.

//You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
//Author : @hahwul

function invokeWith(msg) {
     var method = msg.getRequestHeader().getMethod();
     var url = msg.getRequestHeader().getURI().toString();
     code =        'require "net/http"\n';
     code = code + 'require "uri"\n';
     code = code + 'uri = URI.parse("'+url+'")\n';
     code = code + 'http = Net::HTTP.new(uri.host, uri.port)\n';
    
     if(method == "GET"){
        code = code + 'request = Net::HTTP::Get.new(uri.request_uri)\n';
     }
     else if(method == "POST"){
           code = code + 'request = Net::HTTP::Post.new(uri.request_uri)\n';
        body = msg.getRequestBody().toString();
        body = body.trim();
        if(isJson(body)){
           a=1;// JSON Proc
         }
        else{
            if(ismultipart(msg.getRequestHeader())){
              a=1;// Multipart Proc        
               }  
            else{
    body=body.split('&');
              code = code + 'request.set_form_data({';
    for(i=0;i<body.length;i++)
    {
        keyval = body[i].split('=');
     code = code + '"'+ decodeURIComponent(keyval[0]) + '" => ' + '"' + decodeURIComponetn(keyval[1]) + '",';
    }

            code = code + '"endendend"=>"endendend"}';
            }
        }
     }

     code = code + 'request["Accept"] = "*/*"\n';
     code = code + 'request["User-Agent"] = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.13; rv:60.0) Gecko/20100101 Firefox/60.0"\n';
     code = code + 'request["Connection"] = "close"\n';
     code = code + 'request["Accept-Language"] = "ko-KR,ko;q=0.8,en-US;q=0.5,en;q=0.3"\n';
     code = code + 'request["Accept-Encoding"] = "gzip, deflate"\n';
     code = code + 'response = http.request(request)\n';
     code = code + 'puts "[$] Code: "+response.code\n';
     code = code + '#puts "[$] Response Body: "+response.body\n';
     code = code + '\n';

print(code);
     selected = new java.awt.datatransfer.StringSelection(code);
     clipboard = java.awt.Toolkit.getDefaultToolkit().getSystemClipboard();
     clipboard.setContents(selected,null);
}

function isJson(str)
{
    try{
        JSON.parse(str);
    }
    catch(e){
        return false;
    }
        return true;
}

function ismultipart(header){
    type = header.getHeader(org.parosproxy.paros.network.HttpHeader.CONTENT_TYPE);
    if(type == null )
        return false;
    if(type.contains("multipart/form-data"))
            return true;
    return false;
}

Run!

Context menu > Request_to_ruby

복사된 코드로 테스트해보면 잘 요청됩니다 :)