微软AD域控自助改密系统搭建

自助改密系统研究

同事有这么一个需求,希望能够通过一个系统自助改密、以及忘记密码后可以自助重置密码。
PS: 收了人两瓶可乐,在不把这个系统研究出来有点过分了
可以配合邮件推送密码过期通知脚本使用 windows AD域控密码过期邮件通知迭代版本

1.域控配置

1.1 系统环境信息

  • SSP为Self Service Password改密系统,版本为1.5.4
  • DC为windows2016域控
主机名 OS版本 ip地址 cpu 内存 磁盘
SSP Ubuntu 22.04 10.22.1.5 2core 4G 80G
DC windows2016 10.22.1.2 4core 8G 80G

1.2 windows AD安装证书服务器










1.3 windows ad配置ldaps

配置完成后需要重启域控服务器










2.Self Service Password

2.1 Self Service Password安装源部署

  • 获取软件包源
cat /etc/apt/sources.list.d/ltb-project.list
deb [arch=amd64 signed-by=/usr/share/keyrings/ltb-project.gpg] https://ltb-project.org/debian/stable stable main
  • 获取证书
wget -O - https://ltb-project.org/documentation/_static/RPM-GPG-KEY-LTB-project | gpg --dearmor | sudo tee /usr/share/keyrings/ltb-project.gpg >/dev/null
  • 更新
apt update

2.2 安装Self Service Password

  • 安装smarty3

如果smarty3_3.1.47-2_all.deb包不存在,可以访问http://ftp.us.debian.org/debian/pool/main/s/smarty3/查找最新版本,并下载。
如果安装smarty3_3.1.47-2_all.deb报错,可以在安装完成后执行apt install -f进行修复

wget http://ftp.us.debian.org/debian/pool/main/s/smarty3/smarty3_3.1.47-2_all.deb
dpkg -i smarty3_3.1.47-2_all.deb
  • 直接安装self-service-password
apt install self-service-password -y

2.3 配置apache2虚拟域名

  • 配置self-service-password.conf域名配置
cat >> /etc/apache2/sites-enabled/self-service-password.conf << EOF
<VirtualHost *:80>
	# The ServerName directive sets the request scheme, hostname and port that
	# the server uses to identify itself. This is used when creating
	# redirection URLs. In the context of virtual hosts, the ServerName
	# specifies what hostname must appear in the request's Host: header to
	# match this virtual host. For the default virtual host (this file) this
	# value is not decisive as it is used as a last resort host regardless.
	# However, you must set it for any further virtual host explicitly.
	ServerName resetpass.sec.lab

	DocumentRoot /usr/share/self-service-password/htdocs
	DirectoryIndex index.php
  <Directory /usr/share/self-service-password/htdocs>
       AllowOverride None
       Require all granted
  </Directory>

	# Available loglevels: trace8, ..., trace1, debug, info, notice, warn,
	# error, crit, alert, emerg.
	# It is also possible to configure the loglevel for particular
	# modules, e.g.
	#LogLevel info ssl:warn

	ErrorLog ${APACHE_LOG_DIR}/ssp-error.log
	CustomLog ${APACHE_LOG_DIR}/ssp-access.log combined

	# For most configuration files from conf-available/, which are
	# enabled or disabled at a global level, it is possible to
	# include a line for only one particular virtual host. For example the
	# following line enables the CGI configuration for this host only
	# after it has been globally disabled with "a2disconf".
	#Include conf-available/serve-cgi-bin.conf
</VirtualHost>

# vim: syntax=apache ts=4 sw=4 sts=4 sr noet
EOF
  • 开启ssp站点
a2ensite self-service-password
  • 重启apache2服务
systemctl restart apache2

2.4 配置LDAPS证书

在self-service-password应用与微软域账号对接需要配置ldaps协议才行,在Linux中需要安装openldap才能配置证书

  • 安装软件
sudo apt install slapd ldap-utils -y
  • 转换AD域控证书
mkdir -p /etc/ldap/cert/
openssl x509 -inform der -in sec_dc_ca.cer -out /etc/ldap/cert/dc.pem
  • 调用证书

在ldap配置文件名中调用

root@ubuntu:~# cat /etc/ldap/ldap.conf
#
# LDAP Defaults
#

