1. CSRF vs SSRF 

์„œ๋น„์Šค ๊ฐ„ HTTP ํ†ต์‹ ์ด ์ด๋ค„์งˆ ๋•Œ ์š”์ฒญ ๋‚ด์— ์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’์ด ํฌํ•จ๋  ์ˆ˜ ์žˆ๋Š”๋ฐ, ์ด๋•Œ ์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’์œผ๋กœ ์ธํ•ด ๊ฐœ๋ฐœ์ž๊ฐ€ ์˜๋„ํ•˜์ง€ ์•Š์€ ์š”์ฒญ์ด ์ „์†ก๋  ์ˆ˜ ์žˆ๋‹ค.

Server-side Request Forgery(SSRF)๋Š” ์›น ์„œ๋น„์Šค์˜ ์š”์ฒญ์„ ๋ณ€์กฐํ•˜๋Š” ์ทจ์•ฝ์ ์œผ๋กœ, ์„œ๋ฒ„ ์ธก์—์„œ ์œ„์กฐ๋œ HTTP ์š”์ฒญ์„ ๋ฐœ์ƒ์‹œ์ผœ ์ง์ ‘์ ์ธ ์ ‘๊ทผ์ด ์ œํ•œ๋œ ์„œ๋ฒ„ ๋‚ด๋ถ€ ์ž์›์— ์ ‘๊ทผํ•˜์—ฌ ์™ธ๋ถ€๋กœ ๋ฐ์ดํ„ฐ ์œ ์ถœ ๋ฐ ์˜ค๋™์ž‘์„ ์œ ๋ฐœํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

๊ณต๊ฒฉํ˜•ํƒœ๋งŒ ๋ณด๋ฉด ์œ„์กฐ๋œ HTTP ์š”์ฒญ(Request Forgery)๋ฅผ ์ด์šฉํ•œ ๊ณต๊ฒฉ์ด๊ธฐ ๋•Œ๋ฌธ์— CSRF(Cross Site Request Forgery)์™€ ์œ ์‚ฌํ•˜๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์œผ๋‚˜ ๊ณต๊ฒฉ์ž์˜ ๊ณต๊ฒฉ์ด ๋ฐœํ˜„๋˜๋Š” ์ง€์ ์ด ์„œ๋ฒ„ ์ธก(Server Side)์ธ์ง€ ํด๋ผ์ด์–ธํŠธ ์ธก(Client Side)์ธ์ง€์˜ ์—ฌ๋ถ€์— ๋”ฐ๋ผ์„œ ๊ณต๊ฒฉ ํ˜•ํƒœ๊ฐ€ ๊ตฌ๋ถ„๋  ์ˆ˜ ์žˆ๋‹ค. CSRF๊ฐ€ ์‚ฌ์šฉ์ž์˜ ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ํ•˜์ด์žฌํ‚นํ•˜์—ฌ ์‚ฌ์šฉ์ž๋กœ ํ•˜์—ฌ๊ธˆ ์•…์„ฑ ์š”์ฒญ์„ ์ˆ˜ํ–‰ํ•˜๊ฒŒ ๋งŒ๋“ ๋‹ค๋ฉด, SSRF๋Š” ์ ‘๊ทผ์ด ์ œํ•œ๋œ ๋‚ด๋ถ€ํ™˜๊ฒฝ์— ์ถ”๊ฐ€ ๊ณต๊ฒฉ(Post-Exploitation)์ด ๊ฐ€๋Šฅํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ณต๊ฒฉ์˜ ์˜ํ–ฅ๋„๊ฐ€ ๋†’์•„์งˆ ์ˆ˜๋ฐ–์— ์—†๋‹ค.

 

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์ด ํด๋ผ์šฐ๋“œ ์ธํ”„๋ผ๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ MSA(๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค)ํ˜•ํƒœ๋กœ ๋ณ€ํ™”ํ•˜๋ฉด์„œ ์‹œ์Šคํ…œ ๊ฐ„ ์—ฐ๊ณ„ ๋ฐ ์‚ฌ์šฉ์ž ๊ถŒํ•œ๋ถ€์—ฌ ๋“ฑ์œผ๋กœ ์ธํ•ด ๊ฐœ๋ฐœ ์ธํ”„๋ผ์˜ ์•„ํ‚คํ…์ฒ˜๊ฐ€ ๋ณต์žกํ•ด์ง€๋ฉด์„œ ๋ณด์•ˆ ์ด์Šˆ๊ฐ€ ์ง€์†์ ์œผ๋กœ ์ฆ๊ฐ€๋˜์—ˆ๋‹ค.

 

