1. 문제 설명
이번 문제도 SQLi 문제이다.
문제를 클릭해보자.
문제를 클릭하면 다음과 같은 화면을 볼 수 있었다.
이게 뭐지?
done! (0/70)??
뭔가 시도 횟수를 의미하는것 같았다.
역시나 뭐 별 다른 기능이 없어 view-source를 클릭해서 소스코드를 보았다.
역시나 소스코드가 길었다.
이럴때는 하나하나 분석을 해야한다.
$agent=trim(getenv("HTTP_USER_AGENT"));
$ip=$_SERVER['REMOTE_ADDR'];
if(preg_match("/from/i",$agent))
{
echo("<br>Access Denied!<br><br>");
echo(htmlspecialchars($agent));
exit();
}
먼저 getenv 함수를 사용해서 HTTP_USER_AGENT 값을 가져온다.
가져온 값에서 trim 함수를 이용해서 나온 값을 agent라는 변수에 담는다.
그럼 getenv 함수와 trim 함수를 알아보자.
getenv 함수는 위와과 같이 정의 되어있다.
getenv 함수는 환경 변수의 값을 가져오는 함수이다.
그리고 인자 값에 가져오고 싶은 환경 변수 값을 적어주는데
문제에서는 "HTTP_USER_AGENT"가 인자로 들어가있다.
HTTP_USER_AGENT는 웹 사이트를 접속한 컴퓨터의 웹 브라우저 정보를 의미한다.
다음은 trim 함수이다.
trim 함수는 whitespace 즉 공백을 strip 하는 함수이다.
하지만 문장 가운데 존재하는 공백이 아니라 처음 시작과 끝의 공백을 제거하는 함수이다.
예를 들어, __hello__라는 문장이 있다고 가정하자.
하지만 trim 함수를 사용하면 처음 시작 2개의 공백과 끝의 2개의 공백을 제거하여
다음과 같은 문장이 나온다.
여기까지 정리하자면 접속한 웹 브라우저의 정보를 읽어와 앞 뒤 공백을 제거한다는 의미이다.
다음 코드는 $ip=$_SERVER['REMOTE_ADDR'] 인데
REMOTE_ADDR는 웹 사이트에 접속한 컴퓨터의 ip 주소를 가져온다.
그 다음 preg_match 함수가 또 보인다. (preg_match -> 클릭)
regexp 하는 역할을 하는데 agent라는 변수에 들어간 웹 브라우저 정보에서 from을 필터링한다.
다음 코드를 보자.
$db = dbconnect();
$count_ck = mysqli_fetch_array(mysqli_query($db,"select count(id) from chall8"));
if($count_ck[0] >= 70)
{
mysqli_query($db,"delete from chall8");
}
$result = mysqli_query($db,"select id from chall8 where agent='".addslashes($_SERVER['HTTP_USER_AGENT'])."'");
$ck = mysqli_fetch_array($result);
웹 브라우저 정보에 "from"이라는 단어가 없으면 dbconnect 함수를 이용해서 db에 접속한다.
그 후 다음과 같은 쿼리로 DB에 쿼리를 날린다.
말 그대로 chall8이라는 테이블에 id의 갯수가 몇개있는지 묻는 쿼리이다.
그 후 count된 id의 갯수를 count_ck라는 변수에 넣고
count_ck가 70개 이상이면 chall8 테이블을 삭제한다.
만약에 70개 미만이면 코드가 밑으로 내려와 DB에 다음과 같은 쿼리로 묻는다.
위의 쿼리의 의미를 해석 하기 전에 addslashes 함수에 대해서 알아보자.
addslashes 함수에 대한 설명은 여기에 자세히 설명이 되어있다. (클릭)
$_SERVER['HTTP_USER_AGENT']는 유저의 브라우저 접속환경을 의미한다.
브라우저 접속환경이 왜필요할까?
서버의 입장에서는 브라우저별로 스타일을 다르게 보여지게 하거나 필요한 동작이 다를 수 있다.
무튼 다시 쿼리문을 정리하자면 다음과 같이 정리할 수 있다.
그럼 해석 해보자면 chall8이라는 테이블에서 어떤 사용자의 user_agent 값이 있다면
그 user_agent 값을 가지는 id를 보여달라는것이다.
만약 나의 id는 test이고 user_agent값이 123123이라면
chall8이라는 테이블에서 agent의 값이 123123인 id는 누구냐라는 의미이다.
쿼리상 문제가 없으면 id값(test)이 이제 ck라는 변수에 들어가게된다.
다음 코드를 보자.
if($ck)
{
echo "hi <b>".htmlentities($ck[0])."</b><p>";
if($ck[0]=="admin")
{
mysqli_query($db,"delete from chall8");
solve(8);
}
}
if(!$ck)
{
$q=mysqli_query($db,"insert into chall8(agent,ip,id) values('{$agent}','{$ip}','guest')") or die("query error");
echo("<br><br>done! ({$count_ck[0]}/70)");
}
ck 변수에 이제 값(id)이 있다면 누구인지 echo 함수를 이용해서 알려주고
만약에 admin이라면 chall8이라는 테이블을 삭제 시킨 후
solve(8)이라는 함수를 실행 시키면서 문제를 clear 할 수 있다.
하지만 ck라는 변수 즉 user_agent로 조회를 했을때 id가 없다면 insert 함수를 이용해서
chall8이라는 테이블에 agent, ip, id 값을 각각 넣어준다.
여기까지 정리하면 다음과 같다.
해당 문제는 id를 admin으로 맞춰줘야지만 solve(8) 함수를 실행시킬 수 있다.
그렇기 위해서는 admin의 user_agent 값을 알아야한다.
그 이유는 id를 뽑기 위해서는 밑의 쿼리를 사용해야하기 때문이다.
하지만 우리는 admin의 user_agent 값을 모른다.
아니면 chall8이라는 테이블에 admin이라는 id를 가진 값이 없을 수도 있다.
따라서 admin의 user_agent 값을 알아내던, chall8이라는 테이블에 id가 admin을 생성해줘야할것같다.
2. 문제 풀이
이번 문제를 풀기 위해선 2개의 가정을 생각해보았다.
- chall8이라는 테이블에 id가 admin이 있을 경우 -> admin의 user_agent 값을 알아내야함 or 우회
- chall8이라는 테이블에 id가 admin이 없을 경우 -> chall8에 id가 admin을 insert 해야함
위의 처럼 정리 해보았다.
확률상 1번일 확률은 적다고 생각했다.
왜냐하면 php 코를 봤을 때 admin의 user_agent를 알아내기 위한 방법이 현재로써는 보이지 않았다.
따라서 2번의 가정으로 문제를 풀어봤다.
그 이유는 php 코드를 봤을때 아래의 그림처럼 insert문이 존재했기 때문이다.
그리고 insert에서 사용자의 입력이나 값이 들어가는곳은 2곳이다.
agent와 ip이다.
ip는 서버에 접속한 클라이언트의 ip를 $ip=$_SERVER['REMOTE_ADDR']로 설정하기 때문에
ip에 SQLi를 하기는 어렵다고 생각하고 agent 변수에
즉, user-agent 값을 수정해서 insert문을 이용해서 id가 'guest'가 아닌 'admin'으로 수정하려고한다.
user-agent 값을 수정하기 위해서는 web-proxy를 사용해야한다.
이렇게 웹 프록시로 보면 User-Agent에 접속한 브라우저나 환경 값들이 들어가 있다.
여기에 SQLi를 수행할것이다.
먼저 SQLi가 잘 먹히는지 확인하기 위해
프록시를 사용하여 User-Agent에 guest01이라고 입력하고 서버로 전송해보자.
chall8 테이블에 guest01이라는 user-agent값이 없으니 아래의 SQLi로 insert할것이다.
그렇다면 다시 guest01를 그대로 user-agent 값에 넣어서 서버로 전송하면 어떻게 될까?
chall8에 guest01이 라는 user-agent가 insert 되었기 때문에 "hi guest"라는 문구를 볼 수가 있다.
그렇다면 이제 user-agent를 변경해서 insert가 되는것을 확인했으니
chall8에 존재하지않는 user-agent 값과 뒤의 'guest' 부분을 'admin'으로 바꿔주고
다시 insert 했을때 사용했던 user-agent 값으로 변경하여 서버로 전송하면
"hi guest"가 아닌 admin이 될것이다.
페이로드는 다음과 같다.
이렇게 SQL문을 만들 수 있다.
뒤에 #이라고 넣어준 이유는 뒤에 남아 있는 원래의 SQL문을 주석 처리하기 위함이다.
이렇게 SQL문을 만들었으면 이제 직접 프록시를 이용하여 작성한 페이로드를 넣어보자.
guest02라는 새로운 user-agent 값과 아이피 주소, 그리고 guest가 아닌 admin을 넣어준다.
그 후 정상적으로 chall8이라는 테이블에 등록되었다는 done!이라는 문구를 볼 수 있다.
다시 guest02로 요청을 한다.
요청을 하게 되면 서버에서 clear 했다는 문구를 볼 수 있다.
'웹 해킹 > Webhacking.kr' 카테고리의 다른 글
10. old-10 (0) | 2021.11.08 |
---|---|
9. old-09 (0) | 2021.11.08 |
7. old-07 (0) | 2021.11.04 |
6. old-06 (0) | 2021.11.03 |
5. old-05 (0) | 2021.11.01 |