CSRF(Cross-Site Request Forgery)란 사용자가 의도하지 않은 행위를 클라이언트 측에서 하게 만드는 공격이다. 이러한 공격은 사용자의 인증된 세션을 기반으로 하기 때문에, 사용자가 대상 사이트에 로그인이 되어 있다는 전제 하에 작동되는 공격이다.
CSRF 공격의 대표적인 예로써, 사용자가 뱅킹 사이트에 로그인이 되어 있을 때 해당 뱅킹 사이트의 송금 페이지와 동일한 레이아웃과 디자인을 가지는 페이지를 공격자가 생성하고 해당 페이지를 사용자, 즉 피해자에게 해당 페이지의 링크와 함께 송금 요청 메일을 보낼 때, 사용자가 링크를 클릭하여 실제 뱅킹 사이트의 송금 페이지와 동일한 디자인을 갖춘 피싱 페이지에 접속하여 수신자와 송금 금액을 입력하고 송금 버튼을 눌러 송금을 진행할 때, 이때 실제적으로 사용자가 입력한 값이 뱅킹 사이트의 이체 프로세스로 넘어가는 것이 아닌, 실제로는 공격자가 Input 태그의 hidden 유형으로 숨겨둔 값이 넘어가게 되어 실제로 이체되는 금액과 수신자의 경우, 공격자가 세팅한 값으로 넘어가게 된다. 따라서 사용자가 이체하기 버튼을 눌러 이체를 완료하기는 했지만, 실제적으로 받는 사람과 송금 금액은 공격자가 세팅한 값으로 이체가 되는 유형의 공격의 예가 존재한다.
아래의 예는 위에서 설명한 CSRF 공격을 실제로 실행해보기 위해 데모 사이트를 만들어 진행한 과정이다.
Example
실행 환경
공격자 도메인: http://localhost:8000
피해자 도메인: http://localhost:3000
주요 기술 스택: Node.js, MongoDB
송금 페이지: /transaction
위 그림은 실제 뱅킹 사이트에서 송금 페이지의 역할을 담당하는 데모 페이지이다.
위 그림은 실제 뱅킹 사이트의 송금 페이지를 모방한 공격자가 생성한 송금 페이지이다. 공격자의 페이지의 경우 포트(port)가 8000번으로, 실제 송금 페이지 데모와의 구별을 위해 Attacker라는 텍스트를 해당 사이트에 표시하였다. 위 그림에서 보듯이 현재 실제 뱅킹 사이트의 송금 페이지와 공격자의 송금 페이지는 의도적으로 삽입한 Attacker라는 텍스트를 제외하고는 동일한 레이아웃과 디자인을 갖추었다.
실제 뱅킹 사이트에서 test@test.com이라는 사용자로 로그인 한 사용자가, victim@victim이라는 사용자에게 7000원이라는 금액을 송금하기 위해 위 입력 필드에 해당 정보들을 기입하고 Send 버튼을 눌러 이체를 진행한다.
이체를 진행하면 위 사진의 도메인에서 볼 수 있듯이, 실제 뱅킹 사이트의 도메인으로 이체가 완료된 것을 볼 수 있다.
이체 결과를 확인하기전에 공격자가 생성한 송금 페이지의 이체 과정으로 넘어가는 코드들을 확인해보도록 하자.
아래 transaction.ejs 파일은 공격자가 만든 송금 페이지에서 이체 과정에 관여하는 코드로, 아래 코드에서 볼 수 있듯이 실제 송금 과정으로 넘어갈 때, 해당 과정으로 넘어가는 값들은 test@test.com이라는 사용자가 입력한 값이 아닌 공격자가 input 태그의 hidden 유형으로 숨겨둔 값으로 받는 사람의 경우, pentester@pentest.com, 송금 금액은 100,000원으로 세팅되어 있다.
transaction.ejs |
<form action="http://localhost:3000/transaction" method="POST"> <div class="form-control"> <label for="recipient">Recipient Email</label> <input type="email" id="recipient"> </div> <div class="form-control"> <label for="amount">Amount</label> <input type="text" id="amount"> </div> <input type="hidden" name="recipient" value="pentester@pentest.com"> <input type="hidden" name="amount" value="100000" > <button class="btn">Send</button> </form> |
위 그림에서 보듯이 실제 뱅킹 사이트에 연결되어 있는 MongoDB에서 송금 내역을 저장하는 collection을 살펴보면 test@test.com이라는 사용자가 입력한 값(victim@victim.com, 7000)이 아닌, 공격자가 input 태그의 hidden 유형으로 숨겨 둔 값(pentester@pentest.com, 100000)으로 저장이 되어진 것을 확인 할 수 있다.
대응 방안
- Referer 주소 검증
- CSRF Token 생성 및 검증
CSRF 공격의 대응 방안으로는 송금 과정으로 넘어갈 때, 개발자가 만든 송금 페이지를 화이트리스트에 등록하여 해당 Referer 주소만 허용하도록 한다. 즉 Referer 주소를 검증하여 개발자가 의도한 곳에서가 아닌 곳에서의 송금 요청은 허용하지 않도록 한다.
혹은 CSRF Token을 생성하여 모든 요청에 대해 생성한 CSRF Token을 요청 헤더에 포함하여 서버에 저장되어 있는 CSRF 토큰 값과 비교 검증을 실시하도록 한다. 또한 CSRF 토큰을 지속적으로 사용하는 것이 아닌, Request의 한 사이클 동안만 유지되도록 하여, 한 사이클이 끝나면 새로운 CSRF 토큰을 발급하도록 한다.
아래는 실제 뱅킹 사이트 데모에 CSRF 토큰을 적용한 코드와 적용 결과이다.
/transaction 경로에 대해 GET 요청 시 발급한 CSRF 토큰 전달 및 POST 요청 시 csrfProtection 미들웨어로 검증 진행 |
const csrf = require('csurf'); const csrfProtection = csrf({ cookie: true }); router.get('/transaction', csrfProtection, function (req, res) { if (!res.locals.isAuth) { return res.status(401).render('401'); } const csrfToken = req.csrfToken(); res.render('transaction', {csrfToken: csrfToken}); }); router.post('/transaction', csrfProtection, async function(req, res) { if (!res.locals.isAuth) { return res.redirect('/login'); } const transaction = { sender: res.locals.user.email, recipient: req.body.recipient, amount: +req.body.amount } await db.getDb().collection('transactions').insertOne(transaction); res.redirect('success'); }) |
실제 뱅킹 사이트의 transaction.ejs |
<form action="/transaction" method="POST"> <input type="hidden" value="<%= csrfToken %>" name="_csrf"> <div class="form-control"> <label for="recipient">Recipient Email</label> <input type="email" id="recipient" name="recipient"> </div> <div class="form-control"> <label for="amount">Amount</label> <input type="number" id="amount" name="amount"> </div> <button class="btn">Send</button> </form> |
'InfoSec Log > WEB Hacking' 카테고리의 다른 글
OWASP Top 10 - 2021 (0) | 2024.10.11 |
---|---|
[WEB Hacking] XXE (XML External Entity) 취약점 (2) | 2024.10.10 |