# See ldap.conf(5) for details
# This file should be world readable but not world writable.

#BASE	dc=example,dc=com
#URI	ldap://ldap.example.com ldap://ldap-provider.example.com:666

#SIZELIMIT	12
#TIMELIMIT	15
#DEREF		never

# TLS certificates (needed for GnuTLS)
#TLS_CACERT	/etc/ssl/certs/ca-certificates.crt
TLS_CACERT	/etc/ldap/cert/dc.pem
TLS_REQCERT allow

2.5 安装sendmail软件

  • 直接安装sendmail软件
apt install sendmail -y && systemctl start sendmail && systemctl enable sendmail

2.6 配置自助重置密码

在新版本中,配置文件在/etc/self-service-password/config.inc.php

root@ubuntu:/usr/share/self-service-password/conf# cat config.inc.php
<?php
#==============================================================================
# LTB Self Service Password
#
# Copyright (C) 2009 Clement OUDOT
# Copyright (C) 2009 LTB-project.org
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# GPL License: http://www.gnu.org/licenses/gpl.txt
#
#==============================================================================

#==============================================================================
# All the default values are kept here, you should not modify it but use
# config.inc.local.php file instead to override the settings from here.
#==============================================================================

#==============================================================================
# Configuration
#==============================================================================

# Debug mode
# true: log and display any errors or warnings (use this in configuration/testing)
# false: log only errors and do not display them (use this in production)
$debug = false;

# LDAP
# LDAP的URL,如果是微软域,自助改密需要使用ldaps协议
$ldap_url = "ldaps://10.22.1.2:636";
$ldap_starttls = false;
# 自动改密系统绑定的ldap账号信息和OU信息
$ldap_binddn = "cn=administrator,cn=users,dc=sec,dc=lab";
# 绑定的ldap账号密码
$ldap_bindpw = 'password';
// for GSSAPI authentication, comment out ldap_bind* and uncomment ldap_krb5ccname lines
//$ldap_krb5ccname = "/path/to/krb5cc";
# ldap ou 信息
$ldap_base = "dc=sec,dc=lab";
# ldap取值用户的属性 sAMAccountName
$ldap_login_attribute = "sAMAccountName";
# ldap取值用户属性 cn
$ldap_fullname_attribute = "cn";
# ldap过滤
$ldap_filter = "(&(objectClass=user)(sAMAccountName={login}))(!(userAccountControl:1.2.840.113556.1.4.803:=2)))";
$ldap_use_exop_passwd = false;
$ldap_use_ppolicy_control = false;

# Active Directory mode
# true: use unicodePwd as password field
# false: LDAPv3 standard behavior
# 开启ad模式
$ad_mode = true;
$ad_options=[];
# Force account unlock when password is changed
# 启用修改密码强制解锁
$ad_options['force_unlock'] = true;
# Force user change password at next login
# 关闭修改密码后下次登录还需要修改密码
$ad_options['force_pwd_change'] = false;
# Allow user with expired password to change password
# 允许密码过期用户充值密码
$ad_options['change_expired_password'] = true;

# Samba mode
# true: update sambaNTpassword and sambaPwdLastSet attributes too
# false: just update the password
$samba_mode = false;
$samba_options=[];
# Set password min/max age in Samba attributes
#$samba_options['min_age'] = 5;
#$samba_options['max_age'] = 45;
#$samba_options['expire_days'] = 90;

# Shadow options - require shadowAccount objectClass
$shadow_options=[];
# Update shadowLastChange
$shadow_options['update_shadowLastChange'] = false;
$shadow_options['update_shadowExpire'] = false;

# Default to -1, never expire
$shadow_options['shadow_expire_days'] = -1;

# Hash mechanism for password:
# SSHA, SSHA256, SSHA384, SSHA512
# SHA, SHA256, SHA384, SHA512
# SMD5
# MD5
# CRYPT
# ARGON2
# clear (the default)
# auto (will check the hash of current password)
# This option is not used with ad_mode = true
//$hash = "clear";
//$hash_options=[];

# Prefix to use for salt with CRYPT
$hash_options['crypt_salt_prefix'] = "$6$";
$hash_options['crypt_salt_length'] = "6";

