https://core-research-team.github.io/2020-10-01/Expressjs
위 링크를 통해 공부한 내용을 정리하기 위한 포스팅입니다.
https://velog.io/@hyejinjeong9999/MySQL-%EC%BF%BC%EB%A6%AC-%EB%A1%9C%EA%B7%B8-%ED%99%95%EC%9D%B8
위 포스팅을 통해 로그 기록을 확인할 수 있도록 수정했다.
const express = require('express');
const mysql = require('mysql');
const app = express();
const port = 3000;
// MySQL 연결 설정
const connection = mysql.createConnection({
host: '127.0.0.1',
user: 'root',
password: '',
database: 'test'
});
// JSON 요청 본문을 파싱하기 위한 미들웨어 설정
app.use(express.json());
app.use(express.urlencoded());
// 로그인 페이지
app.get('/login', (req, res) => {
res.send(`
<h2>Login Page</h2>
<form method="post" action="/login">
<label for="id">ID:</label>
<input type="text" id="id" name="id" required />
<label for="password">Password:</label>
<input type="password" id="password" name="password" required />
<button type="submit">Login</button>
</form>
`);
});
// 로그인 처리
app.post('/login', (req, res) => {
const { id, password } = req.body;
console.log(id);
console.log(password);
const query = 'SELECT * FROM users WHERE id = ? AND password = ?';
// prepared statement 사용
connection.query(query, [id, password], (error, results) => {
if (error) {
console.error('Error in query:', error);
return res.status(500).send('Server Error');
}
console.log('Query Results: ', results);
if (results.length > 0) {
res.send('Login Successful');
} else {
res.send('Login Failed');
}
});
});
// 서버 시작
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`);
});
실습을 위한 전체 코드 내용이다.
이런 식으로 로그인 페이지를 구성했다.
id를 admin으로, pw를 admin으로 하여 로그인하면 로그인이 된다.
콘솔에서는 입력한 id와 pw와 결과를 출력하도록 했다.
익스플로잇
prepared statement로 짜여져 있기 때문에 기본적인 익스 시도는 당연히 먹히지 않는다.
저런 식으로 싱글 쿼터를 사용하면 자동으로 `\`으로 이스케이프 시키는 것을 확인할 수 있다.
하지만 맨 위의 링크에서 설명하는 것과 같이 중첩된 자바스크립트 객체로 보내보겠다.
id에는 { id : 1 }이, password에는 { password : 1 }이 전달되게 된다. `connection.query()`를 통해 mysql에 전달되면 위와 같이 전달된다. 아마 key 값에는 식별자임을 나타내기 위해 백틱(`)으로 둘러싼 후 입력되는 것으로 추측된다.(아닐 수도 있습니다. 훈수 환영합니다.)
1. where id = 'id'
2. where id = `id`
3. where id = "id"
각각의 차이점이 무엇인지 아는가?
1번은 싱글 쿼터이므로, 문자열을 나타낸다. 따라서 1번째 경우의 결과는 false이다.
2번은 백틱이다. 식별자( identifier )를 나타낸다. id라는 컬럼을 나타내므로, 2번째 경우의 결과는 true이다.
3번은 더블 쿼터이다. `ANSI_QUOTES` 설정이 존재한다. 이 설정이 켜져 있다면 문자열로만 인식한다. 하지만 꺼져있다면 문자열로도 인식할 수 있고 식별자로도 인식할 수 있다.
https://dev.mysql.com/doc/refman/8.0/en/sql-mode.html#sqlmode_ansi_quotes
`ANSI_QUOTES`에 대한 내용이다.
따라서 id = `id`는 true이고, true 와 1의 비교는 boolean 연산이기 때문에 true가 된다.
json 형식일 때 중첩 자바스크립트 객체를 통해 prepared statement가 뚫리는 것을 볼 수 있었다. 그런데 우리가 주로 접하는 웹사이트들은 form 태그를 통해 전달되기 때문에 `application/x-www-form-urlencoded` 형식으로 전달되게 된다. 그럴 때에는 어떻게 해야할까?
위와 같이 배열 표기 방식을 사용하여 보내면 마찬가지로 구할 수 있다. node.js에서 사용하는 `urlencoded()` 미들웨어가 자바스크립트 객체로 변환하여 주기 때문에, json 형식으로 보냈을 때와 같은 형식으로 `connection.query`에 보내진다.
근데 필자는 여기서 실수를 했다. `password[password]=1`을 보냈어야 했는데 `password[id]=1`을 보냈다. 그럼에도 admin으로 로그인이 성공적으로 됐다. 그 이유가 뭘까?
admin 계정의 아이디와 비밀번호가 admin / admin으로 동일하기 때문이다.
레코드를 추가해서 확인해보았다.
따라서 위 쿼리는 `id` 칼럼과 `password` 칼럼의 값이 같은 경우를 찾는 것이다.
해결 방법
.toString()을 적용하면 해당 문제를 해결할 수 있다.
위와 같이 하면 정상적인 접근은 제대로 로그인이 되면서 자바스크립트 객체에 대해선 Object로 입력이 된다.
'분류 전 > 개념 노트장' 카테고리의 다른 글
[Flask] session 구조 (0) | 2024.04.11 |
---|---|
[SQL] information_schema가 필터링 됐을 때 Union SQL Injection (0) | 2024.03.29 |
[SQL] mysql boolean 비교 시 동작 방식과 false injection (0) | 2024.02.23 |
[python] pickle 모듈 간단 사용법 및 취약점 (1) | 2024.02.12 |
[SQL] order by 0 과 order by if(1=1, 0, 0)의 차이 (0) | 2024.02.10 |