CSRF란?
희생자가 CSRF 스크립트가 포함된 웹 페이지에 접속하면 희생자의 권한을 통해 공격자가 의도한 행위(등록, 수정, 삭제 등)를 실행하게 하는 취약점이다. XSS 공격은 악성 스크립트가 클라이언트에서 실행되는데 반해, CSRF 공격은 희생자가 악성 스크립트(등록, 수정, 삭제하는 스크립트)를 서버에 요청한다는 점에 차이가 있다.
app.py를 분석해야 한다.
@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>'
flag 함수부터 봐야 한다. sessionid 값을 생성하기 때문이다. session_id를 만들고 비어있는 session_storage에 키를 session_id, 값을 admin으로 설정하고 check_csrf를 호출한다. session_id가 넘어간다.
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)
늘 그랬듯이 admin의 vuln 웹 페이지에 param 인자값을 설정하여 read_url 함수를 호출한다. cookie에는 session_id가 들어있다.
driver.get("http://127.0.0.1:8000/")
driver.add_cookie(cookie)
driver.get(url)
read_url 함수 일부이다. 127.0.0.1:8000 웹 페이지의 쿠키에 sessionid 이름으로 session_id값을 저장한다. 그리고 url(check_csrf에서 만든 url)에 해당하는 웹 페이지를 연다.
@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'
아직 입력값은 언급하지 않았지만 입력값에 의해 url은 change_password 웹 페이지에 접근하도록 할 것이다. url parameter의 pw의 값을 pw 변수에 저장한다. 쿠키에 저장된 sessionid도 session_id 변수에 저장한다. flag 함수에서 session_storage에 session_id를 키로, admin을 값으로 저장했기에 username에는 admin이 들어간다.
users = {
'guest': 'guest',
'admin': FLAG
}
users의 admin에 해당하는 값이 pw의 값으로 바뀔 것이다. 즉, 지금 admin의 비밀번호가 바뀐다.
@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>'
이제 login 페이지에서 로그인을 수행한다. 지금부터는 admin이 아닌 나의 웹 페이지에서 실행한다. pw에는 바뀐 admin의 비밀번호가 들어가 있다. 따라서 login 창에서 id는 admin, password는 바꾼 비밀번호를 입력하면 / 페이지로 redirect 하는 객체가 생성된다. session_storage에 새로 생성한 session_id를 키로, admin을 값으로 저장된다. response 객체의 쿠키에는 sessionid 이름에 session_id 값을 새로 만들어 설정된다. 그리고 리턴함으로써 / 페이지로 이어진다.
@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"}')
login 함수에서 cookie에 sessionid를 키로 session_id를 값으로 저장했기에 session_id 변수에 session_id 값이 저장된다. login 함수에서 session_storage에 session_id를 키로 admin을 값으로 저장했기에 username에는 admin이 저장된다. username이 admin이기에 FLAG가 출력된다.
처음에 flag 페이지에서 다음과 같이 입력하면 된다.
<img src="/change_password?pw=1">
이미지를 불러오는 태그다. 위의 과정이 이해됐으면 입력값을 넣었을 때 동작 과정이 쉽게 이해될 것이다.
'dreamhack > Web Hacking' 카테고리의 다른 글
Mango (0) | 2023.02.13 |
---|---|
image-storage (0) | 2023.02.13 |
command-injection-1 (0) | 2023.02.12 |
CSRF-1 (0) | 2023.02.12 |
XSS-2 (0) | 2023.02.12 |