# USE rate-limiting by IP and/or by user
$use_ratelimit = false;
# dir for json db's (system default tmpdir)
#$ratelimit_dbdir = '/tmp';
# block attempts for same login ?
$max_attempts_per_user = 2;
# block attempts for same IP ?
$max_attempts_per_ip = 2;
# how many time to refuse subsequent requests ?
$max_attempts_block_seconds = "60";
# Header to use for client IP (HTTP_X_FORWARDED_FOR ?)
$client_ip_header = 'REMOTE_ADDR';
# JSON file to filter by IP
#$ratelimit_filter_by_ip_jsonfile = "/usr/share/self-service-password/conf/rrl_filter_by_ip.json";

# Local password policy
# This is applied before directory password policy
# Minimal length
$pwd_min_length = 8;
# Maximal length
$pwd_max_length = 0;
# Minimal lower characters
$pwd_min_lower = 0;
# Minimal upper characters
$pwd_min_upper = 0;
# Minimal digit characters
$pwd_min_digit = 0;
# Minimal special characters
$pwd_min_special = 0;
# Definition of special characters
$pwd_special_chars = "^a-zA-Z0-9";
# Forbidden characters
#$pwd_forbidden_chars = "@%";
# Don't reuse the same password as currently
$pwd_no_reuse = true;
# Check that password is different than login
$pwd_diff_login = true;
# Check new passwords differs from old one - minimum characters count
$pwd_diff_last_min_chars = 0;
# Forbidden words which must not appear in the password
$pwd_forbidden_words = array();
# Forbidden ldap fields
# Respective values of the user's entry must not appear in the password
# example: $pwd_forbidden_ldap_fields = array('cn', 'givenName', 'sn', 'mail');
$pwd_forbidden_ldap_fields = array();
# Complexity: number of different class of character required
$pwd_complexity = 3;
# use pwnedpasswords api v2 to securely check if the password has been on a leak
$use_pwnedpasswords = false;
# Show policy constraints message:
# always
# never
# onerror
$pwd_show_policy = "always";
# Position of password policy constraints message:
# above - the form
# below - the form
$pwd_show_policy_pos = "above";

# disallow use of the only special character as defined in `$pwd_special_chars` at the beginning and end
$pwd_no_special_at_ends = false;

# Who changes the password?
# Also applicable for question/answer save
# user: the user itself
# manager: the above binddn
$who_change_password = "manager";

# Show extended error message returned by LDAP directory when password is refused
$show_extended_error = false;

## Standard change
# Use standard change form?
$use_change = true;

## SSH Key Change
# Allow changing of sshPublicKey?
$change_sshkey = false;

# What attribute should be changed by the changesshkey action?
$change_sshkey_attribute = "sshPublicKey";

# What objectClass is required for that attribute?
$change_sshkey_objectClass = "ldapPublicKey";

# Ensure the SSH Key submitted uses a type we trust
$ssh_valid_key_types = array('ssh-rsa', 'ssh-dss', 'ecdsa-sha2-nistp256', 'ecdsa-sha2-nistp384', 'ecdsa-sha2-nistp521', 'ssh-ed25519');

# Who changes the sshPublicKey attribute?
# Also applicable for question/answer save
# user: the user itself
# manager: the above binddn
$who_change_sshkey = "user";

# Notify users anytime their sshPublicKey is changed
## Requires mail configuration below
$notify_on_sshkey_change = false;

## Questions/answers
# Use questions/answers?
$use_questions = false;
# Allow to register more than one answer?
$multiple_answers = false;
# Store many answers in a single string attribute
# (only used if $multiple_answers = true)
$multiple_answers_one_str = false;

# Answer attribute should be hidden to users!
$answer_objectClass = "extensibleObject";
$answer_attribute = "info";

# Crypt answers inside the directory
$crypt_answers = true;

# Extra questions (built-in questions are in lang/$lang.inc.php)
# Should the built-in questions be included?
$questions_use_default = true;
#$messages['questions']['ice'] = "What is your favorite ice cream flavor?";

# How many questions must be answered.
#  If = 1: legacy behavior
#  If > 1:
#    this many questions will be included in the page forms
#    this many questions must be set at a time
#    user must answer this many correctly to reset a password
#    $multiple_answers must be true
#    at least this many possible questions must be available (there are only 2 questions built-in)
$questions_count = 1;

