You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过ldapsearch及Go语言查询Windows AD的MinPwdLen等属性

查询Windows AD密码策略属性(MinPwdLen、MinPwdAge等)

一、关键概念澄清

你在JXplorer中看到的CN=Min-Pwd-Length,CN=Schema,CN=Configuration,DC=XXXX是AD Schema中该属性的定义元数据(比如属性语法、规则),不是实际生效的域密码策略值。实际密码策略值存储在域对象或精细密码策略容器中,这也是ldapdomaindump获取该属性的核心逻辑。

二、ldapsearch命令行查询示例

1. 查询默认域密码策略

默认域密码策略直接存储在域根对象中,执行以下命令:

ldapsearch -x -H ldap://<AD服务器地址>:389 -D "<绑定用户DN>" -W -b "<域根DN>" -s base "(objectClass=domain)" pwdMinLength minPwdAge pwdMaxAge

参数说明:

  • -x:使用简单绑定认证
  • -H:指定AD服务器的LDAP地址
  • -D:有权限查询AD的用户DN(比如CN=admin,CN=Users,DC=corp,DC=com)
  • -W:交互式输入用户密码
  • -b:查询的基准DN(即你的域根,比如DC=corp,DC=com)
  • -s base:仅查询基准DN本身(不递归子节点)
  • (objectClass=domain):过滤条件,匹配域根对象
  • 末尾属性映射:pwdMinLength对应MinPwdLen,minPwdAge对应MinPwdAge,pwdMaxAge对应密码最长有效期

2. 查询精细密码策略(Fine-Grained Password Policies)

如果AD使用了精细密码策略,查询基准DN为密码策略容器:

ldapsearch -x -H ldap://<AD服务器地址>:389 -D "<绑定用户DN>" -W -b "CN=Password Settings Container,CN=System,<域根DN>" -s sub "(objectClass=msDS-PasswordSettings)" msDS-MinimumPasswordLength msDS-MinimumPasswordAge

属性映射:

  • msDS-MinimumPasswordLength → MinPwdLen
  • msDS-MinimumPasswordAge → MinPwdAge

三、Go语言查询示例(基于go-ldap库)

以下实现基于github.com/go-ldap/ldap/v3库,底层封装了ldap_search_ext_s逻辑:

package main

import (
	"fmt"
	"log"

	"github.com/go-ldap/ldap/v3"
)

func main() {
	// AD连接配置
	ldapAddr := "<AD服务器地址>:389"
	bindDN := "<绑定用户DN>"
	bindPwd := "<用户密码>"
	domainBaseDN := "<域根DN>"

	// 建立LDAP连接
	l, err := ldap.DialURL(fmt.Sprintf("ldap://%s", ldapAddr))
	if err != nil {
		log.Fatalf("LDAP连接失败: %v", err)
	}
	defer l.Close()

	// 用户绑定认证
	if err := l.Bind(bindDN, bindPwd); err != nil {
		log.Fatalf("用户绑定失败: %v", err)
	}

	// 构建查询请求
	searchReq := ldap.NewSearchRequest(
		domainBaseDN,
		ldap.ScopeBaseObject, // 仅查询基准DN本身
		ldap.NeverDerefAliases,
		0,
		0,
		false,
		"(objectClass=domain)",
		[]string{"pwdMinLength", "minPwdAge", "pwdMaxAge"}, // 目标属性
		nil,
	)

	// 执行查询
	sr, err := l.Search(searchReq)
	if err != nil {
		log.Fatalf("LDAP查询失败: %v", err)
	}

	// 处理结果
	if len(sr.Entries) == 0 {
		log.Fatal("未找到域根对象")
	}
	entry := sr.Entries[0]

	fmt.Printf("域DN: %s\n", entry.DN)
	fmt.Printf("最小密码长度(MinPwdLen): %s\n", entry.GetAttributeValue("pwdMinLength"))

	// 解析minPwdAge(AD中存储为100纳秒间隔字符串)
	if minPwdAgeStr := entry.GetAttributeValue("minPwdAge"); minPwdAgeStr != "" {
		minPwdAge, err := ldap.ParseInterval(minPwdAgeStr)
		if err != nil {
			log.Printf("解析minPwdAge失败: %v", err)
		} else {
			fmt.Printf("最小密码年龄(MinPwdAge): %s\n", minPwdAge.String())
			fmt.Printf("最小密码年龄(天数): %.1f\n", minPwdAge.Hours()/24)
		}
	}
}

四、Cgo直接调用ldap_search_ext_s示例

如果你的Go应用直接通过Cgo调用系统LDAP库的ldap_search_ext_s函数,参考以下片段:

package main

/*
#include <ldap.h>
*/
import "C"
import (
	"fmt"
	"log"
	"unsafe"
)

func main() {
	// 初始化LDAP连接
	ld := C.ldap_init(C.CString("<AD服务器地址>"), C.LDAP_PORT)
	if ld == nil {
		log.Fatal("LDAP初始化失败")
	}
	defer C.ldap_unbind_ext_s(ld, nil, nil)

	// 设置LDAPv3协议
	version := C.int(C.LDAP_VERSION3)
	C.ldap_set_option(ld, C.LDAP_OPT_PROTOCOL_VERSION, unsafe.Pointer(&version))

	// 用户绑定
	bindDN := C.CString("<绑定用户DN>")
	bindPwd := C.CString("<用户密码>")
	if err := C.ldap_simple_bind_s(ld, bindDN, bindPwd); err != C.LDAP_SUCCESS {
		log.Fatalf("绑定失败,错误码: %d", err)
	}
	C.free(unsafe.Pointer(bindDN))
	C.free(unsafe.Pointer(bindPwd))

	// 构建查询参数
	baseDN := C.CString("<域根DN>")
	filter := C.CString("(objectClass=domain)")
	attrs := []*C.char{C.CString("pwdMinLength"), C.CString("minPwdAge"), nil}
	var res *C.LDAPMessage

	// 调用ldap_search_ext_s执行查询
	if err := C.ldap_search_ext_s(
		ld,
		baseDN,
		C.LDAP_SCOPE_BASE,
		filter,
		&attrs[0],
		0,
		nil,
		nil,
		nil,
		0,
		&res,
	); err != C.LDAP_SUCCESS {
		log.Fatalf("查询失败,错误码: %d", err)
	}
	defer C.ldap_msgfree(res)

	// 释放资源
	C.free(unsafe.Pointer(baseDN))
	C.free(unsafe.Pointer(filter))
	for _, attr := range attrs {
		if attr != nil {
			C.free(unsafe.Pointer(attr))
		}
	}

	// 遍历结果
	entry := C.ldap_first_entry(ld, res)
	for entry != nil {
		// 获取pwdMinLength
		val := C.ldap_get_values(ld, entry, C.CString("pwdMinLength"))
		if val != nil {
			fmt.Printf("最小密码长度: %s\n", C.GoString(val[0]))
			C.ldap_value_free(val)
		}
		// 获取minPwdAge
		val = C.ldap_get_values(ld, entry, C.CString("minPwdAge"))
		if val != nil {
			fmt.Printf("最小密码年龄(原始值): %s\n", C.GoString(val[0]))
			C.ldap_value_free(val)
		}
		entry = C.ldap_next_entry(ld, entry)
	}
}

内容的提问来源于stack exchange,提问作者DC007

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 07:32:30