촉촉한초코칩

DreamHack Web Hacking Stage 5 본문

Study/Web Hacking

DreamHack Web Hacking Stage 5

햄친구베이컨 2022. 7. 8. 21:32

Stage 5 - Cross Site Request Forgery (CSRF)

ClientSide: CSRF

 

교차 사이트 요청 위조(Cross Site Request Forgery, CSRF)

  • 의도치 않은 요청에 동의하게 하는 공격
  • 웹 페이지를 만들어서 이용자 입력을 유도하고, 이용자가 값을 입력하면 주요 포털 사이트 등으로 전송하여 이용자가 동의한 것과 같은 요청을 발생시킨다.

 

Cross Site Request Forgery (CSRF)

  • 임의 이용자의 권한으로 임의 주소에 HTTP 요청을 보낼 수 있는 취약점
  • 공격자 : 임의 이용자의 권한으로 서비스 기능을 사용해 이득을 취할 수 있다.

ex) CSRF 취약점이 존재하는 예제 코드 

이용자로부터 예금주와 금액을 입력받고 송금을 수행한다. 이때 계좌 비밀번호, OTP 등을 사용하지 않기 때문에 로그인한 이용자는 추가 인증 정보 없이 해당 기능을 사용할 수 있다. 

 

Cross Site Request Forgery 동작 

  • 공격에 성공하기 위해서는 공격자가 작성한 악성 스크립트(HTTP 요청을 보내는 코드)를 이용자가 실행해야 한다.
  • ex) 이용자에게 메일 보내거나, 게시판에 글을 작성해 이용자가 조회하도록 유도

 

요청을 보내는 스크립트 작성 방법

  • CSRF 공격 스크립트는 HTML 또는 Javascript를 통해 작성할 수 있다. 
  • HTML의 img 태그, form 태그 사용 > 태그를 사용해서 HTTP 요청을 보내면 HTTP 헤더인 Cookie에 이용자의 인증 정보가 포함된다.

  • img 태그 공격 코드 예시 : 이미지의 크기를 줄일 수 있는 옵션을 제공하며 이용자에게 들키지 않고 임의 페이지에 요청을 보낼 수 있다.

  • Javascript 공격 코드 예시 : 새로운 창을 띄우고 현재 창의 주소를 옮기는 등의 행위가 가능하다.

 

Cross Site Request Forgery 실습

  • Figure2 코드를 이용하여 HTML 태그를 사용해 계좌 잔고 1,000,000원 이상으로 늘려보기
<img src="/sendmoney?to=dreamhack&amount=1337">
<img src=1 onerror="fetch('/sendmoney?to=dreamhack&amount=1337');">
<link rel="stylesheet" href="/sendmoney?to=dreamhack&amount=1337">
<img src='http://bank.dreamhack.io/sendmoney?to=dreamhack&amount=1000000' width=0px height=0px>

 

XSS와 CSRF의 차이

공통점

  • 클라이언트를 대상으로 하는 공격
  • 이용자가 악성 스크립트가 포함된 페이지에 접속하도록 유도해야 한다. 
  • 스크립트를 웹 페이지에 작성해 공격한다.

차이점 (목적)

  • XSS : 인증 정보인 세션 및 쿠키 탈취를 목적으로 하며 공격할 사이트의 오리진에서 스크립트를 실행시킨다.
  • CSRF : 이용자가 임의 페이지에 HTTP 요청을 보내는 것을 목적으로 하는 공격이다. 또한 공격자는 악성 스크립트가 포함된 페이지에 접근한 이용자의 권한으로 웹 서비스의 임의 기능을 수행할 수 있다. 

 

정리

  • Cross Site Request Forgery (CSRF): 사이트 간 요청 위조. 이용자가 자신의 의지와는 무관하게 공격자가 의도한 행위를 특정 웹사이트에 요청하게 만드는 공격

 

CSRF 실습 

  • 관리자만 수행할 수 있는 메모 작성 기능 이용하기 
  • 파이썬 Flask 프레임워크를 통해 구현되었으며 CSRF를 통해 관리자의 기능을 이용해야 하기 때문에 관리자가 방문하는 시나리오가 필요하다. (셀레늄을 통해 구현)
  • 문제 목표 : CSRF를 통해 관리자 계정으로 특정 기능 실행시키기 

페이지 설명 

엔드포인트 설명
/ 인덱스 페이지
/vuln 이용자가 입력한 값을 출력한다. 
이때 XSS가 발생할 수 있는 키워드는 필터링한다.
/memo 이용자가 메모를 남길 수 있으며, 작성한 메모를 출력한다.
/admin/notice_flag 메모에 FLAG를 작성한다. 
로컬 호스트에서 접속해야 하며 사이트 관리자만 사용할 수 있다.
/flag 전달된 URL에 임의 이용자가 접속하게끔 한다.

