본문 바로가기
Infra & Security Eng/Semi Project

세미 프로젝트 - OSI 7계층 취약점 분석: 7계층(응용 계층) - SQL Injection공격 - 공부용

by 엔지니어 E 2026. 2. 25.
반응형
※ 이 과정은 외부와 격리된 독립적인 가상 네트워크 내에서 실습하였습니다

주제
OSI 7계층 중 **7계층(응용 계층)**의 애플리케이션 입력값 검증 미비로 인한 SQL Injection 인증 우회 공격 분석

목적
사용자와 애플리케이션이 상호작용하는 7계층에서, 입력 데이터가 서버 내부의 데이터베이스 쿼리문 구조를 조작하는 SQL Injection 공격을 수행한다. 이를 통해 실제 비밀번호를 몰라도 인증을 우회하여 시스템 권한을 획득함으로써 응용 계층의 논리적 보안 결함을 실증함

공격 메커니즘
1. 입력값 조작: 로그인 폼의 비밀번호 입력란에 SQL 특수문자(', --)와 논리 연산자(OR 1=1)를 주입한다
쿼리 논리 변조: 서버 측에서 실행되는 SQL문의 WHERE 절을 항상 참(True)이 되도록 조작하여, DB가 유효한 사용자임을 강제로 승인하게 만든다.
2. 인증 우회: 데이터베이스의 로직을 직접 공격하여 아이디/비밀번호 검증 단계를 무력화하고 관리자 권한으로 로그인에 성공한다

네트워크 구성도
1. DNS: 192.168.11.10 osi7.sbl 도메인을 WEB 서버(11.11)로 연결
2. WEB: 192.168.11.11 사용자에게 로그인 화면(HTML/PHP) 제공 및 입력값 전달
3. WAS: 192.168.11.12 사용자 역할 (정상 접속 시도)
4. DB: 192.168.21.10 실제 회원 정보(MEMBER 테이블)가 저장된 저장소
5. Router 1, 2: 11.13 / 21.1 11.x 대역과 21.x 대역 사이의 통신 중계 (게이트웨이)
WEB 서버 
기존 쿼리를 아래 쿼리를 바꾸고, 기존 아이디: user01 / 비밀번호: passxxx 이 아닌 비밀번호: ' OR 1=1 -- (-- 다음에 띄어쓰기 한칸 까지 포함) 를 입력하면 홈페이지 로그인이 가능 하다 (sql injection 공격)

기존 쿼리 login.php
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
<?php
session_start();
require_once $_SERVER['DOCUMENT_ROOT'] . '/comm/Database.php';

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $id = $_POST['username'];
    $pw = $_POST['password'];

    try {
        $db = Database::getInstance()->getConnection();
        $sql = "SELECT MEMBER_ID, NAME FROM MEMBER WHERE MEMBER_ID = :id AND PASSWORD = :pw";
        $stid = oci_parse($db, $sql);

        oci_bind_by_name($stid, ':id', $id);
        oci_bind_by_name($stid, ':pw', $pw);
        oci_execute($stid);

        if ($row = oci_fetch_array($stid, OCI_ASSOC)) {
            // 로그인 성공: 세션에 사용자 정보 저장
            $_SESSION['MEMBER_ID'] = $row['MEMBER_ID'];
            $_SESSION['NAME'] = $row['NAME'];
            header("Location: /main/main.php");
            //header("Location: /board/board.php");
            exit;
        } else {
            echo "<script>alert('아이디 또는 비밀번호가 일치하지 않습니다.'); history.back();</script>";
        }
    } catch (Exception $e) {
        die("로그인 오류: " . $e->getMessage());
    }
}
?>
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

바꾼 꿔리. login.php
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] == 'POST') {
    $id = $_POST['username']; 
    $pw = $_POST['password'];

    $conn = oci_connect("OSI701", "oracle", "192.168.10.161:1521/xe");

    if (!$conn) { die("DB 연결 실패"); }


    $sql = "SELECT NAME FROM OSI701.MEMBER WHERE MEMBER_ID = '" . $id . "' AND PASSWORD = '" . $pw . "'";
* 디비버에서 MEMBER_ID 부분 확인 
    $stid = oci_parse($conn, $sql);
    oci_execute($stid);

    if ($row = oci_fetch_array($stid, OCI_ASSOC)) {
        $_SESSION['user_name'] = $row['NAME']; 
        
      
        header("Location: /main/main.php"); 
        exit(); 
    } else {
        echo "<script>alert('실패'); history.back();</script>";
    }
    oci_close($conn);
}
?>
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

쿼리 해석
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ
<?php
session_start(); - session_start: 브라우저와 서버 간의 연결 고리(세션)을 만든다. 로그인 정보를 유지하기 위해 필수이다

