开源 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 协议》,转载必须注明作者和本文链接
你还差得远呐!
讨论数量: 1

太深奥了,看不懂,新手求个GO DEMO项目地址

3年前 评论
程序员的猫 (楼主) 3年前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!