使用明文密码进行简单的登录
这是之前讲fscan用到的代码,这个代码已经可以给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。
|
|
|
---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
配置好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进行认证则明文密码就是空,在检查时候就会出现错误。
接着主要进行三个步骤①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")}
设置好预身份认证之后就可以发送到KDC了,接着就是一些报错的处理以及返回包解密了。
接收请求阶段
这个阶段也就是AS_REP数据包的解密了,在ASExchange函数中进入Verify,最后进入DecryptEncPart,我们需要定义各种类型解密的函数:
容易看到,这些GetKeyFromxxx都是上述预身份认证用到的函数,也就是获取:
key = types.EncryptionKey{ KeyType: etypeID, KeyValue: k,}
最终会进行到b, err := crypto.DecryptEncPart(k.EncPart, key, keyusage.AS_REP_ENCPART)进行解密。这样一次请求也就完成了。
添加Socks5代理
gokrb5本身没有提供能够代理的函数,所以我们要做的就是进入sendToKDC函数中,找到最终发送请求的地方,自己手动实现一个Socks5代理请求,其他代理同理。
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插件(一)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论