* MSA ๊ด€๋ จ ๋ธ”๋กœ๊ทธ :https://wooaoe.tistory.com/57

( Monolithic Architecture๋Š” ์†Œํ”„ํŠธ์›จ์–ด์˜ ๋ชจ๋“  ๊ตฌ์„ฑ์š”์†Œ๊ฐ€ ํ•œ ํ”„๋กœ์ ํŠธ์— ํ†ตํ•ฉ๋˜์–ด ์žˆ๋Š” ํ˜•ํƒœ๋กœ, ํ•˜๋‚˜๋กœ ๋ฌถ์—ฌ์žˆ๋Š”? ๊ฐœ๋…์ด๋ฉด, MSA๋Š” ์„œ๋น„์Šค๋ณ„๋กœ ๋”ฐ๋กœ๋”ฐ๋กœ ๋‚˜๋‰˜์–ด์ ธ ์žˆ๊ณ   API๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ƒํ˜ธ์ž‘์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ์ •๋„๋กœ ์ดํ•ดํ•˜๋ฉด ๋ ๋“ฏ.. ํ‹€๋ ธ๋‹ค๋ฉด ์•Œ๋ ค์ฃผ์„ธ์š” )

 

 

 

์›น ์„œ๋น„์Šค๊ฐ€ ๋ณด๋‚ด๋Š” ์š”์ฒญ์„ ๋ณ€์กฐํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์š”์ฒญ ๋‚ด์— ์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’์ด ํฌํ•จ๋˜์–ด์•ผ ํ•œ๋‹ค.

์ž…๋ ฅ๊ฐ’์ด ํฌํ•จ๋˜๋Š” ์˜ˆ์‹œ๋กœ๋Š”

1) ์›น ์„œ๋น„์Šค๊ฐ€ ์ด์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ URL์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ฑฐ๋‚˜,

2) ์š”์ฒญ์„ ๋ณด๋‚ผ URL์— ์ด์šฉ์ž ๋ฒˆํ˜ธ์™€ ๊ฐ™์€ ๋‚ด์šฉ์ด ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ,

3) ์ด์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์ด HTTP Body์— ํฌํ•จ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๋‹ค.

 

 

2. ์ด์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ URL์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒฝ์šฐ

#pip3 install flask requests # ํŒŒ์ด์ฌ flask, requests ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•˜๋Š” ๋ช…๋ น์ž…๋‹ˆ๋‹ค.
#python3 main.py # ํŒŒ์ด์ฌ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๋ช…๋ น์ž…๋‹ˆ๋‹ค.
from flask import Flask, request
import requests
app = Flask(__name__)
@app.route("/image_downloader")
def image_downloader():
# ์ด์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ URL์— HTTP ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ํŽ˜์ด์ง€ ์ž…๋‹ˆ๋‹ค.
image_url = request.args.get("image_url", "") # URL ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ image_url ๊ฐ’์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
response = requests.get(image_url) # requests ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด์„œ image_url URL์— HTTP GET ๋ฉ”์†Œ๋“œ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ๊ฒฐ๊ณผ๋ฅผ response์— ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.
return ( # ์•„๋ž˜์˜ 3๊ฐ€์ง€ ์ •๋ณด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
response.content, # HTTP ์‘๋‹ต์œผ๋กœ ์˜จ ๋ฐ์ดํ„ฐ
200, # HTTP ์‘๋‹ต ์ฝ”๋“œ
{"Content-Type": response.headers.get("Content-Type", "")}, # HTTP ์‘๋‹ต์œผ๋กœ ์˜จ ํ—ค๋” ์ค‘ Content-Type(์‘๋‹ต ๋‚ด์šฉ์˜ ํƒ€์ž…)
)
@app.route("/request_info")
def request_info():
# ์ ‘์†ํ•œ ๋ธŒ๋ผ์šฐ์ €(User-Agent)์˜ ์ •๋ณด๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ํŽ˜์ด์ง€ ์ž…๋‹ˆ๋‹ค.
return request.user_agent.string
app.run(host="127.0.0.1", port=8000)

 

