본문 바로가기

4-2. 2024-1 심화 스터디/웹 취약점 분석

[3주차] Injection 취약점 분석/실습 (1/2)

INJECTION

: 신뢰할 수 없는 데이터가 명령 또는 쿼리의 일부로 전송될 때 발생함  -> 공격자가 임의의 코드를 실행하거나 민감한 데이터에 접근 가능

1. Command Injection 

  • 취약한 애플리케이션을 통해 호스트 운영 체제에서 임의 명령을 실행하는 것이 목표인 공격.
  • 애플리케이션이 안전하지 않은 사용자 제공 데이터(양식, 쿠키, HTTP 헤더 등)를 시스템 셸에 전달할 때 가능한 공격이다.
  • 인젝션 공격 중 사용자의 입력을 통해 시스템 명령어를 실행시키는 공격
  • 주요 정보 노출 시 루트 권한 탈취 위험 有
  • 웹을 통해 시스템명령어(커맨드)를 실행하는 공격

 대응 방법

  • 사용자의 입력값이 원래의 의도에 맞는지 검사한다. → 입력받아야 할 값이 ip 주소이면 ip주소 입력이 맞는지 확인하는 과정을 추가한다.
  • 문자열을 입력받아야 하는 경우라면, 몇가지 연산자나 문자의 입력을 필터링하는 연산을 추가한다.

 

2. SQL Injection

  • 공격자가 보안상의 취약점을 이용하여, 임의의 SQL문을 삽입하는 공격으로 데이터베이스가 비정상적인 동작을 하도록 조작하는 행위이다.
  • 사용자 입력값을 검증하지 않는 경우 설계된 쿼리문에 의도하지 않은 쿼리를 임의로 삽입할 수 있는 공격

대응 방법

  • 소스 코드에 사용자의 입력 값을 확실하게 검증해주는 구문을 추가해 필요한 데이터의 형식을 정확히 검사해준다.
  • 파라미터 쿼리를 통해 사용자 입력 값을 문자열로만 처리하도록 한다.

 

3. SQL Injection (Blind)

  • 데이터베이스에 참 또는 거짓 질문을 하고 애플리케이션의 응답에 따라 답변을 결정하는 SQL Injection 공격의 한 유형
  • 웹 애플리케이션이 일반 오류 메시지를 표시하도록 구성되었지만 SQL Injection에 취약한 코드를 완화하지 못한 경우 사용된다.
  • 참(True)인 쿼리문과 거짓(False)인 쿼리문 입력 시 반환되는 서버의 응답이 다른 것을 이용하여 이를 비교하여 데이터를 추출하는 공격

대응 방법

  • 입력값 검증과 필터링을 통해 특정 SQL 구문을 포함하는 것을 방지한다.
  • 익명성 쿼리를 사용해 정확한 에러 메세지가 아닌 일반적인 오류 메세지만 표시해 시스템에 대한 정보를 수집하기 어렵게 만든다.

 


[실습]

1. Command Injection 

- security level: low

더보기

[국예린]

기본 로컬호스트 ip 127.0.0.1을 입력해 보았다.

ping 명령어를 수행하고 있다.

 

소스코드를 먼저 보았다.

  

$target = $_REQUEST[ 'ip' ];

target 변수에 Form에 입력한 ip를 저장한다.

$cmd = shell_exec( 'ping  ' . $target ); -> 윈도우일때 실행되는 shell_exec 함수 $cmd = shell_exec( 'ping  -c 4 ' . $target ); -> unix 계열 운영체제일때 실행되는 shell_exec 함수

shell_exec : 시스템 명령어를 내리는 함수.

시스템 명령어로 ping -c 4 . $target이 전달되었는데, target 변수에 의해 입력받은 ip값에 대해 ping을 4번 수행하게 된다. (이런 시스템 명령어는 터미널 창에서 명령을 내리는 것과 같은 기능을 한다. -c : 핑을 보내는 횟수)

 

위의 화면과 같이 핑을 4번 보내는 모습을 볼 수 있다.

리눅스 환경에서는 ;를 통해 명령어를 여러개 수행할 수 있는데, 같은 명령어 뒤에 ; ls를 수행해보면  

 

핑 명령 수행 후 디렉토리 리스트들을 보여주는 모습이다. (만약 IP주소를 주지 않아도 ; 뒤의 명령 실행이 가능하다)

해당 내용을 이용해 dvwa 환경에서 ;ls 명령어를 실행해보았다.

 

디렉토리 리스트가 나오는 것을 볼 수 있다.

;cat /etc/passwd 를 입력해보았다.

 

