【fscan插件】| 用gokrb5构建fscan插件(一)

admin 2025年3月27日13:48:39评论6 views字数 8484阅读28分16秒阅读模式

使用明文密码进行简单的登录

这是之前讲fscan用到的代码,这个代码已经可以给fscan添加功能了。

【fscan插件】| 用gokrb5构建fscan插件(一)
// SetConfig 配置 Kerberos 客户端func SetConfig() *config.Config {    KrbConfig := config.New()// 设置默认域(REALM),转换为大写    KrbConfig.LibDefaults.DefaultRealm = strings.ToUpper(Domain)// 设置 UDP 优先级限制    KrbConfig.LibDefaults.UDPPreferenceLimit = 1// 设置 TGS(Ticket Granting Service)加密类型,默认使用 AES256    KrbConfig.LibDefaults.DefaultTGSEnctypeIDs = []int32{18} // AES256_CTS_HMAC_SHA1_96    KrbConfig.LibDefaults.DefaultTGSEnctypeIDs = append(KrbConfig.LibDefaults.DefaultTGSEnctypeIDs, etypeID.RC4_HMAC) // 兼容 NTLM(RC4-HMAC)// 设置 TGT(Ticket Granting Ticket)加密类型,与 TGS 类似    KrbConfig.LibDefaults.DefaultTktEnctypeIDs = []int32{18} // AES256_CTS_HMAC_SHA1_96    KrbConfig.LibDefaults.DefaultTktEnctypeIDs = append(KrbConfig.LibDefaults.DefaultTktEnctypeIDs, etypeID.RC4_HMAC) // 兼容 NTLM(RC4-HMAC)// 配置 Kerberos 认证服务器(KDC)信息    var realm config.Realm    realm.Realm = Domain    realm.KDC = []string{strings.ToUpper(fmt.Sprintf("%s:88", DC))} // KDC 服务器地址,端口号 88    realm.DefaultDomain = Domain// 赋值给 KrbConfig    KrbConfig.Realms = []config.Realm{realm}return KrbConfig}// GetServiceTicket 通过 Kerberos 获取目标服务的 Ticketfunc GetServiceTicket(info *common.HostInfo) error {// 生成 Kerberos 配置    krbConfig := SetConfig()// 使用明文密码进行 Kerberos 认证    cl := client.NewWithPassword(Username, Domain, Password, krbConfig,         client.DisablePAFXFAST(true), // 关闭 PA-FX-FAST 预认证机制        client.AssumePreAuthentication(false), // 允许无预认证    )// 登录 Kerberos 账户    err := cl.Login()if err != nil {return err    }// 请求目标服务的 Kerberos 票据    _, _, err = cl.GetServiceTicket(Spn)if err != nil {return err    }    fmt.Println("Login!")return nil}

多种Key进行登录

发送请求阶段

接下来会讲解如何二开gokrb5包以支持socks5代理、使用Aes256、Aes128、ntlm哈希登录。在登录之前,我们需要配置Client客户端配置,它可以控制加密类型、UDP优先级别、可代理可转发等。Client的原型如下,我们需要修改Config的配置。

