前言

railgun工具插件开发依赖库,由于go是编译语言,所以要实现动态代码执行,需要提前解析依赖;

我单独分离了依赖库,可用于插件编写,请提前具备go语言的基础。

目前支持poc/exp的编写。

编写模板

https://github.com/lz520520/railgun-plugin-demo

exp编写规范

exp文件创建

根目录为modules/exps/exp_plugins,往下每个产品单独目录,目录名以exp_开头,每个目录下存在每个产品对应的各种漏洞exp的go文件,文件也是以exp_开头

image-20220503184153128

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_开头,一定要首字母大写,结构体内只需要继承exp_templates.ExpTemplate即可

image-20241008131503277

接着就是exp的利用方法,因为一个漏洞可能有多种利用方式,并且可能会有多种不同payload,所以会如上图形成各种利用方法。

均以Attack_开头,后面子方法名任意,一定要严格按照这个,否则无法解析。

方法签名如下

func (self *exp) Attack_check() (expResult exp_model.ExpResult)
func (self *exp) Attack_echocmd() (expResult exp_model.ExpResult)
func (self *exp) Attack_sleep() (expResult exp_model.ExpResult)

编写exp举例

如一个S2-045,代码如下

func (self *Exp_S2_045) Attack_cmd2() (expResult exp_model.ExpResult) {
    cmd := self.MustGetStringParam("cmd")

    // 构造payload
    headers := self.GetInitExpHeaders()
    shellPayload := `%{...}`
    shellPayload = strings.Replace(shellPayload, "lz520520", cmd, 1)
    headers["Content-Type"] = []string{shellPayload}

    // 发送请求
    httpresp := self.HttpGetWithoutRedirect(self.Params.BaseParam.Target, headers)
    if httpresp.Err != nil {
        expResult.Err = httpresp.Err.Error()
        return
    }
    expResult.Result = httpresp.Body
    return

}

由于结构体继承了exp_templates.ExpTemplate,可以使用父类的方法来处理(PS:这里只是引用了其他语言里基于对象的说法,go里没有对象)

image-20220503185333176

self.Params里有需要的各种请求参数。

image-20241008131822865

image-20241008131840656

打印结果

打印结果有两种方法,将结果如下保存到相应结构体里即可

expResult.Result = httpresp.Body

或者调用logTpl.go里的方法打印也可。

// 成功信息打印
func (self *ExpTemplate) EchoSuccessMsg(format string, a ...any) {}

// 告警信息打印
func (self *ExpTemplate) EchoWarnMsg(format string, a ...any) {}

// 普通信息打印
func (self *ExpTemplate) EchoInfoMsg(format string, a ...any) {}

// 错误信息打印
func (self *ExpTemplate) EchoErrMsg(format string, a ...any) {}

// 调试信息打印,开启DEBUG方可显示
func (self *ExpTemplate) EchoDebugMsg(format string, a ...any) {}

// 不换行信息打印
func (self *ExpTemplate) EchoInfoMsgWithoutReturn(format string, a ...any) {}

// 不换行错误信息打印
func (self *ExpTemplate) EchoErrMsgWithoutReturn(format string, a ...any) {}

漏洞信息注册

漏洞编写完后,除了具体的利用过程,还有一些漏洞信息需要注册的。(重构后,该部分优化为yml文件编写,并提供了UI进行生成,如下)

通过UI模块可以快速生成一个漏洞的基本信息,以及每个利用方法对应的参数

image-20241008132212636

点击生成即可生成yml文件

image-20241008132336431

然后将yml文件放到与漏洞代码文件相同目录,在代码里指定yml文件名即可。

image-20241008132506833

后续二次修改,直接修改yml文件即可,关于yml的所有参数结构体参考如下

const (
    TypeNormalParam     = "normal"
    TypeFileParam       = "file"
    TypeProcessParam    = "process"
    TypeLabelParam      = "label"
    TypeTextAreaParam   = "textarea"
    TypeEditParam       = "edit"
    TypeCheckBoxParam   = "checkbox"
    TypeComboboxParam   = "combobox"
    TypeCodeEditorParam = "code"
)

type ExpInfo struct {
    App  string `json:"app" yaml:"app"`
    Name string `json:"name" yaml:"name"`

    Author string `json:"author" yaml:"author"`
    PluginVersion string `json:"plugin_version" yaml:"plugin_version"`

    DisclosureDate string `json:"disclosure_date" yaml:"disclosure_date"`
    CreateDate string `json:"create_date" yaml:"create_date,omitempty"`
    UpdateDate string `json:"update_date" yaml:"update_date,omitempty"`

    Scope string `json:"scope" yaml:"scope,omitempty"`

    Description string `json:"description" yaml:"description,omitempty"`

    VulID string `json:"vul_id" yaml:"vul_id,omitempty"`

    Detail string `json:"detail" yaml:"detail,omitempty"`

    Payload string `json:"payload" yaml:"payload,omitempty"`

    Repository string `json:"repository" yaml:"repository,omitempty"`

    Reference string `json:"reference" yaml:"reference,omitempty"`

    VulType   string `json:"vul_type" yaml:"vul_type,omitempty"`
}

