올 여름쯤이였나요? oioi님의 이야기로 쓸만한 Burp 플러그인 하나를 사용중입니다.
오늘은 Reflected XSS를 찾는데 있어 탁월한 성능을 보여주는 Reflector에 대한 이야기를 하려합니다.

Reflector?


공식 Git에는 아래와 같이 소개되어 있습니다.

"Burp Suite extension is able to find reflected XSS on page in real-time while browsing on web-site and include some features as:"


딱 Reflected XSS를 찾기 위한 툴입니다. 대충 보면 Sentinel과 비슷할 것 같지만, 이 친구는 Burp pro 버전의 Passive scan과 유사한 기능을 가집니다.
Proxy를 통해 수집되는 정보에 대해 자동으로 체크해보고 결과를 분석가에게 통보해줍니다.

모든 URL에 대해 전수 조사하기 떄문에 놓칠 수 있는 부분을 잡아주지만 그만큼 다수 페이지에 의도하지 않은 요청도 발생할 수 있기 때문에 양날의 칼 같은 존재입니다.
그래도 XSS 코드에 뻗을 서비스면.. 기능 문제가 심히 의심되므로 개인적으로 아주 좋은 Extension으로 생각됩니다.

Install & Setting

Burp suite의 Extension은 자바로 만듭니다. Burp 자체가 자바 기반인 것도 한몫 하겠네요.
먼저 공식 git에서 소스코드를 받아줍니다.

#> git clone https://github.com/elkokc/reflector.git
#> cd reflector

컴파일 후 jar로 압축하여 burp에서 로드하면됩니다.

#> mkdir reles
#> javac -d reles ./src/*.java

class  파일이 생성되었으니 jar 묶어줍시다.

#> jar cvf reflector.jar reles

만들어진 reflector.jar 파일을 BApp Store에서 Manual Install을 통해 설치해줍니다.

https://github.com/elkokc/reflector/blob/master/screenshot/reflector_demo1.gif?raw=true

Code


src 내부에 보면 CheckReflection.java 코드쪽에 검증 하는 로직이 있습니다.
CheckReflection 이란 함수에서 파라미터에 값을 뽑아내고 이를 가지고 요청을 전송해서 문제가 있는지 체크합니다.

    public CheckReflection(Settings settings, IExtensionHelpers helpers, IHttpRequestResponse iHttpRequestResponse, IBurpExtenderCallbacks callbacks) {
                    this.settings = settings;
                    this.helpers = helpers;
                    this.callbacks = callbacks;
                    this.iHttpRequestResponse = iHttpRequestResponse;
                    this.bodyOffset = helpers.analyzeResponse(iHttpRequestResponse.getResponse()).getBodyOffset();
                }

                    public List<Map> checkResponse() {
                    List<Map> reflectedParameters = new ArrayList<>();
                    List<IParameter> parameters = helpers.analyzeRequest(iHttpRequestResponse).getParameters(); // 파라미터 가져옴
                    byte[] request = iHttpRequestResponse.getRequest();
                    for (IParameter parameter : parameters){ // 파라미터 만큼 전송
                        byte[] bytesOfParamValue = helpers.urlDecode(parameter.getValue().getBytes());
                        if (bytesOfParamValue.length > 2)
                        {
                            byte b = request[parameter.getValueStart() - 1];
                            if(parameter.getType() == IParameter.PARAM_JSON && b != QUOTE_BYTE){ // QUOTE_BYTE 여부 확인
                                continue;
                            }
                            List<int[]> listOfMatches = getMatches(iHttpRequestResponse.getResponse(), bytesOfParamValue);
                            if (!listOfMatches.isEmpty())
                            {
                    Map parameterDescription = new HashMap();
                    parameterDescription.put(NAME, parameter.getName());
                    parameterDescription.put(VALUE, parameter.getValue());
                    parameterDescription.put(TYPE, parameter.getType());
                    parameterDescription.put(VALUE_START, parameter.getValueStart());
                    parameterDescription.put(VALUE_END, parameter.getValueEnd());
                    parameterDescription.put(MATCHES, listOfMatches);
                    parameterDescription.put(REFLECTED_IN, checkWhereReflectionPlaced(listOfMatches));
                    reflectedParameters.add(parameterDescription);
                }
            }
        }
        if ( settings.getAggressiveMode() && !reflectedParameters.isEmpty() )
        {
            Aggressive scan = new Aggressive(settings, helpers, iHttpRequestResponse, callbacks, reflectedParameters);
            scan.scanReflectedParameters();
        } else if ( settings.getCheckContext() && !reflectedParameters.isEmpty() ) {
            String symbols = "",
                    body = new String(iHttpRequestResponse.getResponse()).substring(this.bodyOffset);
            ArrayList<int[]> payloadIndexes = null;
            //cycle by parameters
            for (Map parameter : reflectedParameters) {
                payloadIndexes = new ArrayList<>();

                for (int[] indexPair: (ArrayList<int[]>) parameter.get(MATCHES)) {
                    int[] tmpIndexes = new int[] { indexPair[0] - this.bodyOffset, indexPair[1] - this.bodyOffset };
                    payloadIndexes.add( tmpIndexes );
                }

                ContextAnalyzer contextAnalyzer = new ContextAnalyzer( body.toLowerCase(), payloadIndexes );
                symbols = contextAnalyzer.getIssuesForAllParameters();
                if ( symbols.length() > 0 ) {
                    parameter.put(VULNERABLE, symbols);
                }
            }
        }
        return reflectedParameters;
    }