// Client side configuration and state.type Client struct { Credentials *credentials.Credentials Config      *config.Config settings    *Settings sessions    *sessions cache       *Cache}
func NewKerbConfig(domain string, dc string, etypeid int32)(*config.Config, error){ KrbConfig := config.New()// 设置Realm名称 KrbConfig.LibDefaults.DefaultRealm = strings.ToUpper(domain)// 设置允许的加密类型 KrbConfig.LibDefaults.PermittedEnctypeIDs = []int32{etypeid} KrbConfig.LibDefaults.PermittedEnctypeIDs = append(KrbConfig.LibDefaults.PermittedEnctypeIDs, etypeID.RC4_HMAC)// 设置 TGS 和 TGT 票据的加密类型 ID KrbConfig.LibDefaults.DefaultTGSEnctypeIDs = []int32{etypeid} KrbConfig.LibDefaults.DefaultTGSEnctypeIDs = append(KrbConfig.LibDefaults.DefaultTGSEnctypeIDs, etypeID.RC4_HMAC) KrbConfig.LibDefaults.DefaultTktEnctypeIDs = []int32{etypeid} KrbConfig.LibDefaults.DefaultTktEnctypeIDs = append(KrbConfig.LibDefaults.DefaultTktEnctypeIDs, etypeID.RC4_HMAC)// 设置 UDP 优先级限制 KrbConfig.LibDefaults.UDPPreferenceLimit = 1// 设置 Kerberos 配置的可代理和可转发选项 KrbConfig.LibDefaults.Proxiable = true KrbConfig.LibDefaults.Forwardable = true KrbConfig.LibDefaults.Forwardable = true// 设置 Kerberos Realm 配置 var realm config.Realm realm.Realm = domain realm.KDC = []string{strings.ToUpper(fmt.Sprintf("%s:88", dc))} // 设置 KDC 地址,通常是域控制器地址 realm.DefaultDomain = domain// 将 Realm 添加到配置对象 KrbConfig.Realms = []config.Realm{realm}return KrbConfig, nil}

需要注意的地方是,必须配置realm.KDC的值,可代理可转发选项。使用不同的类型密码进行登录加密类型也是不一样的,所以也需要配置LibDefaults.DefaultTGSEnctypeIDs。

认证方式
加密类型
etypeID
明文密码
依赖于 KDC 支持的类型,通常包括AES和RC4
18(AES256)、17(AES128)、23(RC4_HMAC)
NTLM
RC4-HMAC
23
AES256
AES256-CTS-HMAC-SHA1-96
18
AES128
AES128-CTS-HMAC-SHA1-96
17

配置好config之后我们需要建立连接对象,也就是初始化Credentials。默认的Credentials是不支持NTLM哈希的,所以我们需要手动给他加上:

type Credentials struct { username        string displayName     string realm           string cname           types.PrincipalName keytab          *keytab.Keytab password        string attributes      map[string]interface{} validUntil      time.Time authenticated   bool human           bool authTime        time.Time groupMembership map[string]bool sessionID       string ntlmHash        string// add Aes256          string// add Aes128          string// add}
cl = client.NewWithAes256Key(krb.Username, krb.Domain, krb.Aes256, KrbConfig, client.DisablePAFXFAST(true), client.AssumePreAuthentication(true))--------------------------------------func NewWithAes256Key(username, realm, aes256 string, krb5conf *config.Config, settings ...func(*Settings)) *Client { creds := credentials.New(username, realm)return &Client{  Credentials: creds.WithAesKey(aes256),  Config:      krb5conf,  settings:    NewSettings(settings...),  sessions: &sessions{   Entries: make(map[string]*session),  },  cache: NewCache(), }}func (c *Credentials) WithAesKey(aes256 string) *Credentials { c.Aes256 = aes256 c.keytab = keytab.New() // clear any keytabreturn c}

初始化client客户端之后,需要使用内置的Login函数进行登录,在这个函数中以及子函数会进行大量的检查,如果我们使用的是ntlm进行认证则明文密码就是空,在检查时候就会出现错误。

【fscan插件】| 用gokrb5构建fscan插件(一)

接着主要进行三个步骤①New一个AS_REQ请求②ASExchange③登录成功后为当前client添加Session。我们主要要看的是ASExchange函数,这个函数中首先需要看的是setPAData函数,这是用于预身份认证的,由于默认的gokrb5只支持明文密码形式,所以需要改改这个函数。在这个函数中,首先可以看到etn := cl.settings.preAuthEType它获取了我们的加密类型ID,这里需要将他改为从 cl.Config.LibDefaults.PermittedEnctypeIDs中获取,因为上面的NewKerbConfig已经对不同的认证密码类型赋值了不同的加密类型。其实如果使用的是明文密码,这个函数也是会根据选定的加密类型将明文密码转换成不同的类型加密密码。

func (cl *Client) Key(etype etype.EType, kvno int, krberr *messages.KRBError) (types.EncryptionKey, int, error) {if cl.Credentials.HasKeytab() && etype != nil {return cl.Credentials.Keytab().GetEncryptionKey(cl.Credentials.CName(), cl.Credentials.Domain(), kvno, etype.GetETypeID()) } elseif cl.Credentials.HasPassword() {if krberr != nil && krberr.ErrorCode == errorcode.KDC_ERR_PREAUTH_REQUIRED {   var pas types.PADataSequence   err := pas.Unmarshal(krberr.EData)if err != nil {return types.EncryptionKey{}, 0, fmt.Errorf("could not get PAData from KRBError to generate key from password: %v", err)   }   key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), krberr.CName, krberr.CRealm, etype.GetETypeID(), pas)return key, 0, err  }  key, _, err := crypto.GetKeyFromPassword(cl.Credentials.Password(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID(), types.PADataSequence{})return key, 0, err } elseif cl.Credentials.HasHash() {// Add the type for encryption  key, _, err := crypto.GetKeyFromNTLMHash(cl.Credentials.Hash(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID())return key, 0, err } elseif cl.Credentials.HasAes256() {  key, _, err := crypto.GetKeyFromAes256(cl.Credentials.GetAes256(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID())return key, 0, err } elseif cl.Credentials.HasAes128() {  key, _, err := crypto.GetKeyFromAes128(cl.Credentials.GetAes128(), cl.Credentials.CName(), cl.Credentials.Domain(), etype.GetETypeID())return key, 0, err }return types.EncryptionKey{}, 0, errors.New("credential has neither keytab or password to generate key")}
【fscan插件】| 用gokrb5构建fscan插件(一)

设置好预身份认证之后就可以发送到KDC了,接着就是一些报错的处理以及返回包解密了。

接收请求阶段

这个阶段也就是AS_REP数据包的解密了,在ASExchange函数中进入Verify,最后进入DecryptEncPart,我们需要定义各种类型解密的函数:

【fscan插件】| 用gokrb5构建fscan插件(一)

容易看到,这些GetKeyFromxxx都是上述预身份认证用到的函数,也就是获取:

key = types.EncryptionKey{ KeyType: etypeID, KeyValue: k,}

最终会进行到b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.AS_REP_ENCPART)进行解密。这样一次请求也就完成了。

添加Socks5代理

gokrb5本身没有提供能够代理的函数,所以我们要做的就是进入sendToKDC函数中,找到最终发送请求的地方,自己手动实现一个Socks5代理请求,其他代理同理。

【fscan插件】| 用gokrb5构建fscan插件(一)
func (cl *Client) parseSocks() error { input := cl.Config.Socks.Server regex := `^([0-9]{1,3}.){3}[0-9]{1,3}:[0-9]{1,5}$` re := regexp.MustCompile(regex)if re.MatchString(input) {  port := strings.Split(input, ":")[1]  var portNum int  _, err := fmt.Sscanf(port, "%d", &portNum)if err != nil || portNum < 1 || portNum > 65535 {return errors.New("invalid port number")  }if cl.Config.Socks.Version == 5 {   cl.Config.Socks.Version = 2  } elseif cl.Config.Socks.Version == 4 {   cl.Config.Socks.Version = 1  }return nil }return errors.New("invalid Socks Server 'IP:Port' format")}// sendKDCTCP sends bytes to the KDC via Socks.func (cl *Client) sendKDCTCPSocks(realm string, b []byte) ([]byte, error) { var r []byte// 返回可用 KDC 的计数和按首选项顺序键控的 KDC 主机名的映射 _, kdcs, err := cl.Config.GetKDCs(realm, true)if err != nil {return r, err } r, err = cl.dialSendTCPSocks(kdcs, b)if err != nil {return r, err }return checkForKRBError(r)}func (cl *Client) dialSendTCPSocks(kdcs map[int]string, b []byte) ([]byte, error) { var errs []stringfor i := 1; i <= len(kdcs); i++ {  tcpAddr, err := net.ResolveTCPAddr("tcp", kdcs[i])if err != nil {   errs = append(errs, fmt.Sprintf("error resolving KDC address: %v", err))continue  }  proxyDial := socks.DialSocksProxy(cl.Config.Socks.Version, cl.Config.Socks.Server)  conn, err := proxyDial("tcp", tcpAddr.String())if err != nil {   errs = append(errs, fmt.Sprintf("error setting dial timeout on connection to %s: %v", kdcs[i], err))continue  }if err := conn.SetDeadline(time.Now().Add(5 * time.Second)); err != nil {   errs = append(errs, fmt.Sprintf("error setting deadline on connection to %s: %v", kdcs[i], err))continue  }// conn is guaranteed to be a TCPConn  rb, err := sendTCP(conn.(*net.TCPConn), b)if err != nil {   errs = append(errs, fmt.Sprintf("error sneding to %s: %v", kdcs[i], err))continue  }return rb, nil }return nil, fmt.Errorf("error sending to a KDC: %s", strings.Join(errs, "; "))}

原文始发于微信公众号(半只红队):【fscan插件】| 用gokrb5构建fscan插件(一)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年3月27日13:48:39
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【fscan插件】| 用gokrb5构建fscan插件(一)https://cn-sec.com/archives/3888379.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息