type ExpUIPayload struct {
    Name   string       `json:"name" yaml:"name"`
    //Check  bool         `json:"check" yaml:"check"`
    Alias  string       `json:"alias" yaml:"alias,omitempty"`
    Params []ExpUIParam `json:"params" yaml:"params,omitempty"`
    Layout ExpUILayout  `json:"layout" yaml:"layout,omitempty"`
    Rule   ExpUIRule    `json:"rule" yaml:"rule,omitempty"`
}
type ExpUIParam struct {
    Key          string            `json:"key" yaml:"key"`
    Title        string            `json:"title" yaml:"title"`
    ParamType    string            `json:"param_type" yaml:"param_type"`
    Tips         string            `json:"tips" yaml:"tips,omitempty"`
    DefaultValue interface{}       `json:"default_value" yaml:"default_value,omitempty"`
    Required     bool              `json:"required" yaml:"required,omitempty"`
    AliasMap     map[string]string `json:"alias_map" yaml:"alias_map,omitempty"`
}
type ExpUILayout struct {
    LabelSpan   int `json:"label_span" yaml:"label_span,omitempty"`
    ResultWidth int `json:"result_width" yaml:"result_width,omitempty"`
}

type ExpUIRule struct {
    FilterCheckKey string                   `json:"filter_check_key" yaml:"filter_check_key,omitempty"` // 过滤检查的key
    FilterRules    map[string]ExpFilterRule `json:"filter_rules" yaml:"filter_rules,omitempty"`         // 过滤规则
    DynamicFormMap map[string]interface{}   `json:"dynamic_form_map" yaml:"dynamic_form_map,omitempty"`
}

type ExpFilterRule struct {
    Includes []string `json:"includes" yaml:"includes"`
    Excludes []string `json:"excludes" yaml:"excludes"`
}

type ExpRegisterMsg struct {
    Info     ExpInfo        `json:"info" yaml:"info"`
    Payloads []ExpUIPayload `json:"payloads" yaml:"payloads"`
}

最终效果

编写完成后,在利用模块点击热更新重新加载所有即可更新插件。

image-20241008132816590

右键菜单可查看详情

image-20241008133021634

双击指定插件行,即可开始利用,选择不同payload会根据之前yml里配置的参数信息来动态渲染表单。

image-20241008132937440

执行效果

image-20241008133445142

常用函数

HTTP方法

编写方法内部通过接收者的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),
		},
	}

消息打印

// 成功信息打印
func (self *ExpTemplate) EchoSuccessMsg(format string, a ...any) {}

// 告警信息打印
func (self *ExpTemplate) EchoWarnMsg(format string, a ...any) {}

// 普通信息打印
func (self *ExpTemplate) EchoInfoMsg(format string, a ...any) {}

// 错误信息打印
func (self *ExpTemplate) EchoErrMsg(format string, a ...any) {}

// 调试信息打印,开启DEBUG方可显示
func (self *ExpTemplate) EchoDebugMsg(format string, a ...any) {}

// 不换行信息打印
func (self *ExpTemplate) EchoInfoMsgWithoutReturn(format string, a ...any) {}

// 不换行错误信息打印
func (self *ExpTemplate) EchoErrMsgWithoutReturn(format string, a ...any) {}

反序列化gadget辅助方法(新增)

// 添加EncodeEcho系列Gadget请求头
func (self *ExpTemplate) AddEncodeCmdHeader(srcheaders lzhttp.Header, cmd string) {
}


// 添加Echo系列Gadget请求头
func (self *ExpTemplate) AddPlainCmdHeader(srcheaders lzhttp.Header, cmd string) {
}

// 检查是否回显利用成功
func (self *ExpTemplate) CheckRespHeader(headers lzhttp.Header) bool {
	return false
}

// 解析EncodeEcho系列响应数据
func (self *ExpTemplate) ParserEncodeCmdResult(rawResult string) (parserResult string, err error) {
	return parserResult, nil
}

// 解析Echo系列响应数据
func (self *ExpTemplate) ParserPlainCmdResult(rawResult string) (parserResult string, err error) {
	return parserResult, nil
}

// 回调延迟利用函数,判断是否存在漏洞
func (self *ExpTemplate) CheckGagdetWithSleep(fun func() error, delay time.Duration) (status bool, err error) {
	return
}

yso利用链(新增)

通过以下函数传入paylod名称以及参数来实现获取yso payload。

func YsoserialPayloadGenerator(payloadType, cmd string) (payload []byte) {
	return
}

