본문 바로가기
정보보안(웹해킹)/SQLInjection

SQL injection 과 id = 0

by 끊임없는정진 2022. 10. 26.

※ 영문사이트를 참고해서 오타, 오역 및 잘못된 표현이 있을 수도 있습니다. 피드백 남겨주시면 성실히 수정하도록 하겠습니다.

 

 

▶ 의문점의 발생

 

MySQL환경에서 SQL에 대한 공부를 해나가던 중, 예상치 못하게 id=0을 넣으니 지정된 row의 전체 데이터를 출력하는 것을 확인할 수가 있었다.

 

id값에 0을 대입해버리니 바로 모든 행을 출력해버린다.

 

또한, 왜 이렇게 되는지 확인하기 위해서 jj_password가 어떤 값을 출력하는지 찾은 결과 다음과 같았다.

 

1을 출력하는 것을 확인할 수 있다.

 

 

▶My SQL에서의 Type Conversion

 

MySQL의 데이터타입 중에는 문자열 타입의 VARCHAR이 있다(MySQL의 데이터 타입 종류에 대해서는 따로 정리하는 시간을 갖도록 하겠다.). 많은 경우에 VARCHAR을 ID,PW,닉네임 등의 정보저장 데이터로 활용하곤 하는데, 가끔 예상치 못하게 명령어가 서로 다른 데이터타입을 비교하는 명령어를 내릴 때가 있다. 그럴 경우를 대비해서 MySQL은 Type Conversion이라는 기능을 갖고 있다. (이처럼 데이터 형식이 다른 개체의 데이터 형식으로 변환되는 것을 '묵시적 변환'이라고 한다. 반대로 CAST, CONVERT 함수를 이용해서 변환하는 방식은 '명시적 변환'이라고도 한다.)

Type Conversion은 조작(operands)에 있어서 서로 다른 데이터 타입을 사용할 때, 서로 비교가능하게 전환하는 기능이다. 예를 들면, 다음과 같은 경우를 들 수가 있겠다.

 

명령어 결과값
SELECT 1+'1'; 2
SELECT CONCAT(2, 'test'); '2 test'
SELECT 38.8, CAST(38.8 AS CHAR); 38.8, '38.8'
SELECT 38.8, CONCAT(38.8); 38.8, '38.8'

 

Type Conversion에도 규칙이 있다. 공식적으로 밝힌 그 규칙은 다음과 같다.