image_downloader

(1) ์ด์šฉ์ž(ํด๋ผ์ด์–ธํŠธ) - URL ์ž…๋ ฅ 

(2) ์„œ๋ฒ„ - ์‘๋‹ต (์‘๋‹ต ๋ฐ์ดํ„ฐ / HTTP ์‘๋‹ต ์ฝ”๋“œ) 

์ด์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ image_url์„ requests.get ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•ด GET ๋ฉ”์†Œ๋“œ๋กœ HTTP ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

๋ธŒ๋ผ์šฐ์ €์—์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ URL์„ ์ž…๋ ฅํ•˜๋ฉด ๋“œ๋ฆผํ•ต ํŽ˜์ด์ง€์— ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

http://127.0.0.1:8000/image_downloader?image_url=https://dreamhack.io/assets/dreamhack_logo.png


request_info
์›น ํŽ˜์ด์ง€์— ์ ‘์†ํ•œ ๋ธŒ๋ผ์šฐ์ €์˜ ์ •๋ณด(User-Agent)๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค. 

Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4558.0 Safari/537.36

์ด๋•Œ, img_url์—์„œ ์ž…๋ ฅ๊ฐ’์œผ๋กœ request_info์˜ ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•œ๋‹ค. 

๊ทธ๋Ÿผ image_downloader์—์„œ๋Š” http://127.0.0.1:8000/request_info URL์— HTTP ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

http://127.0.0.1:8000/image_downloader?image_url=http://127.0.0.1:8000/request_info

 

๋ฐ˜ํ™˜ํ•œ ๊ฐ’์„ ํ™•์ธํ•ด๋ณด๋ฉด ๋ธŒ๋ผ์šฐ์ € ์ •๋ณด๊ฐ€ python-requests/<LIBRARY_VERSION>์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์ ‘์†ํ•œ ๋ธŒ๋ผ์šฐ์ € ์ •๋ณด๋กœ python-requests๊ฐ€ ์ถœ๋ ฅ๋œ ์ด์œ ๋Š” ์›น ์„œ๋น„์Šค์—์„œ HTTP ์š”์ฒญ์„ ๋ณด๋ƒˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ด์ฒ˜๋Ÿผ ์ด์šฉ์ž๊ฐ€ ์›น ์„œ๋น„์Šค์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์˜ API ์ฃผ์†Œ๋ฅผ ์•Œ์•„๋‚ด๊ณ , image_url์— ์ฃผ์†Œ๋ฅผ ์ „๋‹ฌํ•˜๋ฉด ์™ธ๋ถ€์—์„œ ์ง์ ‘ ์ ‘๊ทผํ•  ์ˆ˜ ์—†๋Š” ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค์˜ ๊ธฐ๋Šฅ์„ ์ž„์˜๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

 

3. ์›น ์„œ๋น„์Šค์˜ ์š”์ฒญ URL์— ์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’์ด ํฌํ•จ๋˜๋Š” ๊ฒฝ์šฐ

