[정보보안] CSRF 공격하기
CSRF 공격기법
크로스 사이트 요청 위조 공격
(CSRF: cross site request forgery attack)
▪XSS 와 유사하게 활용 가능
▪XSS 와 차이
-> XSS는 특정 사이트를 신뢰하는 정책을 이용한 코드 삽입하여 사용자가 script
실행
-> CSRF는 특정 인증된 사용자의 요청을 신뢰하는 정책을 이용해
script 요청을 서버에서 실행시킴.
공격 경로 예시
공격자가 시스템 관리자에게 스크립트가 포함된 메일 전송
▪인증된 세션을 가지고 있는 관리자가 메일을 읽음.
▪브라우저에서 인증된 세션과 함께 악성 스크립트가 실행.
▪관리자 세션 탈취 성공.
실습
입력받은 URL을 확인하는 봇이 구현된 서비스에서 CSRF 취약점을 이용한 FLAG 획득 문제이다.
문제는 여기있다.
csrf 페이지에 접속해보자.
csrf 페이지에 접속하면 csrf 파라미터로 alert(1)를 실행하는 스크립트 구문을 전달한다.
응답 화면에서는 script 구문이 필터링된 것을 볼 수 있다.
<script>alert(1)</script> -> <*>alert(1)
memo 페이지에 접속해보자.
memo 페이지에 접속하면 memo 파라미터로 hello를 전달하고,
응답 화면에서는 hello를 출력한다.
notice flag 페이지에 접속해보자.
notice flag 페이지에 접속하면 Acess Denied 문구가 출력된다.
flag 페이지에 접속해보자.
flag 페이지에는 제출하는 버튼이 보인다. 아까 csrf 페이지와 경로가 같은 것으로 보인다.
csrf 페이지에서 보았던 alert(1)를 출력하는 구문을 보내보자.
good 이라는 메세지가 출력되는게 끝이다.
뭔가 힌트가 더 필요할 것 같으니 소스코드를 확인해보자.
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for, session, g
from selenium import webdriver
import urllib
import os
app = Flask(__name__)
app.secret_key = os.urandom(32)
try:
FLAG = open('./flag.txt', 'r').read()
except:
FLAG = '[**FLAG**]'
def read_url(url, cookie={'name': 'name', 'value': 'value'}):
cookie.update({'domain':'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)
driver.set_page_load_timeout(3)
driver.get('http://127.0.0.1:8000/')
driver.add_cookie(cookie)
driver.get(f'http://127.0.0.1:8000/csrf?csrf={urllib.parse.quote(url)}')
except:
driver.quit()
return False
driver.quit()
return True
@app.route('/')
def index():
return render_template('index.html')
@app.route('/csrf')
def csrf():
csrf = request.args.get('csrf', '').lower()
xss_filter = ['frame', 'script', 'on']
for _ in xss_filter:
csrf = csrf.replace(_, '*')
return csrf
@app.route('/flag', methods=['GET', 'POST'])
def flag():
if request.method == 'GET':
return render_template('flag.html')
elif request.method == 'POST':
csrf = request.form.get('csrf', '')
if not read_url(csrf):
return '<script>alert("wrong??");history.go(-1);</script>'
return '<script>alert("good");history.go(-1);</script>'
memo_text = ''
@app.route('/memo')
def memo():
global memo_text
text = request.args.get('memo', None)
if text:
memo_text += text.replace('<', '<') + '\n'
return render_template('memo.html', memo=memo_text)
@app.route('/admin/notice_flag')
def admin_notice_flag():
global memo_text
if request.remote_addr != '127.0.0.1':
return 'Access Denied'
if request.args.get('userid', '') != 'admin':
return 'Your not admin'
memo_text += f'[Notice] flag is {FLAG}\n'
return 'Ok'
app.run(host='0.0.0.0', port=8000)
1) csrf 페이지
csrf 페이지는 csrf 파라미터로 전달된 값에서 frame, script, on 을 *로 치환하는 필터링이 존재한다.
2) memo 페이지
memo 페이지는 memo 파라미터로 전달된 값에서 < 를 < 로 치환하는 필터링이 존재한다.
3) notice flag 페이지
notice flag 페이지는 접속 요청 사용자의 ip 주소가 127.0.0.1이면 Access Denied 문자열을 출력하고
userid 파라미터 값이 admin이 아니면 Your not admin 문자열을 출력한다.
ip 주소가 127.0.0.1이고, userid가 admin이면 memo에 flag를 작성한다.
4) flag 페이지
flag 페이지는 csrf 파라미터 값을 전달하고, read_url(csrf) 함수가 정상 동작한다면 good 문자열을 출력한다.
read_url(csrf) 함수가 정상 동작하지 않으면, worng 문자열을 출력한다.
5) read_url(url, cookie={'name':'name', 'value':'value')
read_url() 함수는 url과 cookie를 매개변수로 갖는 함수이다.
함수가 호출되면 cookie 변수 값이 'domain':'127.0.0.1' 로 업데이트 된다.
그리고 driver 객체가 선언된다.
*driver: 파이썬의 selenium 라이브러리를 이용한 크롬 웹 브라우저 제어(webdriver) 객체가 저장된 변수
driver는 http://127.0.0.1:8000/ 페이지에 접속하고, cookie에 저장된 값을 cookie로 추가한다.
그리고 driver는 http://127.0.0.1:8000/csrf?csrf={urllib.parse.quote(url)} 에 접속한다.
* urllib.parse.quote(url): 아스키 형식이 아닌 문자열을 url 인코딩 하는 함수이다.
굉장히 복잡하지만 정리를 해보자.
1) csrf 페이지는 스크립트 구문(script, iframe, on) 필터링이 있는 페이지이다.
2) memo 페이지는 최종적으로 flag가 출력되는 페이지다.
3) notice flag 페이지는 userid 값이 admin 이면 웹 서버 local 환경에서 flag를 memo로 추가하는 페이지다.
4) flag 페이지는 csrf 페이지를 웹 서버 local IP로 접속하는 페이지이다.
우리는 4)flag 페이지를 이용해 웹 서버가 local에서 3)notice flag 페이지에 접속하도록 한 뒤에
memo로 flag를 출력하도록 하는 전략을 짤 수 있겠다.
4)flag 페이지에 csrf 파라미터 값을 입력하고 제출 버튼을 누르면 서버에서는 아래 url에 접속한다.
http://127.0.0.1:8000/csrf?csrf={사용자가 입력한 csrf 파라미터 값}HTML
여기에서 csrf 파라미터 값만 조작할 수 있기 때문에 이 값으로 notice flag 페이지를 접속하도록 유도해야한다.
script, iframe, on 문자열이 치환되어 있지만, 단순 웹 페이지에 접속하는 구문만 실행시키려면
img 태그의 src 속성을 이용할 수 있다. 코드를 작성해보자.
<img src=/admin/notice_flag?userid=admin>HTML
위 img 태그는 /admin/notice_flag?userid=admin 경로에서 이미지를 불러오는 태그인데
실제 이미지는 아니지만 일단 이미지를 불러오려는 시도를 하기 때문에 페이지에 접속하게 된다.
이 때 공격 구문이 성공할 수 있다. 진행시켜보자.
url_read() 함수가 정상적으로 동작되었나보다.
수행이 되었다면 memo 페이지에 flag 값이 출력되어 있을것이다.
가보자.
flag가 출력되어 있다.