시스템 사용자의 리스트가 출력되는 모습을 볼 수 있다.

 

+) ;id를 해보면?

현재 권한이 출력되며, daemon의 권한으로 웹 애플리케이션을 실행하고 있음을 알 수 있다. 이 권한으로는 관리자 권한으로만 실행할 수 있는 명령을 실행할 수 없다.

 


 

[류현주]

사용자의 요청이 그대로 쉘 코드에 들어간다.

127.0.0.1; ls -la 를 입력하면 디렉토리를 포함한 정보를 리스트업해준다.

 

 


 

[백서이]

admin/password으로 로그인

ping -c4d에 $target이라는 변수가 오는데

이 target 변수는 위에 있는 request, 즉 웹 요청으로부터 전달된 ip값임을 확인할 수 있음

⇒ 전달 받은 ip 주소에 대해서 ping 명령어를 실행하게 됨

 

 

따라서, 세미콜론을 입력하기만 하면 핑과는 상관없이 마음대로 다른 명령어를 실행할 수 있음/시스템에서 사용되는 명령어를 실행할 수 있음

 

; ls

데몬이라는 권한이 웹 어플리케이션을 실행하고 있는 권한(이 권한으로는 관리자 권한으로만 실행될 수 있는 명령은 사용할 수가 없음)

 

 

 

 


 

[송연수]

ip 주소를 입력할 수 있어 보인다  

low버전 소스코드를 보면 입력 값 그대로 사용하는 걸 볼 수 있다(취약)  

핑 날려보기

 

ip주소 이후 명령어 구분자 ; 을 통해 두 가지의 명령어를 실행하게 작성

ping 127.0.0.1 이후 디렉토리 리스트를 출력하게 해본다

 

 

 

 

 

 

 

- security level: medium

더보기

[국예린]

low에서 입력한 ;id를 다시 입력해 보았다.

 

아무 일도 일어나지 않아 소스코드를 보았다.

 

 

현재 해당 페이지에서는 세미콜론과 &&연산자(윈도우에서 세미콜론의 역할)를 지워 커맨드 인젝션 공격에 대응하고 있다.

그렇기 때문에 | ( 파이프라인. 앞 명령을 뒤로 넘겨줌)을 이용해 다시 |id를 입력해보았다.

사용자 권한 확인 가능

+) &(백그라운드로 명령 실행)을 사용한 &id로도 실행이 가능하다.

가능한 명령어로 다시 사용자 리스트를 확인할 수 있도록 해보았다.

성공

+) | cat /etc/passwd로도 가능하다.

 


 

[류현주]

보안 레벨 미디움의 소스코드이다. low 레벨처럼 세미콜론으로 명령어를 여러 개 넣을 수 없게 블랙리스트가 설정되어있다.! && 도 불가능한데, |나 || 는 블랙리스트에 없으므로 사용할 수 있다.

127.0.0.1| cat /etc/passwd 를 입력해보자.

내용이 전부 출력된다.

 


[백서이]

세미콜론을 지음으로써 커맨드 인젝션 공격에 대응하는 중

&&는 윈도우에서 세미콜론과 마찬가지 역할을 하는 명령어 구분자로 쓰임

파이프는 앞 명령의 결과를 뒤에 명령어 입력으로 넘겨줄 때 사용하는 특수문자 but 뒤에 명령어가 실행되는 중  

 

&는 백그라운드로 명령을 실행할 때 사용하는 특수문자 but &로 앞에 핑 명령을 백그라운드에서 실행되게 만들고 뒤에 id명령어는 그대로 실행됨  

 


[송연수]

low와 다른 점은 블랙리스트로 제한하고 있지만 그 종류가 많지 않다

다른 문자 | (파이프)를 사용  

 

 

 

 

 

- security level: high

더보기

[국예린]

medium에서의 | id, & id가 수행되지 않는다.

소스코드를 보니 아까보다 필터링되는 연산자들이 늘었다.

그러나 소스코드를 잘 보면 |(파이프라인) 뒤에 공백이 하나 있다.

해당 부분을 우회해 |id를 입력해보았다.

 

수행된다.

 

|cat /etc/passwd를 입력해보니

 

공격이 가능했다.

 


[류현주]

high 답게 블랙리스트의 종류가 많아졌다. 그런데 자세히 보니 ‘|’를 차단할 때 공백이 들어가는 허점이 있다. 공백을 포함한 ‘| ‘을 막기때문에 공백이 없으면 차단이 안 된다!

127.0.0.1|cat /etc/passwd 를 입력해보자.

 

성공

[백서이]

‘|’가 아니라 “| “(공백이 들어감)→ 실수된 부분

 

