드림핵 Web Hacking-3일차

지선·2023년 7월 19일

드림핵WebHacking

목록 보기
4/12

코로나 이슈+계절 기말로 오랜만에 들어왔습네다 ㅎ.. ㅠ

CSRF(Cross Site Request Forgery)

이용자를 속여 의도치 않은 요청에 동의하게 하는 공격,
임의 이용자의 권한으로 임의 주소에 HTTP 요청을 보낼 수 있는 취약점
->이용자의 권한으로 서비스 기능 사용해 이득 취할 수 o

예시

# 이용자가 /sendmoney에 접속
@app.route('/sendmoney')
def sendmoney(name):
    # 송금을 받는 사람과 금액을 입력받음.
    to_user = request.args.get('to')
	amount = int(request.args.get('amount'))
	
    # request.args.get('~~'): ~~에 해당하는 것을 입력(받을 수) 있음
    # 매번 나오는 거니까 확실하게 해두자
    
	# 송금 기능 실행 후, 결과 반환	
	success_status = send_money(to_user, amount)
	
	if success_status:
		return "Send success."
	else:
		return "Send fail."

이때의 문제점: 인증 정보 x 기능 수행

공격 시나리오

<img src="/sendmoney?to=dreamhack&amount=100000">
<img src=1 onerror="fetch('/sendmoney?to=dreamhack&amount=100000');">
# fetch(url): GET 요청
<link rel="stylesheet" href="/sendmoney?to=dreamhack&amount=1337">
# href="url"라는 링크가 현재 html의 stylesheet이라는 뜻 (rel=relation)

CSRF 공격의 성공?
공격자가 작성한 악성 스크립트(HTTP 요청 보내는 코드)를 실행해야함
->메일, 게시판 글 작성을 통한 이용자의 조회 유도 방법

HTTP 헤더의 Cookie에 이용자의 인증 정보가 포함될 수 있기 위해 사용하는 태그
1. img 태그: 이미지를 불러옴
2. form 태그: 웹 페이지에 입력된 양식을 전송

CSRF 공격

# html img 태그 공격
<img src='http://bank.dreamhack.io/sendmoney?to=dreamhack&amount=1337' width=0px height=0px>
# width, height을 0으로 설정->보이지 x 이용자 몰래 요청 가능
/* Javascript에서 새 창 띄우기 */
window.open('http://bank.dreamhack.io/sendmoney?to=dreamhack&amount=1337');

/* Javascript에서 현재 창의 주소 옮기기 */
/* href: 현재 접속 중인 페이지 정보, 값 변경 가능->다른 페이지로 이동 가능 */
location.href = 'http://bank.dreamhack.io/sendmoney?to=dreamhack&amount=1337';
/* 주어진 링크로 현재 창의 주소 옮기기 */

/*기존에 있는 페이지를 새로운 페이지로 변경시킴*/
location.replace('http://bank.dreamhack.io/sendmoney?to=dreamhack&amount=1337');

/*location.href='~~~~'*/
/*location.replace('~~~')*/

XSS, CSRF 비교

공통점:

  • 클라이언트 대상 공격
  • 이용자가 악성 스크립트가 포함된 페이지에 접속해야함
    차이점:
    목적의 차이
  • XSS: 세션 및 쿠키 탈취 목적, 공격 사이트의 오리진에서 스크립트 실행
  • CSRF: 이용자가 임의 페이지에 HTTP 요청을 보내는 것이 목적, 악성 스크립트가 포함된 페이지에 접근한 이용자의 권한으로 웹 서비스 실행

CSRF-1

#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
import urllib
import os

app = Flask(__name__)
# Flask 객체 생성, 현재 모듈의 이름을 Flask에 전달하여 객체 생성
app.secret_key = os.urandom(32)
# Flask의 비밀 키 설정, os.urandom(32): 운영체제에서 안전한 난수 생성 (32바이트)

try:
    FLAG = open("./flag.txt", "r").read()
    # flag.txt 파일을 r (읽기모드)로 열기 위한 파일 객체 반환 -> read()로 파일 읽어줌
except:
    # 예외 발생시 실행, FLAG 변수에 [**FLAG**] 문자열 할당 
    FLAG = "[**FLAG**]"


def read_url(url, cookie={"name": "name", "value": "value"}):
# read_url 함수 정의, 매개변수는 url과 cookie이고 cookie는 기본 값으로 {"name": "name", "value": "value"}를 가짐
    cookie.update({"domain": "127.0.0.1"})
    #cookie의 딕셔너리에 {"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(_)
            #미리 정의된 옵션 추가 (브라우저 헤드리스 모드 실행, 화면 크기, GPU 사용 등)
        driver = webdriver.Chrome("/chromedriver", options=options)
        # chromedriver를 사용해 webdriver객체 생성
        driver.implicitly_wait(3)
        driver.set_page_load_timeout(3)
        driver.get("http://127.0.0.1:8000/")
    	# driver.get("http://127.0.0.1:8000/"): webdriver로 "http://127.0.0.1:8000/" 방문
    	driver.add_cookie(cookie)
        전달된 쿠키 webdriver에 추가
        driver.get(url)
    except Exception as e:
        driver.quit()
        print(str(e))
        # return str(e)
        return False
    driver.quit()
    return True