# Should the user be able to select registered question(s) by entering only the login?
$question_populate_enable = false;

## Token
# Use tokens?
# true (default)
# false
$use_tokens = true;
# Crypt tokens?
# true (default)
# false
$crypt_tokens = true;
# Token lifetime in seconds
$token_lifetime = "300";

## Mail
# LDAP mail attribute
$mail_attributes = array( "mail", "gosaMailAlternateAddress", "proxyAddresses" );
# Get mail address directly from LDAP (only first mail entry)
# and hide mail input field
# default = false
$mail_address_use_ldap = true;
# Who the email should come from

# 设置邮箱名
$mail_from = "xxx@qq.com";
# 设置发送邮件的名字
$mail_from_name = "Self Service Password";
# 设置邮件内的提示词
$mail_signature = "本邮件为通过密码自助修改LDAP账号密码,无需回复,如有重置密码遇到问题可以联系运维";
# Notify users anytime their password is changed
$notify_on_change = true;
# PHPMailer configuration (see https://github.com/PHPMailer/PHPMailer)
# 设置sendmail程序路径
$mail_sendmailpath = '/usr/sbin/sendmail';
# 设置发送mail的协议
$mail_protocol = 'smtp';
$mail_smtp_debug = 0;
$mail_debug_format = 'html';
$mail_smtp_auth = true;
# 设置发送的smtp服务器
$mail_smtp_host = 'smtp.qq.com';
# smtp邮箱名
$mail_smtp_user = 'xxx@qq.com';
# 邮箱密码
$mail_smtp_pass = 'Password';
# smtp端口
$mail_smtp_port = 465;
$mail_smtp_timeout = 30;
$mail_smtp_keepalive = false;
# smtp登录是的安全协议
$mail_smtp_secure = 'ssl';
$mail_smtp_autotls = true;
$mail_smtp_options = array();
$mail_contenttype = 'text/plain';
$mail_wordwrap = 0;
$mail_charset = 'utf-8';
$mail_priority = 3;

## SMS
# 关闭 sms
$use_sms = false;
# SMS method (mail, api)
$sms_method = "mail";
$sms_api_lib = "lib/smsapi.inc.php";
# GSM number attribute
$sms_attributes = array( "mobile", "pager", "ipPhone", "homephone" );
# Partially hide number
$sms_partially_hide_number = true;
# Send SMS mail to address. {sms_attribute} will be replaced by real sms number
$smsmailto = "{sms_attribute}@service.provider.com";
# Subject when sending email to SMTP to SMS provider
$smsmail_subject = "Provider code";
# Message
$sms_message = "{smsresetmessage} {smstoken}";
# Remove non digit characters from GSM number
$sms_sanitize_number = false;
# Truncate GSM number
$sms_truncate_number = false;
$sms_truncate_number_length = 10;
# SMS token length
$sms_token_length = 6;
# Max attempts allowed for SMS token
$max_attempts = 3;

# Encryption, decryption keyphrase, required if $use_tokens = true and $crypt_tokens = true, or $use_sms, or $crypt_answer
# Please change it to anything long, random and complicated, you do not have to remember it
# Changing it will also invalidate all previous tokens and SMS codes
# 需要自己在这里设置一个keyphrase内容随机
$keyphrase = "keyphrase" ;

# Reset URL (if behind a reverse proxy)
#$reset_url = $_SERVER['HTTP_X_FORWARDED_PROTO'] . "://" . $_SERVER['HTTP_X_FORWARDED_HOST'] . $_SERVER['SCRIPT_NAME'];

# Display help messages
$show_help = true;

# Default language
$lang = "zh-CN";

# List of authorized languages. If empty, all language are allowed.
# If not empty and the user's browser language setting is not in that list, language from $lang will be used.
$allowed_lang = array();

# Display menu on top
$show_menu = true;

# Logo
$logo = "images/ltb-logo.png";

# Background image
$background_image = "images/unsplash-space.jpeg";

# Path is relative to htdocs/html and the custom CSS file should be created in css/ directory. For example: "css/sample.css"
$custom_css = "";
$display_footer = true;

