본문 바로가기
HACKING_GAME/WEB

root-Me#4 Revoked Token - JWT

by asdf12345 2020. 12. 14.

root-Me Revoked Token - JWT 풀고 싶으신가요?

Do you want to solve root-Me Revoked Token - JWT?

 

 

 

 

레고레고

Let's make it happen~!

 

 

[ root-Me#4 Revoked Token - JWT ]

Table of contents

#0 Concept

#1 Problem

#2 How to Solve

 

 

#0. Concept

JWT 란?

인증을 위해 base64로 암호화된 3개의 값으로 구성된 token이다.

+3가지 구성(Header(Data), Payload(Algorithm), 서명(Algorithm에 의해 암호화된 Data))

 

@ 란?

python에서 '@'은 decorator라고 불리며, 함수에 부가 기능(ex, jwt 인증)을 간편하게 추가하고 싶을때 이용된다.

# jwt 정상 인증 후에 protected()가 실행된다
@app.route('/web-serveur/ch63/admin', methods=['GET'])
@jwt_required
def protected():
    access_token = request.headers.get("Authorization").split()[1]
    with lock:
        if access_token in blacklist:
            return jsonify({"msg":"Token is revoked"})
        else:
            return jsonify({'Congratzzzz!!!_flag:': FLAG})
 

 

RFC4648 & RFC7515 란?

인터넷 개발에 필요한 정보를 기록한 문서이다. RFC4648, RFC7515는 base64 Padding 관련 차이점이 있다.

RFC7515는 jwt 인증 시, Base64URL Encoding을 이용한다.

RFC4648는 jwt 인증 시, Base64, Base64URL Encoding을 이용한다.

 

#1. Problem

JWT - 취소된 토큰

문제 풀이 화면으로 넘어가면, HTTP METHOD와 API 경로를 알려주는 화면을 볼 수 있다.

이 문제는 서버의 로직 일부와 문제가 함께 제공된다. 소스코드를 보니, 3개의 로직이 보인다.

1. 로그인 후 access_token을 생성

2. 생성 후 바로 blacklist list 에 추가된 access_token,,

3. flag를 얻기위해 admin API에 접근해, blacklist에 없는 정상 access_token을 이용해 jwt 인증 필요

@app.route("/web-serveur/ch63/")
def index():
    return "POST : /web-serveur/ch63/login <br>\nGET : /web-serveur/ch63/admin"
 
# Standard login endpoint
@app.route('/web-serveur/ch63/login', methods=['POST'])
def login():
    try:
        username = request.json.get('username', None)
        password = request.json.get('password', None)
    except:
        return jsonify({"msg":"""Bad request. Submit your login / pass as {"username":"admin","password":"admin"}"""}), 400
 
    if username != 'admin' or password != 'admin':
        return jsonify({"msg": "Bad username or password"}), 401
 
    access_token = create_access_token(identity=username,expires_delta=datetime.timedelta(minutes=3))
    ret = {
        'access_token': access_token,
    }
   
    with lock:
        blacklist.add(access_token)
 
    return jsonify(ret), 200
 
# Standard admin endpoint
@app.route('/web-serveur/ch63/admin', methods=['GET'])
@jwt_required
def protected():
    access_token = request.headers.get("Authorization").split()[1]
    with lock:
        if access_token in blacklist:
            return jsonify({"msg":"Token is revoked"})
        else:
            return jsonify({'Congratzzzz!!!_flag:': FLAG})
 
 
if __name__ == '__main__':
    scheduler = BackgroundScheduler()
    job = scheduler.add_job(delete_expired_tokens, 'interval', seconds=10)
    scheduler.start()
    app.run(debug=False, host='0.0.0.0', port=5000)

 

#3. How to solve

로직을 살펴보고 필요값을 얻어보자.

 

1. 로그인하여 access_token을 생성

2. 생성 후 바로 blacklist list 에 추가된 access_token

3. flag를 얻기위해 blacklist에 없는 정상 access_token을 이용해 jwt 인증

-> 토큰으로 jwt 정상 인증을 할 수 있지만, blacklist에 저장된 accesstoken값과 일치하면 안된다.

 

 

 

Base64 encoding과 Base64 URL encoding 표기의 차이를 이용해 Singing 마지막에 = 을 집어넣어 해결했다.

 

 

풀이#2 : '/' 대신 '-' 삽입하여 우회 가능

풀이#3 : 리스트에 2 원소를 넣어 우회 가능

 

 

+이용자가 적은 서버를 위주로 코딩하는 개발자와 남의 폰을 훔쳐 안드로이드 data 하위 파일에 존재하는 기한이 지난 JWT Token을 소유한다는 가정하에,,또는 XSS를 통해 기한이 지난 JWT Token획득이 가능하다는 가정하에 이용가능한 취약점이라고 생각된다. 세션 기한이 존재하나 무력화할 수 있다. 

 

'HACKING_GAME > WEB' 카테고리의 다른 글

root-Me#7 Node.js Eval  (0) 2021.04.08
root-Me#6 Graphql  (0) 2021.01.04
root-Me#5 SQL Injection - Routed  (0) 2020.12.20
root-ME#2 Java Server-side Template Injection  (0) 2020.07.24
root-me#1 Local File Inclusion - Double encoding  (0) 2020.07.13