체크하는 규칙은 ContextAnalyzer.java 에 정의되어 있습니다.

private String checksContextSecurity(String reflectedPayload, String context){
        String contextChars = null;
        switch (context) {
            case CONTEXT_OUT_OF_TAG: {
                if (reflectedPayload.contains("<")) {
                    contextChars = "<";
                }
            }
            break;
            case CONTEXT_IN_ATTRIBUTE_Q: {
                if (reflectedPayload.contains("'")) {
                    contextChars = "'";
                }
            }
            break;
            case CONTEXT_IN_ATTRIBUTE_DQ: {
                if (reflectedPayload.contains("\"")) {
                    contextChars = "\"";
                }
            }
            break;
            case CONTEXT_IN_TAG: {
                if (reflectedPayload.length() > 0)
                    contextChars = reflectedPayload;
                else
                    contextChars = "ALL";
            }
            break;
            case CONTEXT_IN_SCRIPT_TAG_STRING_Q: {
                if (reflectedPayload.contains("'")) {
                    contextChars = "'";
                }
            }
            break;
            case CONTEXT_IN_SCRIPT_TAG_STRING_DQ: {
                if (reflectedPayload.contains("\"")) {
                    contextChars = "\"";
                }
            }
            break;
            case CONTEXT_IN_SCRIPT_TAG: {
                if (reflectedPayload.length() > 0)
                    contextChars = reflectedPayload;
                else
                    contextChars = "ALL";
            }
            break;
        }
        return contextChars;
    }

Conclusion

한.. 4년전쯤에 Reflected XSS 테스트 쉽게하려고 툴 만들어서 돌리던 기억이 납니다.
곰곰히 생각해보면 Java로 짜서 Burp에 올렸더라면 조금 더 효과적이였을 것 같단 생각이 드네요. 아쉽

편리한 툴이니 잘 활용하시되 의존하진 맙시다 :)

Reference

https://github.com/elkokc/reflector

댓글 3개:

  1. Releases file
    https://github.com/elkokc/reflector/releases

    답글삭제
  2. reflector 찾아보다보니 역시 여기까지 옵니다 ㅋㅋ 유익한 글에 대한 감사의 의미로 살포시 눌러주고 갑니다. ㅎㅎ

    답글삭제