SK shieldus Rookies 16기 (클라우드 보안 기술 #02)

만두다섯개·2023년 12월 15일
0

SK 루키즈 16기

목록 보기
32/52

주요 정보

  • 교육 과정명 : 클라우드기반 스마트융합보안 과정 16기
  • 교육 회차 정보 : '23. 12. 15. 클라우드 보안 기술 #02

학습 참고

https://docs.google.com/document/d/1Y7wtAHji99RyS7h77oyr2W28Z2t7I9Uu_c7N7YPjRls/edit

SQL Injection 취약점 원인 종류

SQL Injection 취약점이란? 외부 입력값을 내부 처리에 사용 시, 외부 입력값에 처리를 조작하는 문자열 포함 여부를 확인하지 않고 사용할 때, 의도한 처리와는 다르게 변경되어 수행된다.
여기서 말하는 처리 과정과 구문 삽입들은 아래와 같다.

쿼리문을 만들고 실하는 처리 => SQL 삽입
XPath or XQuery 구문을 이용해 XML 문서를 조작하는 처리 => XPath or XQuery 삽입
LDAP 서버에 쿼리를 실행하는 처리 => LDAP 삽입
운영체제 명령어를 실행하는 처리 => Command 삽입

SQL Injection 공격 결과 : 권한 밖 데이터 접근, DBMS 시스템 제어권 탈취, 쿼리로 기능 우회 또는 오용 가능
SQL Injection 방어 방법 : 입력값 검증, 제한해 외부에서 전달 값에 처리 조작 문자열 여부 확인이 필요. (여러 방법론이 존재, 이스케이프 처리, 실행 결과를 이미 정해진 구문으로 매핑, 등)

SELECT * FROM member where id = ?

사용자 입력 ID와 일치하는 회원 정보를 조회해서 제공 목적으로 소스코드가 존재한다.
정상적 사용자 => ID를 검색해서 원하는 회원 정보를 조회하자!
공격자 => 테이블에 있는 정보를 모두 가져오자! 서비스 이용하지 못하게 하자!

에러를 유발하는 입력

Error Based SQL Injection : 입력값으로 오류를 유발하는 값을 전달
생성되는 오류 메시지를 통해 정보를 수집, 수집한 정보를 이용해 추가 공격을 계획한다.

SELECT * FROM member where id = 123'

123' 이라는 잘못된 형식으로 인해 오류 메시지가 출력되면, => 해당 ID 컬럼의 타입이 Number 타입인 것을 알 수 있다.

항상 참이 되는 입력

쿼리문의 조건식이 항상 참이 되게 만드는 입력
권한 밖에 데이터 접근, 조회 가능

SELECT * FROM member where id = ' or 1 = 1

=> 모든 회원의 정보가 조회된다.

UNION 구문을 이용

Union Based SQL Injection
공격자가 원하는 결과를 얻기 우해 UNION 구문을 이용해 결합해 실행한다.
기존 쿼리 결과와 공격자가 알고자 하는 정보가 함께 노출(제공)된다.

SELECT * FROM member where id = 123 and 1 =2 UNION select 1, 2, 3, 4 from tablename

공격자가 1, 2, 3, 4 라는 tablename의 데이터를 얻는다.

Stored Procedure 호출하는 입력

DBMS의 객체. SQL에서의 메소드로서 사용된다. (데이터베이스에서 정의된 미리 컴파일된 SQL 쿼리 및 명령문의 집합)
DB의 제어권을 탈취하는 것이 가능하다.

SELECT * FROM member where id = 123 ; exec xp_cmdshell 'cmd.exe /c dir'

xp_cmdshell : MS-SQL 제공하는 시스템 Stored Procedure => 매개변수 전달 값을 DBMS 쉘에서 실행해고 그 결과를 반환한다.
즉, 'cmd.exe /c dir' 에서 /c dir 디렉터리 내용을 반환한다.

Injection Flaws 실습

WIN XP - WebGoat 환경 실습

String SQL Injection

SELECT * FROM user_data where last_name = ?

라는 SQL 구문을 사용한다고 한다.
모든 테이블의 정보를 가져오기 위해, ' or 'a' = 'a 을 입력해보자.
아래와 같은 결과가 나온다.

Numeric SQL Injection

주어진 조건을 확인해보자

기상 상태를 확인하기 위해 나의 지역(local)을 선택하고 Go! 버튼을 누르면 기상 테이블이 조회된다.
이때, 모든 테이블 정보를 확인하려면 어떻게 하면 될까?

각자 자른 location 선택 시, station 매개변수 전달 값이 다르다.

따라서 해당 station에 SQL Injection 수행해 보자

모든 결과값이 호출되었다!

취약점 방어 실습

웹 서버의 소스 코드를 확인해 보자.
WebGoat/src/main/java/org/owasp/webgoat/lessons/SqlNumericinjection.java 에 아래 코드가 존재한다.

protected Element injectableQuery(WebSession s)
	{
		ElementContainer ec = new ElementContainer();

		try
		{

			ec.addElement(makeStationList(s));

			String query;

			station = s.getParser().getRawParameter(STATION_ID, null);

			if (station == null)
			{
				query = "SELECT * FROM weather_data WHERE station = [station]";
			}
			else
			{
				query = "SELECT * FROM weather_data WHERE station = " + station;
			}

			ec.addElement(new PRE(query));

			if (station == null) return ec;

			Connection connection = DatabaseUtilities.getConnection(s);

			try
			{
				Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,
																	ResultSet.CONCUR_READ_ONLY);
				ResultSet results = statement.executeQuery(query);

여기에서 사용자로부터 받은 STATION_ID를 검증 없이 쿼리문 생성 시 사용한다. 아래 그림을 참고해보자.

이제 해당 코드를 취약점에 안전하도록 변경해 보자.

setter 메서드, getter 메서드란?

Setter 메서드는 객체 지향 프로그래밍에서 사용되는 용어이다.
주로 클래스의 속성 값을 설정하는 메서드를 가리킵니다.
일반적으로 변수는 Private로 선언되어 외부에서 사용할 수 없다.
그러나 setter 메소드는 클래스의 인스턴스 변수를 외부에서 설정하거나 갱신 가능하게 만들어 준다.

다시 돌아와서, 취약점 보완 코드를 아래와 같이 작성한다.

protected Element injectableQuery(WebSession s)
	{
		ElementContainer ec = new ElementContainer();

		try
		{

			ec.addElement(makeStationList(s));

			String query;

			station = s.getParser().getRawParameter(STATION_ID, null);

			if (station == null)
			{
				query = "SELECT * FROM weather_data WHERE station = [station]";
			}
			else
			{
				// query = "SELECT * FROM weather_data WHERE station = " + station;
				query = "SELECT * FROM weather_data WHERE station = ? "; // 변수 타입 고려 X, 변수를 ? 사용해 기본 쿼리문 사용
			}

			ec.addElement(new PRE(query));

			if (station == null) return ec;

			Connection connection = DatabaseUtilities.getConnection(s);

			try
			{
				//Statement statement = connection.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY);
				PreparedStatement statement = connection.prepareStatement(query, ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);	//PreparedStatement 객체 생성												
				//ResultSet results = statement.executeQuery(query);
				statement.setInt(1, Integer.parseInt(station));  //  setter 메서드를 사용해 변수를 설정하고 쿼리를 실행한다. 
				ResultSet results = statement.executeQuery();

이제 수정한 내용을 저장하고, 다시 Numeric SQL Injection 을 실행시킨다. (OWASP WebGoat 가 다시 재시작되어 들어가야 한다)

아래와 같이 오류가 발생한다! 방어 성공이다.

  1. 문제 풀고 -> 소스코드 수정 -> 저장 -> SQL Injection -> 강사님 오류코드
  2. 소스코드 수정 -> 저장 -> SQL Injection -> 빨간 오류코드

Blind SQL Injection

쿼리 실행 결과에 따라 서버의 반응으로부터 참 또는 거짓임을 알 수 있는 경우

정상적인 실행 결과 => 참 또는 거짓

SELECT * FROM member where id = 123

위 문장에 대해 참이 나오면, 해당 123 사용자가 존재. 거짓이 나오면 123 사용자가 존재하지 않는다.
여기서, 공격 가능 여부를 확인한다.

SELECT * FROM member where id = 123 and 쿼리문

쿼리문 결과가 제공 => 해당 쿼리문에 대한 결과가 참임을 확인 가능.
쿼리문 결과가 제공 X => 해당 쿼리문에 대한 결과가 거짓임을 확인 가능.
참 또는 거짓의 결과로 원하는 데이터를 조금씩 획득하다 보면, 공격자가 원하는 데이터(테이블 정보, 데이터, 등..)을 알 수 있다.

Blind Numeric SQL Injection

문제를 살펴보자.

환경 : 계좌번호 검증 사이트
문제 : pins 테이블의 pin 필드의 값을 찾아라.
The goal is to find the value of the field pin in table pins for the row with the cc_number of 1111222233334444. The field is of type int, which is an integer.

PIN 이란? Personal Identification Number의 약자로, 4~8개 숫자로 구성된 비밀번호이다.

접근

1234 라는 계좌번호를 입력하면, account_number 변수로 전달된다.

그 후, Account number is valid 라고 나온다.

한번 SQL Injection 공격을 시도해 보자

이번에는 오류가 An error occurred, please try again. 이라고 나왔다.

공격 목표를 위해서는 아래와 같은 쿼리 구문이 실행되어야 한다.

select pin from pins where cc_number = '1111222233334444' 

따라서 기존 쿼리문 AND 공격 쿼리문 결과가 참 / 거짓으로 원하는 pin 값을 찾아야 한다.

select * from accounts where account_number = 101 and (select pin from pins where cc_number = '1111222233334444') > 1000

기존 쿼리문은 계속 참이 나온다. 그리고 공격 쿼리문 조건인 1000보다 크다면 참일 것이다. 즉,
참 AND 참 => 참
참 AND 거짓 => 거짓
이 방식의 반복으로 임의의 수를 지정해 해당 pin의 결과를 찾아보자.

해결방식

이를 이용하면 최종 값이 2364로 나온다.

Blind String SQL Injection

문제를 살펴보자.

조건: 입력 폼은 계좌번호 유효성 검증 결과를 유효/유효하지 않음 두 가지로 나타내준다.
목표 : The goal is to find the value of the field name in table pins for the row with the cc_number of 4321432143214321. and put that value in the form.

접근

  1. 입력으로부터 쿼리를 생성 후 처리한다. 처리 결과값을 두 가지 결과로 알려준다.
  2. 목표 값 유추 쿼리문 사용한다.

사용 쿼리문 탐색을 위해 SQL Injection 시도해보자.

'or 1 = 1

이 값을 입력했다.

앗! 친절하게도 어떤 쿼리를 쓰는지 알려주었다.

SELECT * FROM user_data WHERE userid ='

' 를 사용하지 않고 넣어보자

현재 쿼리가 '(싱글 쿼터)없이 1을 사용함 확인.
이제 AND 와 유추 쿼리문을 사용하자.

1 and (select name from pins where cc_number = '4321432143214321'  ) = '목표 값'

을 이용해 목표인 값을 찾아보자.
당연히 이 쿼리문만 이용한다면, 결과값을 예/아니오 만 알 수 있으니, 위 쿼리문으로는 목표값을 얻을 수 없다.

해결과정

  1. 접근에서 만든 쿼리문을 이용해 값의 길이를 확인한다.
101 AND (select length(ascii(substr(name, 4))) from pins where cc_number = '4321432143214321') > 2

SQL에서는 문자열을 비교할 때 ASCII 코드값을 사용한다 .
따라서 아래와 같이 ascii 메소드(언어에서 제공하는 메소드)를 사용해 비교하게 한다.

1 and (select ascii(substr(name,1)) from pins where cc_number = '4321432143214321') > 20 = '목표 값'
101 and (select ascii(substr(name, 1, 1)) from pins where cc_number = '4321432143214321') < 74

101 and (select ascii(substr(name, 1, 1)) from pins where cc_number = '4321432143214321') < 74

결론

UNION Based SQL Injection

  • bee-box OS가 실행중 이어야 한다.
  • kali linux 웹 브라우저에서 http://beebox/bWAPP/sqli_1.php 주소로 접속 후 계정(bee / bug)로그인 후, SQL Injection (GET/Search)로 들어간다.

조건 : 영화 이름으로 영화를 검색하는 사이트이다.
목표 : 해당 사이트에 등록된 사용자의 계정 정보를 탈취하시오.

접근

해당 문제는 영화 일부를 검색하면, 해당 이름이 들어간 영화 목록을 출력한다.
man 이라는 검색어 결과를 개발자 도구로 사용하는 과정을 살펴보자.

SELECT ? FROM ? WHRER title like %title(여기서는 man) 을 알 수 있다. 
select * from movies where title like '%man%'  와 같다고 가정 가능(MySQL은 대소문자 구분 X)

전체 쿼리문을 알 수 없어도, 현재 존재하는 쿼리 문 뒤에 공격 쿼리문을 생성해 UNION을 사용해 가져와 보자.

SQL Injection 목적의 문장을 입력하기 위해 man' 를 입력해보자.
아래 사진과 같은 오류 메시지가 나온다.

SELECT ? FROM ? WHRER title like ' % man' %' 에서  마지막 %' 로 인해 오류 발생.

아래와 같은 사실을 확인 가능하다
1. MySQL 사용
2. 입력값을 검증, 제한 없이 그대로 쿼리문 생성 및 실행에 사용하고 있는 것을 확인 ⇒ 인젝션이 가능

이제 테이블 정보에 대해 더 확인해보자.
1. 컬럼 개수 확인
정상 쿼리의 실행 결과가 반환하는 컬럼의 개수를 확인하자. => 해당 개수에 맞는 쿼리문을 작성해야 웹 서버에서 정상적으로 값을 내보내 줄 것이다.

select * from movies where title like '%man' or 'a' = 'a' order by 1 -- %'

위 sql 쿼리 문장을 사용한다면, 1

위 문장은 조회 결과에서 첫번째 컬럼의 값을 기준으로 정렬한다.

해결 과정

man' UNION SELECT * FROM * --'

결론

SQL Inejction 방어 기법

구조화된 쿼리 이용 쿼리문 정의하고 실행

Java 인 경우 => PreparedStatement 객체 이용한다

입력값 쿼리 조작 문자열 포함 여부 확인

구조화된 쿼리를 이용하지 않는 경우, 입력값에 쿼리 조작 문자열 포함 여부를 확인하고 사용한다.
1. 쿼리 조작 문자를 제거하고 사용한다.
2. 안전한 형태로 변경 후 사용한다. => 이스케이프 처리

최근의 SQL Injection 방어 방식

SQL Injection 취약점의 학습내용은 레거시한 방법들이고, 실제에서는 별도의 XML 문서로 출력 후 쿼리를 사용한다.
비지니스 로직(결과를 화면에 출력해라~) + 쿼리문 => 한 소스코드로 두지 않는다.

비지니스 로직과 쿼리문을 별도의 위치에 지정하고, 필요시 쿼리를 별도로 호출해 사용한다.

이를 지원하는 프레임워크, 라이브러리를 사용한다. ( 예시 mybatis)

내 생각 : 비지니스 로직과 쿼리문을 별도로 분리한다 => 분리되어 있는 쿼리문을 참조 시, 별도 로직으로 매핑된다.

위 사진에서 처럼, $ 기호를 사용해 변수 값이 문자열이 결합하는 형태로 처리한다면, 아래 사진에서는 매핑 과정에서 $에서 #(?를 이용하는)사용한다.

개인적인 생각 : 어떻게 보면 더 쿼리 사용을 보안성 강화라는 이름으로 더 복잡하게 처리하게 되고, 다양한 보안 약점이 생길 수 있을 것 같다. (특정 쿼리문 사용 매핑과정 확인의 어려움)
장점으로는 쿼리문 사용이 쉬워짐으로써 보안성과 사용 용이성이 생긴다.
장, 단점이 있는것 같다.

sqlmap 실습

kali linux에서 sqlmap을 사용해 http://victim:8080/openeg/login.do URL을 탐색해보자.

sqlmap이란? 공개 모의침투 도구로 SQL구문삽입(SQL Injection) 취약점을 탐지/진단하고 데이터베이스에 직간접적으로 접근할 수 있는 취약점 분석 도구이다.

외부 사이트 대상(공공기관, 금융기관, 인증기관 등)으로 해당 프로그램 사용 금지

추가 정보 : https://eliez3r.github.io/post/2019/10/25/study-db-sqlmap.html

profile
磨斧爲針

0개의 댓글