[Writeup] Winner of Life – WITHCON 2017

소스까지 꺼낸 후 시간이 모자라서, 인증하지 못한 Winner of Life 문제를 간단하게 정리한다.

 

메인 페이지의 소스를 보면, 주석으로 leak.tar 라는 파일에 대한 힌트를 주고 있다. leak.tar 파일에는 MySQL 시스템 변수를 출력한 var_dump 파일과 크론에 대한 정보를 가지고 있는 cron_info 라는 파일이 들어가 있다. 이 두가지 파일에서 몇몇 힌트를 얻을 수 있다. 먼저 var_dump 파일에는 secure_file_priv 변수에 /var/www/html 이라는 값이 들어있고, 이는 MySQL load_file 함수를 통해 해당 디렉토리에 접근할 수 있음을 의미한다. 그리고, cron_info 파일에서는 /var/www/html/e57717591ebe1d829b3def08f229a53b.php 파일의 5분마다 실행된다는 것을 알 수 있다.

 

해당 사이트를 조금 살펴보면, statistical 페이지(statistical.php)의 no 변수가 SQL 인젝션에 취약한 것을 확인할 수 있다. 정확하게는 일부러 공격에 노출시키면서, 다수의 문자열을 제한하고 있다. 제한하고 있는 문자열은 아래와 같다. 물론, 아래의 문자열은 별도로 제공되지 않기 때문에 쿼리를 만들면서 계속 우회해 나가야 한다.

 

UNION 키워드가 사용 가능했기 때문에, 이를 통해 출력되는 컬럼수는 쉽게 추측할 수 있다. 좀 짜증나는 건, 출력되는 첫 번째 컬럼(라운드 횟수)만 화면에 출력되는데, 이게 정수만 출력되게 되어 있어서 블라인드 SQL 인젝션을 사용해야 한다는 것이었다. 아래와 같은 쿼리를 사용할 수 있었고, 블라인드 SQL 인젝션으로 모든 소스 파일을 꺼내왔다.

 

소스를 분석해 보면, LOG와 ATTEMP 테이블이 존재한다. LOG는 로또 번호를 생성하여 기록하며, ATTEMP 테이블은 사용자가 입력한 번호를 기록한다. Round 별로 LOG와 ATTEMP 테이블을 검색하여 LOG 테이블에 기록된 로또 번호와 ATTEMP 테이블에 기록된 사용자 입력 번호가 동일할 경우, history 페이지(history.php)에서 FLAG를 출력하도록 되어 있다. 즉, LOG 테이블에서 로또 번호를 꺼내야 한다. 또, 블라인드 SQL 인젝션이다.

 

그런데, 여기에 추가적인 장벽이 존재한다. LOG 테이블에는 1 라운드를 제외하고는 로또 번호가 기록되어 있지 않다. 따라서, SQL 인젝션으로 LOG 테이블에서 로또 번호를 꺼내와도 소용이 없다. 사용자가 로또 번호를 입력하는 buy_check.php를 공략해야 한다. buy_check.php는 아래와 같다.

 

살펴보면, 숫자만 입력 가능하도록 두 가지 장치를 해 두었다. 첫 번째는 변수 이름과 값이 동일(Request Body를 보면, 1=1&2=2&3=3과 같이 전달된다.)해야 하며, is_numeric 함수를 통해 정수임을 확인하고 있다. 여기에 몇몇 취약점이 있다. 먼저 변수 이름($k)과 변수 값($v)을 비교할 때 느슨한 비교(==, !=)를 쓰고 있다. 따라서, 1 == ‘1aaa’의 결과는 TRUE이다. 이것만으로도 충분하지만, is_numeric 함수 또한 NULL 이후 입력된 값은 검사하지 않는 취약점이 있다. 결국, INSERT INTO ATTEMP VALUE(‘a’,’b’),(‘c’,’d’)와 같은 SQL 쿼리로 인젝션할 수 있다. 내가 작성한 Payload는 아래와 같다.

 

이리하여, 인증도 못할 FLAG을 얻었다.