바로 공격 가능

 

 


[송연수]

medium보다 많은 종류를 블랙리스트 처리하고 있으나..

자세히 보면 실수로 | 뒤에 공백을 넣었다

파이프 바로 뒤에 붙여서 공격 쿼리를 넣으면 성공한다  

 

 

 

- security level: impossible 

더보기

[국예린]

 

소스코드를 먼저 보았다.

 

ip로 입력받은 값을 . 을 사용해 구분하고 있다.

또, ip주소는 4가지의 숫자로 이루어져 있기 때문에 .으로 구분된 각 값이 숫자가 맞는지 확인하는 함수들이 있다.

입력받아야 할 것 외의 다른 것을 입력받는 상황을 차단해 공격을 막고 있다.

 

 

 

 

 

2. SQL Injection 

- securiy level: low

더보기

[국예린]: where 우회를 사용한 실습 

1을 입력해 보았다.

form에 ‘ 를 입력해보자 에러 페이지가 출력되었다.

 

소스코드를 보면

 

<?php

if( isset( $_REQUEST[ 'Submit' ] ) ) {
    // Get input
    $id = $_REQUEST[ 'id' ];

    switch ($_DVWA['SQLI_DB']) {
        case MYSQL:
            // 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"]);
            break;
        case SQLITE:
            global $sqlite_db_connection;

            #$sqlite_db_connection = new SQLite3($_DVWA['SQLITE_DB']);
            #$sqlite_db_connection->enableExceptions(true);

            $query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
            #print $query;
            try {
                $results = $sqlite_db_connection->query($query);
            } catch (Exception $e) {
                echo 'Caught exception: ' . $e->getMessage();
                exit();
            }

            if ($results) {
                while ($row = $results->fetchArray()) {
                    // 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>";
                }
            } else {
                echo "Error in fetch ".$sqlite_db->lastErrorMsg();
            }
            break;
    } 
}

?>

 

user_id 부분에 사용자가 입력한 값이 $id 에 들어가게 된다. 만약 저기 작은 따옴표가 하나 더 들어가게 된다면, 작은 따옴표가 3개가 되면서 에러가 발생한다.

가장 기본적인 sql injection 구문을 활용해 보았다.

 

1’or’1’=’1 : 모든 조건문이 항상 참이 되도록 아이디가 1이라는 것과 상관 없이 모든 아이디가 출력되도록 한다.

 

테이블의 모든 정보가 출력되었다.

+) ‘# : mysql 데이터베이스에서 # 이후의 내용은 다 주석 처리가 되기 때문에 쿼리문 구조에 영향을 주지 않기 때문에 실행되지 않는다.


 

[류현주]

user id를 입력하라길래 admin을 입력했더니 아무 일도 일어나지 않았다. 그래서 소스코드를 살펴봤다.

 

db에서의 유저 id를 입력하라는 것이었다. 그래서 1을 입력했더니..

admin 계정의 정보가 떴다.

sql 인젝션이 가능한지 확인하기 위해 작은 따옴표를 붙여서 시험해보자.  

오류가 떴는데, 1’ …즉 따옴표를 붙여서 오류가 났고, 사용자가 sql 문법에 관여할 수 있다는 뜻이 된다.

그럼 id의 값이 항상 참이 되게 만들고 뒤는 다 주석처리해보자.

 

1’ or 1=1#을 입력했더니

짠 전부 나옴

[백서이]

 

1. where 구문 우회

유저 아이디에 사용자가 입력한 값이 $id라는 변수로 들어가게 되어 있는데, 이미 작음 따옴표로 둘러싸여져 있음

이제 여기에 ‘’’로 들어가면 에러가 발생하는 것

 →1’or’1’=’1을 입력하면 모든 정보가 나옴

2. column 개수 알아내기 

 

원래의 쿼리문이 몇개의 칼럼을 리턴하는지 알아내야함

1’ union select 1#

1’union select 1,2#

1’union select 1,1,1#

다른 방법 : ORDER BY : 어떤 칼럼을 기준으로 정렬할 때 사용하는 키워드

1’ order by 1#
1’ order by 2#

1’ order by 3#

⇒ 따라서 2개 밖에 없다!

 

3. Union 공격

 

데이터베이스명 조회 

1’ union select schema_name,1 from information_schema.schmeata #

 

dvwa데이터베이스의 테이블 명 조회

1’ union select table_schema, table_name from information_schema.tables where table_schema =’dvwa’ #

 

users 테이블 칼럼 조회

1’ union select table_name, column_name from information_schema.columns where table_schema =’dvwa’ and table_name =’users’ #

