開源 POC 框架學習 (kunpeng)

程式設計師的貓發表於2020-07-02

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 協議》,轉載必須註明作者和本文連結

你還差得遠吶!

相關文章