실습 환경 구축
이번 SQL Injection 실습에서는 이전 포스팅에서 구축한 서버를 바탕으로 실습이 진행됩니다!
https://younngjun.tistory.com/62
현재 MySQL DBMS의 login Database의 user Table에 id: admin, pw: admin123의 데이터를 추가해놓은 상황입니다.
ID와 PW를 입력했을 때 login Database에 (id,pw) 쌍을 확인한 후에, 로그인 성공 여부와 이를 결정할 query문을 반환하도록 서버를 구현하였습니다.
SQL Injection 실습
1. SQL Injection이란?
데이터베이스와 연동되어 있는 애플리케이션의 입력값을 조작하여 DBMS가 의도되지 않은 결과를 반환하도록 하는 공격 기법입니다. 이를 통해 애플리케이션에서 사용하고 있는 DB 정보 조회는 물론 변조 또는 삭제까지 할 수 있으며, 사용자 및 관리자에 대한 인증 절차를 우회할 수 있습니다.
'입력값을 조작한다' 정확히 어떤 의미일까요?
SQL문에서는 where로 입력되는 조건문을 항상 참으로 만들 수 있는 방법이 있습니다. 실제로 입력되는 값이 맞는 값이 아니더라도 말입니다. 앞서 로그인 성공 여부를 판단하는 query문을 예시로 설명드리겠습니다.
2. SQL Injection 실습
먼저 id와 pw에 아무런 값도 대입하지 않고 로그인을 시도해보겠습니다.
id의 값과 pw의 값이 데이터베이스에 저장된 (id: 'admin', pw: 'admin123')과 일치하지 않아서 where 조건문이 거짓을 반환하여 login failed가 발생한 것을 확인할 수 있습니다.
그렇다면 where 조건문을 항상 참으로 만들 수 있다면 어떻게 될까요?
조건값에 조건문을 참으로 만들 수 있는 값을 대입해보도록 하겠습니다.
다음은 id와 pw에 asdf' or '1' = '1 을 대입한 결과입니다.
login이 성공한 것을 확인할 수 있습니다. asdf라는 임의의 값을 query문에 넣었어도 뒤에 or '1'='1' 이 항상 참이라는 결과값을 도출하기 때문에 where 조건문이 참을 반환하여 로그인이 성공하게 되는 것입니다.
이처럼 조건값에 #(주석) 및 or 등을 SQL문이 결과적으로 참이 될 수 있다면, SQL 삽입 공격에 사용되는 SQL문은 무엇이라도 SQL 삽입 공격에 사용될 수 있습니다.
대응 방법
어떻게 이러한 공격이 가능했을까요? 바로 문자열을 이어붙여서 Query를 만들었기 때문에 개발자가 어디서부터 어디까지가 사용자의 입력값인지 알 수 없었습니다.
그렇다면 어떻게 SQL Injection 공격에 대응할 수 있을까요?
① 따옴표 앞에 \ 를 붙여서, 따옴표가 명령어가 아닌 문자로 인식하도록 강제 문자화를 수행합니다. 다음과 같이 말이죠.
select id from user where id='admin\' -- ' and pw = 'admin123'
addslashes는 '/"/\/null 의 값 앞에 \가 붙게 하는 함수입니다.
$id = addslashes($_GET['id_box']);
$pw = addslashes($_GET['pw_box']);
그럼 결과는 어떨까요? 참인 query문을 입력하더라도 로그인이 실패하는 것을 알 수 있습니다.
그렇지만 이 방식에는 문제점이 있습니다. 위처럼 문자가 아닌 숫자 값을 입력 받는 경우에는 따움표를 사용하지 않기 때문에, 따움표 앞에 \ 를 추가하는 방법으로는 막을 수 없습니다.
② 사용자가 입력할 수 있는 범위를 지정해두고 그곳만 입력이 가능하도록 하는 Prepared Statement 를 이용한 프로그래밍 방식입니다.
select id from user where id=? and pw = ?
Preapared Statement 프로그래밍 방식이 무엇일까요? 요약하자면 물음표 부분만 비워두고 명령어를 미리 준비시켜놓는 것입니다.
사용자가 어떤 값을 입력하든 물음표 부분에만 들어가고, 명령어는 이미 준비가 되어 있는 상황이기 때문에 어떤 데이터가 입력이 되어도 명령어를 수정할 수 없게 되는 것입니다.
다음은 Prepared Statement를 적용한 login_safe.php 파일입니다. id, pw에 입력할 부분만 ?로 처리하고, 입력된 id, pw 값을 순서대로 ? 에 대입하는 것입니다.
<?php
$id = $_GET['id_box']; //GET 방식을 통해 전달된 id_box 변수에 있는 값을 저장함
$pw = $_GET['pw_box']; //GET 방식을 통해 전달된 pw_box 변수에 있는 값을 저장함
$db_conn = mysqli_connect("localhost", "webhacking", "webhacking", "login");//DB에 연결을 시도함(DB가 설치된 컴퓨터 주소, 아이디, 패스워드, 사용할 Database)
if(!$db_conn) // 만약 db_conn 변수에 아무것도 없다면(오류가 났다면)
{
echo mysqli_connect_error(); // 연결시 나타나는 에러 확인
echo "connect error"; // 에러가 났음을 화면에 출력해줌
exit(); // PHP 강제 종료
}
$query = "select id from user where id=? and pw=?"; //실행시키고자 하는 쿼리를 준비, 유동적으로 변해야하는부분은 ?로 처리
$stmt = mysqli_prepare($db_conn, $query); //쿼리가 준비됬으면 ?부분을 채울 준비를 시킨다
if($stmt === false)// 준비가 제대로 안돼면?
{
echo('prepared statement failed : '.mysqli_error($db_conn));//에러발생했으니까 종료
exit();
}
$bind = mysqli_stmt_bind_param($stmt, "ss", $id, $pw); // 준비까지 잘 됬다면, ?부분에 값을 넣을것인데 순서대로(준비된 쿼리, 어떤타입의 값이 몇개, 실제 넣을 값들1, 실제 넣을 값들2)
if($bind === false) //? 부분에 잘 값을 넣지 못했다면
{
echo('binding failed : '.mysqli_error($db_conn)); // 에러발생했으니까 종료
exit();
}
$exec = mysqli_stmt_execute($stmt); //잘 준비도 됬고, ?부분에 값도 넣었다면 쿼리를 실행 시킨다!
if($exec === false) // 실행에 실패했다면
{
echo('execute failed : '.mysqli_error($db_conn)); // 에러발생했으니까 종료
exit();
}
$result = mysqli_stmt_get_result($stmt); // 쿼리 잘 실행했으면, 결과값을 받아오자
$result_chk=0; // 로그인 성공여부 체크용 변수 0이면 실패, 1이면 성공이겠죠?
if($result)// 받아와진 결과값이 무언가있다면
{
if($row = mysqli_fetch_assoc($result))
// 불러올 데이터가 있다면
{
echo "id = ".$row['id']; // 로그인 성공 시 화면에 아이디 출력
$result_chk=1; //로그인에 성공했기떄문에 확인용 변수를 1로 변경
}
else
{
echo "login failed"; // 로그인 실패를 화면에 출력
}
mysqli_free_result($result);// 지금까지 사용한 결과값을 삭제해 주는 역할
mysqli_stmt_close($stmt);// 다썼으면, 준비했던 쿼리도 삭제해서 공간 비우는 역할
}
else // 명령어 실행결과가 없을경우(필드명도 없을 경우)
{
echo "query error"; // 명령문이 제대로 실행되지않았음을 화면에 출력함
}
mysqli_close($db_conn); //연결되어있던 Database 연결 해제
?>
이처럼 Prepared Statement 프로그래밍 방식을 적용하여 SQL Injection 공격에 효과적으로 대응할 수 있습니다.
'보안 > 웹 해킹' 카테고리의 다른 글
Blind SQL Injection 실습 (0) | 2023.04.05 |
---|---|
리눅스 가상환경에 서버 구축 및 간단한 로그인 페이지 구현 (0) | 2023.01.13 |