2주차 - SQL 보고서

Bae YuSeon·2022년 9월 21일
1

E-COPS

목록 보기
3/9
post-thumbnail

정규세션 복습

SQL

  • RDBMS의 데이터를 관리(정의하고 질의, 수정 등)을 하기 위해 설계된 특수 목적의 프로그래밍 언어

SQL 구조
SQL 구조
SQL 문법 - DML을 중심으로

  • Create: 새로운 테이블 생성
    CREATE TABLE table_name (column1 datatype, column2 datatype, column3 datatype, ....)
  • Insert: 새로운 데이터 생성
    INSERT INTO table_name VALUES (value1, value2, value3, ...)
  • SELECT: 데이터 조회
    SELECT column1, column2, .. FROM table_name WHERE condition

    SELECT, FROM, WHERE 외에 또 다른 조건 설정문
    -GROUP BY: 데이터 그룹화
    -HAVING: GROUP BY에 조건을 추가할 때 사용
    -ORDER BY: 정렬

  • Update: 데이터 수정
    UPDATE table_name SET column1 = value1, column2 = value2, ...
    WHERE condition
  • Delete: 데이터 삭제
    DELETE FROM table_name WHERE condition

SQL Injection 공격 사례 및 공격 방법

SQL 인젝션

  • 이용자의 입력값이 SQL 구문의 일부로 사용될 경우 해커에 의해 조작된 SQL 구문이 데이터베이스에 그대로 전달되어 비정상적인 DB 명령을 실행시키는 공격 기법
  • 어플리케이션의 사용자 입력 값에 SQL 코드를 삽입 또는 추가하고, 해당 SQL 구문을 가장 마지막의 SQL 서버에서 전달하여 해석 및 실행하는 과정에서 발생하는 공격

SQL Injection 공격 사례

  • 2011년에 일어난 소니 데이터베이스 공격으로, 약 100만 명의 회원 정보와 350만 개의 디지털 쿠폰이 유출되었다. 이 공격은 소니컴퓨터엔터테인먼트가 구축한 플레이스테이션 네트워크(PSN) 서비스가 SQL 인젝션 공격에 노출된 것이다. 이 공격은 웹 서버와 두 개의 파이어월(신뢰하지 않는 외부 네트워크와 신뢰하는 내부 네트워크 사이를 지나는 패킷을 미리 정한 규칙에 따라 차단하거나 보내주는 기능을 하는 하드웨어나 소프트웨어로, 쉽게 말해 해커의 접속을 아예 봉쇄하거나 외부에서 침투하는 해커로부터 시스템을 지키는 프로그램) 뒷단의 어플리케이션 서버에서 시작되었다. 공격은 처음에 구매 행위로 위장을 해 네트워크 보안 시스템에 발견되지 않았고, 이에 어플리케이션 서버의 알려진 취약점을 이용해 데이터베이스 서버에 액세스하는데 사용할 소프트웨어를 심었다. 데이터베이스 서버는 3번째 파이어월 뒤에 있었는데, 소니는 이러한 취약점을 사전에 인지하지 못했었고, SQL 인젝션 공격이 가능했던 것이다.

SQL Injection 공격 방법

  • 1.논리적 에러를 이용한 SQL Injection

예) 회원 ID를 입력하여 모든 회원정보를 조회하는 것
로그인 화면에서 사용자가 ID가 '1'인 사용자 정보를 요청하면 웹 애플리케이션이 내부의 데이터베이스로 WHERE 조건문이 있는 SQL 쿼리문
SELECT name, email FROM users WHERE ID='1'을 전송한다. 이 쿼리가 실행되면 데이터베이스 users라는 사용자 테이블에서 ID가 1인 사용자 정보를 반환하여
웹 어플리케이션을 통해 클라이언트에 전달한다.

공격대상: select * from users where userid='ID' and userpassword='PASSWORD'
공격예시: select * from users where userid=' ' or 1=1-- and userpassword='PASSWORD'
‘ OR 1=1 -- 을 추가
=> WHERE 절이 모두 참('1'='1')이 되고, -- 를 통해 뒤의 구문을 모두 주석 처리
=> users 테이블에 있는 모든 정보를 조회 가능

  • 2.UNION Based SQL Injection

UNION: 합집합으로 두 개의 SELECT구문의 결과를 모두 포함시키는 키워드

