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