如何进行POC的快速编写&现有POC叠加更改&批量扫描[上]
2023-1-7 23:2:3 Author: 猫哥的秋刀鱼回忆录(查看原文) 阅读量:9 收藏

免责声明

请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,猫哥的秋刀鱼回忆录/Xk Team及文章作者不为此承担任何责任。

前言

本文是献给小白的,我们在这一章内容中主要为大家介绍如何快速用Python语言编写一个简单的漏洞检测工具,下一张我们会进行叠加多线程,并且如何实现现有POC加上批量扫描。众所周知快速的使用Python进行POC&EXP的编写,是一个成熟大的红队人员的基本功底,但是随着Go语言的兴起,单体扫描器产品就逐渐有了趋向基于Go语言的开发势态,但是这并不是说能够直接取代Python在便捷/可靠/简易的牢固地位,在谈及如此,我们是要面对众多思路叠加的编写之苦的,如何在高效率的情况下,对一个已知的漏洞不借助他人的工具来快速的进行扫描检测和利用,这件事已经不在乎说是脚本不脚本小子,而是一个合格的基本象征,就是说我可以写的烂,写的菜,代码优化度低,但是我不能不会写。如何去熟练的掌握?我有以下几个小小的建议:

  1. 菜鸟教程Python教程速通浏览,查漏补缺(基于你在有C/C++/面向对象的基础)
  2. PeiQi漏洞文库/漏洞库挑选漏洞当作实践目标,挑选不同的漏洞进行编写测试
  3. 把大佬写的代码好好拜读拜读,读一下SQLmap的源代码
  4. 妈的请遵循网络安全法!本文只是讲解一些思路!

POC测试对象

我们今天拿:Kyan 网络监控设备 hosts 账号密码泄露漏洞来做一个示例,因为闲客师傅前两天估计手痒写了一个,附上Github地址:https://github.com/Axianke/KyanRCE,FOFA:title="platform - Login",这个漏洞无非就是在URL后面加上hosts,就可以看到泄露的后台账户密码,是不是很简单呢?

POC
http://xxx.xxx.xxx.xxx/hosts

单体检测的“壳”

众所周知,只要是龟,都tm有壳,只要是单体检测的POC,那也有一套范式,或者说就是一个基本的壳子,主要来节省大把大把敲击键盘的时间,壳里面有什么?

  1. 提供功能的模块(内置/外部引用)
  2. 自定义的函数
  3. 执行的开口

一说模块

Python 内置的 requests 模块,主要用来发送 HTTP 请求,而 argparse 模块是 Python 内置的用于命令项选项与参数解析的模块,argparse 模块可以让人轻松编写用户友好的命令行接口,能够帮助程序员为模型定义参数。我们可以通过它创建一个带命令行界面的程序,传递不同的参数,执行不同的操作,几乎在所有POC中我们外部传参调用的就是这个 argparse 模块,非常的方便,要比原生的 input 好用的多的多

首先我们如何用reques 模块发送一个GET请求呢?因为无论哪种主动检测都逃不脱数据的请求

好,通过这样我们就可以实现对百度的GET请求,并可以在其中做一些附加,比说如通过回显进行一些判断啊,之类的就可以简单的实现GET类请求漏洞检测,如果得提交一段数据那就是POST,是不是查一下教程不就是会写了吗?

二说其他

对于POC的主题就是把模块的功能写在函数中,让它去执行,我相信这很好理解,无非就是判断判断指纹是还是不是?内容有还是没有?回显还是不回显,这只是一个简单的判断问题而已

既然我们单体检测脚本test.py就是如此,我们就得思考一下:比如现在有很多个POC,有的可能是你自己写的,有的也可能是别人写的,每个POC如果都要使用嵌入新代码的方式来进行扫描,是不是很麻烦?因为你得写很多代码,大量能复用但是被你写重复的代码。所以这里最优的方法就是用框架集成POC,然后POC统一输入输出。所以我们还得写一个外部套壳的脚本 main.py对吧