INTERNAL_API = "http://api.internal/"
# INTERNAL_API = "http://172.17.0.3/"
@app.route("/v1/api/user/information")
def user_info():
user_idx = request.args.get("user_idx", "")
response = requests.get(f"{INTERNAL_API}/user/{user_idx}")
@app.route("/v1/api/user/search")
def user_search():
user_name = request.args.get("user_name", "")
user_type = "public"
response = requests.get(f"{INTERNAL_API}/user/search?user_name={user_name}&user_type={user_type}")

 

user_info

์ด์šฉ์ž๊ฐ€ ์ „๋‹ฌํ•œ user_idx ๊ฐ’์„ ๋‚ด๋ถ€ API์˜ URL ๊ฒฝ๋กœ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

์ด์šฉ์ž๊ฐ€ ์œ„์™€ ๊ฐ™์ด user_idx๋ฅผ 1๋กœ ์„ค์ •ํ•˜๊ณ  ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ,

http://172.17.0.3/v1/api/user/information?user_idx=1

 

์›น ์„œ๋น„์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฃผ์†Œ์— ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

http://172.17.0.3/user/1

 

user_search

์ด์šฉ์ž๊ฐ€ ์ „๋‹ฌํ•œ user_name ๊ฐ’์„ ๋‚ด๋ถ€ API์˜ ์ฟผ๋ฆฌ๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

์ด์šฉ์ž๊ฐ€ user_name์„ โ€œhelloโ€๋กœ ์„ค์ •ํ•˜๊ณ  ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, 

http://172.17.0.3/v1/api/user/search?user_name=hello

 ์›น ์„œ๋น„์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฃผ์†Œ์— ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

http://172.17.0.3/user/search?user_name=hello&user_type=public

 

์ด๋•Œ, ์›น ์„œ๋น„์Šค๊ฐ€ ์š”์ฒญํ•˜๋Š” URL์— ์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’์ด ํฌํ•จ๋˜๋ฉด ์š”์ฒญ์„ ๋ณ€์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’ ์ค‘ URL์˜ ๊ตฌ์„ฑ ์š”์†Œ ๋ฌธ์ž๋ฅผ ์‚ฝ์ž…ํ•˜๋ฉด API ๊ฒฝ๋กœ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ๋ฅผ ๋“ค์–ด, ์˜ˆ์‹œ ์ฝ”๋“œ์˜ user_info ํ•จ์ˆ˜์—์„œ user_idx์— ../search๋ฅผ ์ž…๋ ฅํ•  ๊ฒฝ์šฐ ์›น ์„œ๋น„์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ URL์— ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

http://api.internal/search

 

..๋Š” ์ƒ์œ„ ๊ฒฝ๋กœ๋กœ ์ด๋™ํ•˜๊ธฐ ์œ„ํ•œ ๊ตฌ๋ถ„์ž๋กœ, ํ•ด๋‹น ๋ฌธ์ž๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒฝ๋กœ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•ด๋‹น ์ทจ์•ฝ์ ์€ ๊ฒฝ๋กœ๋ฅผ ๋ณ€์กฐํ•œ๋‹ค๋Š” ์˜๋ฏธ์—์„œ Path Traversal์ด๋ผ๊ณ  ๋ถˆ๋ฆฐ๋‹ค.

 

์ด ์™ธ์—๋„, # ๋ฌธ์ž๋ฅผ ์ž…๋ ฅํ•ด ๊ฒฝ๋กœ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, user_search ํ•จ์ˆ˜์—์„œ user_name์— secret&user_type=private#๋ฅผ ์ž…๋ ฅํ•  ๊ฒฝ์šฐ ์›น ์„œ๋น„์Šค๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ URL์— ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

http://api.internal/search?user_name=secret&user_type=private#&user_type=public

 

# ๋ฌธ์ž๋Š” Fragment Identifier ๊ตฌ๋ถ„์ž๋กœ, ๋’ค์— ๋ถ™๋Š” ๋ฌธ์ž์—ด์€ API ๊ฒฝ๋กœ์—์„œ ์ƒ๋žต๋œ๋‹ค.