# Where to log password resets - Make sure apache has write permission
# By default, they are logged in Apache log
#$reset_request_log = "/var/log/self-service-password";

# Invalid characters in login
# Set at least "*()&|" to prevent LDAP injection
# If empty, only alphanumeric characters are accepted
$login_forbidden_chars = "*()&|";

## Captcha
$use_captcha = false;

## Default action
# change
# sendtoken
# sendsms
$default_action = "change";

## Rest API
$use_restapi = false;

## Extra messages
# They can also be defined in lang/ files
#$messages['passwordchangedextramessage'] = NULL;
#$messages['changehelpextramessage'] = NULL;

## Pre Hook
# Launch a prehook script before changing password.
# Script should return with 0, to allow password change.
# Any other exit code would abort password modification
#$prehook = "/usr/share/self-service-password/prehook.sh";
# Display prehook error
#$display_prehook_error = true;
# Encode passwords sent to prehook script as base64. This will prevent alteration of the passwords if set to true.
# To read the actual password in the prehook script, use a base64_decode function/tool
#$prehook_password_encodebase64 = false;
# Ignore prehook error. This will allow to change password even if prehook script fails.
#$ignore_prehook_error = true;

## Post Hook
# Launch a posthook script after successful password change
#$posthook = "/usr/share/self-service-password/posthook.sh";
# Display posthook error
#$display_posthook_error = true;
# Encode passwords sent to posthook script as base64. This will prevent alteration of the passwords if set to true.
# To read the actual password in the posthook script, use a base64_decode function/tool
#$posthook_password_encodebase64 = false;

# Force setlocale if your default PHP configuration is not correct
#setlocale(LC_CTYPE, "en_US.UTF-8");

# Hide some messages to not disclose sensitive information
# These messages will be replaced by badcredentials error
# by default mailnomatch is obscured since it can disclose account existence
$obscure_failure_messages = array("mailnomatch");
$obscure_usernotfound_sendtoken = true;

# HTTP Header name that may hold a login to preset in forms
#$header_name_preset_login="Auth-User";

# The name of an HTTP Header that may hold a reference to an extra config file to include.
#$header_name_extra_config="SSP-Extra-Config";

# Cache directory
$smarty_compile_dir = "/var/cache/self-service-password/templates_c";
$smarty_cache_dir = "/var/cache/self-service-password/cache";

# Smarty debug mode - will popup debug information on web interface
$smarty_debug = false;

# Allow to override current settings with local configuration
if (file_exists (__DIR__ . '/config.inc.local.php')) {
    require_once __DIR__ . '/config.inc.local.php';
}

# Smarty
if (!defined("SMARTY")) {
    define("SMARTY", "/usr/share/php/smarty3/Smarty.class.php");
}

# Set preset login from HTTP header $header_name_preset_login
$presetLogin = "";
if (isset($header_name_preset_login)) {
    $presetLoginKey = "HTTP_".strtoupper(str_replace('-','_',$header_name_preset_login));
    if (array_key_exists($presetLoginKey, $_SERVER)) {
        $presetLogin = preg_replace("/[^a-zA-Z0-9-_@\.]+/", "", filter_var($_SERVER[$presetLoginKey], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH));
    }
}

# Allow to override current settings with an extra configuration file, whose reference is passed in HTTP_HEADER $header_name_extra_config
if (isset($header_name_extra_config)) {
    $extraConfigKey = "HTTP_".strtoupper(str_replace('-','_',$header_name_extra_config));
    if (array_key_exists($extraConfigKey, $_SERVER)) {
        $extraConfig = preg_replace("/[^a-zA-Z0-9-_]+/", "", filter_var($_SERVER[$extraConfigKey], FILTER_SANITIZE_STRING, FILTER_FLAG_STRIP_HIGH));
        if (strlen($extraConfig) > 0 && file_exists (__DIR__ . "/config.inc.".$extraConfig.".php")) {
            require_once  __DIR__ . "/config.inc.".$extraConfig.".php";
        }
    }
}
root@ubuntu:/usr/share/self-service-password/conf#





posted @ 2024-01-18 15:52  二乘八是十六  阅读(1503)  评论(6编辑  收藏  举报