728x90
반응형
개요
graphql을 다루는 문제다.
기능 분석
처음 접근하면 이런 페이지가 나온다. 아무런 상호작용할 부분이 없기에 첨부된 파일을 확인해보았다.
const express = require("express");
const { graphqlHTTP } = require("express-graphql");
const {
GraphQLObjectType,
GraphQLSchema,
GraphQLID,
GraphQLString,
GraphQLList,
} = require("graphql");
const rateLimit = require("express-rate-limit");
const depthLimit = require("graphql-depth-limit");
const path = require("path");
const app = express();
const limiter = rateLimit({ windowMs: 60 * 1000, max: 10 });
app.use(limiter);
app.use(express.static(path.join(__dirname, "public")));
const users = [
{ id: "1", name: "Monica", friends: ["2", "3"] }, //other fields may exist
{ id: "2", name: "Rachel", friends: ["4"] },
{ id: "3", name: "Ross", friends: ["5"] },
{ id: "4", name: "Phoebe", friends: ["6"] },
{ id: "5", name: "Joey", friends: ["6"] },
{ id: "6", name: "Chandler", friends: [] }
];
const getUserById = (id) => users.find((u) => u.id === id);
const noIntrospectionRule = (context) => ({
Field(node) {
if (node.name.value === "__schema" || node.name.value === "__type") {
throw new Error("Introspection is disabled");
}
},
});
const UserType = new GraphQLObjectType({
name: "User",
fields: () => ({
id: { type: GraphQLID },
name: { type: GraphQLString },
friends: {
type: GraphQLList(UserType),
resolve: (user, args, context) => {
context.traversedFromFriendChain = true;
return user.friends.map(getUserById);
},
}
// other fields may exist...
}),
});
const QueryType = new GraphQLObjectType({
name: "Query",
fields: {
me: { type: UserType, resolve: () => getUserById("1") },
user: {
type: UserType,
args: { id: { type: GraphQLID } },
resolve: (_, { id }) => getUserById(id),
},
},
});
const schema = new GraphQLSchema({ query: QueryType });
app.use(
"/graphql",
graphqlHTTP((req) => ({
schema,
graphiql: true,
validationRules: [depthLimit(3), noIntrospectionRule],
context: {},
}))
);
app.listen(1337, () => console.log("App running on port 1337"));
/graphql 페이지가 있어 접근해주었다.
그럼 위와 같은 graphql을 사용할 수 있는 페이지가 나온다.
{
user(id:1){
id
name
friends{
id
}
}
}
위와 같이 사용하면 서버로부터 응답값을 받을 수 있다.
익스플로잇
const users = [
{ id: "1", name: "Monica", friends: ["2", "3"] }, //other fields may exist
{ id: "2", name: "Rachel", friends: ["4"] },
{ id: "3", name: "Ross", friends: ["5"] },
{ id: "4", name: "Phoebe", friends: ["6"] },
{ id: "5", name: "Joey", friends: ["6"] },
{ id: "6", name: "Chandler", friends: [] }
];
주석을 보면 표시되지 않은 다른 필드가 있다는 것을 알 수 있다.
const noIntrospectionRule = (context) => ({
Field(node) {
if (node.name.value === "__schema" || node.name.value === "__type") {
throw new Error("Introspection is disabled");
}
},
});
하지만 introspection이 막혀있다. 따라서 다른 방안을 모색해야 한다.
그냥 flag 필드가 있는지 확인해보면 flag 필드가 없기 때문에 에러가 반환되는 것을 알 수 있다.
문제 description에 secretFlag가 눈에 띈다. 카멜 케이스로 작성된 것이 필드에 쓰였을 것으로 보인다.
아까와 달리 secretFlag에 null이 적혀서 반환됐다. secretFlag라는 필드가 있다는 의미이다. 따라서 모든 user들을 순회하며 이 필드를 확인하면 될 것이다.
6번째 user의 secretFlag 필드에 flag가 있는 것을 확인할 수 있다.
shaktictf{monica_doesnt_know_this_one}
대응 방안
- introspection이 적절히 막혀 있다. 하지만 secretFlag 필드에 대한 조회를 방지하기 위해선 사용자로부터 입력값 제어를 제한하든지, secretFlag 필드에 대한 접근을 추가로 막아야할 것이다.
728x90
반응형
'분류 전 > CTF' 카테고리의 다른 글
[ShaktiCTF25] brain_games 풀이 (2) | 2025.07.30 |
---|---|
[ShaktiCTF25] Hooman 풀이 (3) | 2025.07.30 |
[DownUnderCTF 2025] rocky 풀이 (0) | 2025.07.22 |
[DownUnderCTF 2025] mini-me 풀이 (0) | 2025.07.22 |
[L3ak CTF 2025] babyrev 풀이 (1) | 2025.07.16 |