[Dreamhack] TODO List 0.0.1
✏️ 풀이
문제에 접속해보면 로그인과 회원가입 기능이 존재한다.
이번 문제에서는 소스 코드가 많아서 핵심 소스 코드만 살펴볼 예정이다.
create.sql 소스 코드를 보면, Users, Todolist 테이블에는 admin 계정과 데이터가 존재하고, Todo 테이블에서는 flag가 있다.
INSERT INTO Users (username, email, password) VALUES (
'admin',
'admin@dreamhack.io',
'helloworld' -- redacted
);
INSERT INTO Todolist (user_id, name) VALUES (
1,
'admin'
);
INSERT INTO Todo (todo_list_id, title, description, is_completed) VALUES (
1,
'flag',
'DH{sample_flag}',
1
);
index.vue 소스 코드를 보면 중간에 shareTodo 버튼이 주석 처리가 되어있는 것을 알 수 있다.
<template>
<div class="container">
<div v-if="isLoggedIn" class="top-right">
<button class="logout-button" @click="logout">Logout</button>
</div>
<div v-if="isLoggedIn">
<h1>Your Todo Lists</h1>
<button @click="toggleAddTodo">+ Add Todo</button>
<div v-if="showAddForm">
<input type="text" v-model="newTodo.title" placeholder="Title" required>
<textarea v-model="newTodo.description" placeholder="Description" required></textarea>
<input type="date" v-model="newTodo.dueDate">
<button @click="addTodo">Submit</button>
</div>
<ul v-if="todoLists.length">
<li v-for="item, idx in todoLists" :key="idx">
<span :class="{ completed: item.is_completed }">: </span>
<input type="checkbox" v-model="item.is_completed" @change="updateTodoStatus(item)">
<!--
under construction
<button @click="shareTodo"> Share </button>
-->
</li>
</ul>
<p v-else>No Todo Lists found.</p>
</div>
<div v-else>
<h1>Welcome to Our Todo App</h1>
<p>You are not logged in.</p>
<button class="button" @click="navigateToLogin">Login</button>
<button class="button" @click="navigateToSignup">Sign Up</button>
</div>
</div>
</template>
계정을 생성하여 패킷을 확인해보면, 아래와 같이 admin 계정의 userId
가 1번이므로 2번으로 생성된 것을 확인할 수 있다.
Todo도 생성하여 확인해보면 id 1번은 flag 값이 있어 2번으로 생성된 것을 알 수 있다.
shareTodo 기능은 버튼이 활성화 되어 있지 않지만, 기능이 있는 것으로 보아 flag 값이 있는 admin 계정의 Todo를 공유해서 flag 값을 획득해야할 것 같다. 아래 ShareTodo 함수를 살펴보면 /api/shareTodo 경로에서 POST 메소드로 data를 JSON 형태로 보내는 기능이 존재한다.
async function shareTodo(todo) {
try {
const data = JSON.stringify(
todo
);
const token = localStorage.getItem('auth_token');
if (!token) {
throw new Error('login plz');
}
const response = await fetch('/api/shareTodo', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`,
},
body: data,
});
if (!response.ok) {
throw new Error('Failed to share api');
}
} catch (error) {
console.error('Error share todo:', error);
alert('Error share todo');
}
}
todolist.js 에서는 userId를 이용해서 todoList를 가져오고, sharedList에 공유되어 있는 todo를 가져와 반환해서 보여주는 것 같다.
import { verifyToken } from '../utils/auth';
import { openDatabase } from '../utils/db';
export default defineEventHandler(async (event) => {
try {
const userData = verifyToken(event.req);
const db = await openDatabase();
const todoList = await db.all('SELECT * FROM Todo where todo_list_id=(SELECT id FROM Todolist WHERE Todolist.user_id = ?)', [userData.userId]);
const sharedList = await db.all('SELECT * FROM TodoShares where user_id= ? ', [userData.userId]);
for (const shared of sharedList){
if (shared.permission_type === "owner" || shared.permission_type === "shared")
todoList.push(await db.get('SELECT * FROM Todo where id = ?',[shared.todo_id]));
};
return todoList;
} catch (error) {
return createError({
statusCode: 401,
statusMessage: 'Unauthorized: ' + error.message
});
}
});
shareTodo.js 코드를 살펴보면, todo의 id를 통해서 is_completed가 되었는지 확인하고, 아니라면, id와 target_id를 받아와서 target_id에 있는 유저의 Todo List에 Todo를 공유해주는 것을 알 수 있다.
import { readBody, createError } from 'h3';
import { openDatabase } from '../utils/db';
export default defineEventHandler(async (event) => {
const userData = verifyToken(event.req);
const db = await openDatabase();
const body = await readBody(event);
const todo = body;
try {
const todo_data = await db.get(
'SELECT * FROM Todo WHERE id = ?', [todo.id]
);
if (todo_data.is_completed === 1) {
return { message: 'you cannot share already completed todo', id: todo_data.id}
}
const result = await db.run(
`INSERT INTO TodoShares (todo_id, user_id, permission_type) VALUES
(?, ?, ?)`,
[todo_data.id, todo.target_id, 'shared']
);
return { success: true, message: 'Todo shared successfully', id: result.lastID };
} catch (error) {
throw createError({ statusCode: 500, statusMessage: 'Database error: ' + error.message });
}
});
Todo List에서 체크 표시를 하면 burp에서 /api/updateTodo 경로로 value를 True라는 값으로 보낸다.
updateTodo.js 코드를 살펴보면, value와 id 값을 받아와서 is_completed 값을 수정해준다. 따라서 flag의 Todo 에서 updateTodo 경로를 이용해서 value의 값을 false로 바꿔보자.
import { readBody, createError } from 'h3';
import { openDatabase } from '../utils/db';
export default defineEventHandler(async (event) => {
const userData = verifyToken(event.req);
const db = await openDatabase();
const body = await readBody(event);
const { id, value} = body;
try {
const result = await db.run(
`UPDATE todo
SET is_completed = ?
WHERE id = ?`,
[value, id]
);
return { success: true, message: 'Todo updated successfully', id: result.lastID };
} catch (error) {
throw createError({ statusCode: 500, statusMessage: 'Database error: ' + error.message });
}
});
flag 값의 Todo의 id는 1번이므로 아래와 같이 id 파라미터를 추가해서 value의 값을 false로 보내면 update가 잘 된 것을 확인할 수 있다.
shareTodo에서는 아래와 같이 id와 target_id를 받아오므로, 아래처럼 /api/shareTodo 경로로 flag의 id는 1번이고, 공유할 유저의 id는 2번이므로 아래와 같이 설정할 수 있다.
try {
const todo_data = await db.get(
'SELECT * FROM Todo WHERE id = ?', [todo.id]
);
if (todo_data.is_completed === 1) {
return { message: 'you cannot share already completed todo', id: todo_data.id}
}
const result = await db.run(
`INSERT INTO TodoShares (todo_id, user_id, permission_type) VALUES
(?, ?, ?)`,
[todo_data.id, todo.target_id, 'shared']
);
return { success: true, message: 'Todo shared successfully', id: result.lastID };
} catch (error) {
throw createError({ statusCode: 500, statusMessage: 'Database error: ' + error.message });
}
2번 계정으로 flag Todo를 공유했으므로, test 계정에 들어가 Todo를 다시 확인해보면 flag 값을 확인할 수 있다.
댓글남기기