def check_csrf(param, cookie={"name": "name", "value": "value"}):
	#check_csrf 함수 정의, 매개변수 param, cookie
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
    #urllib.parse.quote(~~):~~를 url 인코딩 시켜줌
    return read_url(url, cookie)


@app.route("/")
def index():
    return render_template("index.html")


@app.route("/vuln")
def vuln():
    param = request.args.get("param", "").lower()
    # 매개변수를 소문자로 변환해서 전달
    xss_filter = ["frame", "script", "on"]
    # xss_filter로 frame, script, on이 포함되는 경우 필터링
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param


@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
    # GET 메소드인경우
        return render_template("flag.html")
    elif request.method == "POST":
    # POST 메소드인경우
        param = request.form.get("param", "")
        if not check_csrf(param):
        #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
    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":
    #127.0.0.1이 아닌 로컬호스트인 경우 차단
        return "Access Denied"
    if request.args.get("userid", "") != "admin":
    #userID가 admin이 아닌 경우에도 차단
        return "Access Denied 2"
    memo_text += f"[Notice] flag is {FLAG}\n"
    # 127.0.0.1이고 userid가 admin이라면 flag를 메모에 추가!
    return "Ok"


app.run(host="0.0.0.0", port=8000)

코드는 살펴보았으니 페이지에 들어가서 살펴보자
vuln(csrf) page에 처음으로 들어가보면

script가 * 로 필터링이 되어있다.

memo 페이지는 이전 XSS과 같고,
notice flag 페이지가 추가되었다.

vuln(csrf) page에서 테스트해보고
flag 페이지에 들어가서 CSRF 공격을 수행하면 되는 듯 하다.

frame, script, on 이것들은 필터링되니 이것들을 사용하지 않거나,
아니면 우회하는 방법으로 공격을 하면 될 것 같다.
참고로 소문자로 변환하니까 ScriPT 이런식으로는 안된다.

그냥 편하게 img 태그 써야겠다.
/admin/notice_flag에

memo_text += f"[Notice] flag is {FLAG}\n"
    # 127.0.0.1이고 userid가 admin이라면 flag를 메모에 추가!

이게 있었는데 img 태그의 url을 /admin/notice_flag로 해놓고, userid가 admin이라는 것을 넣어 짜주었더니

<img src="http://127.0.0.1:8000/admin/notice_flag?userid=admin">

메모장에 flag가 출력이 되었다.

csrf-2

#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
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**]"

users = {
    'guest': 'guest',
    'admin': FLAG
}
# users를 살펴보면 guest:guest로 로그인 할 수 있다.

session_storage = {}
  
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()
        print(str(e))
        # return str(e)
        return False
    driver.quit()
    return True
# 7월 18일에 코드가 바꼈다고 함 !


def check_csrf(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("/")
def index():
    session_id = request.cookies.get('sessionid', None)
    # request.cookies.get: 쿠키 값 불러오기
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    return render_template('index.html', text=f'Hello {username}, {"flag is " + FLAG if username == "admin" else "you are not an admin"}')
	# username이 admin이라면 FLAG 출력

@app.route("/vuln")
def vuln():
    param = request.args.get("param", "").lower()
    xss_filter = ["frame", "script", "on"]
    for _ in xss_filter:
        param = param.replace(_, "*")
    return param


@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", "")
        session_id = os.urandom(16).hex()
        # 세션 아이디는 무작위로 16바이트 길이로 설정
        session_storage[session_id] = 'admin'
        if not check_csrf(param, {"name":"sessionid", "value": session_id}):
            return '<script>alert("wrong??");history.go(-1);</script>'

        return '<script>alert("good");history.go(-1);</script>'


@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    elif request.method == 'POST':
    	#username, password 받아오기
        username = request.form.get('username')
        password = request.form.get('password')
        try:
            pw = users[username]
        except:
            return '<script>alert("not found user");history.go(-1);</script>'
        if pw == password:
            resp = make_response(redirect(url_for('index')) )
            #make_response: 플라스크로 서버 동작 시킬 때
            session_id = os.urandom(8).hex()
            session_storage[session_id] = username
            resp.set_cookie('sessionid', session_id)
            return resp 
        return '<script>alert("wrong password");history.go(-1);</script>'


@app.route("/change_password")
def change_password():
    pw = request.args.get("pw", "")
    session_id = request.cookies.get('sessionid', None)
    try:
        username = session_storage[session_id]
    except KeyError:
        return render_template('index.html', text='please login')

    users[username] = pw
    return 'Done'

app.run(host="0.0.0.0", port=8000)

기본으로 보이는 페이지는
vuln(csrf) page
flag
login
이 3개.
vuln(csrf) page, flag는 아까랑 같다.
login 페이지에 들어가서 일단 아는 아이디 비번 guest:guest를 입력해주자
그러면

/change_password는 직접 검색을 해야 들어갈 수 있다. (로그인 한 상태에서!)
로그인 안했으면 index page만 보임
/change_password 코드에서 pw 바꿀 수 있게끔 만들어놨다.

<img src="http://127.0.0.1:8000/change_password?pw=1234">

그래서 pw를 1234로 바꾼 후, admin으로 로그인해줬더니

성공

profile
긍정왕되기

0개의 댓글