wargame.kr의 11번째 문제인 tmitter다.
admin의 아이디로 로그인하라는 설명과 함께 어떤 테이블의 구조를 보여주고 있다. 간단한 32글자짜리 문자열 id, ps 컬럼이 있는 테이블인데 문제풀이의 중요한 힌트가 될지도 모른다. 일단 Start 버튼을 눌러 문제를 확인해보자.
유사 트위터 서비스인지 트'미'터의 로고와 함께 로그인, 회원가입 버튼이 두 개 놓여있다. HTML 코드에서는 특별한 점은 없었으며 현재는 아이디를 모르기 때문에 회원가입하러 Sign Up 버튼을 눌러 join.php로 이동했다.
간단한 입력폼 두 개와 join 버튼이 놓여 있다. 특이한 점은 ID 필드에는 최소 4글자, PS 필드에는 최소 7글자 제한이 걸려있으며 두 입력폼 모두 최대 32글자 제한이 걸려있는데 최소 글자 제한은 chk() 함수를 이용하여 확인하고 최대 글자 제한은 HTML 태그의 maxlength로 제한하고 있다. 둘 다 어쨌든 쉽게 우회할 수 있는 부분이긴 한데 코드를 좀 더 살펴보니 아래쪽에 다음과 같은 힌트가 있었다.
힌트에서는 'admin' 계정으로 회원가입하라고 지시하고 있다. 물론 당연히 쉽게 가입되지는 않을테지만 일단 어떤 결과가 나오는지 시도해보면 다음과 같이 이미 존재하는 계정이라고 생성이 거부된다.
이때 ID 필드에 입력된 값이 그대로 거부 메시지로 출력되는 것 같은데 그렇다면 이런저런 특수문자도 같이 넣어서 어떻게 필터링되는지 확인해볼 수 있다.
이런저런 값을 넣어본 결과 따옴표, 쌍따옴표, 역슬래시 등이 필터링되는 것으로 보아 mysql_real_escape_string() 함수가 적용되고 있다고 추측할 수 있다.
이 함수도 우회할 수 있는 취약점이 몇 개 있긴 하지만 대부분 특정 상황에서 발생하는 것이기 때문에 이 문제에서는 이 함수 자체를 우회한다기보다는 다른 방법을 사용해봐야 할 것 같다. HTML 코드를 살펴봐도 별다른 정보를 얻을 수 없기 때문에 주어진 상황에서 무엇이 문제 해결의 단서가 될 지 추측해봐야 하는데 제일 먼저 생각나는 것은 문제 시작 전 힌트에서 알려준 테이블 구조다.
create table tmitter_user(
idx int auto_increment primary key,
id char(32),
ps char(32)
);
단순히 admin의 아이디로 로그인하라는 힌트 말고 테이블 구조를 알려준 이유가 분명히 있을 것 같은데, 다시 생각해보니 32란 값을 어디서 본 것 같다. 어디였을까?
그것은 바로 회원가입 폼에서 제한하고 있는 입력값의 최대 길이와 동일한 값이었다. 물론 당연히 데이터베이스 테이블에서 받아들일 수 있는 최대 길이가 32글자이므로 이처럼 제한하는 것은 당연한데 만약 32글자가 넘는 값을 입력하면 어떻게 될까? ID 필드에서 maxlength 속성을 지우고 한번 가입해보았다.
'1234567890123456789012345678901234567890' 이라는 40자리 문자를 아이디로 입력했음에도 불구하고 로그인할때는 32글자인 '12345678901234567890123456789012'로 로그인해야 했다. 로그인 입력폼에도 32글자 제한이 걸려있었으며 이를 초과해서 40자리 문자를 입력하면 로그인이 실패하는 것으로 보아 데이터베이스에 아이디, 비밀번호 입력값이 삽입될 때 32글자로 잘리는 것 같다.
백엔드 단에서 자르는 것인지 데이터베이스에 INSERT할 때 자동으로 잘리는 것인지 확인해보기 위해 직접 입력해보면 해당 입력값이 너무 길다고 SQL 쿼리가 실패하는 것을 볼 수 있다. SELECT 문에서는 아무런 문제가 없기 때문에 아마 백엔드에서 입력을 32자리까지 자르고 데이터베이스에 삽입하는 것 같은데 이를 어떻게 응용할 수 있을까? 그 해답은 역시 테이블 구조에 있었다.
예전에 php와 mysql을 이용해서 웹사이트를 만드는 수업을 들었을 때는 항상 VARCHAR를 사용했다. 그때는 단순히 가변 길이라는 특성 하나만 보고 별 생각없이 사용했는데 이번 문제에서 제공하는 테이블에서는 CHAR 자료형을 사용하고 있다. 그래서 데이터 비교 시 CHAR 자료형의 특성을 찾아본 결과 위와 같은 자료를 찾을 수 있었는데 간단히 얘기하면 CHAR 자료형은 고정 길이인 특성상 만약 입력된 데이터가 최대 길이가 미치지 못할 경우 데이터가 저장될 때 남는 공간을 오른쪽 여백으로 채워서 패딩한다는 특성이 있다고 한다.
즉 이 트미터의 유저 테이블처럼 CHAR(32)인 자료형이 있다고 하면 'admin'을 입력했을때 32글자를 맞추기 위해 'admin '같은 값으로 저장된다는 것이다. 그렇다면 이 admin 계정도 결국엔 비교할 때 32글자로 공백 패딩될 테니 우리도 이 값을 입력하면 되지 않을까? 하지만 아까 시도했을때 이미 계정이 존재한다는 메시지가 나타났다. 그렇다면 어떻게 이를 우회하여 새로운 admin 계정으로 가입할 수 있을까? 이에 대한 해답은 위에서 시도해본 SELECT 문에 있었다.
아까 테이블을 만들고 32글자가 넘는 값을 테이블에 삽입하려고 시도하면서 SELECT로 32글자가 넘는 값을 조회해보았다. 당연히 SELECT에서는 단순 조회기 때문에 문제가 없었는데 그렇다면 생각해 볼 것은 이 트미터 서비스의 백엔드단에서 조회를 위해 넘기는 아이디값을 32글자로 잘라서 조회할까였다. 확인 결과 그건 아닌 것을 알 수 있었다.
ID 필드를 32글자 넘게 입력하고 비밀번호를 각각 다른 문자열로 입력했을 때 동일한 아이디의 계정이 두 개 생성되어 따로따로 로그인할 수 있는 것을 알 수 있었다. 즉 중복 조회에 사용되는 아이디와 실제로 데이터베이스에 삽입되는 아이디는 다른 것이다.
그럼 이를 응용할 수 있는 방법은 무엇일까? 그것은 바로 32글자를 넘는 공백 패딩된 'admin' 아이디를 직접 입력하는 것이다. 하지만 'admin '처럼 공백만 입력하면 백엔드단에서 trim()으로 처리되는지 admin으로 변환되어 사용되기 때문에 실패한다. 그래서 맨 마지막에 숫자 1이든 어떤 글자든 넣어서 불필요한 공백이 아닌 문자열 내부의 공백으로 인식되어 trim되지 않도록 넘겨주면 된다.
이렇게 넘겨주면 비교 시에는 'admin 1' 처럼 비교되어 당연히 32글자를 넘는 값은 테이블에 존재하지 않을 테니 중복된 아이디가 아니라고 인식되고 테이블에 삽입할 때는 32글자로 잘라서 맨 끝의 1과 몇개의 공백은 사라지고 'admin' 문자열과 27개의 공백 패딩으로 이루어진 값으로 변환되어 삽입된다. 그래서 우리가 admin 계정으로 로그인을 시도할 때 아까 본 CHAR 자료형 특성상 우리의 'admin' 입력값은 'admin' 문자열과 27개의 공백 패딩으로 이루어진 값으로 변환되어 아까 삽입된 문자열과 일치, admin 계정으로 로그인할 수 있게 된다.
물론 실제 관리자 계정은 아닌지 프로필 사진이 다르긴 하다. 하지만 어쨌든 admin의 아이디로 로그인했기 때문에 플래그를 얻을 수 있었다.
이번 문제는 정말 고민을 많이 했던게 다른 문제들처럼 view-source가 있는 것도 아니고 어떤 특별한 우회 방법도 보이지 않았기 때문에 다른 문제를 먼저 풀 때까지도 못 풀었던 문제다. 하지만 테이블의 자료형인 CHAR와 VARCHAR의 차이점을 알고 사용자가 입력한 값이 어떻게 처리되는지 추측하면서 단서를 찾을 수 있었던 것 같다. SQL Injection 문제는 아니지만 역시 데이터베이스에 대한 지식이 부족하다는 것을 느낄 수 있게 해준 문제였다.
'챌린지 > Wargame.kr' 카테고리의 다른 글
bughela - img recovery (0) | 2020.12.28 |
---|---|
bughela - type confusion (0) | 2020.12.27 |
bughela - md5_compare (0) | 2020.12.22 |
bughela - DB is really GOOD (0) | 2020.12.22 |
bughela - md5 password (0) | 2020.12.22 |