web buuctf [HCTF 2018]admin1

查看首页源代码,有注释


考虑这道题很有可能是用admin或者伪造admin进行登录,
用admin进行登录,随便填写密码进不去,发现页面有注册选项,用admin注册提示已经被注册
方法一:burp爆破
进入登录界面,用户名输admin,密码随便填,抓包对密码进行爆破;
web buuctf [HCTF 2018]admin1
文章图片

web buuctf [HCTF 2018]admin1
文章图片

爆破之后,提示429和302,可以看一下429的response,提示的是太多请求的问题,
更改options中的选项
web buuctf [HCTF 2018]admin1
文章图片

web buuctf [HCTF 2018]admin1
文章图片

控制数据量,我因为正好吃饭就调成300,很慢,大家可以自行调节,正常的返回结果就有一个payload的长度是不同的
web buuctf [HCTF 2018]admin1
文章图片

可以确定admin的密码是123,登录即可得到flag
web buuctf [HCTF 2018]admin1
文章图片


方法2:
随便注册个账户,然后登录进页面,进行最简单的工作,逐个页面查看源代码,看有无有用的信息和漏洞,在change password页面下发现注释

登录下载下来发现是网站的源码文件,打开\hctf_flask-master\app,先看里面的routes.py路由文件,逐个看一下,\hctf_flask-master\app\templates\index.html中
{% include('header.html') %} {% if current_user.is_authenticated %} Hello {{ session['name'] }} {% endif %} {% if current_user.is_authenticated and session['name'] == 'admin' %} hctf{xxxxxxxxx} {% endif %}Welcome to hctf{% include('footer.html') %}

【web buuctf [HCTF 2018]admin1】
关于flask里面的session:
flask 是非常轻量级的 Web框架 ,其 session 存储在客户端中,也就是说其实只是将相关内容进行了加密保存到session中。和服务端的session不同,服务端的session保存在服务端中,依靠客户端cookie值中的sessionId来进行识别。
也就是说可以通过更改session里面的用户名为admin,便可实现伪造admin登录
通过burp抓取session,是一串码,需要进行解密
GitHub - noraj/flask-session-cookie-manager: Flask Session Cookie Decoder/Encoder
github上有flask-session的加解密代码,
因为我的电脑上安了python3和python2,所以安装flask的时候出了点问题,pip3 install flask确保代码执行没有问题
C:\Users\86155>python flask-session-cookie-manager-master\flask_session_cookie_manager3.py decode usage: flask_session_cookie_manager3.py decode [-h] [-s ] -c flask_session_cookie_manager3.py decode: error: the following arguments are required: -c/--cookie-valueC:\Users\86155>python flask-session-cookie-manager-master\flask_session_cookie_manager3.py decode -h usage: flask_session_cookie_manager3.py decode [-h] [-s ] -c optional arguments: -h, --helpshow this help message and exit -s , --secret-key Secret key -c , --cookie-value Session cookie value

可以看到需要secret key和session cookie value两个参数
secret可以在\hctf_flask-master\app\config.py中看到
class Config(object): SECRET_KEY = os.environ.get('SECRET_KEY') or 'ckj123' SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:adsl1234@db:3306/test' SQLALCHEMY_TRACK_MODIFICATIONS = True

session value就是抓包所得,这里有一点session value的获得过程,首先,要用你已经注册的账号先登录进去,此时记录的session记录的用户名是你注册的名字,这时候再将获取的session更改,例如:登录change password,进行抓包,运行解密代码
C:\Users\86155>python flask-session-cookie-manager-master\flask_session_cookie_manager3.py decode -s "ckj123" -c ".eJw9kEGLwjAQhf_KMmcPbWwvgoddUkuFmaBEZXIRXWvbxLhQldWI_32jCx4f783Hm3eH9b6vTy2Mzv2lHsC628HoDh9bGAHrWc4rHqKcO9ZfllZVIFlkZDlHbxwFzjAcWiqnB7OaOlWajkSVoTWd0gtBfmoxLASLl86UjDm9dEYWCYXWYYlBldQ9OSSKgHY2jJmWtUuxXEYuefRVZqxLntrIJlGar_E-Z4E5WWNR71qjeQyPAXyf-v36_OPq4_sF1I3gsLSx8i3WOpB0VyXxl0WRcNQcOEVdDTGWxTC7YTl36nP8wnV-09Rv0tZPFDX_znHjowGbne-OMIDLqe5fu0GawuMPRadr_A.Yi2c_Q.litxhAuL4tDmrJkduha2BNut3I8" {'_fresh': True, '_id': b'a49af704da0c5b34186692fd76839a4beebd8fb7b826b9566bc356cfb95882ee5dd1478d0c38cb7687a324782aa950ea4cf2b8f940ead84961d19cc966c17ae6', 'csrf_token': b'186c5c66226e491830ca4b6ec651273cf3420dd8', 'image': b'naN6', 'name': 'admin', 'user_id': '11'}

name字段为你登录的账户,根据要求要改成admin, 因为我已经做完了,所以我解密完之后还是admin,我就不改了,大家做的时候注意一点,再将改好的进行加密,encode -s "ckj123" -t "secret structure",
web buuctf [HCTF 2018]admin1
文章图片

替换转发
web buuctf [HCTF 2018]admin1
文章图片

方法3:unicode编码覆盖
在查看route.py代码中
@app.route('/register', methods = ['GET', 'POST']) def register():if current_user.is_authenticated: return redirect(url_for('index'))form = RegisterForm() if request.method == 'POST': name = strlower(form.username.data) if session.get('image').lower() != form.verify_code.data.lower(): flash('Wrong verify code.') return render_template('register.html', title = 'register', form=form) if User.query.filter_by(username = name).first(): flash('The username has been registered') return redirect(url_for('register')) user = User(username=name) user.set_password(form.password.data) db.session.add(user) db.session.commit() flash('register successful') return redirect(url_for('login')) return render_template('register.html', title = 'register', form = form)@app.route('/login', methods = ['GET', 'POST']) def login(): if current_user.is_authenticated: return redirect(url_for('index'))form = LoginForm() if request.method == 'POST': name = strlower(form.username.data) session['name'] = name user = User.query.filter_by(username=name).first() if user is None or not user.check_password(form.password.data): flash('Invalid username or password') return redirect(url_for('login')) login_user(user, remember=form.remember_me.data) return redirect(url_for('index')) return render_template('login.html', title = 'login', form = form)def strlower(username): username = nodeprep.prepare(username) return username

在login和register函数中,均对传参username进行了strlower处理,查看下面的函数,是对username调用函数nodeprep.prepare进行处理,unicode转化,而nodeprep是从Twisted模块导入的,在requirements.txt文件中发现Twisted==10.2.0,与最新版本差距很大,
注册和登录各调用了一次这个函数,我们倒着进行推算,要使传进去的值服务器识别成admin,
函数的作用是将大写转化为小写
但它同时会将unicode字符?转换成A,而A再调用一次nodeprep.prepare函数会把A转换成a
也就是说我们可以使用?dmin注册登录,登陆后再想办法让服务器再执行一次nodeprep.prepare,便可以变成admin账户
方法4:条件竞争
这种是理想状态下的解题方式,但是需要大量线程同时进行,精确度也无法控制和硬件关联性很强,而且如果数量很多也会报429
因为是flask框架,并发式运行会产生漏洞,当以session name=admin进行登录,login上的session name为admin,同时进行change函数,当session name=admin ,session保留的name为admin,执行change,就会调用session里的name,这样相当于给admin改了密码,这种更改条件性太强,现实的渗透很少能够用到,就不尝试了。


    推荐阅读