Blind SQL Injection
개발의 편의를 위해 에러메시지를 띄우는 것은 Error-based SQL Injection에 취약하다는 것을 저번 포스트에서 알 수 있었다.
하지만 에러메시지를 없앤다고 해서 SQLi로부터 자유로워지는 것은 아니다.
로그인 페이지의 경우, 로그인이 성공적으로 됐다면 어떤 페이지로 리다이렉션이 되거나 하고, 실패했다면 실패했다는 문구가 뜨는 경우가 대부분이다.
혹은, ID의 중복 체크를 하는 경우에도 실패 / 성공 여부는 사용자에게 알려주어야만 한다.
이러한 필연적인 기능을 Blind SQL Injection에서 공격점으로 잡는다.
SQLi가 가능한 곳에서 공격자는 중복체크할 ID 이외 하나의 무언가를 `and`로 묶어 더 질문한다.
normaltic이라는 ID는 사용 가능해? and DB명의 첫글자는 a야?
DBMS(DB Management System)는 이러한 질문에 의심없이 답해준다.
`normaltic`이라는 ID가 사용가능하고, DB의 이름 첫 글자가 a라면 해당 질문은 `True`가 된다.
즉, `사용가능한 ID입니다`라는 답변을 받을 수 있는 것이다. 만약, DB 이름의 첫글자가 a가 아니라면 `사용가능하지 않은 ID`라는 답변을 받게 될 것이다.
`사용가능한 ID` / `사용가능하지 않은 ID`라는 답변을 기준으로 공격자는 DB명의 첫글자부터 마지막 글자까지 전부 알아낼 수 있다. 이러한 질문을 공격 input으로 표현하면 다음과 같다.
normaltic' and (SELECT SUBSTR(Database(),1,1))='a
`SUBSTR(str, pos, len)` 함수는 문자열(str)에서 pos 위치부터 len개의 글자를 뽑아내는 함수다. `Database` 함수를 통해 얻어낸 DB명 문자열로부터 첫번째 글자만 따오겠다는 것이다.
하지만 이렇게 글자를 하나씩 물어보는 것은 굉장히 비효율적이다. 시간복잡도가 O(N²)다.
Binary Search
물론 하나씩 물어보는 방법으로도 언젠가는 정보를 다 얻어낼 수 있겠지만, 조금 더 빠른 방법을 택한다면 효율을 높일 수 있다.
그 방법으로 적합한 것이 이진 탐색(Binary Search)다.
1부터 100까지의 숫자 중 상대가 고른 하나의 숫자를 맞추는 숫자 맞추기 게임을 예로 들자면, 다음과 같은 질문을 할 수 있다.
그 숫자는 50보다 큰가요?
맞다면 그 다음은 75보다 큰지, 또 맞다면 88보다 큰지 묻게 될 것이다. 이진 탐색 또한 이러한 방식으로 찾고자 하는 것을 탐색하는 방식이다. 이러한 방식을 채택하게 된다면 시간복잡도가 O(NlogN)이 될 것이다.
이렇게 시간복잡도가 O(N²)일 때와 O(NlogN)일 때는 차이가 크다.
ASCII
컴퓨터 세상에서는 문자를 숫자로 표현해야 한다. 그 표준이 되어주는 것이 바로 `ASCII 코드`다. 예를 들어 A는 10진수로 65가 되고, d는 100이 된다. SQL에서도 문자를 숫자로 치환해줄 수 있다.
`ASCII` 함수를 사용하면 된다.
SELECT ASCII('A')
이제 문자를 이렇게 숫자로 치환함으로써 이진 탐색이 가능해진다. 숫자, 알파벳, 특수문자는 ASCII에서 32부터 126까지에 해당한다. 그럼 그 중간값인 79를 기준으로 Blind SQL Injection을 진행할 수 있다.
SELECT (SELECT ASCII(SUBSTR(Database(),1,1)))>79
만약 DB명의 첫글자가 79(대문자 O)보다 크다면 이렇게 1(True)이 된다. 이 원리를 이 글 앞부분에 나온 질문에 적용하면 된다.
normaltic' and (SELECT ASCII(SUBSTR(Database(),1,1)))>79 #
만약 `사용가능한 ID`가 나온다면, 79와 126의 사이, 103으로 다시 물어보고, 맞다면 또다시 103과 126의 사이... 이런 식으로 반복한다.
이렇게 반복하다보면 97보다 크고 98보다는 크지 않다는 것을 알게 된다. 그 말인즉, 첫글자는 98(소문자 b)이라는 것이다.
이를 두번째 글자에, 세번째 글자에, 마지막 글자에까지 적용한다면 최종적으로 DB의 이름을 알아낼 수 있다.
blindSqli
DB의 이름을 알아냈다면, 그 다음 순서로 테이블의 이름, 칼럼의 이름, Flag를 알아내면 된다.
해왔던대로 똑같이 알아내면 된다.
normaltic' and (SELECT ASCII(SUBSTR(table_name,1,1)) FROM information_schema.tables WHERE table_schema='blindSqli' LIMIT 0,1)>79 #
...
flagTable
member
plusFlag_Table
이렇게 세 개의 테이블이 존재한다.
normaltic' and (SELECT ASCII(SUBSTR(column_name,1,1)) FROM information_schema.columns WHERE table_name='flagTable' LIMIT 0,1)>79 #
이 방식을 통해 `flagTable`이라는 테이블에는
idx
flag
이렇게 두 개의 칼럼이 존재한다는 것을 알 수 있다.
그럼 마지막으로 `flag` 칼럼을 조회해 Flag를 알아내면 된다.
normaltic' and (SELECT ASCII(SUBSTR(flag,1,1)) FROM flagTable LIMIT 0,1)>79 #
segfault{Flag 모자이크}
'Study > with normaltic' 카테고리의 다른 글
XSS - Cross Site Scripting (0) | 2025.06.22 |
---|---|
SegFault CTF - 7주차 (1) | 2025.05.28 |
Error-based SQL Injection (0) | 2025.05.25 |
SegFault CTF - 6주차 (0) | 2025.05.14 |
SegFault CTF - 5주차 - 번외 (0) | 2025.05.07 |