๋”ฐ๋ผ์„œ ํ•ด๋‹น URL์€ ์‹ค์ œ๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ URL์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

http://api.internal/search?user_name=secret&user_type=private

 

 

4. ์›น ์„œ๋น„์Šค์˜ ์š”์ฒญ Body์— ์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’์ด ํฌํ•จ๋˜๋Š” ๊ฒฝ์šฐ

# pip3 install flask
# python main.py
from flask import Flask, request, session
import requests
from os import urandom
app = Flask(__name__)
app.secret_key = urandom(32)
INTERNAL_API = "http://127.0.0.1:8000/"
header = {"Content-Type": "application/x-www-form-urlencoded"}
@app.route("/v1/api/board/write", methods=["POST"])
def board_write():
session["idx"] = "guest" # session idx๋ฅผ guest๋กœ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.
title = request.form.get("title", "") # title ๊ฐ’์„ form ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
body = request.form.get("body", "") # body ๊ฐ’์„ form ๋ฐ์ดํ„ฐ์—์„œ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
data = f"title={title}&body={body}&user={session['idx']}" # ์ „์†กํ•  ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.
response = requests.post(f"{INTERNAL_API}/board/write", headers=header, data=data) # INTERNAL API ์— ์ด์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ๊ฐ’์„ HTTP BODY ๋ฐ์ดํ„ฐ๋กœ ์‚ฌ์šฉํ•ด์„œ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
return response.content # INTERNAL API ์˜ ์‘๋‹ต ๊ฒฐ๊ณผ๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
@app.route("/board/write", methods=["POST"])
def internal_board_write():
# form ๋ฐ์ดํ„ฐ๋กœ ์ž…๋ ฅ๋ฐ›์€ ๊ฐ’์„ JSON ํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
title = request.form.get("title", "")
body = request.form.get("body", "")
user = request.form.get("user", "")
info = {
"title": title,
"body": body,
"user": user,
}
return info
@app.route("/")
def index():
# board_write ๊ธฐ๋Šฅ์„ ํ˜ธ์ถœํ•˜๊ธฐ ์œ„ํ•œ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.
return """
<form action="/v1/api/board/write" method="POST">
<input type="text" placeholder="title" name="title"/><br/>
<input type="text" placeholder="body" name="body"/><br/>
<input type="submit"/>
</form>
"""
app.run(host="127.0.0.1", port=8000, debug=True)

 

board_write

์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’์„ HTTP Body์— ํฌํ•จํ•˜๊ณ  ๋‚ด๋ถ€ API๋กœ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

์ „์†กํ•  ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ ์„ธ์…˜ ์ •๋ณด๋ฅผ "guest" ๊ณ„์ •์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.

 

internal_board_write

board_write ํ•จ์ˆ˜์—์„œ ์š”์ฒญํ•˜๋Š” ๋‚ด๋ถ€ API๋ฅผ ๊ตฌํ˜„ํ•œ ๊ธฐ๋Šฅ์ด๋‹ค.

์ „๋‹ฌ๋œ title, body ๊ทธ๋ฆฌ๊ณ  ๊ณ„์ • ์ด๋ฆ„์„ JSON ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ฆฌํ„ดํ•ด์ค€๋‹ค. 

 

title /body ์ž…๋ ฅ์ฐฝ 

  • title = hi / body = mnzy ์ž…๋ ฅ

์š”์ฒญ์„ ์ „์†กํ•  ๋•Œ ์„ธ์…˜ ์ •๋ณด๋ฅผ "guest"๋กœ ์„ค์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— user๊ฐ€ "guest"์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

์˜ˆ์‹œ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๋ฉด, ๋‚ด๋ถ€ API๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „์— ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

data = f"title={title}&body={body}&user={session['idx']}

