开源 POC 框架学习 (kunpeng)
1. 概述
[root@localhost kunpeng]# cloc ./
166 text files.
166 unique files.
19 files ignored.
github.com/AlDanial/cloc v 1.70 T=0.44 s (344.3 files/s, 63719.0 lines/s)
-------------------------------------------------------------------------------
Language files blank comment code
-------------------------------------------------------------------------------
Go 71 642 353 18046
CSS 2 967 33 3686
HTML 12 115 60 1138
JSON 35 0 0 805
JavaScript 10 264 122 602
Markdown 5 111 0 469
Python 12 136 39 426
C/C++ Header 1 31 10 49
C 1 12 4 39
Lua 1 9 9 35
Java 1 16 26 23
YAML 1 2 0 18
Bourne Shell 1 4 0 12
-------------------------------------------------------------------------------
SUM: 153 2309 656 25348
-------------------------------------------------------------------------------
[root@localhost kunpeng]#
2. 编译
git clone https://github.com/opensec-cn/kunpeng.git
cd kunpeng
# 静态资源打包进工程的小程序
git clone https://github.com/mjibson/esc
cd esc
go build
# 打包JSON插件到项目代码中
./esc/main -include='\.json$' -o plugin/json/JSONPlugin.go -pkg jsonplugin plugin/json/
# 编译c版本(所有语言均可使用)
go build -buildmode=c-shared --ldflags="-w -s -X main.VERSION=20191218" -o kunpeng_c.so
# 编译Go专用版本(不支持win)
go build -buildmode=plugin --ldflags="-w -s -X main.VERSION=20191218" -o kunpeng_go.so
# 样例测试
python example/call_so_test.py
go run example/callsoTest.go
3. 使用方法
接口调用说明
/* 传入需检测的目标JSON,格式为:
{ "type": "web", //目标类型web或者service "netloc": "http://xxx.com", //目标地址,web为URL,service格式为123.123.123.123:22 "target": "wordpress", //目标名称,GO插件注册时使用的字符串(模糊匹配)、JSON插件的target属性(模糊匹配)、CVE编号(例:CVE-xx-xxx)、KPID(例:KP-0013)编号,决定使用哪些POC进行检测,具体查看 /doc/plguin.md "meta":{ "system": "windows", //操作系统,部分漏洞检测方法不同系统存在差异,提供给插件进行判断 "pathlist":[], //目录路径URL列表,部分插件需要此类信息,例如列目录漏洞插件 "filelist":[], //文件路径URL列表,部分插件需要此类信息,例如struts2漏洞相关插件 "passlist":[] //自定义密码字典 } // 非必填 } 返回是否存在漏洞和漏洞检测结果*/
Check(taskJSON string) string
// 获取插件列表信息
GetPlugins() string
/* 配置设置,传入配置JSON,格式为:
{ "timeout": 15, // 插件连接超时 "aider": "http://123.123.123.123:8088", // 漏洞辅助验证接口,部分漏洞无法通过回显判断是否存在漏洞,可通过辅助验证接口进行判断。python -c'import socket,base64;exec(base64.b64decode("aGlzdG9yeSA9IFtdCndlYiA9IHNvY2tldC5zb2NrZXQoc29ja2V0LkFGX0lORVQsc29ja2V0LlNPQ0tfU1RSRUFNKQp3ZWIuYmluZCgoJzAuMC4wLjAnLDgwODgpKQp3ZWIubGlzdGVuKDEwKQp3aGlsZSBUcnVlOgogICAgdHJ5OgogICAgICAgIGNvbm4sYWRkciA9IHdlYi5hY2NlcHQoKQogICAgICAgIGRhdGEgPSBjb25uLnJlY3YoNDA5NikKICAgICAgICByZXFfbGluZSA9IGRhdGEuc3BsaXQoIlxyXG4iKVswXQogICAgICAgIGFjdGlvbiA9IHJlcV9saW5lLnNwbGl0KClbMV0uc3BsaXQoJy8nKVsxXQogICAgICAgIHJhbmtfc3RyID0gcmVxX2xpbmUuc3BsaXQoKVsxXS5zcGxpdCgnLycpWzJdCiAgICAgICAgaHRtbCA9ICJORVcwMCIKICAgICAgICBpZiBhY3Rpb24gPT0gImFkZCI6CiAgICAgICAgICAgIGhpc3RvcnkuYXBwZW5kKHJhbmtfc3RyKQogICAgICAgICAgICBwcmludCAiYWRkIityYW5rX3N0cgogICAgICAgIGVsaWYgYWN0aW9uID09ICJjaGVjayI6CiAgICAgICAgICAgIHByaW50ICJjaGVjayIrcmFua19zdHIKICAgICAgICAgICAgaWYgcmFua19zdHIgaW4gaGlzdG9yeToKICAgICAgICAgICAgICAgIGh0bWw9IlZVTDAwIgogICAgICAgICAgICAgICAgaGlzdG9yeS5yZW1vdmUocmFua19zdHIpCiAgICAgICAgcmF3ID0gIkhUVFAvMS4wIDIwMCBPS1xyXG5Db250ZW50LVR5cGU6IGFwcGxpY2F0aW9uL2pzb247IGNoYXJzZXQ9dXRmLThcclxuQ29udGVudC1MZW5ndGg6ICVkXHJcbkNvbm5lY3Rpb246IGNsb3NlXHJcblxyXG4lcyIgJShsZW4oaHRtbCksaHRtbCkKICAgICAgICBjb25uLnNlbmQocmF3KQogICAgICAgIGNvbm4uY2xvc2UoKQogICAgZXhjZXB0OnBhc3M="))'在辅助验证机器上运行以上代码,填入http://IP:8088,不开启则留空。
"http_proxy": "http://123.123.123.123:1080", // HTTP代理,所有插件http请求流量将通过代理发送(需使用内置的http请求函数util.RequestDo) "pass_list": ["passtest"], // 默认密码字典,不定义则使用硬编码在代码里的小字典 "extra_plugin_path": "/tmp/plugin/" // 除已编译好的插件(Go、JSON)外,可指定额外插件目录(仅支持JSON插件),指定后程序会周期读取加载插件 }*/
SetConfig(configJSON string)
// 开启web接口,开启后可通过web接口进行调用,webapi调用格式请查看例子:/example/call_webapi_test.py
StartWebServer(bindAddr string)
// 获取当前版本 例如:20190227
GetVersion() string
4. 使用例子
下面kuepng_c.so对应的头文件
// 查看.so导出函数:
// objdump -tT kunpeng_c.so
// nm -D kunpeng_C.so
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package kunpeng */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
#endif
/* Start of preamble from import "C" comments. */
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt64 GoInt;
typedef GoUint64 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
/*
static assertion to make sure the file is being used on architecture at least with matching size of GoInt.*/
typedef char _check_for_64_bit_pointer_matching_GoInt[sizeof(void*)==64/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
extern void StartWebServer(char* p0);
extern char* Check(char* p0);
extern char* GetPlugins();
extern void SetConfig(char* p0);
extern void ShowLog();
extern char* GetVersion();
extern void StartBuffer();
extern char* GetLog(char* p0);
#ifdef __cplusplus
}
#endif
#coding:utf-8
import time
import json
from ctypes import *
# 加载动态连接库
kunpeng = cdll.LoadLibrary('./kunpeng_c.so')
# 定义出入参变量类型
kunpeng.GetPlugins.restype = c_char_p
kunpeng.Check.argtypes = [c_char_p]
kunpeng.Check.restype = c_char_p
kunpeng.SetConfig.argtypes = [c_char_p]
kunpeng.GetVersion.restype = c_char_p
# 获取插件信息
out = kunpeng.GetPlugins()
print(out)
# 修改配置
config = {
'timeout': 10, # 'aider': 'http://xxxx:8080', # 'http_proxy': 'http://xxxxx:1080', # 'pass_list':['xtest'] # 'extra_plugin_path': '/home/test/plugin/',}
kunpeng.SetConfig(json.dumps(config))
# 开启日志打印
kunpeng.ShowLog()
# 扫描目标
task = {
'type': 'web', 'netloc': 'http://www.google.cn', 'target': 'web'}
task2 = {
'type': 'service', 'netloc': '192.168.0.105:3306', 'target': 'mysql'}
out = kunpeng.Check(json.dumps(task))
print(json.loads(out))
out = kunpeng.Check(json.dumps(task2))
print(json.loads(out))
5. 项目的目录结构
.
├── config
│ └── config.go # 配置文件
├── doc
│ ├── img.png
│ └── plugin.md # 漏洞poc列表
├── example # 使用示例,包括各种语言的调用示例 C、go、java、js、lua、python
│ ├── call_so_test.c
│ ├── callsoTest.go
│ ├── call_so_test.java
│ ├── call_so_test.js
│ ├── call_so_test.lua
│ ├── call_so_test.py
│ ├── call_webapi_test.py
│ ├── nmap_kunpeng # 先使用nmap扫描开放端口,然后在调用kunpeng进行检查
│ │ ├── nmap_kunpeng.py
│ │ ├── README.md
│ │ └── requirements.txt
│ └── poc-scanner # 使用kunpeng做的一个扫描器,后面会有详细的学习记录
.....├── go.mod # go mod文件
├── go.sum
├── kunpeng_c.h # 生成的C语言头文件
├── kunpeng_c.so # go build生成的so文件
├── kunpeng_go.so
├── LICENSE
├── main.go # 程序入口文件 主要就上面实现的那几个导出函数
├── note.md
├── plugin
│ ├── go
│ ├── go.go
│ ├── json
.....
│ │ ├── init.go
│ │ ├── JSONPlugin.go
│ │ ├── phpmyadmin_deserialization.json
.....
│ ├── json.go
│ └── plugin.go
├── README.md
├── util
│ ├── aider.go
│ ├── fun.go
│ ├── log.go
│ └── net.go
└── web # 使用gin框架写的http接口,一个get,一个post请求,比较简单。
└── api.go
25 directories, 185 files
6. web/api.go
该部分比较简单,只有三个简单的请求,主要就是调用该框架实现的核心函数 GetPlugins()、Scan()、config.Set()
// StartServer 启动web服务接口
func StartServer(bindAddr string) {
router := gin.Default() // 创建路由对象 router.GET("/api/pluginList", func(c *gin.Context) { // 添加一个get请求 c.JSON(200, plugin.GetPlugins()) // 该请求以json格式返回插件相关的信息 }) router.POST("/api/check", func(c *gin.Context) { // 添加一个post请求,调用插件开始扫描 var json plugin.Task if err := c.ShouldBindJSON(&json); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } result := plugin.Scan(json) // 开始扫描 c.JSON(200, result) /// 返回扫描结果 }) router.POST("/api/config", func(c *gin.Context) { // 添加一个pos请求,设置config buf := make([]byte, 2048)
n, _ := c.Request.Body.Read(buf) config.Set(string(buf[0:n])) c.JSON(200, map[string]bool{"success": true}) })
router.Run(bindAddr) // h监听并在 bindAddr (例如: 0.0.0.0:38080 )上启动服务}
对应的python测试代码
import time
from ctypes import *
import json
import requests
kunpeng = cdll.LoadLibrary('../kunpeng_c.so') # 加载so文件
kunpeng.StartWebServer.argtypes = [c_char_p] # 说明StartWebServer函数的参数类型
kunpeng.StartWebServer("0.0.0.0:38080".encode("utf-8")) # 启动kunpneg的web服务
time.sleep(5) # 等待5s
api = 'http://127.0.0.1:38080'
# 请求接口,获取插件信息
plugin_list = requests.get(api + '/api/pluginList').json()
print(plugin_list)
# 请求接口,设置相应配置
config = {
'timeout': 10, # 'aider': 'http://xxxx:8080', # 'http_proxy': 'http://xxxxx:1080', 'pass_list':['xtest'], # 'extra_plugin_path': '/home/test/plugin/',}
requests.post(api + '/api/config',json=config)
task = {
'type': 'web', 'netloc': 'http://www.google.cn', 'target': 'web', 'meta':{ 'system': '', 'pathlist':[], 'filelist':[], 'passlist':[] }}
task2 = {
'type': 'service', 'netloc': '192.168.0.105:3306', 'target': 'mysql', 'meta':{ 'system': '', 'pathlist':[], 'filelist':[], 'passlist':[] }}
result = requests.post(api + '/api/check',json=task).json() # 开始第一个扫描任务
print(result)
result = requests.post(api + '/api/check',json=task2).json() # 开始第二个扫描任务
print(result)
7. plugin部分代码
7.1. 如何实现一个插件
kunpeng支持json插件与go插件,参见作者写的README及example目录中的现有插件。
- golang插件例子1
// 包名需定义goplugin
package goplugin
// 引入plugin
import (
"fmt" "kunpeng/plugin" "github.com/go-redis/redis")
// 定义插件结构,info,result需固定存在
type redisWeakPass struct {
info plugin.Plugin // 插件信息 result []plugin.Plugin // 漏洞结果集,可返回多个}
func init() {
// 注册插件,定义插件目标名称 plugin.Regist("redis", &redisWeakPass{})}
func (d *redisWeakPass) Init() plugin.Plugin{
d.info = plugin.Plugin{ Name: "Redis 未授权访问/弱口令", // 插件名称 Remarks: "导致敏感信息泄露,严重可导致服务器直接被入侵控制。", // 漏洞描述 Level: 0, // 漏洞等级 {0:"严重",1:"高危",2:"中危",3:"低危",4:"提示"} Type: "WEAKPASS", // 漏洞类型,自由定义 Author: "wolf", // 插件编写作者 References: plugin.References{ URL: "https://www.freebuf.com/vuls/162035.html", // 漏洞相关文章 CVE: "", // CVE编号,没有留空或不申明 KPID: "KP-0008", // kunpeng的POC编号,累加数字 }, } return d.info}
func (d *redisWeakPass) GetResult() []plugin.Plugin {
var result = d.result d.result = []plugin.Plugin{} return result}
func (d *redisWeakPass) Check(netloc string, meta plugin.TaskMeta) bool {
for _, pass := range meta.PassList { client := redis.NewClient(&redis.Options{ Addr: netloc, Password: pass, DB: 0, }) _, err := client.Ping().Result() if err == nil { client.Close() result := d.info result.Request = fmt.Sprintf("redis://%s@%s", pass, netloc) if pass == "" { result.Remarks = fmt.Sprintf("未授权访问,%s", result.Remarks) } else { result.Remarks = fmt.Sprintf("弱口令:%s,%s", pass, result.Remarks) } d.result = append(d.result, result) return true } } return false}
- JSON插件例子
{
"//": "用 Google 的方式进行注释", "//": "插件所属应用名,自由定义", "target": "wordpress", "meta":{ "//": "插件名称", "name": "WordPress example.html jQuery DomXSS", "//": "漏洞描述", "remarks": "WordPress example.html jQuery 1.7.2 存在DomXSS漏洞", "//": "漏洞等级 {0:严重,1:高危,2:中危,3:低危,4:提示}", "level": 3, "//": "漏洞类型,自由定义", "type": "XSS", "//": "插件编写作者", "author": "wolf", "references": { "//": "漏洞相关文章", "url":"https://www.seebug.org/vuldb/ssvid-89179", "//": "CVE编号,没有留空", "cve":"", "//": "kunpeng的POC编号,累加数字", "kpid":"KP-0003" } }, "request":{ "//": "漏洞请求URL", "path": "/wp-content/themes/twentyfifteen/genericons/example.html", "//": "请求POST内容,留空即为GET", "postData": "" }, "verify":{ "//": "漏洞验证类型 {string:字符串判断,regex:正则匹配,md5:文件md5}", "type": "string", "//": "漏洞验证值,与type相关联", "match": "jquery/1.7.2/jquery.min.js" }}
7.2. 插件的调用部分如何实现的
核心代码就以下6个文件,看起来比较简单的样子。
plugin
├── go
├── go.go
├── json
│ ├── docker_api.json
│ ├── init.go
│ ├── JSONPlugin.go
├── json.go
└── plugin.go
main.go
主要的8个导出函数
extern void StartWebServer(char* p0);
extern char* Check(char* p0);
extern char* GetPlugins();
extern void SetConfig(char* p0);
extern void ShowLog();
extern char* GetVersion();
extern void StartBuffer();
extern char* GetLog(char* p0);
7.2.1. StartWebServer
启动web api
//export StartWebServer
func StartWebServer(bindAddr *C.char) {
go web.StartServer(C.GoString(bindAddr))}
7.2.2. GetLog
获取log,待补充
7.2.3. GetVersion
获取Version,没啥好说的
7.2.4. SetConfig
待补充
7.2.5. GetPlugins
循环GoPlugins与JSONPlugins两个map,将插件的信息放到plugins,方便其他部分用。
// GetPlugins 获取插件信息
func GetPlugins() (plugins []map[string]interface{}) {
for name, pluginList := range GoPlugins { for _, plugin := range pluginList { info := plugin.Init() pluginMap := util.Struct2Map(info) delete(pluginMap, "request") delete(pluginMap, "response") pluginMap["target"] = name plugins = append(plugins, pluginMap) } } for name, pluginList := range JSONPlugins { for _, plugin := range pluginList { pluginMap := util.Struct2Map(plugin.Meta) delete(pluginMap, "request") delete(pluginMap, "response") pluginMap["target"] = name plugins = append(plugins, pluginMap) } } sort.Stable(pluginsSlice(plugins)) return plugins}
7.2.6. Check
待补充
大概调用流程:
Check() -> plugin.Scan() -> plugin.Run()/jsonCheck()
//export Check
func Check(task *C.char) *C.char {
util.Logger.Info(C.GoString(task)) var m plugin.Task // 插件任务m err := json.Unmarshal([]byte(C.GoString(task)), &m) if err != nil { util.Logger.Error(err.Error()) return C.CString("[]") } util.Logger.Info(m) // 打印log result := plugin.Scan(m) // 开始扫描 if len(result) == 0 { return C.CString("[]") } b, err := json.Marshal(result) if err != nil { util.Logger.Error(err.Error()) return C.CString("[]") } return C.CString(string(b)) // 返回扫描结果}
7.2.7. StartBuffer
待补充
7.2.8. ShowLog
待补充
8. example/poc-scanner的实现
目录结构如下:
├── app.py # 程序入口
├── config.py
├── extra
│ └── ugj_59eeb735187efa73c0c4e94dcada4570.json
├── poc_server.conf
├── README.md
├── requirements.txt
├── static # 静态资源文件
│ └── js
│ ├── formating.js
│ └── md5.min.js
├── templates # 静态模板文件
│ ├── edit.html
│ ├── login.html
│ ├── message.html
│ ├── navbar.html
│ ├── panel_header.html
│ ├── pluginList.html
│ ├── register.html
│ ├── result.html
│ └── scan.html
├── tools
│ ├── basetornado.py
│ ├── command.py # 程序启动代码
│ ├── handlers
│ │ ├── Index.py # 插件列表、自行添加的json插件的修改删除、 用户登录退出
│ │ ├── __init__.py
│ │ ├── Message.py
│ │ ├── Register.py # 添加新json插件
│ │ └── Scan.py # 执行扫描任务的部分
│ ├── __init__.py
│ └── so_proxy.py # 对kunpeng_c.co的封装
└── upso.sh
核心就是对Kunpeng_c.so的封装,其余部分是一个tornado写的网站。
9. 后记
年前就开始写了,然后… 有些关键的地方没写清楚,有时间在补充
水平有限,大佬多多指教
感谢opensec-cn的开源
10. QA
10.1. example例子(call_webapi_test.py)报错
Traceback (most recent call last):
File "call_webapi_test.py", line 8, in <module> kunpeng.StartWebServer("0.0.0.0:38080")ctypes.ArgumentError: argument 1: <class 'TypeError'>: wrong type
修改第8行为kunpeng.StartWebServer("0.0.0.0:38080".encode("utf-8"))
,问题解决
本作品采用《CC 协议》,转载必须注明作者和本文链接
太深奥了,看不懂,新手求个GO DEMO项目地址