if ($_SERVER['REQUEST_METHOD'] == 'POST') { - if: 사용자가 로그인 버튼을 눌러서 POST 방식으로 데이터를 보냈을 때만 아래 내용 실행 
    $id = $_POST['username']; - $_POST 사용자가 입력창에 친 아이디와 비밀번호를 변수에 각각 저장함 
    $pw = $_POST['password'];

    $conn = oci_connect("OSI701", "oracle", "192.168.10.161:1521/xe"); - oci_connect: 오라클 DB에 접속 (아이비, 비번,접속 주소 순서) * 접속 주소는 팀원 PC인 192.168.10.161의 1521 내 XE 서비스를 가리킨다
* 디비서버가 팀원디비에서 가져오기때문에 팀원 디비를 가져오는 명령어를 씀

    if (!$conn) { die("DB 연결 실패"); } - if(!$conn): 만약 DB 접속에 실패핳면 "DB 연결 실패" 라는 메세지를 띄우고 프로그램을 종료

    // MEMBER_ID 컬럼 사용 확인
    $sql = "SELECT NAME FROM OSI701.MEMBER WHERE MEMBER_ID = '" . $id . "' AND PASSWORD = '" . $pw . "'";
- $sql: DB에 보낼 질문(쿼리)을 만든다 *입력값을 따옴표(') 안에 그대로 넣기 때문에 SQL 인젝션 공격에 뚫리게 된다
    $stid = oci_parse($conn, $sql); - oci_parse: 작성한 SQL 쿼리문을 DB가 이해할 수 있도록 번역(컴파일) 준비를 한다
    oci_execute($stid);

    if ($row = oci_fetch_array($stid, OCI_ASSOC)) { - oci_fetch_array: DB가 돌려준 결과(이름 등)를 한 줄씩 읽어와서 $row 변수에 담는다 
        $_SESSION['user_name'] = $row['NAME']; - $_SESSION: DB에서 가져온 실제 사용자 이름을 세션 주머니('user_name')에 저장함, 이래야 나중에 main.php에서 "홍길동님 환영합ㅎ니다" 라고 띄울 수 있음
        header("Location: /main/main.php"); - header: 로그인이 성공했으니, 화면을 다른 페이지로 강제 이동시킴 
        exit(); 
    } else {
        echo "<script>alert('실패'); history.back();</script>"; - echo: 로그인이 실패했을때 경고창을 띄우고 이전 페이지로 돌려보냄
    }
    oci_close($conn); - oci_close: DB 사용이 끝났으므로 연결을 안전하게 닫음
}
?>
ㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡㅡ

정상적인 로그인 시도 (비번: 1234)
SELECT NAME FROM MEMBER WHERE ID = 'user01AND PASSWORD = '1234'
* 원래 따옴표가 1234를 안전하게 감싸고 있음

공격 시 로그인 시도 (비번: ' OR 1=1 -- )
SELECT NAME FROM MEMBER WHERE ID = 'user01AND PASSWORD = '' OR 1=1 -- '
* 따옴표 두 개가 자기들 끼리 만나서('') 비번 칸을 비워버리고, 그 뒤에 OR 1=1 이라는 새로운 명령어를 만들어냄, password 다음에는 빈공간이 되고 남는건 or 1=1 이기 때문에 1=1은 참이므로 로그인이 된다

즉,
1. 감옥 파괴 (''): 입력한 '가 코드의 '와 만나 **빈 따옴표('')**를 만들고 비번 칸을 강제로 닫음
2. 명령 탈출 (OR 1=1): 닫힌 따옴표 뒤에 적은 **OR 1=1**이 글자가 아닌 실제 DB 명령어로 돌변함.
3. 강제 승인 (True): 비번이 틀려도 **"또는 1=1(참)"**이라는 조건 때문에 DB가 로그인을 무조건 허용함
보안 대책 
1. Prepared Statement(매개변수화 쿼리) 사용: 사용자 입력값을 SQL 명령어의 일부가 아닌 단순한 '텍스트'로 처리하도록 코드를 수정하여 공격 구문이 실행되지 않게 한다.
2. 입력값 검증(Input Validation): 특수문자(', --, ; 등)가 포함된 입력값을 사전에 필터링하거나 차단한다.
3. 최소 권한 원칙: 웹 애플리케이션이 DB에 접근할 때 모든 권한이 아닌, 특정 작업에 필요한 최소한의 권한만 가진 계정을 사용하게 설정한다.

실증 결과 분석
1. 인증 우회 성공: 비밀번호에 ' OR 1=1 --를 주입했을 때, DB 내의 실제 비밀번호와 일치 여부를 확인하지 않고도 로그인이 성공함을 확인하였다
2. 데이터 제어권 획득: 응용 프로그램의 코드 결함이 데이터베이스 서버 전체의 기밀성을 위협하며, 데이터 유출 및 변조로 이어질 수 있음을 실증하였다

결론
1. 응용 계층 보안의 핵심: 7계층 보안은 시스템이나 네트워크 설정보다 **"소스 코드의 논리적 무결성"**이 최우선임을 확인하였다.
2. 입력 데이터의 신뢰성: 외부에서 들어오는 모든 데이터는 잠재적인 공격 수단이 될 수 있음을 인지하고, 애플리케이션 설계 단계부터 *'시큐어 코딩'**을 적용하는 것이 필수적임을 도출하였다.