(※출처: https://dev.mysql.com/doc/refman/8.0/en/type-conversion.html)

규칙 1. 비교대상이 둘다 NULL일 경우, 결과값은 NULL을 출력한다. 다만, 비교연산자 <=>는 참을 출력한다.

규칙 2. 비교대상이 둘다 String일 경우, String으로 비교한다.

규칙 3. 비교대상이 둘다 integer일 경우, integer로 비교한다.

규칙 4. 16진수값은 number와 비교되지 않는 한, binary string으로 취급한다.

규칙 5. TIMESTAMP 또는 DATETIME열과 다른 상수와 비교가 되면 상수가 타임스탬프로 변환된다. ODBC(쉽게 말해, MS사의 데이터베이스 접근 소프트웨어 기준)와 호환성이 좋게하기 위함이다. 다만, IN() 안의 상수에는 적용되지 않는다.

한줄의(a single-row) 서브쿼리문의 DATETIME값에 해당하는 값은 integer값을 출력한다. 만일 DATE값으로 비교하고 싶다면 CAST를 사용해서 명시적으로 변환해야 한다.

규칙 6. 하나가 decimal값이면 비교는 다른 인수에 따라 달라진다. 다른 인수가 decimal 또는 integer 값이라면 decimal값으로 비교를 수행하고 floating-point값이면 floating-point값으로 비교를 수행한다.

규칙 7. 다른 경우에는, floating-point으로 비교한다. 예를 들어, string과 numeric값을 비교한다면, floating-point 숫자간의 비교로 수행한다.

 

만약 내가 했던 것과 같이 varchar타입인 id와 numeric값에 해당하는 0을 비교해버린다면, 규칙7에 따라 floating-point로 비교를 수행해야 하는데 string값을 numeric값으로 수정하면 0으로 바뀌는 부분이 있고, 그렇지 않은 부분이 있다. 예를 들면, 12abc5의 경우 12로 변환하고, 5.6xyz0의 경우 5.6으로 변환한다. 앞의 숫자들은(leading digits) 출력하는데, 나머지는 무시해버린다. 그리고 나서 다시 정리해보면 왜 저렇게 출력되는지 이해가 된다.

 

우선, varchar로 설정된 id값은 전부 알파벳으로 시작하고, 그리고 varchar의 값은 leading digits만 숫자로 변환하고 나머지는 무시해버린다. 만약, leading digits가 없다면 0을 출력한다. 마지막으로 둘을 floating-point로 비교한다. 결국 내가 입력한 jj_id = 0 에 해당되는 값을 찾는 것이고(모든 아이디가 문자로 시작해서 모든 아이디를 출력했다.), 0 = 0 을 floating-point로 연산하는 과정이 되어버려서 TRUE, 즉, 1을 출력하게 된다.

(※출처 : https://stackoverflow.com/questions/52677765/unexpected-result-using-select-where-id-0-on-varchar-id-in-mysql

https://stackoverflow.com/questions/60662191/mysql-select-on-varchar-column-with-0-zero-as-criteria-returns-all-rows

)

 

 

▶SQL Injection과 id = 0

 

이 과정을 SQL Injection에 활용할 수도 있다. 다음과 같은 SELECT문을 생각해보자

 

SELECT * FROM member WHERE jj_id = '$jj_id' and jj_password = '$jj_password'

 

앞서 말했다시피, id = 0 을 출력하고 뒤쪽은 주석처리를 해버리면 된다. 그 전에 보기 전에 내가 전에 쓴 'SQL 연산자 정리' 글을 보고 오는 것을 추천한다. 이미 해당글에서 비트 연산과 연산자 수행 순서는 설명한 뒤니깐 하지 않도록 하겠다. 

연산자를 둘러보고 왔다면 관계연산자 = 보다 앞서는 기호 중에서 활용할 만한 문자를 찾아보자. 대표적으로 이항 연산자, 비트 연산자 두개가 있다. 

 

SELECT * FROM member WHERE jj_id = ''>>'1'# and jj_password = '$jj_password' (비트연산자 활용)
SELECT * FROM member WHERE jj_id = ''%'1'# and jj_password = '$jj_password' (이항연산자 활용)

 

''는 연산에 들어가면 0으로 취급하여 결과를 출력한다(사실 어떠한 문자를 넣어도 숫자로 시작하지 않으면 0을 출력하기 때문에 상관없다. 위에서 설명한 varchar 와 numeric비교 참조). 결국 0에 대한 해당 비트연산은 한칸을 옮긴다해도 0을 출력할 수 밖에 없고(id = 0), 문자로 시작하는 모든 사람들의 정보를 출력해버리게 된다. 

 

이항연산자의 경우, 1'-'1'# / '+''# / 1'*''# / '%'1'# 와 같이 수많은 베리에이션을 만들 수도 있고,

비트연산자의 경우도 역시, '&''# / '^''# / '>>'1'# / '<<'1'# 와 같이 수많은 베리에이션을 만들 수 있다.

 

'정보보안(웹해킹) > SQLInjection' 카테고리의 다른 글

SQL injection in 'order by' clause  (0) 2022.12.09
SQL Injection WAF 우회기법  (0) 2022.11.21
[php] Error-based SQLi 연습세팅 & 대응방안  (0) 2022.11.04
Error Based SQL Injection  (0) 2022.11.03
Union SQL Injection  (1) 2022.11.03

댓글