정상적인 쿼리문에 하나의 추가 쿼리를 삽입하여 원하는 정보를 획득하는 방법으로, 공격자가 or 이라는 구문 대신 UNION 키워드를 삽입한다.
UNION Injection이 성공하기 위해서는 UNION하는 두 테이블의 컬럼 수가 같아야 하고, UNION하는 두 테이블의 데이터 형이 같아야 한다.
공격예시: SELECT * FROM users WHERE id = '1' UNION SELECT name, pw FROM users--
=> 원래 실행되어야 하는 ID가 1인 사용자 정보 이외에도 WHERE 조건문이 없는 SELECT 구문 결과를 포함하게 되어 모든 사용자의 이름과 비밀번호가 반환

  • 3.Blind SQL Injection

데이터베이스 조회 후 결과를 이용자가 화면에서 직접 확인하지 못할 때 참/거짓 반환 결과로 데이터를 획득하는 공격 기법
한 바이트 씩 비교하여 공격하는 방식으로 다른 공격에 비해 많은 시간 소요

예) Question #1. dreamhack 계정의 비밀번호 첫 번째 글자는 'x' 인가요?
-Answer. 아닙니다
Question #2. dreamhack 계정의 비밀번호 첫 번째 글자는 'p' 인가요?
-Answer. 맞습니다 (첫 번째 글자 = p)
Question #3. dreamhack 계정의 비밀번호 두 번째 글자는 'y' 인가요?
-Answer. 아닙니다.
Question #4. dreamhack 계정의 비밀번호 두 번째 글자는 'a'인가요?
-Answer. 맞습니다. (두 번째 글자 = a)

이렇게 스무고개 게임이나 up-down 게임처럼 질문을 하고 이에 대한 답을 얻어서 데이터베이스의 내용을 알아낼 수 있다.

SQL 실습

프로그래머스 > SQL 고득점 Kit > SELECT & SUM,MAX,MIN

  • 모든 레코드 조회하기

동물 보호소에 들어온 모든 동물의 정보를 ANIMAL_ID순으로 조회하는 SQL문을 작성해주세요.

  • 역순 정렬하기

동물 보호소에 들어온 모든 동물의 이름과 보호 시작일을 조회하는 SQL문을 작성해주세요.

ORDER BY: SELECT문으로 검색된 데이터를 오름차순(ASC)이나 내림차순 (DESC)으로 정렬시킬 때 사용된다. 디폴트 값은 ASC로, 생략 가능하다.

  • 아픈 동물 찾기

동물 보호소에 들어온 동물 중 아픈 동물의 아이디와 이름을 조회하는 SQL 문을 작성해주세요. 이때 결과는 아이디 순으로 조회해주세요.

  • 어린 동물 찾기

동물 보호소에 들어온 동물 중 젊은 동물1의 아이디와 이름을 조회하는 SQL 문을 작성해주세요. 이때 결과는 아이디 순으로 조회해주세요.

  • 동물의 아이디와 이름

동물 보호소에 들어온 모든 동물의 아이디와 이름을 ANIMAL_ID순으로 조회하는 SQL문을 작성해주세요.

  • 여러 기준으로 정렬하기

동물 보호소에 들어온 모든 동물의 아이디와 이름, 보호 시작일을 이름 순으로 조회하는 SQL문을 작성해주세요. 단, 이름이 같은 동물 중에서는 보호를 나중에 시작한 동물을 먼저 보여줘야 합니다.

  • 상위 n개의 레코드

동물 보호소에 가장 먼저 들어온 동물의 이름을 조회하는 SQL 문을 작성해주세요.

LIMIT 숫자: 지정한 갯수만큼의 자료를 보여준다.
LIMIT 시작위치, 반환갯수: 시작 위치의 바로 다음부터 지정한 갯수만큼의 자료를 보여준다.

  • 최댓값 구하기

가장 최근에 들어온 동물은 언제 들어왔는지 조회하는 SQL 문을 작성해주세요.

  • 최솟값 구하기

동물 보호소에 가장 먼저 들어온 동물은 언제 들어왔는지 조회하는 SQL 문을 작성해주세요.

  • 동물 수 구하기

동물 보호소에 동물이 몇 마리 들어왔는지 조회하는 SQL 문을 작성해주세요.

count(*) : 조회되는 데이터의 갯수를 리턴해주는 명령어이다.

  • 중복 제거하기