=>userid, firstname, lastname, surname이 나옴

1’union select user, password from user #

 

 


[송연수]

id를 넣으면

id, firtst name, surname을 출력해준다

사용자 입력 값 검증 없음

쿼리 함수가 실패하면 or die로 가서 오류 메세지를 출력

 

1’

→ SELECT first_name, last_name FROM users WHERE user_id = 'id’';

일부러 구문 오류 나게 유도

sql injection 가능성 높음 → 입력 값에 따라 쿼리가 달라졌기 때문에

1' or '1' = '1

→ SELECT first_name, last_name FROM users WHERE user_id = '1' or '1' = '1';

'1' = '1' 으로 항상 참 = 전체 결과 출력


union 활용

1' union select 1#

컬럼 수가 다르다

 

1' union select 1,2#

오류 없이 출력

1' union select 1,2,3# → 처음과 같은 오류 발생 = 칼럼 수는 2

 

1' UNION SELECT schema_name,1 FROM information_schema.schemata#

information_schema에 데이터베이스 정보가 있고, 데이터베이스 이름은 schemata 테이블에 들어있음

칼럼 수를 맞춰주기 위해서 schema_name,**1**

→ dvwa라는 데이터베이스 확인

 

1' UNION SELECT table_name,table_schema FROM information_schema.tables WHERE table_schema='dvwa'#

dvwa데이터베이스의 테이블을 조회

→ users 테이블 확인

 

1' UNION SELECT table_name, column_name FROM information_schema.columns WHERE table_schema='dvwa' and table_name='users'#

users테이블의 칼럼 조회

→ password 궁금

 

1' UNION SELECT user,password FROM users#

user와 password를 조회

 

 

- security level: medium

더보기

[국예린]

 

이번엔 입력이 아니라 정해진 값을 선택하도록 설정되어있다.  

low와 달리 작은 따옴표가 없다. 숫자 입력으로 공격해야 한다.

burp suite를 실행해 intercept 기능을 사용하고자 한다.

 

intercept is on → User ID : 1 submit
입력값 수정
성공

+) union 공격도 가능하다.

password는 변환 사이트에서 변환 가능하다.


 

[류현주]

이번에는 텍스트 인풋칸이 없고 1~5까지의 유저 아이디를 선택하게 되어있다. 시험삼아 3을 눌러보니..

이렇게 나왔다.  

버프스위트 프록시 탭에서 살펴보니

 

이런 식으로 id=3을 눌렀다고 떴다. dvwa 페이지에서는 id를 수정할 수 없지만 여기서는 가능하다!

 

 

 

이렇게 쿼리문을 수정해주고, forward 버튼을 누르면..

성공!

 

 


[송연수]

사용자 입력 값을 제한하려는 모습

 

따옴표가 없음 = 숫자로  

 

1 or 1=1

burp suite로 바꿔준다

 

 

 

 

 

 

 

- security level: high

더보기

[국예린]

url방식으로 바뀌었다.

새 창에서 입력시 원래 페이지에 결과가 나타나는 형식이다.

현재 LIMIT을 사용해 출력되는 레코드의 수가 무조건 하나가 되도록 조정하고 있다.

앞서 나온 이야기대로 #을 통해 주석처리를 해 보았다.

공격에 성공한 모습이다.

+) union 공격도 똑같다.  


[류현주]

이번에는 이런 화면이 떴다.

해당 링크를 눌렀더니

 

세션 id를 바꿀 수 있는 창이 나왔다. 시험삼아서 4를 입력했더니

그냥 간단하게 출력되고 끝났다.

그럼 여기에 sql 문법 오류가 있는 구문을 입력하면 공격이 진행되는지 확인하기 위해 1’ or 1=1#를 입력했다.

성공  

 


[송연수]

새로운 창에서 받는다  

출력 값을 1개로 제한하려고 하고 있다..

→ 뒷 부분 구문을 #으로 주석 처리

1' or '1'='1'# → SELECT first_name, last_name FROM users WHERE user_id = '1' or '1'='1'# LIMIT 1;

 

 

 

 

- security level: impossible 

더보기

[국예린]

소스코드를 보았다.

id값이 숫자인지 확인하고, prepare(미리 쿼리문의 형태를 작성), bindparam(id를 따로 지정), execute 등 함수를 여러 번 호출하는 식으로 쿼리문을 호출한다.

이런 식으로 쿼리를 호출하면 데이터베이스가 어떤 것이 코드이고 데이터인지 파악할 수 있고, 사용자의 입력 값을 문자열로만 처리하기 때문에 sql injection공격을 막을 수 있다.(sql 쿼리문의 중간에 사용자 입력이 들어가던 기존 형식과 달리 아예 문자열 형식으로 사용되면 sql 쿼리문이 조작될 수 없다.)

 

 

 