使用例子参考:exp_ysoDemo.go

目前支持的payload

推荐利用链:CommonsCollectionsK1CommonsCollectionsK2CommonsBeanutilsNoCC1CommonsBeanutilsNoCC2以及它们的扩展,其余的利用链就URLDNSJRMPClient和最新的FindClassByDNS

payload名称 传参举例 备注
URLDNS xxx.dnslog.cn dns解析
CommonsCollections1 calc 无回显
CommonsCollections2 calc 无回显
CommonsCollections3 calc 无回显
CommonsCollections4 calc 无回显
CommonsCollections5 calc 无回显
CommonsCollections6 calc 无回显
CommonsCollections10 calc 无回显
Jdk7u21 calc 无回显
Jdk8u20 calc 无回显
JRMPClient 192.168.1.1:1099
CommonsBeanutilsNoCC1TomcatEncodeEcho 调用AddEncodeCmdHeader插入命令 tomcat编码回显
CommonsBeanutilsNoCC1SpringEncodeEcho 调用AddEncodeCmdHeader插入命令 spring编码回显
CommonsBeanutilsNoCC1Sleep sleep 10秒检测漏洞
CommonsBeanutilsNoCC1SpringEcho 调用AddPlainCmdHeader插入命令 spring明文回显
CommonsBeanutilsNoCC1TomcatEcho 调用AddPlainCmdHeader插入命令 tomcat明文回显
CommonsBeanutilsNoCC1 calc 无回显
CommonsBeanutilsNoCC2TomcatEncodeEcho 调用AddEncodeCmdHeader插入命令 tomcat编码回显
CommonsBeanutilsNoCC2Sleep sleep 10秒检测漏洞
CommonsBeanutilsNoCC2SpringEncodeEcho 调用AddEncodeCmdHeader插入命令 spring编码回显
CommonsBeanutilsNoCC2TomcatEcho 调用AddPlainCmdHeader插入命令 tomcat明文回显
CommonsBeanutilsNoCC2SpringEcho 调用AddPlainCmdHeader插入命令 spring明文回显
CommonsBeanutilsNoCC2 calc 无回显
CommonsCollectionsK1TomcatEncodeEcho 调用AddEncodeCmdHeader插入命令 tomcat编码回显
CommonsCollectionsK1TomcatEcho 调用AddPlainCmdHeader插入命令 tomcat明文回显
CommonsCollectionsK1SpringEchoEcho 调用AddEncodeCmdHeader插入命令 spring编码回显
CommonsCollectionsK1SpringEcho 调用AddPlainCmdHeader插入命令 tomcat明文回显
CommonsCollectionsK1Sleep sleep 10秒检测漏洞
CommonsCollectionsK1 calc 无回显
CommonsCollectionsK2TomcatEncodeEcho 调用AddEncodeCmdHeader插入命令 tomcat编码回显
CommonsCollectionsK2TomcatEcho 调用AddPlainCmdHeader插入命令 tomcat明文回显
CommonsCollectionsK2SpringEchoEcho 调用AddEncodeCmdHeader插入命令 spring编码回显
CommonsCollectionsK2SpringEcho 调用AddPlainCmdHeader插入命令 tomcat明文回显
CommonsCollectionsK2Sleep sleep 10秒检测漏洞
CommonsCollectionsK2 calc 无回显
FindClassByDNS xxx.dnslog.cn 自动化检测多条利用链依赖

其他方法

// 获取设置的http头部
func (self *ExpTemplate) GetInitExpHeaders() (headers lzhttp.Header) {
	return
}

goutils

// 获得当前程序所在的目录
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) {}



// 安全添加URI,如果uri已存在则不追加
func SafeAddUri(target, uri string, check string) (result string) {
	return
}
// 追加URL,基于当前目录
func AppendUri(target, uri string) (result string) {
	return
}
// 获取hostname,如192.168.1.1:88
func GetHostname(target string) (hostname string) {
	return
}

编码转换模块调用(2022-12-11)

在编写插件的时候,经常会遇到各种编码、加密算法的调用,每次都需要自己去查代码编写,railgun其实本身就有编码转换模块,已经支持了大多数常用的编码转换,索性就实现在插件里可以快速调用编码转换模块。

第一步:在编码转换模块调试好需要的编码转换逻辑,然后点击"生成代码"。

将命令进行JavaRuntimeExec编码并且进行URL二次编码,生成代码所示如下,把编码转换封装到一个函数里方便调用。

image-20241008133752798

PS: 如果对参数选项需要自定义,只需要改动函数入参即可。

第二步:将自动生成的函数代码复制到插件代码中,然后调用就可以了,效果如下

image-20221211175544085

注意事项

  1. 编写导入的库仅支持go自带库以及当前依赖库,第三方库不支持调用。
  2. 代码文件中禁用声明NewExp/NewPoc函数