동물 보호소에 들어온 동물의 이름은 몇 개인지 조회하는 SQL 문을 작성해주세요. 이때 이름이 NULL인 경우는 집계하지 않으며 중복되는 이름은 하나로 칩니다.

DISTINCT 컬럼명: 조회하려는 컬럼의 중복된 값은 삭제한 후 결과가 나온다. COUNT(DISTINCT 컬럼명): 중복되지 않은 서로 다른 자료갯수를 확인 할 수 있다.

WHERE 칼럼명 IS NULL: 칼럼의 값이 NULL인 경우만 값을 불러온다.
WHERE 칼럼명 IS NOT NULL: 칼럼의 값이 NULL이 아닌 경우만 값을 불러온다.

SQL Injection 문제

Dreamhack simple_sqli 문제

문제링크 Dreamhack simple_sqli 문제

풀이

문제 소스코드

#!/usr/bin/python3
from flask import Flask, request, render_template, g
import sqlite3
import os
import binascii

app = Flask(__name__)
app.secret_key = os.urandom(32)

try:
    FLAG = open('./flag.txt', 'r').read()
except:
    FLAG = '[**FLAG**]'

DATABASE = "database.db"
if os.path.exists(DATABASE) == False:
    db = sqlite3.connect(DATABASE)
    db.execute('create table users(userid char(100), userpassword char(100));')
    db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')
    db.commit()
    db.close()

def get_db():
    db = getattr(g, '_database', None)
    if db is None:
        db = g._database = sqlite3.connect(DATABASE)
    db.row_factory = sqlite3.Row
    return db

def query_db(query, one=True):
    cur = get_db().execute(query)
    rv = cur.fetchall()
    cur.close()
    return (rv[0] if rv else None) if one else rv

@app.teardown_appcontext
def close_connection(exception):
    db = getattr(g, '_database', None)
    if db is not None:
        db.close()

@app.route('/')
def index():
    return render_template('index.html')

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'GET':
        return render_template('login.html')
    else:
        userid = request.form.get('userid')
        userpassword = request.form.get('userpassword')
        res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')
        if res:
            userid = res[0]
            if userid == 'admin':
                return f'hello {userid} flag is {FLAG}'
            return f'<script>alert("hello {userid}");history.go(-1);</script>'
        return '<script>alert("wrong");history.go(-1);</script>'

app.run(host='0.0.0.0', port=8000)

코드를 보다보면

db.execute(f'insert into users(userid, userpassword) values ("guest", "guest"), ("admin", "{binascii.hexlify(os.urandom(16)).decode("utf8")}");')

guest 계정은 비밀번호가 나와있지만, admin 계정 비밀번호는 숨겨져있다는 것을 알 수 있다.

우리가 로그인을 시도할 때 실행되는 SQL query문을 보면

res = query_db(f'select * from users where userid="{userid}" and userpassword="{userpassword}"')

users 테이블에 입력한 userid와 userpassword가 일치하는 데이터를 조회한다.

현재 우리는 admin 계정의 password를 모르는 상태이기 때문에 주어진 ID admin으로만 로그인을 해야 한다. 따라서 query문을 이렇게 바꿔주면

select * from users where userid="admin"-- " and userpassword="{userpassword}"'

ID 검색 조건만을 처리하도록 두고 뒤의 userpassword 조회 부분은 주석처리가 돼서 WHERE 절이 참이 된다.

따라서 로그인을 할 떄 저러한 query문을 날려줄 수 있게 userid를 admin"--으로 입력해봤더니

로그인에 성공했다!!

Lord of SQL Gremlin

문제링크 Lord of SQL Gremlin

풀이

query문을 이용해야 겠다는 생각이 들어 처음에 id=' or 1=1을 뒤에 추가해줬는데 아무런 변화가 없었다.

id=' or 1=1 뒷 부분을 주석 처리를 해 주지 않은게 생각나 다시 주석처리를 하고 id=' or 1=1--을 추가해주었는데
여전히 실패했다.

그래서 이번에는 %23(MySQL의 주석)을 추가해서 id=' or 1=1%23이라고 작성했다. 그랬더니 성공했다!!

+) 주석처리를 해주지 않으려면

id가 아니라 pw부분에 pw=' or '1'='1을 추가해주면 된다.

QnA)
마지막 문제에서 왜 --은 주석처리가 안 될까??

0개의 댓글

관련 채용 정보