外部集成的“壳”

首先如果是小脚本就没必要很规范,直接干就完事,但是如果不想改别人的代码,怕出BUG,所以采用外部调用的方法会更加的稳妥,简单讲一下如何实现。

进程的创建与管理

subprocess是python内置的模块,这个模块中的Popen可以查看用户输入的命令行是否存在,如果存在,把内容写入到stdout管道中,如果不存在,把信息写入到stderr管道,需要注意的是,这个模块的返回结果只能让开发者看一次,如果想多次查看,需要在第一次输出的时候,把所有信息写入到变量中。

线程池的引用与使用

我们知道系统处理任务时,需要为每个请求创建和销毁对象。当有大量并发任务需要处理时,再使用传统的多线程就会造成大量的资源创建销毁导致服务器效率的下降。这时候,线程池就派上用场了。线程池技术为线程创建、销毁的开销问题和系统资源不足问题提供了很好的解决方案。线程池的基本实现方法: (1)线程池管理器。创建并维护线程池,根据需要调整池的大小,并监控线程泄漏现象。 (2)工作线程。它是一个可以循环执行任务的线程,没有任务时处于 Wait 状态,新任务到达时可被唤醒。 (3)任务队列。它提供一种缓冲机制,用以临时存放待处理的任务,同时作为并发线程的 monitor 对象。 (4)任务接口。它是每个任务必须实现的接口,工作线程通过该接口调度任务的执行。 简而言之:就是把并发执行的任务传递给一个线程池,来替代为每个并发执行的任务都启动一个新的线程。只要池里有空闲的线程,任务就会分配给一个线程执行。

pool = ThreadPool(poolsize)
requests = makeRequests(some_callable,list_of_args,callback)
[pool.putRequest(req) for req in requests]
pool.wait()

第一行的意思是创建一个可存放poolsize个数目的线程的线程池。第二行的意思是调用makeRequests创建请求。 some_callable是需要开启多线程处理的函数,list_of_args是函数参数,callback是可选参数回调,默认是无。第三行的意思是把运行多线程的函数放入线程池中。最后一行的意思是等待所有的线程完成工作后退出。

所以我们这样写:

好了,到这里我们基本算是有了一些比较翔实的思路,我们拿闲客的师傅的脚本来说说详细实现的一些问题

实现单体检测

模块部分

import requests
from urllib.parse import urljoin
import argparse
import re
import urllib3
urllib3.disable_warnings()

导入我们的请求模块requests,还有urllib3模块是一个第三方的网络请求模块,可以用来取消warn,并且不去验证证书。外部传参调用的这个 argparse模块re模块主要功能是通过正则表达式是用来匹配处理字符串的。urllib.parse主要用来把URL字符串拆分成URL组件,或者把URL组件拼装成URL字符串,而这里使用了从urllib.parse 模块中导入urljoin()的方法,主要就是来拼接URL。除了可以组接URL,还可以对网址进行拼接。urllib3.disable_warnings()用来屏蔽证书警告

urllib.parse 模块中urljoin()方法用途-Such As:

# 连接两个参数的url,
# 将第二个参数中缺的部分用第一个参数的补齐,如果第二个有完整的路径,则以第二个为主
 
from urllib.parse import urljoin
 
print(urljoin('www.baidu.com''?id=2#comment'))
print(urljoin('www.baidu.com''https://blog.csdn.net/python'))

结果如下:

www.baidu.com?id=2#comment
https://blog.csdn.net/python

argparse模块方法用途:

在使用命令行运行程序时,可以让程序接收命令行传过来的参数。我们在命令行窗口中使用到的shell命令,部分命令可以在后面加上参数,例如经常会使用的命令ls,当使用ls -l命令时,可以输出当前目录的文件和文件夹的详细信息,这里后面的-l就是附加的参数。如果我们想让自己的程序在命令行中运行的时候可以加上相关的参数,就用到了argparse这个python官方模块。我们常常把argparse的使用简化成下面四个步骤:

  1. import argparse
  2. parser = argparse.ArgumentParser()
  3. parser.add_argument()
  4. parser.parse_args()

