[TOC]
railgun工具插件开发依赖库,由于go是编译语言,所以要实现动态代码执行,需要提前解析依赖;
我单独分离了依赖库,可用于插件编写,请提前具备go语言的基础。
目前支持poc/exp的编写。
https://github.com/lz520520/railgun-plugin-demo
根目录为modules/exps/exp_plugins
,往下每个产品单独目录,目录名以exp_
开头,每个目录下存在每个产品对应的各种漏洞exp的go文件,文件也是以exp_
开头
import (
"github.com/lz520520/railgunlib/pkg/templates/exp_templates"
"github.com/lz520520/railgunlib/pkg/register/exp_register"
"github.com/lz520520/railgunlib/pkg/templates/exp_model"
)
声明一个exp结构体,结构体名严格按照Exp_
开头,一定要首字母大写,结构体内只需要继承exp_templates.ExpTemplate
即可
接着就是exp的利用方法,因为一个漏洞可能有多种利用方式,并且可能会有多种不同payload,所以会如上图形成各种利用方法。
这里类型分为GetMsg/Cmd/Reverse/Upload四种,后跟payload的序号数字,一定要严格按照这个,否则无法解析。
方法签名如下
func (self *exp) GetMsg1(cmd string) (expResult exp_model.ExpResult)
func (self *exp) Cmd1(cmd string) (expResult exp_model.ExpResult)
func (self *exp) Reverse1(ip, port string) (expResult exp_model.ExpResult)
func (self *exp) Upload1(filename string, content string) (expUploadResult exp_model.ExpUploadResult)
PS: 返回值均为结构体,详细说明可直接查看代码注释
如一个S2-045,代码如下
func (self *Exp_VUL1) Cmd2(cmd string) (expResult exp_model.ExpResult) {
// 获取请求头配置
headers := self.GetInitExpHeaders()
// 构造payload,头部插入payload
shellPayload := `%{...}`
shellPayload = strings.Replace(shellPayload, "lz520520", cmd, 1)
headers.Set("Content-Type", shellPayload)
// 发送请求
httpresp := self.HttpGet(self.Params.Target, headers)
if httpresp.Err != nil {
expResult.Err = httpresp.Err
return
}
expResult.Result = httpresp.Body
return
}
由于结构体继承了exp_templates.ExpTemplate
,可以使用父类的方法来处理(PS:这里只是引用了其他语言里基于对象的说法,go里没有对象)
self.Params里有需要的各种请求参数。
打印结果有两种方法,将结果如下保存到相应结构体里即可
expResult.Result = httpresp.Body
或者调用logTpl.go里的方法打印也可。
// 调试信息打印,开启DEBUG方可显示
func (self *ExpTemplate) EchoDebugMsg(msg string) {}
// 信息打印
func (self *ExpTemplate) EchoInfoMsg(msg string) {}
// 不换行信息打印
func (self *ExpTemplate) EchoInfoMsgWithoutReturn(msg string) {}
// 错误信息打印
func (self *ExpTemplate) EchoErrMsg(msg string) {}
// 不换行错误信息打印
func (self *ExpTemplate) EchoErrMsgWithoutReturn(msg string) {}
漏洞编写完后,除了具体的利用过程,还有一些漏洞信息需要注册的。
编写在init方法内即可,因为go会自动运行init方法。
调用exp_register.ExpStructRegister
传入漏洞结构体的引用,以及注册信息即可。
注册信息分为三部分,漏洞基础信息、子选项扩展,payload别名。(后两个是可选)
简易的就是如下
// exp 信息栏
type ExpMsg struct {
Author string // 作者信息
Time string // 编写时间
Range string // 影响范围
ID string // CVE等编号
Describe string // 漏洞描述
Details string // 漏洞利用详细说明,每个payload都有一些操作步骤,可在此详细说明。
Payload string // 简易payload,用于提示用
}
示例
效果
上面编写exp时,每个payload都有一个序号,这个可以基于每个payload来指定他所需要设置的子选项,如果key设置成""
,则表示对所有payload都生效。
ExpSubOption说明
type ExpSubOption struct {
CmdContent string // cmd默认内容
CmdSubOptions []ExpSubOptionItem // cmd子选项自绘
UploadPath string // 上传路径默认值
UploadContent string // 上传内容默认值
UploadSubOptions []ExpSubOptionItem // 上传子选项自绘
Gadgets []string // gadget选项
UploadModes []UploadModeType // 上传类型,插件暂时不提供其他选项
}
type ExpSubOptionItem struct {
StaticText string // 标签名称
Key string // 提取选项值所需要的参数
Value interface{} // 只有两种类型,string和[]string,分别对应Edit和ComboBox
DefaultWidth int32 // 默认选项框长度,如果为0,则根据Value长度自适应。
Position int // 位置,只有0/2两个值,因为cmd选项太多,如果需要第二行,则该值设置成2,
}
举例
cmdSubOptions := []exp_model.ExpSubOptionItem{
{
StaticText: "前缀: ",
Key: "Prefix",
Value: "",
DefaultWidth: 1,
},
{
StaticText: "请求方法: ",
Key: "Method",
Value: []string{
"GET",
"POST",
},
},
{
StaticText: "加密算法: ",
Key: "algorithm",
Value: []string{
"CBC",
"GCM",
},
},
{
StaticText: "Key: ",
Key: "key",
Value: "kPH+bIxk5D2deZiIxcaaaA==",
},
{
StaticText: "Cookie关键词: ",
Key: "rememberMe",
Value: "rememberMe",
},
{
StaticText: "Key数量: ",
Key: "KeyNum",
Value: []string{
"100",
"1000",
},
},
}
效果
由于上面编写exp时,每个方法名是固定的,都是以数字编号,如果每个序号需要有别名,可使用别名
AliasMap: map[string]string{
"1": "KeyCheck",
"2": "NoDependEcho",
"3": "TomcatEcho",
"4": "AllGadgetsTest",
"5": "GadgetCheckWithSleep",
},
效果
执行效果
并且如果修改后可右键刷新,实现实时修改调整
编写方法内部通过接收者的self变量调用即可
// -----------------------HTTP请求----------------------------------
func (self *ExpTemplate) HttpGetWithoutRedirect(target string, headers lzhttp.Header) (resp lzhttp.HttpResp) {
return
}
func (self *ExpTemplate) HttpPostWithoutRedirect(target, data string, headers lzhttp.Header) (resp lzhttp.HttpResp) {
return
}
func (self *ExpTemplate) HttpPutWithoutRedirect(target, data string, headers lzhttp.Header) (resp lzhttp.HttpResp) {
return
}
func (self *ExpTemplate) HttpPostMultiWithoutRedirect(target string, postMultiParts []lzhttp.PostMultiPart, headers lzhttp.Header) (resp lzhttp.HttpResp) {
return
}
注意事项
HttpPostWithoutRedirect
: 如果header不设置Content-Type
,默认为application/x-www-form-urlencoded
HttpPostMultiWithoutRedirect
: 这个对应的是multipart/form-data
,该方法需要传入一个PostMultiPart结构体,如下
multiParts := []lzhttp.PostMultiPart{
{
FieldName: "filename",
FileName: "favicon.png",
ContentType: "",
Content: []byte(content),
},
}
// 调试信息打印,开启DEBUG方可显示
func (self *ExpTemplate) EchoDebugMsg(msg string) {}
// 信息打印
func (self *ExpTemplate) EchoInfoMsg(msg string) {}
// 不换行信息打印
func (self *ExpTemplate) EchoInfoMsgWithoutReturn(msg string) {}
// 错误信息打印
func (self *ExpTemplate) EchoErrMsg(msg string) {}
// 不换行错误信息打印
func (self *ExpTemplate) EchoErrMsgWithoutReturn(msg string) {}
// 获取设置的http头部
func (self *ExpTemplate) GetInitExpHeaders() (headers lzhttp.Header) {
return
}
// 安全获取map里的value
func (self *ExpTemplate) GetItemSafe(s map[string]string, key string) (ret string) {
return
}
// 只有当前URL没有路径/目录时,会添加URI
func (self *ExpTemplate) AddUri(target, uri string) (result string) {
return
}
// 追加URL,基于当前目录
func (self *ExpTemplate) AppendUri(target, uri string) (result string) {
return
}
// 获取hostname,如192.168.1.1:88
func (self *ExpTemplate) GetHostname(target string) (hostname string) {
return
}
// 获得当前程序所在的目录
func GetCurrentProcessFileDir() string {}
// 随机生成 MD5 HASH 值
func RandomMD5Hash() string {}
// 随机生成指定长度的字符串
func RandomHexString(size int) (ret string) {}
// 生成 `UUID` V4 字符串
func UUIDv4() string {}
// 获得当前用户的主目录
func UserHomeDir() string {}
// 是否正确的IP格式
func IsValidIP(ip string) bool {}
// 检测文件是否存在
func FileExists(filename string) bool {}
// 在时间范围内执行系统命令,并且将输出返回(stdout和stderr)
func ExecCmdWithTimeout(timeout time.Duration, arg ...string) ([]byte, error) {}
同exp文件规则。
根目录为modules/pocs/poc_plugins
,往下每个产品单独目录,目录名以poc_
开头,每个目录下存在每个产品对应的各种漏洞exp的go文件,文件也是以poc_
开头
import (
"github.com/lz520520/railgunlib/pkg/templates/poc_templates"
"github.com/lz520520/railgunlib/pkg/register/poc_register"
"github.com/lz520520/railgunlib/pkg/templates/poc_model"
)
声明一个poc结构体,结构体名严格按照Poc_
开头,一定要首字母大写,结构体内只需要继承poc_templates.PocTemplate
即可
接着就是poc的验证方法,因为一个漏洞可能有多种验证方式,存在不同payload,所以会有多个以Poc[num]
格式的方法。
方法签名如下,一定要严格按照该命名,否则无法解析。
func (self *Poc) Poc[num]() (pocResult poc_model.PocPerPayloadResult)
一个向日葵poc如下
type Poc_SunloginRCE struct {
poc_templates.PocTemplate
}
type jsonResult struct {
Code_ int `json:"__code"`
Enabled string `json:"enabled"`
VerifyString string `json:"verify_string"`
Code int `json:"code"`
}
func (self *Poc_SunloginRCE) Poc1() (pocResult poc_model.PocPerPayloadResult) {
// 默认配置
pocResult.Status = false
headers := self.GetInitPocHeaders()
resp := self.HttpGet(self.AddUri(self.Params.Target, "/cgi-bin/rpc?action=verify-haras"), headers)
if resp.Err != nil {
return
}
jr := new(jsonResult)
err := json.Unmarshal([]byte(resp.Body), jr)
if err != nil {
pocResult.Err = err
return
}
if jr.VerifyString != "" {
pocResult.Status = true
pocResult.Messages = "CID=" + jr.VerifyString
}
//"{\"success\":false,\"msg\":\"Verification failure\"}"
return
}
由于结构体继承了poc_templates.PocTemplate
,可以使用父类的方法来处理(PS:这里只是引用了其他语言里基于对象的说法,go里没有对象)
self.Params里有需要的各种请求参数。
返回值是一个结构体,当漏洞存在Status为true
type PocPerPayloadResult struct {
Status bool // 漏洞是否存在
Messages string // 漏洞信息
Err error // 错误
}
相较于exp来说,会简单许多,同样也是写在init()函数里
func init() {
registerMsg := poc_register.PocRegisterMsg{Msg: poc_model.PocMsg{
Author: "lz520520",
Time: "2022-02-16",
Range: "",
ID: "",
Describe: "向日葵高端口RCE",
},
Proto: poc_model.PocHTTP,
}
poc_register.PocStructRegister(&Poc_SunloginRCE{}, registerMsg)
}
调用poc_register.PocStructRegister
传入漏洞结构体的引用,以及注册信息即可。
注册信息目前只有量部分,漏洞基础信息,以及漏洞协议类型。
// poc 信息栏
type PocMsg struct {
Name string // 漏洞名称,如果为空,则直接用结构体名
Author string // 作者
Time string // 发布时间
Range string // 影响范围
ID string // 漏洞编号
Describe string // 漏洞描述
}
漏洞协议类型,大多是HTTP协议,其他协议主要是涉及端口,需要设置。
var (
PocHTTP PocProto = "HTTP"
PocSMB PocProto = "SMB"
PocRDP PocProto = "RDP"
)
同样修改后可右键刷新,实现实时修改调整
除此之外,在漏洞名上可右键-漏洞信息
这里不赘述了,和exp里的基本一样,直接看代码也行。
- 编写导入的库仅支持go自带库以及当前依赖库,第三方库不支持调用。
- 代码文件中禁用声明
NewExp/NewPoc
函数