3. SQL Injection (Blind)

- security level: low

더보기

[국예린]

기존 sql 내용처럼 1을 입력하면 해당 아이디가 데이터베이스에 존재한다고만 나온다.

이상한 숫자를 쳐보면 “User ID is MISSING from the database” 라고 나온다.

입력값을 수정해보니 해당 아이디가 존재한다고 나온다.

 

조금 변형을 주었더니 다시 없다.

and 이후에 1=1, 1=2와 같은 조건이 실행되고 있다고 한다.

만약 백그라운드에서 sql 쿼리문이 실행되고 있다고 한다면 현재 형태는

SELECT user from users where id=’1’ AND 1=1# → 전체가 참인 조건이 되어 현재 존재한다고 나오고,

SELECT user from users where id=’1’ AND 1=2# → 거짓인 조건문이 되어 사용자가 없다고 나온 것이다.

이렇게 and 같은 연산이 백그라운드에서 실행중이라는 것은, sql 쿼리문이 뒤에 있다는 것을 알려주는 중요한 힌트가 된다.

→ 일반 sql injection처럼 직접적인 결과를 얻어낼 수는 없지만, admin이라는 사용자 존재하느냐 등 명제를 제시 후 and 뒤에 1=1 1=2와 같이 다른 조건을 주어 해당 명제가 참인지 거짓인지 찾을 수 있게 된다.

 


 

[류현주]

GET 방식으로 입력을 받는다.

블라인드 sql 인젝션 화면도 유저 id를 입력할 수 있는 창이 있다.

1을 입력하니..

유저 아이디가 1인 회원이 db에 존재한다는 것을 알려주었다. 랜덤으로 숫자를 더 입력해봤는데, 6은 없고, 5는 있었다.

일단 order by 명령어로 컬럼 수가 몇 개 있는지 알아내보자…

1’ order by 1# 의 결과

 

→ 불완전한 sql문이 입력되는 것을 보아 sql injection이 가능한 것임을 알 수 있음!

order by 2도 같음

 

근데 order by 3은

오류가 난다. 따라서 컬럼은 2개가 있다는 것.

db 이름의 길이를 알아내기 위해 length(database()) 함수를 사용해보자.

 

2’ AND LENGTH(DATABASE())=1# 을 입력했더니…

2, 3도 같은 결과값이 나왔지만

2’ AND LENGTH(DATABASE())=4# 처럼 4를 입력했을 때는 있다고 나왔다.

그렇다면 db이름의 길이는 4이다.

 

이제 길이를 알았으니 이름이 뭔지도 알 수 있게 된다. sqlmap을 사용하지 않고 순차적으로 정보를 알아낼 수 있다. 

 


[백서이]

-> 아이디가 존재한다고만 알려주고 있음

->아이디가 존재하지 않는다고 말함

->1’ and 1=1#

=>백그라운드에서는 SELECT user from users where id=’1’ And 1=1#

->존재하지 않음

 


[송연수]

존재 여부만 파악 가능

어떤 사용자가 있는 지 여부를 판단하기 위해 sleep 사용

있는 경우 → 1’ AND sleep(5)# → 5초 지연

없는 경우 → 6’ AND sleep(5)# → 바로 응답

 

- security level: medium

더보기

[류현주]

입력값을 POST 방식으로 보내고,

 

입력값에 sql 인젝션에 쓰이는 특수 문자들이 있으면 에러가 나오게 처리해놨다. 그렇지만

 

여기서 사용자의 입력값이 그대로 sql 쿼리가 된다는 것이 취약점이 된다.  


  

[백서이]

버프스위트를 이용해서 사용함

맨 마지막에 id 1 or 1=1&submit=submit=> 결과가 나옴

 

 

 

 

 

- security level: high

더보기

[류현주]

high는 블라인드 말고 일반 sql 인젝션의 high버전처럼 id를 입력할 수 있는 새로운 창을 생성할 수 있게 되어있다.

 

 

소스코드를 살펴보니 사용자가 전달한 세션 id 및 쿠키를 그대로 사용해서 sql 쿼리를 생성한다. 따라서 sql 쿼리를 악의적으로 생성할 수 있게 된다는 취약점이 있는 것을 확인할 수 있다.  


 

[백서이]

 

 

=>소스코드를 보면 한개의 값만 출력할 수 있도록 해놓음

 

SQL 인젝션 공격이 실행되었음