上面四个步骤:首先导入该模块;然后创建一个解析对象;然后向该对象中添加你要关注的命令行参数和选项,每一个add_argument方法对应一个你要关注的参数或选项;最后调用parse_args()方法进行解析;解析成功之后即可使用。举个例子:

import argparse

parser = argparse.ArgumentParser(description="这里描述解析对象")
parser.add_argument('-f','--file',help = '参数注释写在这',default='')
parser.add_argument('-u','--url',help = '参数注释写在这',default='')
args = parser.parse_args()

def XX(xx,yy):
pass

if __name__ == '__main__':
print(args.xx,args.xx)

记住程序启动的时候如果发现 --help/-h参数,就会显示帮助信息,parser.add_argument方法中你少来-h┗|`O′|┛ 嗷~~。

主体部分

这个就是程序的入口处,我们在写之前要明白自己的这个程序有哪些功能,要怎么来实现它?在这里先做一个判断作为执行哪部分的切入点:如果通过-u参数传入的url不是空的 and -f参数传入的地址是空的,那我们就分别执行POC检测&EXP执行利用。反之,我们就进行批量目标检测。解决完一个小小的入口问题后,我们就得来分别实现这些功能。

装逼部分

字符串logo化,在线网站就有这个功能,一个脚本是否强大不重要,一定要装逼才是真的。它是直接一个print给搞出来了。

name = """
██╗  ██╗     ███████╗██╗  ██╗██████╗ 
╚██╗██╔╝     ██╔════╝╚██╗██╔╝██╔══██╗
 ╚███╔╝█████╗█████╗   ╚███╔╝ ██████╔╝
 ██╔██╗╚════╝██╔══╝   ██╔██╗ ██╔═══╝ 
██╔╝ ██╗     ███████╗██╔╝ ██╗██║     
╚═╝  ╚═╝     ╚══════╝╚═╝  ╚═╝╚═╝                                      

fofa:" title=="platform - Login" "
"""


print(name)

其实也可以把这一段写在parser = argparse.ArgumentParser(description="这里")这里,formatter_class是一个自定义帮助信息格式化输出的类,同时我们也用一个新模块 textwrap来调整换行符的位置来格式化文本。用来输出我们的示例信息:

碎片部分

header头是我们HTTP请求时或不可缺的一部分,这个可以通过个人需要去自行修改:

headers = {
    'User-Agent''Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:108.0) Gecko/20100101 Firefox/108.0',
    'Accept''text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
    'Content-Type''application/x-www-form-urlencoded',
    'Accept-Encoding''identity',
    'Accept-Language''zh-CN'
}

检测部分

我们自定义一个poc函数去实现它的检测功能,使用 try/except 语句作为一个大的框架在里面去嵌套判断语句,try/except 语句是来实现异常捕捉的,我简单说一下原理,如图所示:

  • 首先,执行 try 子句(在关键字 try 和关键字 except 之间的语句)
  • 如果没有异常发生,忽略 except 子句,try 子句执行后结束
  • 如果在执行 try 子句的过程中发生了异常,那么 try 子句余下的部分将被忽略
  • 如果异常的类型和 except 之后的名称相符,那么对应的 except 子句将被执行
  • 如果一个异常没有与任何的 except  匹配,那么这个异常将会传递给上层的 try 中

讲一下流程:首先我们会用urljoin()方法去把传入的url与\hosts进行一个拼接,然后用reques 模块发送一个GET请求并且配置一些属性,global 设定r为全局变量,然后请求会把返回值传回res.text,可能就有不太明白requests模块的人问,.text是啥意思?

requests对象的get和post方法都会返回一个Response对象,这个对象里面存的是服务器返回的所有信息,包括响应头,响应状态码等。其中返回的网页部分会存在.content和.text两个对象中。

两者区别在于,content中间存的是字节码,而text中存的是Beautifulsoup根据猜测的编码方式将content内容编码成字符串。

直接输出content,会发现前面存在b'这样的标志,这是字节字符串的标志,而text是,没有前面的b,对于纯ascii码,这两个可以说一模一样,对于其他的文字,需要正确编码才能正常显示。大部分情况建议使用.text,因为显示的是汉字,但有时会显示乱码,这时需要用.content.decode('utf-8'),中文常用utf-8和GBK,GB2312等。这样可以手工选择文字编码方式。

所以简而言之,.text是现成的字符串,.content还要编码,但是.text不是所有时候显示都正常,这是就需要用.content进行手动编码。

response.status_code返回指示状态的数字,这里注意命名的规范性,把res全部换成response。提高代码的阅读性。

如果返回的数据包中:状态码等于200并且存在关键词Password,那么输出存在漏洞,并且下一步打开exp.txt这个文件往里面写入存在漏洞的URL地址,反之:就输出不存在漏洞。

利用部分

我们自定义一个exp函数去实现它的利用功能,逻辑相仿,要登陆先凭借一个login.php,我们拿着检测出来的信息先登陆看看

我们要知道利用就得执行我们的命令,这里我不在多说,我只说编写思路,假设我们有这样的一个接口run.php可以执行命令

我们是不是可以继续拼接url与\run.php?这时候用re库中的compole去处理我们获取到的用户名和密码,re库中的findall()函数去提取文本

然后把处理完的数据传到data中,然后就用到requests模块去提交一个POST请求来登陆,具体使用查教程,不细说。

登陆是否成功已使用关键字进行判断,对了,写一个准确的POC&EXP,抓下来的数据包要好好分析,前面忘记说了。接下来使用 try/except 语句作为一个大的框架在里面去嵌套循环语句还是一样,不过你想:我们既然要执行命令,是不是要写一个永真与循环执行啊

如果执行完要退出就输入exit用break语句打断循环即可。反之如果登陆不成功就直接显示登陆失败即可。看清楚我们是用了两个异常捕捉,一个套在一个里面,不要把逻辑搞乱了。至于里面的内容,我想你应该就不用我说了,就是接受一个命令,处理完反复的POST提交,然后拿回显的数据,不要忘记我们是依托于接口\run.php,来进行RCE的!!

批量部分

这一块其实我们就是把一个存满内容的文本打开,然后for循环遍历它,把它的内容遍历出来:

readlines() 方法用于读取所有行(直到结束符 EOF)并返回列表,该列表可以由 for... in ... 结构进行处理

并且如果url地址没有加http://头的话,就给他加上去,然后执行检测就可以了。看到这里单体检测就写好了。

效果检测

我们利用FOFA+脚本来测试:利用fofa_viewer到处资产信息

帮助信息
检测&利用
批量检测

效果不错,就是师傅并没有上多线程哈哈。

总结

网络上关于POC的编写思路以及魔改,批量套壳利用明显是教程很少,或者说大部分干安全的师傅开发的功底其实是不够的,我也是个脚本小子,技术不过关,还是想帮助小白们快速写出自己的东西,这篇文章主要面向那些Python感觉自己会,漏洞也明白原理,但是POC&EXP写不出自己的小白,勉强算是一点小小的教程吧,如有代码优化或错误问题,欢迎大佬斧正。

By:IcMl0x824  脚本By:xianke


文章来源: http://mp.weixin.qq.com/s?__biz=Mzk0NjMyNDcxMg==&mid=2247498285&idx=1&sn=85e02d40150a94e9d8d7b7f8b85564dd&chksm=c3056faaf472e6bc2968828cf6b791c063e8e4d0f14993863b44c5110c773e2b22f0b1ca37ea#rd
如有侵权请联系:admin#unsafe.sh