data = f"title={title}&body={body}&user={session['idx']}"

 

๋ฐ์ดํ„ฐ๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ ์ด์šฉ์ž์˜ ์ž…๋ ฅ๊ฐ’์ธ title, body ๊ทธ๋ฆฌ๊ณ  user์˜ ๊ฐ’์„ ํŒŒ๋ผ๋ฏธํ„ฐ ํ˜•์‹์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.

์ด๋กœ ์ธํ•ด ์ด์šฉ์ž๊ฐ€ URL์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ตฌ๋ถ„ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•˜๋Š” ๊ตฌ๋ถ„ ๋ฌธ์ž์ธ &๋ฅผ ํฌํ•จํ•˜๋ฉด ์„ค์ •๋˜๋Š” data์˜ ๊ฐ’์„ ๋ณ€์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

 title์—์„œ title&user=admin๋ฅผ ์‚ฝ์ž…ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด data๊ฐ€ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค.

title=title&user=admin&body=body&user=guest

 

๋‚ด๋ถ€ API์—์„œ๋Š” ์ „๋‹ฌ๋ฐ›์€ ๊ฐ’์„ ํŒŒ์‹ฑํ•  ๋•Œ ์•ž์— ์กด์žฌํ•˜๋Š” ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ๊ฐ’์„ ๊ฐ€์ ธ์™€ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— user์˜ ๊ฐ’์„ ๋ณ€์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.๋‹ค. title&user=admin๋ฅผ ์‚ฝ์ž…ํ–ˆ์„ ๋•Œ์˜ ์‹คํ–‰ ๊ฒฐ๊ณผ๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด user๊ฐ€ "admin"์œผ๋กœ ๋ณ€์กฐ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

 

+) 

  • ์ƒ์œ„ ๋””๋ ‰ํ† ๋ฆฌ ์šฐํšŒ : ../๋กœ ๋ฃจํŠธ๋กœ ์ด๋™
    • localhost services access
      • ?url=http://localhost/server-status
    • file system access
      • ?url=file:///etc/passwd
  • ํ™•์žฅ์ž ์šฐํšŒ : %00 / ? / #

 

[์ฐธ๊ณ ]

 

Dreamhack | ๊ฐ•์˜ | Dreamhack

 

dreamhack.io

 

SSRF ์ทจ์•ฝ์ ์„ ์ด์šฉํ•œ ๊ณต๊ฒฉ์‚ฌ๋ก€ ๋ถ„์„ ๋ฐ ๋Œ€์‘๋ฐฉ์•ˆ

01. SSRF ๊ฐœ์š” ์„œ๋ฒ„ ์ธก์—์„œ ์œ„์กฐ๋œ HTTP ์š”์ฒญ์„ ๋ฐœ์ƒ์‹œ์ผœ ์ง์ ‘์ ์ธ ์ ‘๊ทผ์ด ์ œํ•œ๋œ ์„œ๋ฒ„ ๋‚ด๋ถ€ ์ž์›์— ์ ‘๊ทผํ•˜์—ฌ ์™ธ๋ถ€๋กœ ๋ฐ์ดํ„ฐ ์œ ์ถœ ๋ฐ ์˜ค๋™์ž‘์„ ์œ ๋ฐœํ•˜๋Š” ๊ณต๊ฒฉ์„ SSRF(Server Side Request Forgery)๋ผ๊ณ  ํ•œ๋‹ค.

www.igloo.co.kr

 

SSRF

Server Side Request Forgery ์›น ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์™ธ๋ถ€๋กœ request๋ฅผ ๋ณด๋‚ผ ๋Œ€์ƒ์„ ์„ค์ •ํ•  ๋•Œ, ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์™ธ๋ถ€๋กœ request๋ฅผ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ ์ธ์ž์— user input์ด ๋“ค์–ด๊ฐ„๋‹ค๋ฉด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

umbum.dev

 

 

mnzy๐ŸŒฑ
-->