1) /vuln

  • 이용자가 전달한 param 파라미터의 값을 출력한다. 
  • 이때 이용자의 파라미터에 frame, script, on 세 가지의 악성 키워드가 포함되어 있으면 이를 * 문자로 치환한다.
  • 키워드 필터링은 XSS 공격을 방지하기 위한 목적으로 존재한다.
@app.route("/vuln") # vuln 페이지 라우팅 (이용자가 /vuln 페이지에 접근시 아래 코드 실행)
def vuln():
    param = request.args.get("param", "").lower()   # 이용자가 입력한 param 파라미터를 소문자로 변경
    xss_filter = ["frame", "script", "on"]  # 세 가지 필터링 키워드
    for _ in xss_filter:
        param = param.replace(_, "*")   # 이용자가 입력한 값 중에 필터링 키워드가 있는 경우, '*'로 치환
    return param    # 이용자의 입력 값을 화면 상에 표시

2) /memo

  • 이용자가 전달한 memo 파라미터 값을 기록하고, render_template 함수를 통해 출력한다.
@app.route('/memo') # memo 페이지 라우팅
def memo(): # memo 함수 선언
    global memo_text # 메모를 전역변수로 참조
    text = request.args.get('memo', '') # 이용자가 전송한 memo 입력값을 가져옴
    memo_text += text + '\n' # 메모의 마지막에 새 줄 삽입 후 메모에 기록
    return render_template('memo.html', memo=memo_text) # 사이트에 기록된 메모를 화면에 출력

3) /admin/notice_flag

  • 로컬호스트 (127.0.0.1)에서 접근하고 userid 파라미터가 admin일 경우 메모에 FLAG를 작성하고, 조건을 만족하지 못하면 접근 제한 메시지가 출력된다.
  • 페이지 자체는 모두가 접근할 수 있고, userid 파라미터에 admin 값을 넣는 것도 가능하지만 일반 유저가 해당 페이지에 접근할 때의 IP 주소는 조작할 수 없다. 
  • 일반 유저의 IP가 자신의 컴퓨터를 의미하는 로컬호스트 IP가 되는 것은 불가능하기 때문에 이 페이지에 단순히 접근하는 것만으로는 플래그를 획득할 수 없다. 

* 로컬호스트 (Localhost) : 컴퓨터 네트워크에서 사용하는 호스트명으로, 자기자신의 컴퓨터를 의미한다.
IPv4 방식으로 표현 : 127.0.0.1
IPv6 방식으로 표현 : 00:00:00:00:00:00:00:01

@app.route('/admin/notice_flag') # notice_flag 페이지 라우팅
def admin_notice_flag():
    global memo_text # 메모를 전역변수로 참조
    if request.remote_addr != '127.0.0.1': # 이용자의 IP가 로컬호스트가 아닌 경우
        return 'Access Denied' # 접근 제한
    if request.args.get('userid', '') != 'admin': # userid 파라미터가 admin이 아닌 경우
        return 'Access Denied 2' # 접근 제한
    memo_text += f'[Notice] flag is {FLAG}\n' # 위의 조건을 만족한 경우 메모에 FLAG 기록
    return 'Ok' # Ok 반환

4) /flag (메소드에 따라 다른 기능 수행) 

  • GET : 이용자에게 URL을 입력받는 페이지를 제공한다.
  • POST : parm 파라미터 값을 가져와 check_csrf 함수의 인자로 넣고 호출한다. 
    check_csrf 함수는 인자를 닷 CSRF 취약점이 발생하는 URL의 파라미터로 설정하고, read_url 함수를 이용해 방문한다.
    이때 방문하는 URL은 서버가 동작하고 있는 로컬호스트의 이용자가 방문하는 시나리오이기 때문에 127.0.0.1의 호스트로 접속하게 된다. 
    read_url 함수는 셀레늄을 이용해 URL을 방문한다.
@app.route("/flag", methods=["GET", "POST"])    # flag 페이지 라우팅 (GET, POST 요청을 모두 받음)
def flag():
    if request.method == "GET": # 이용자의 요청이 GET 메소드인 경우
        return render_template("flag.html") # 이용자에게 링크를 입력받는 화면을 출력
    elif request.method == "POST":  # 이용자의 요청이 POST 메소드인 경우
        param = request.form.get("param", "")   # param 파라미터를 가져온 후,
        if not check_csrf(param):   # 관리자에게 접속 요청 (check_csrf 함수)
            return '<script>alert("wrong??");history.go(-1);</script>'
        return '<script>alert("good");history.go(-1);</script>'
def check_csrf(param, cookie={"name": "name", "value": "value"}):
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"  # 로컬 URL 설정
    return read_url(url, cookie)  # URL 방문
def read_url(url, cookie={"name": "name", "value": "value"}):
    cookie.update({"domain": "127.0.0.1"})  # 관리자 쿠키가 적용되는 범위를 127.0.0.1로 제한되도록 설정
    try:
        options = webdriver.ChromeOptions() # 크롬 옵션을 사용하도록 설정
        for _ in [
            "headless",
            "window-size=1920x1080",
            "disable-gpu",
            "no-sandbox",
            "disable-dev-shm-usage",
        ]:
            options.add_argument(_) # 크롬 브라우저 옵션 설정
        driver = webdriver.Chrome("/chromedriver", options=options) # 셀레늄에서 크롬 브라우저 사용
        driver.implicitly_wait(3)   # 크롬 로딩타임을 위한 타임아웃 3초 설정
        driver.set_page_load_timeout(3) # 페이지가 오픈되는 타임아웃 시간 3초 설정
        driver.get("http://127.0.0.1:8000/")    # 관리자가 CSRF-1 문제 사이트 접속
        driver.add_cookie(cookie)   # 관리자 쿠키 적용
        driver.get(url) # 인자로 전달된 url에 접속
    except Exception as e:
        driver.quit()   # 셀레늄 종료
        print(str(e))
        # return str(e)
        return False    # 접속 중 오류가 발생하면 비정상 종료 처리
    driver.quit()   # 셀레늄 종료
    return True # 정상 종료 처리

 

취약점 분석

  • /vuln 기능 : 이용자의 입력 값을 페이지에 출력
    필터링 키워드 이외의 <, >를 포함한 다른 키워드와 태그를 사용하여 CSRF 공격을 수행한다.

 

공격 수행 

  • /vuln 페이지에 CSRF 공격을 수행한다.
  • 공격 코드가 삽입된 /vuln 페이지를 다른 이용자가 방문할 경우, 의도하지 않은 페이지로 요청을 전송하는 시나리오의 익스플로잇을 구상해야 한다.
  • 플래그를 얻기 위해서는 /admin/notice_flag 페이지를 로컬호스트에서 접근해야 한다. 
    이를 위해 CSRF 공격으로 /vuln 페이지를 방문하는 로컬호스트 이용자가 /admin/notice_flag 페이지로 요청을 전송하도록 공격 코드를 작성해야 한다. 
  • 로컬 호스트 환경의 이용자가 임의 페이지를 방문하게 하려면 /flag 페이지를 이용해야 한다. 

웹 서버 

  • 먼저 CSRF 취약점 발생 여부를 확인하기 위해 HTTP 응답을 받을 웹 서버가 필요하다. 
    (만약 외부에서 접근 가능한 웹 서버가 없다면 드림핵 툴즈 서비스 이용할 것) https://tools.dreamhack.games
  • 서비스에서 제공하는 Request Bin 기능 : 랜덤한 URL을 제공하고 제공된 URL에 대해 이용자의 접속 기록을 저장하기 떄문에 XSS, CSRF 공격을 테스트하기 좋다. 

CSRF 취약점 테스트 

  • <img> 태그를 사용해 테스트 웹서버 URL에 접속하는 공격 코드를 작성한 뒤, 취약점이 발생하는 페이지에 해당 코드를 삽입한다. 
<img src="https://jugwwka.request.dreamhack.games">

CSRF 공격 코드 작성 및 실행

  • 요청을 보내지 않은 상태에서 메모 메뉴에 들어가면 hello라는 메모만 기록되어 있다. 
  • 로컬호스트에 위치하는 이용자가 /admin/notice_flag 페이지를 방문하도록 해야하기 때문에 공격 코드를 작성해야 한다.
  • 이떄, userid 파라미터가 admin인지 검사하는 부분이 존재하기 때문에 해당 문자열을 포함해야 한다.
<img src="/admin/notice_flag?userid=admin"/>
  • 공격 코드를 성공적으로 전송했다면 로컬호스트에서 http://127.0.0.1:8000/vuln?param=<img src="/admin/notice_flag?userid=admin"/>에 접속하게 된다. 
  • memo 페이지에 접근하면 앞에서 수행한 CSRF 공격으로 관리자가 /admin/notice_flag 페이지를 방문한 것을 확인할 수 있다. 

 

정리

  • 일반 유저가 아닌 로컬호스트의 이용자만 실행할 수 있는 기능을 해당 이용자의 의도와는 무관하게 실행하도록 하는 것에 성공했다. 
  • cSRF 공격은 게시판 서비스에서 공격자가 특정 글을 공지사항으로 업로드하거나, 삭제하는 등의 기능을 실행할 수도 있으며, 공격자의 계정을 게시판 관리자로 승격시키는 등의 행위로도 응용할 수 있다. 

'Study > Web Hacking' 카테고리의 다른 글

Dreamhack - csrf-2  (0) 2022.07.08
Dreamhack - csrf-1  (0) 2022.07.08
Webhacking old-01  (0) 2022.07.08
Webhacking g00gle1  (0) 2022.07.05
Dreamhack - xss-2  (0) 2022.07.05