Bảo Mật Windows Server Từ Cơ Bản Đến Nâng Cao (Phần 1)
Baseline, phân quyền & LAPS, bảo vệ chính PowerShell, mạng SMB/LDAP — hướng dẫn đầy đủ lệnh PowerShell cho IT Admin, đối chiếu Microsoft Learn/CIS/NSA. Phần 1/2 loạt Bảo Mật Windows Server Từ Cơ Bản Đến Nâng Cao.
Đội ngũ STEP Technology
Chuyên gia IT & Hạ tầng
Windows Server của bạn đang đứng ở đâu trong bài này?
Tài khoản Administrator mặc định trên các máy chủ bạn quản lý — còn nguyên tên, còn bật, và dùng chung một mật khẩu cho cả chục máy trong domain, đúng không? RDP có đang mở thẳng ra Internet với vài dòng luật firewall lọc IP "thêm cho chắc" chứ chưa ai từng thử tấn công dò mật khẩu thật vào đó? Nếu một script hoặc một tài khoản dịch vụ bị chiếm quyền chạy lệnh PowerShell trên máy chủ ngay lúc này, bạn có log nào ghi lại chính xác lệnh gì vừa chạy không, hay chỉ có Get-History — thứ biến mất ngay khi phiên đóng lại?
Đây là ba câu hỏi rất cụ thể, và phần lớn máy chủ Windows Server đang chạy ở các công ty Việt Nam sẽ trả lời "không" cho ít nhất một câu. Bài này không nói về khái niệm bảo mật chung chung — nó đi thẳng vào bốn mảng cụ thể: baseline hệ điều hành, quản lý danh tính/truy cập, bảo vệ chính công cụ PowerShell mà bạn dùng để làm mọi việc trên, và hạ tầng mạng ở tầng SMB/LDAP/IPsec. Mỗi mục đều có lệnh PowerShell xác nhận qua tài liệu chính thức của Microsoft, đối chiếu thêm CIS Benchmark khi có, và riêng phần bảo vệ PowerShell (Mục 3) đối chiếu thêm khuyến nghị chung của NSA/CISA cùng các cơ quan an ninh mạng New Zealand/Anh trong tài liệu "Keeping PowerShell: Security Measures to Use and Embrace" (06/2022) — kèm cách xác nhận đã chạy đúng, không lý thuyết suông.
Đây là Phần 1 trong loạt 2 bài. Phần 2 sẽ nói về giám sát/audit log, Windows Defender Application Control (WDAC) và Attack Surface Reduction (ASR), chiến lược sao lưu chống ransomware, và các lớp mã hoá nâng cao hơn.
Một số lệnh trong bài — đổi chính sách khoá tài khoản, đổi cấu hình RDP/Firewall, đăng ký lại endpoint PowerShell Remoting, bật bắt buộc SMB/LDAP signing — có thể làm gián đoạn dịch vụ hoặc khiến bạn tự khoá mình ra khỏi máy chủ nếu làm sai thứ tự. Hãy thử trên môi trường không phải production trước, đảm bảo có bản sao lưu đã kiểm chứng, và thực hiện trong khung giờ bảo trì đã báo trước. Đọc kỹ Bước 0 ngay dưới đây trước khi chạy bất kỳ lệnh nào. Nội dung bài mang tính hướng dẫn kỹ thuật, không thay thế cho việc đánh giá rủi ro riêng của từng hệ thống — mỗi môi trường có ứng dụng, thiết bị và lịch sử cấu hình khác nhau. Người thực hiện chịu trách nhiệm kiểm thử và xác nhận trước khi áp dụng lên hệ thống đang chạy; STEP không chịu trách nhiệm cho thiệt hại hoặc gián đoạn phát sinh từ việc áp dụng trực tiếp các lệnh trong bài mà chưa qua bước kiểm thử này.
Bước 0 — Sao lưu cấu hình và xác nhận đường vào dự phòng
Khác với một bài chỉ bật thêm tính năng, nhiều lệnh trong bài này thay đổi cách bạn đang truy cập vào chính máy chủ — và một phần trong số đó thay đổi cả domain cùng lúc, không chỉ một máy. Hai loại rủi ro cần đường lùi khác nhau, đừng gộp chung:
Rủi ro CẤP MỘT MÁY (console/iDRAC/iLO cứu được):
- Đổi RDP + Windows Firewall (mục 1.2, 2.5): sai một luật, bạn tự khoá phiên RDP đang dùng ra khỏi máy — mục 1.2 (bật firewall mặc định chặn inbound) còn rủi ro cao hơn vì cắt luôn mọi dịch vụ chưa có luật allow, không riêng RDP.
- Vô hiệu hoá tài khoản Administrator mặc định (mục 2.3): làm sai thứ tự với LAPS (mục 2.2) có thể khiến máy không còn tài khoản admin cục bộ nào đăng nhập được.
- Đăng ký lại endpoint PowerShell Remoting / JEA (mục 2.6, 3.4):
Register-PSSessionConfigurationvàUnregister-PSSessionConfigurationkhởi động lại dịch vụ WinRM, cắt ngang mọi phiên remoting đang mở — kể cả DSC đang chạy dở. Xoá listener HTTP ở mục 3.4 trước khi xác nhận chắc HTTPS đã hoạt động còn có thể mất hẳn khả năng remoting từ xa.
Rủi ro CẤP DOMAIN (console một máy KHÔNG cứu được — phải phục hồi/gỡ GPO):
- Áp Microsoft Security Baseline (mục 1.5): đổi cùng lúc 344 setting (Member Server) / 347 setting (Domain Controller) rồi khởi động lại máy — thay đổi lớn nhất toàn bài.
- Đổi chính sách khoá tài khoản / mật khẩu (mục 2.4): đặt ngưỡng khoá quá thấp, chính tài khoản admin của bạn (hoặc tài khoản dịch vụ dùng mật khẩu cũ) bị khoá trước — áp cho mọi tài khoản trong domain cùng lúc.
- Bật Script Block Logging/Transcription, Execution Policy qua GPO (mục 3.1, 3.3): áp cho mọi máy nằm trong phạm vi GPO đó.
- Bật bắt buộc SMB signing / LDAP signing (mục 4.1, 4.3): máy Windows cũ, NAS, máy Linux dùng Samba cũ, hoặc ứng dụng dùng bind LDAP đơn giản không hỗ trợ signing sẽ mất kết nối ngay lập tức, trên mọi máy GPO chạm tới.
- Bật IPsec domain isolation (mục 4.4): hoạt động ở tầng mạng, cắt mọi giao thức cùng lúc (không riêng SMB/LDAP — cả RDP, WinRM, HTTP, SQL) trên mọi máy áp GPO đó, kể cả máy bạn đang ngồi để cấu hình, nếu thiếu danh sách miễn trừ cho DC/DNS/DHCP.
Khi một GPO sai bị áp cho cả domain, ngồi vào console một máy không cứu được gì — máy đó chỉ nhận lại đúng chính sách sai ở lần đồng bộ tiếp theo. Đường lùi thật cho nhóm rủi ro cấp domain là phục hồi GPO từ bản backup (Restore-GPO) hoặc gỡ link GPO khỏi OU trong Group Policy Management Console — đó là lý do bước backup GPO dưới đây bắt buộc phải làm trước, không phải tuỳ chọn.
Việc cần làm trước khi bắt đầu bất kỳ mục nào trong bài:
1. Sao lưu toàn bộ máy chủ và xác nhận phục hồi được — không chỉ có file backup, phải từng thử phục hồi ra máy khác và thấy nó chạy được.
2. Sao lưu toàn bộ GPO của domain, local policy, và Security Options — đây là đường lùi THẬT cho mọi rủi ro cấp domain liệt kê ở trên (mục 1.5, 2.4, 3.1, 3.3, 4.3, 4.4):
# Backup toan bo GPO cua domain
New-Item -ItemType Directory -Path "D:\Backup\GPO" -Force | Out-Null
Backup-GPO -All -Path "D:\Backup\GPO" -Comment "Truoc khi hardening"
# Backup Local Group Policy cua may sap ap baseline (WS2019/2022 dung LGPO.exe, xem muc 1.5)
& "C:\SCT\LGPO.exe" /b "D:\Backup\LocalGPO" /n "Truoc-hardening"
# Backup nhanh Security Options cuc bo (dung cho muc 2.3, 3.3, 4.3 tren may dung rieng)
secedit /export /cfg "D:\Backup\secpol-truoc-hardening.inf"3. Mở thử console/iDRAC/iLO hoặc KVM của nhà cung cấp hạ tầng ngay bây giờ — đây là đường vào khi bạn lỡ tự khoá mình ra khỏi RDP hoặc WinRM trên chính máy đó. Với các thay đổi cấp domain (mục 1.5, 2.4, 3.1, 3.3, 4.3, 4.4), console không cứu được — đường lùi là Restore-GPO từ bản backup ở bước 2, hoặc gỡ link GPO khỏi OU trong Group Policy Management Console.
4. Nếu máy đang cấu hình là Domain Controller: xác nhận bạn biết mật khẩu DSRM (Directory Services Restore Mode) và đã từng thử đăng nhập DSRM ít nhất một lần. Đây là đường vào cuối cùng nếu tài khoản quản trị domain bị khoá hết (xem đường lùi ở mục 2.4) — console/iDRAC không thay thế được nó, vì lúc đó vấn đề là không còn danh tính để đăng nhập, không phải không có đường vào máy.
5. Ghi lại cấu hình hiện tại để so sánh và có gì để lùi về:
$luu = "D:\Backup\hien-trang-truoc-khi-hardening.txt"
"=== Firewall profile ===" | Out-File $luu
Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction, DefaultOutboundAction | Out-File $luu -Append
"=== Firewall rule Remote Desktop ===" | Out-File $luu -Append
Get-NetFirewallRule -DisplayGroup "Remote Desktop" | Select-Object DisplayName, Enabled, Action | Out-File $luu -Append
"=== Cong dang lang nghe ===" | Out-File $luu -Append
Get-NetTCPConnection -State Listen | Select-Object LocalAddress, LocalPort, OwningProcess | Out-File $luu -Append
"=== Account lockout / password policy (domain) ===" | Out-File $luu -Append
Get-ADDefaultDomainPasswordPolicy | Out-File $luu -Append
"=== WinRM listener ===" | Out-File $luu -Append
winrm enumerate winrm/config/listener | Out-File $luu -Append
"=== PSSessionConfiguration hiện có ===" | Out-File $luu -Append
Get-PSSessionConfiguration | Select-Object Name, Enabled | Out-File $luu -Append
"=== SMB server configuration ===" | Out-File $luu -Append
Get-SmbServerConfiguration | Select-Object RequireSecuritySignature, EnableSMB1Protocol | Out-File $luu -AppendFile này liệt kê chính xác chỗ yếu và chỗ mở của máy chủ — cất vào nơi có kiểm soát truy cập, đừng để trên ổ chia sẻ chung hay gửi qua chat, rồi chép ra khỏi máy chủ.
6. Chọn cửa sổ bảo trì, báo trước cho người dùng, và có người thứ hai biết bạn đang làm gì, có số điện thoại liên lạc được.
Cách xác nhận trước khi sang Mục 1: trả lời "có" cho cả bảy câu, còn một câu "chưa" thì dừng lại:
- Đã backup toàn bộ máy chủ và thử phục hồi chưa?
- Đã chạy
Backup-GPO -All(và LGPO/secedit export nếu áp dụng cho máy này) chưa? - Đã mở được console/iDRAC/iLO chưa?
- Nếu là Domain Controller — đã xác nhận và thử mật khẩu DSRM chưa?
- Đã lưu hiện trạng (firewall, cổng đang nghe, WinRM, SMB...) ra ngoài máy chủ chưa?
- Nếu định áp Security Baseline ở mục 1.5 và môi trường có NAS/Samba/thiết bị cũ: đã đọc trước mục 4.1 và 4.3 chưa? (Baseline bật sẵn SMB signing + LDAP signing ngay khi áp — đọc sau thì đã muộn.)
- Đã chọn cửa sổ bảo trì, báo người dùng, và có người thứ hai biết bạn đang làm gì chưa?
1. Baseline cơ bản
1.1 Tự động hoá Windows Update qua PowerShell
Việc cần làm: Đảm bảo máy chủ nhận và cài bản vá đều đặn, không phụ thuộc việc "nhớ" bấm tay.
Vì sao: Baseline nào cũng vô nghĩa nếu hệ điều hành đang chạy bản vá cũ hàng tháng trời — phần lớn khai thác thực tế nhắm vào lỗ hổng đã có bản vá từ lâu.
Cách thực thi — hai lớp, dùng cùng lúc:
(a) Lớp chính sách (khuyến nghị cho môi trường có nhiều máy): cấu hình khoá AU để máy tự tải và cài theo lịch, qua registry (áp được bằng PowerShell) hoặc GPO tương đương Computer Configuration → Administrative Templates → Windows Components → Windows Update:
$auKey = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU"
New-Item -Path $auKey -Force | Out-Null
Set-ItemProperty -Path $auKey -Name "NoAutoUpdate" -Value 0 -Type DWord
Set-ItemProperty -Path $auKey -Name "AUOptions" -Value 4 -Type DWord # 4 = tự tải + cài theo lịch
Set-ItemProperty -Path $auKey -Name "ScheduledInstallDay" -Value 1 -Type DWord # 0=mỗi ngày, 1=Chủ nhật...7=Thứ Bảy
Set-ItemProperty -Path $auKey -Name "ScheduledInstallTime" -Value 3 -Type DWord # 3 = 3:00 sáng
Set-ItemProperty -Path $auKey -Name "NoAutoRebootWithLoggedOnUsers" -Value 1 -Type DWordAUOptions = 4 là "tự tải + cài theo lịch". Nếu không đặt ScheduledInstallDay/ScheduledInstallTime, Windows dùng lịch mặc định mỗi ngày 3:00 sáng — vẫn nên đặt rõ hai khoá này để lịch vá rơi đúng khung giờ bảo trì của bạn, đừng để mặc định.
Nếu máy lấy update qua WSUS nội bộ thay vì Microsoft Update trực tiếp, cần đặt hai nhóm khoá ở hai nhánh registry khác nhau — đây là chỗ rất hay làm sai:
WUServervàWUStatusServer(địa chỉ máy chủ WSUS) → đặt ởHKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdateUseWUServer = 1(công tắc bật dùng WSUS) → đặt ở nhánh conHKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU
Đặt UseWUServer nhầm lên khoá cha thì máy vẫn lặng lẽ lấy update thẳng từ Microsoft Update mà không báo lỗi gì — kiểm bằng Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" | Select-Object UseWUServer.
(b) Lớp thực thi tức thời (dùng khi cần chủ động quét/cài ngay, không chờ lịch): Microsoft cung cấp chính thức Windows Update Agent (WUA) API dạng COM — script mẫu không được hỗ trợ nhưng bản thân API thì có:
$phien = New-Object -ComObject Microsoft.Update.Session
$timKiem = $phien.CreateUpdateSearcher()
$ketQua = $timKiem.Search("IsInstalled=0 and Type='Software' and IsHidden=0")
$danhSach = New-Object -ComObject Microsoft.Update.UpdateColl
foreach ($u in $ketQua.Updates) {
if (-not $u.EulaAccepted) { $u.AcceptEula() }
$null = $danhSach.Add($u)
}
$taiVe = $phien.CreateUpdateDownloader()
$taiVe.Updates = $danhSach
$null = $taiVe.Download()
$caiDat = $phien.CreateUpdateInstaller()
$caiDat.Updates = $danhSach
$ketQuaCai = $caiDat.Install()
$ketQuaCai.ResultCode # 2 = thành công
$ketQuaCai.RebootRequiredScript này phải chạy ngay trên máy đó (console, iDRAC/iLO, hoặc RDP) — bộ API này không hoạt động qua PowerShell Remoting từ xa. Muốn tự động hoá định kỳ, bọc nó vào một Scheduled Task chạy cục bộ bằng Register-ScheduledTask, không cố gọi nó qua Invoke-Command từ máy khác.
Với môi trường nhiều máy chủ, cách chuẩn hơn cả hai lớp trên là quản lý tập trung qua WSUS hoặc Windows Admin Center — đủ khả năng duyệt/nhóm/báo cáo bản vá, thay vì tự ghép registry cho từng máy.
Cách xác nhận:
Get-HotFix | Sort-Object InstalledOn -Descending | Select-Object -First 5 HotFixID, InstalledOn
Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\WindowsUpdate\AU" |
Select-Object NoAutoUpdate, AUOptions, ScheduledInstallDay, ScheduledInstallTime1.2 Windows Firewall — mặc định từ chối, chỉ mở đúng port cần
Việc cần làm: Bật tường lửa trên cả ba profile (Domain, Private, Public), đặt mặc định chặn inbound, chỉ mở đúng những port dịch vụ thật sự cần.
Vì sao: Đây là control đầu tiên trong nhóm "giảm bề mặt tấn công" của mọi baseline — kể cả baseline chính thức mới nhất của Microsoft cho Windows Server 2025 cũng liệt nó lên đầu (xem mục 1.5): "enables the Windows Firewall on all profiles with a default-deny stance for inbound traffic."
Đây là lệnh có khả năng gây gián đoạn cao nhất trong toàn Mục 1. Rất nhiều máy chủ Windows ở SME Việt Nam đang chạy với Windows Firewall tắt hẳn — dịch vụ chạy được là vì tường lửa tắt, không hề có luật allow nào tồn tại. Bật lệnh dưới đây lên một máy như vậy: inbound mặc định chuyển sang Block, và mọi dịch vụ chưa có luật allow rõ ràng chết ngay lập tức — SQL, web, file share, ứng dụng nghiệp vụ, WinRM, backup agent... Phiên RDP đang mở thường vẫn sống (kết nối đã thiết lập từ trước), nên bạn không thấy gì bất thường ngay lúc chạy lệnh — sự cố chỉ lộ ra khi người dùng khác gọi báo mất dịch vụ, hoặc khi bạn đóng phiên RDP và không mở lại được. Liệt kê cổng đang nghe TRƯỚC, mở đủ luật allow cho từng cổng đó, và đặt lưới an toàn (xem mục 2.5) rồi mới bật.
Cách thực thi:
# TRƯỚC KHI BẬT: liệt kê mọi cổng đang thực sự có dịch vụ nghe, để biết cái gì sắp chết
Get-NetTCPConnection -State Listen | Select-Object LocalAddress, LocalPort, OwningProcess |
Sort-Object LocalPort | Format-Table -AutoSize
Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction # ghi lại để lùi
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled True `
-DefaultInboundAction Block -DefaultOutboundAction Allow -NotifyOnListen True -LogBlocked True
# Mở đúng port cần, ví dụ HTTPS cho web server — ĐỔI port/tên cho đúng dịch vụ thật đang chạy
New-NetFirewallRule -DisplayName "Web - HTTPS" -Direction Inbound -Protocol TCP -LocalPort 443 -Action AllowCách xác nhận:
Get-NetFirewallProfile | Select-Object Name, Enabled, DefaultInboundAction
Get-NetFirewallRule -Direction Inbound -Action Allow -Enabled True |
Select-Object DisplayName, @{n='Port';e={($_ | Get-NetFirewallPortFilter).LocalPort}}Đối chiếu danh sách port đang mở với danh sách dịch vụ thật sự cần — luật nào không giải thích được lý do tồn tại thì tắt.
Đường lùi nếu mất dịch vụ ngoài dự kiến:
Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False1.3 Windows Defender — bật đủ lớp, không chỉ real-time
Việc cần làm: Bật Real-time Protection, cloud-delivered protection, và bảo vệ khỏi ứng dụng không mong muốn (PUA).
Vì sao: Nhiều máy chủ chỉ bật mỗi real-time protection theo mặc định rồi coi như xong, trong khi cloud-delivered protection (MAPS) mới là lớp giúp Defender nhận diện mã độc mới chưa có signature.
Cách thực thi:
Set-MpPreference -DisableRealtimeMonitoring $false
Set-MpPreference -MAPSReporting Advanced
Set-MpPreference -SubmitSamplesConsent SendSafeSamples
Set-MpPreference -PUAProtection Enabled
Set-MpPreference -ScanScheduleDay Everyday -ScanScheduleQuickScanTime 02:00:00
Update-MpSignatureTamper Protection không có lệnh PowerShell hay registry nào bật/tắt được — đây là chủ đích thiết kế của Microsoft để mã độc không thể tự vô hiệu hoá Defender bằng script. Chỉ bật qua ứng dụng Windows Security hoặc Intune. Đừng tốn thời gian tìm cmdlet cho việc này.
Cách xác nhận:
Get-MpComputerStatus | Select-Object RealTimeProtectionEnabled, AMServiceEnabled, AntispywareSignatureAge, IsTamperProtected
Get-MpPreference | Select-Object MAPSReporting, PUAProtection1.4 Tắt dịch vụ và tính năng không cần thiết
Việc cần làm: Rà những role/feature/service đang cài mà không có lý do vận hành thật, đặc biệt những thứ từng là nguồn khai thác lớn.
Vì sao: CIS Benchmark cho Windows Server xếp Print Spooler vào diện phải tắt trên máy không làm print server và trên Domain Controller — đây chính là dịch vụ đứng sau lỗ hổng PrintNightmare (CVE-2021-34527) từng bị khai thác diện rộng. Nguyên tắc chung: giảm số dịch vụ đang nghe = giảm số đường tấn công khả dụng.
Cách thực thi:
# Xem toàn bộ role/feature đang cài để đối chiếu với nhu cầu thật
Get-WindowsFeature | Where-Object Installed | Select-Object Name, DisplayName
# Tắt Print Spooler trên máy KHÔNG làm print server (bắt buộc trên Domain Controller theo CIS)
Stop-Service -Name Spooler -Force
Set-Service -Name Spooler -StartupType Disabled
# Gỡ PowerShell 2.0 engine — CHỈ áp dụng cho Windows Server 2019/2022.
# Windows Server 2025 (bản 9/2025 trở đi) đã gỡ hẳn engine này khỏi hệ điều hành —
# lệnh dưới đây sẽ báo lỗi vì feature không còn tồn tại, bỏ qua bước này trên WS2025.
Disable-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root -NoRestartVì sao gỡ PowerShell 2.0 (không chỉ vì cũ): engine này ra đời năm 2009 — trước cả AMSI lẫn Script Block Logging (hai thứ chỉ có từ PowerShell 5). Vì vậy mọi lệnh chạy qua powershell.exe -Version 2 đều không bị ghi log 4104 và không bị AMSI soi — đây chính là đường mã độc dùng để vô hiệu hoá mục 3.1 và 3.2 của bài này. Giữ engine v2 trên máy tức là tự để lại cửa sau cho chính hai biện pháp bạn vừa bật ở Mục 3. Microsoft cũng đã ngừng hỗ trợ và đang gỡ dần PowerShell 2.0 khỏi các bản Windows mới với lý do chính thức "dọn mã cũ, giảm độ phức tạp của hệ sinh thái PowerShell và cải thiện bảo mật Windows".
Tắt Print Spooler trên máy đang thật sự chia sẻ máy in (kể cả in ra PDF qua driver ảo) sẽ làm mọi tác vụ in/print-to-file trên máy đó ngừng hoạt động. Kiểm tra ai đang dùng trước khi tắt diện rộng — không suy đoán.
Cách xác nhận:
Get-Service -Name Spooler | Select-Object Name, Status, StartType
Get-WindowsOptionalFeature -Online -FeatureName MicrosoftWindowsPowerShellV2Root | Select-Object State1.5 Áp Microsoft Security Baseline — PowerShell native (WS2025) hoặc LGPO (WS2019/2022)
Việc cần làm: Thay vì tự tay chỉnh hàng trăm setting rời rạc, áp bộ baseline khuyến nghị chính thức của Microsoft — một lần, đúng theo vai trò máy chủ (Domain Controller / Member Server / Workgroup).
Vì sao: Baseline hiện tại của Microsoft cho Windows Server 2025 (Member Server) gồm 344 setting, Domain Controller 347 setting — tự cấu hình tay từng dòng gần như chắc chắn bỏ sót và không nhất quán giữa các máy.
Cảnh báo về thứ tự đọc bài: baseline này tự động bật sẵn nhiều setting mà Mục 4 của bài sẽ giải thích kỹ — cụ thể là bắt buộc ký số SMB (cả server lẫn client) và bắt buộc ký số LDAP trên bản Domain Controller, cộng thêm tắt hẳn LLMNR/NetBIOS (xem mục 4.6). Nếu bạn áp mục 1.5 trước khi đọc mục 4, và môi trường có NAS/Samba/thiết bị cũ đang nói chuyện qua SMB hoặc LDAP đơn giản, các thiết bị đó mất kết nối ngay khi máy khởi động lại — trước khi bạn kịp đọc cảnh báo "kiểm tra thiết bị cũ trước" ở mục 4.1 và cách theo dõi Event 2887 ở mục 4.3. Nếu môi trường có thiết bị cũ, đọc trước mục 4.1 và 4.3, rồi mới quay lại chạy lệnh dưới đây.
Cách áp phụ thuộc phiên bản Windows Server đang chạy:
| Phiên bản | Công cụ chính thức | Cách áp |
|---|---|---|
| Windows Server 2025 | Module PowerShell Microsoft.OSConfig | Native PowerShell, không cần LGPO/GPO trung gian |
| Windows Server 2019 / 2022 | Security Compliance Toolkit (SCT) + LGPO.exe | Tải baseline GPO từ Microsoft, áp bằng LGPO.exe (local) hoặc import vào GPO domain |
Điều kiện bắt buộc trước khi chạy: đã hoàn thành bước 2 của Bước 0 (Backup-GPO -All cho domain, và với máy chạy local — LGPO.exe /b) — đổi cùng lúc 344/347 setting mà không có bản backup thì không có đường lùi.
Cách thực thi — Windows Server 2025 (OSConfig, khuyến nghị nếu đang chạy bản này):
# 1. Cài module (yêu cầu PowerShell chạy với quyền admin)
Install-Module -Name Microsoft.OSConfig -Scope AllUsers -Force
# 2. Áp baseline đúng vai trò của máy — CHỌN ĐÚNG MỘT DÒNG theo vai trò thật
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/MemberServer -Default
# Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/DomainController -Default
# Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/WorkgroupMember -Default
Restart-ComputerĐây là thay đổi rủi ro cao nhất toàn bài — 344/347 setting cùng lúc, cộng một lần khởi động lại bắt buộc.
Theo tài liệu chính thức: sau khi áp baseline, bạn buộc phải đổi lại mật khẩu Administrator cục bộ (chính sách mật khẩu mới yêu cầu tối thiểu 14 ký tự đủ độ phức tạp) với máy Member server/Workgroup — đổi mật khẩu và đăng nhập thử thành công qua console TRƯỚC KHI chạy Restart-Computer, đừng để tới sau khi khởi động lại mới đổi. Baseline cũng tắt copy/paste file qua phiên RDP theo mặc định — đây là một bước siết có chủ đích (chuyển hướng ổ đĩa qua RDP là một trong những đường phổ biến nhất để đưa công cụ lên máy chủ và mang dữ liệu ra), chỉ bật lại nếu vận hành thật sự cần và ghi vào hồ sơ ngoại lệ:
Set-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/MemberServer `
-Setting RemoteDesktopServicesDoNotAllowDriveRedirection -Value 0rồi khởi động lại máy.
Ngưỡng khoá tài khoản: baseline này tự đặt LockoutThreshold = 3 — khác với khuyến nghị tổng quát ở mục 2.4 (10 lần). Đọc cảnh báo hoà giải ở đầu mục 2.4 trước khi đổi bất cứ thứ gì liên quan tới chính sách khoá tài khoản trên máy đã áp baseline này. Nếu đang dùng song song một cơ chế cấu hình khác (GPO domain chẳng hạn) chỉnh cùng những setting này, hai nguồn sẽ liên tục giành nhau ghi đè (drift control) — chọn một nguồn duy nhất.
Đường lùi nếu cần gỡ baseline (Windows Server 2025):
Remove-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/MemberServer
Restart-ComputerLưu ý theo đúng tài liệu chính thức: lệnh gỡ này đòi hỏi khởi động lại máy mới có hiệu lực (đã thêm Restart-Computer ở trên — bản thân lệnh Remove không tự khởi động lại), và dù chạy xong cũng không đảm bảo khôi phục đúng cấu hình cũ — "during the removal process, when OSConfig reverts security settings, it can't guarantee that these settings return to their premanaged configuration", hành vi này giống hệt cách baseline Intune hoạt động. Đây là lý do bản backup Local GPO (LGPO.exe /b) ở Bước 0 vẫn cần thiết dù đã có lệnh Remove chính thức.
Cách thực thi — Windows Server 2019/2022 (SCT + LGPO.exe):
1. Tải gói Security Compliance Toolkit từ Microsoft Download Center (chọn đúng bản Windows Server tương ứng), giải nén — bên trong có thư mục GPOs (bản backup GPO) và công cụ LGPO.exe.
2. Áp cho một máy đơn lẻ (local policy):
& "C:\SCT\LGPO.exe" /g "C:\SCT\GPOs\MSFT Windows Server 2022 - Member Server"
gpupdate /forceĐường lùi (local policy): dùng đúng bản Local GPO đã backup ở Bước 0 (LGPO.exe /b "D:\Backup\LocalGPO" ...):
& "C:\SCT\LGPO.exe" /g "D:\Backup\LocalGPO\{ten-thu-muc-backup-vua-tao}"
gpupdate /force3. Áp cho toàn domain (import vào GPO, khuyến nghị cho nhiều máy):
New-GPO -Name "CONGTY - MSFT Baseline WS2022 Member Server" |
Import-GPO -BackupGpoName "MSFT Windows Server 2022 - Member Server" `
-Path "C:\SCT\GPOs" `
-TargetName "CONGTY - MSFT Baseline WS2022 Member Server"Đường lùi (domain): gỡ link GPO vừa tạo khỏi OU trong Group Policy Management Console, hoặc Restore-GPO bản backup domain đã tạo ở Bước 0.
Sau đó liên kết (link) GPO vừa tạo vào đúng OU chứa các máy chủ cần áp — bước link OU không có cmdlet chuyên biệt gọn hơn thao tác trong Group Policy Management Console, nên làm qua GUI ở bước liên kết cuối này.
Cách xác nhận:
# Windows Server 2025
Get-OSConfigDesiredConfiguration -Scenario SecurityBaseline/WindowsServer/2025/MemberServer |
ft Name, @{n="Status";e={$_.Compliance.Status}} -AutoSize
# Windows Server 2019/2022 — Policy Analyzer (đi kèm SCT) so sánh baseline đã import với thực tế đang áp2. Quản lý danh tính & truy cập
2.1 Mô hình phân quyền theo tầng (Enterprise Access Model)
Việc cần làm: Không dùng một tài khoản Domain Admin để vừa quản trị Active Directory, vừa quản lý máy chủ ứng dụng, vừa đăng nhập máy trạm thường ngày.
Vì sao: Microsoft nêu rõ trong tài liệu chính thức về mô hình truy cập doanh nghiệp (kế thừa từ AD Tier Model cũ, nay gọi là Enterprise Access Model): chia thành ba mặt phẳng theo thứ bậc — Control plane (quản trị danh tính/AD toàn hệ thống, chính là Tier 0 mở rộng), Management plane (quản trị hạ tầng CNTT toàn doanh nghiệp), Data/Workload plane (quản trị từng ứng dụng/workload riêng) — cộng hai lối truy cập User access (người dùng, gồm cả B2B/B2C) và App access (API/tài khoản dịch vụ). Nguyên tắc cốt lõi: tài khoản có quyền ở mặt phẳng cao hơn không bao giờ đăng nhập vào máy thuộc mặt phẳng thấp hơn — vì nếu máy thấp hơn bị chiếm, kẻ tấn công lấy được token/hash của tài khoản cấp cao đang đăng nhập ở đó, leo thang ngay lên toàn hệ thống.
Cách thực thi: Đây là mô hình tổ chức, không phải một lệnh bật/tắt. Áp dụng cụ thể bằng cách:
- Tách riêng tài khoản: một người dùng có tài khoản riêng cho từng lớp (ví dụ
nva.admin-t0quản trị AD,nva.admin-t1quản trị máy chủ,nvadùng hàng ngày) — không dùng chung một tài khoản cho nhiều lớp. - Nhóm AD riêng cho từng lớp, gán quyền qua Group Policy theo nguyên tắc Deny logon chéo lớp — ví dụ tài khoản Tier 1 bị Deny log on locally / Deny log on through Remote Desktop Services trên các máy Tier 0.
- Kết hợp với Windows LAPS (mục 2.2) để không có tài khoản admin cục bộ nào dùng chung mật khẩu xuyên các lớp.
Cách xác nhận: Rà danh sách thành viên nhóm Domain Admins/Enterprise Admins — càng ít tài khoản càng tốt, và không có tài khoản nào trong đó đang dùng để đăng nhập máy trạm hàng ngày:
Get-ADGroupMember -Identity "Domain Admins" -Recursive | Select-Object Name, SamAccountName2.2 Windows LAPS — mật khẩu Administrator cục bộ đổi tự động, mỗi máy một mật khẩu riêng
Việc cần làm: Loại bỏ tình trạng dùng chung một mật khẩu Administrator cục bộ cho mọi máy chủ — Windows LAPS tự sinh mật khẩu ngẫu nhiên riêng cho từng máy, tự xoay vòng, lưu có kiểm soát trong Active Directory (hoặc Entra ID).
Vì sao: Một mật khẩu Administrator cục bộ dùng chung là con đường di chuyển ngang (lateral movement) nhanh nhất — chiếm được một máy là đăng nhập admin được vào toàn bộ máy còn lại. Windows LAPS là công cụ chính thức của Microsoft giải quyết đúng vấn đề này, kế thừa và thay thế legacy Microsoft LAPS.
Cách thực thi:
Mở rộng schema AD là thay đổi vĩnh viễn cho cả forest — không có lệnh hoàn tác. Thuộc tính schema chỉ có thể đánh dấu "defunct" (ngừng dùng), không bao giờ xoá hẳn được. Chạy đúng trên Schema Master, đảm bảo đã có backup System State của Domain Controller giữ vai trò đó, và đã thử trước trong lab nếu có điều kiện.
# 1. Mở rộng schema AD — CHỈ CHẠY MỘT LẦN cho cả forest, cần quyền Schema Admin
Update-LapsADSchema
# 2. Cấp quyền cho máy tự cập nhật mật khẩu của chính nó lên OU chứa các máy chủ — ĐỔI tên OU cho đúng
Set-LapsADComputerSelfPermission -Identity "OU=MayChu,DC=congty,DC=local"
# 3. Cấp quyền ĐỌC mật khẩu cho đúng nhóm quản trị viên được phép — ĐỔI tên nhóm cho đúng
Set-LapsADReadPasswordPermission -Identity "OU=MayChu,DC=congty,DC=local" -AllowedPrincipals "CONGTY\ServerAdmins"
# 4. Cấp quyền đặt lại thời hạn hết hạn mật khẩu cho cùng nhóm đó
Set-LapsADResetPasswordPermission -Identity "OU=MayChu,DC=congty,DC=local" -AllowedPrincipals "CONGTY\ServerAdmins"Quyền ĐỌC không tự động là quyền GIẢI MÃ. Trên domain có mức chức năng (DFL) 2016 trở lên, Windows LAPS mặc định mã hoá mật khẩu trước khi lưu vào AD (ADPasswordEncryptionEnabled = True theo mặc định). Khi đó nhóm bạn vừa cấp quyền đọc ở bước 3 vẫn có thể gặp lỗi giải mã, vì quyền giải mã do một setting hoàn toàn khác quyết định: ADPasswordEncryptionPrincipal — mặc định chỉ Domain Admins. Cấu hình thêm setting này ở bước 5 dưới đây, trỏ đúng nhóm quản trị bạn muốn. Đừng "chữa" bằng cách nhét nhóm đó vào Domain Admins — làm vậy phá thẳng mô hình phân tầng vừa dạy ở mục 2.1.
5. Bật chính sách qua GPO — mở Group Policy Management Editor → Computer Configuration → Policies → Administrative Templates → System → LAPS, cấu hình setting BackupDirectory = 2 (lưu vào Active Directory — giá trị 1 là lưu vào Microsoft Entra ID, 0 là tắt). Đồng thời cấu hình ADPasswordEncryptionPrincipal = chính nhóm quản trị bạn vừa cấp quyền đọc ở bước 3 (ví dụ CONGTY\ServerAdmins) — bỏ qua setting này thì nhóm đó đọc được bản ghi nhưng không giải mã được mật khẩu. Các giá trị mặc định còn lại của LAPS đã đủ chặt: PasswordLength = 14, PasswordComplexity = 4 (chữ hoa + chữ thường + số + ký tự đặc biệt), PasswordAgeDays = 30 — chỉ cần chỉnh nếu chính sách công ty yêu cầu khác.
Kiểm nhanh sau khi cấu hình xong: Get-LapsADPassword -Identity "TENMAYCHU" — xem hai trường Source (có phải EncryptedPassword không) và DecryptionStatus (phải là Success).
Nếu template LAPS.admx chưa từng được thêm vào GPO Central Store, mục System → LAPS sẽ không xuất hiện trong Group Policy Management Editor. Phải chép thủ công %windir%\PolicyDefinitions\LAPS.admx (và file .adml ngôn ngữ tương ứng) vào Central Store trước.
Lấy mật khẩu khi cần đăng nhập admin cục bộ vào một máy:
# Mặc định (KHUYẾN NGHỊ) — không in mật khẩu ra màn hình, trả về dạng SecureString:
$ket = Get-LapsADPassword -Identity "TENMAYCHU"
$ket.Password # System.Security.SecureString — không đọc được bằng mắt, dùng trực tiếp để tạo credential khi cần-AsPlainText in mật khẩu ra màn hình dạng chữ thường — theo đúng cảnh báo chính thức của Microsoft, tham số này "exposes the returned clear-text password to casual viewing and may pose a security risk... should be used with caution and only in support or testing situations". Nếu bạn đã bật Transcription ở mục 3.1, mật khẩu này còn bị ghi nguyên văn vào file transcript trên đĩa — đúng thứ LAPS được sinh ra để chống (xem cảnh báo ở mục 3.1). Chỉ dùng -AsPlainText khi thật sự cần đọc bằng mắt, và tránh dùng trong phiên đang bật Transcription:
Get-LapsADPassword -Identity "TENMAYCHU" -AsPlainTextXoay vòng mật khẩu ngay lập tức (dùng khi nghi ngờ máy vừa bị lộ mật khẩu — không phải thao tác thường xuyên):
Reset-LapsPasswordCách xác nhận:
Get-LapsADPassword -Identity "TENMAYCHU"
# PasswordUpdateTime phải là ngày gần đây, ExpirationTimestamp phải còn hạn2.3 Hardening tài khoản Administrator mặc định
Việc cần làm: Đổi tên và/hoặc vô hiệu hoá tài khoản Administrator có sẵn — kết hợp với LAPS ở mục 2.2 thay vì dùng riêng lẻ.
Vì sao: Tài khoản Administrator mặc định là mục tiêu dò mật khẩu đầu tiên của mọi công cụ tấn công tự động, vì tên tài khoản luôn cố định bất kể tên máy hay tên công ty.
Cách thực thi — qua Local Security Policy / GPO (đây là setting an toàn không có cmdlet PowerShell chuyên biệt tương đương trực tiếp — cấu hình qua secedit/GPO là đường chính thức của Microsoft cho hai setting Security Options này):
Computer Configuration → Windows Settings → Security Settings → Local Policies → Security Options
- Accounts: Rename administrator account → đặt tên khác, không theo mẫu dễ đoán.
- Accounts: Administrator account status → Disabled — chỉ sau khi bạn đã tạo một tài khoản admin cục bộ thứ hai, đã trỏ LAPS sang tài khoản đó, và đã đăng nhập thử thành công bằng nó. Windows LAPS mặc định quản lý đúng tài khoản Administrator dựng sẵn (SID đuôi
-500) — disable nó mà quên đổi mục tiêu của LAPS trước sẽ khiến máy không còn admin cục bộ nào dùng được.
Bắt buộc đúng thứ tự — làm ngược sẽ mất sạch quyền admin cục bộ của máy đó:
# 1. Tạo tài khoản admin cục bộ dự phòng (ĐỔI tên, đừng dùng đúng tên dễ đoán)
New-LocalUser -Name "svc-backupadmin" -Description "Admin cuc bo du phong do LAPS quan ly"
Add-LocalGroupMember -Group "Administrators" -Member "svc-backupadmin"
# 2. Trỏ LAPS sang tài khoản này qua GPO:
# Computer Configuration -> Administrative Templates -> System -> LAPS ->
# "Name of administrator account to manage" = svc-backupadmin
# BẮT BUỘC: tài khoản phải TỒN TẠI SẴN trên máy, LAPS không tự tạo.
# 3. Ép LAPS xoay mật khẩu ngay rồi ĐĂNG NHẬP THỬ THÀNH CÔNG bằng tài khoản mới
Invoke-LapsPolicyProcessing
Get-LapsADPassword -Identity "TENMAYCHU" # xác nhận PasswordUpdateTime vừa đổi
# 4. CHỈ KHI bước 3 đăng nhập thử thành công mới disable tài khoản dựng sẵn ở Local Security Policy
# ĐƯỜNG LÙI (chạy từ console/iDRAC bằng một tài khoản admin còn sống):
Enable-LocalUser -Name "Administrator"
# hoac: net user Administrator /active:yesĐổi tên tài khoản không đổi SID của nó — SID của Administrator mặc định luôn có đuôi cố định (-500), kẻ tấn công dò được SID vẫn tấn công được bằng SID thay vì bằng tên. Đổi tên chỉ chặn được công cụ dò theo tên, không phải toàn bộ lớp tấn công. Đây là lý do LAPS (mục 2.2) mới là lớp bảo vệ chính, đổi tên chỉ là lớp phụ.
Cách xác nhận:
Get-LocalUser | Where-Object SID -like "*-500" | Select-Object Name, Enabled, SID2.4 Chính sách mật khẩu và khoá tài khoản
Việc cần làm: Đặt độ dài mật khẩu tối thiểu đủ mạnh và ngưỡng khoá tài khoản hợp lý — không quá lỏng (dễ brute-force) cũng không quá chặt (tự biến thành công cụ DoS nhắm vào chính tài khoản admin của bạn).
Vì sao: Microsoft khuyến nghị độ dài tối thiểu 14 ký tự cho chính sách mật khẩu doanh nghiệp. Nhưng cần nói đúng: Microsoft không coi độ dài là yếu tố quan trọng nhất — theo hướng dẫn chính thức, việc quan trọng nhất là chặn các mật khẩu dễ đoán (banned password list) kết hợp bắt buộc MFA; Microsoft thậm chí cảnh báo rằng càng nhiều ràng buộc hình thức (độ dài, ký tự đặc biệt, đổi mật khẩu định kỳ) thì người dùng càng có xu hướng chọn mật khẩu theo khuôn dễ đoán hơn, dễ bị dò/bẻ khoá hơn. Đặt 14 ký tự là mức nền hợp lý, không phải lớp phòng thủ chính.
Với khoá tài khoản, tài liệu chính thức nêu rõ: đặt ngưỡng quá thấp có thể bị lợi dụng ngược — kẻ tấn công cố tình gõ sai mật khẩu nhiều lần để khoá tài khoản hợp lệ của chính bạn, gây gián đoạn dịch vụ. Microsoft Security Compliance Toolkit chốt bộ ba 10 / 15 / 15: khoá sau 10 lần sai, khoá 15 phút, bộ đếm reset sau 15 phút. Microsoft nói thẳng đây là lựa chọn "phải chọn một con số cho baseline" chứ không phải chân lý tuyệt đối — mỗi tổ chức tự cân theo rủi ro của mình.
Nếu bạn đã áp Microsoft Security Baseline (OSConfig) cho Windows Server 2025 ở mục 1.5: baseline đó đã tự đặt ngưỡng khoá 3 lần / 15 phút — thấp hơn nhiều so với 10/15/15 khuyến nghị chung ở trên. Microsoft chọn con số 3 vì baseline WS2025 có thêm cơ chế SMB authentication rate limiter làm chậm việc dò mật khẩu ở tầng khác, nên không cần ngưỡng khoá cao để bù. Đừng chạy Set-ADDefaultDomainPasswordPolicy đè lên nếu đã áp baseline đó — OSConfig có cơ chế chống trôi cấu hình (drift control), sẽ tự kéo ngưỡng về 3, còn lệnh dưới đây kéo về 10 — hai bên giành nhau ghi đè liên tục. Muốn đổi ngưỡng khi đã dùng OSConfig, đổi trong chính OSConfig (-Setting), không đổi từ ngoài. Nội dung dưới đây áp dụng cho domain chưa áp baseline OSConfig (Windows Server 2019/2022, hoặc WS2025 chưa dùng OSConfig).
Cách thực thi (domain-wide, dùng module Active Directory):
Set-ADDefaultDomainPasswordPolicy -Identity "congty.local" `
-MinPasswordLength 14 `
-ComplexityEnabled $true `
-ReversibleEncryptionEnabled $false `
-LockoutThreshold 10 `
-LockoutDuration 00:15:00 `
-LockoutObservationWindow 00:15:00Đặt LockoutThreshold quá thấp (ví dụ 1-2) trên một domain có ứng dụng/tác vụ đang lưu mật khẩu cũ (ứng dụng cũ, tác vụ đã lên lịch dùng credential đã đổi) sẽ khiến chính bạn hoặc tài khoản dịch vụ bị khoá liên tục — đôi khi khoá luôn tài khoản admin bạn đang dùng để sửa lỗi. Trước khi siết ngưỡng khoá trên toàn domain, kiểm tra Event ID 4740 (tài khoản bị khoá) trong 2-4 tuần gần nhất để biết mức độ ảnh hưởng thực tế.
Với máy đứng riêng (không domain), cấu hình cùng nội dung qua Local Security Policy (secedit hoặc Computer Configuration → Security Settings → Account Policies) vì Set-ADDefaultDomainPasswordPolicy chỉ áp dụng cho domain.
Cách xác nhận:
Get-ADDefaultDomainPasswordPolicy | Select-Object MinPasswordLength, LockoutThreshold, LockoutDurationĐường lùi nếu khoá nhầm một tài khoản cụ thể:
Unlock-ADAccount -Identity "ten.tai.khoan"Lệnh trên chỉ mở khoá tức thời — nếu nguyên nhân là một dịch vụ/tác vụ đã lên lịch đang lặp lại mật khẩu cũ, tài khoản sẽ bị khoá lại sau vài giây. Truy đúng nguồn gây khoá bằng Event ID 4740 trên Domain Controller (đọc trường Caller Computer Name để biết máy nào đang gõ sai), rồi sửa/dừng đúng nguồn đó.
Nếu cần lùi hẳn chính sách vừa đổi trên toàn domain:
Set-ADDefaultDomainPasswordPolicy -Identity "congty.local" -LockoutThreshold 0 # 0 = tắt khoá tài khoảnCa xấu nhất — khoá hết mọi tài khoản quản trị domain cùng lúc: đây là lý do Bước 0 yêu cầu bạn xác nhận đã biết và thử mật khẩu DSRM nếu máy là Domain Controller. DSRM là đường vào cuối cùng khi không còn tài khoản domain nào đăng nhập được — console/iDRAC không thay thế được nó.
2.5 RDP hardening
Việc cần làm: Bật Network Level Authentication (NLA), giới hạn IP được kết nối, và ưu tiên không mở RDP thẳng ra Internet.
Vì sao: RDP mở ra Internet không giới hạn là một trong những vector tấn công phổ biến nhất nhắm vào Windows Server — công cụ dò mật khẩu tự động quét cổng 3389 liên tục trên toàn Internet. Thứ tự ưu tiên đúng theo thực tế vận hành: VPN/RD Gateway > giới hạn IP > MFA > NLA, đổi cổng 3389 sang cổng khác gần như không tăng bảo mật (công cụ quét cổng tìm ra trong vài phút) nên xếp cuối, có thể bỏ qua.
Cách thực thi — bật NLA:
Set-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' `
-Name "UserAuthentication" -Value 1Không chạy Restart-Service TermService sau lệnh trên. Thiết lập áp dụng cho các phiên kết nối MỚI, không cần khởi động lại dịch vụ — khởi động lại dịch vụ sẽ đá văng ngay chính phiên RDP đang gõ lệnh này.
Giới hạn IP kết nối qua Windows Firewall — đặt lưới an toàn trước khi siết, đúng như đã làm ở phần Bước 0:
# 1. Xác định đúng IP đang dùng để không tự chặn nhầm mình
Get-NetTCPConnection -LocalPort 3389 -State Established | Select-Object RemoteAddress
# 2. Tạo luật cho phép — ĐỔI địa chỉ IP văn phòng thật vào đây trước khi chạy
New-NetFirewallRule -DisplayName "RDP - Chi IP van phong" -Direction Inbound -Protocol TCP `
-LocalPort 3389 -RemoteAddress "203.0.113.10" -Action Allow
# 3. Đặt lưới an toàn — PHẢI làm bước này trước khi siết ở bước 4.
# Tên task có timestamp để không thành "tên chuẩn ai cũng biết"; chạy lại cả khi
# máy vừa khởi động lại giữa chừng (vd sau Restart-Computer ở mục 1.5).
$tenTask = "TAM-CUU-HO-RDP-$(Get-Date -Format yyyyMMdd-HHmm)"
$hanhDong = New-ScheduledTaskAction -Execute 'powershell.exe' `
-Argument '-NoProfile -Command "Enable-NetFirewallRule -DisplayGroup ''Remote Desktop''"'
$thoiDiem = @(
(New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(15)),
(New-ScheduledTaskTrigger -AtStartup) # chay lai neu may reboot truoc khi toi phut 15
)
$caiDat = New-ScheduledTaskSettingsSet -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries
Register-ScheduledTask -TaskName $tenTask -Action $hanhDong -Trigger $thoiDiem `
-Settings $caiDat -User "SYSTEM" -RunLevel Highest -Force
# 4. Giờ mới siết — chặn mọi IP khác ngoài luật vừa tạo
Disable-NetFirewallRule -DisplayGroup "Remote Desktop"
Enable-NetFirewallRule -DisplayName "RDP - Chi IP van phong"Lưới an toàn ở bước 3 KHÔNG khôi phục đúng cấu hình đã siết — nó bật lại nhóm luật RDP mặc định của Windows (RemoteAddress = Any), nghĩa là RDP mở lại cho TOÀN INTERNET, không chỉ cho IP văn phòng. Nếu bạn mất hơn 15 phút để test (rất dễ xảy ra: đổi máy, nối VPN, gõ mật khẩu, nghe điện thoại xen ngang), task sẽ tự chạy trước khi bạn kịp gỡ nó — bạn test RDP thấy vào được, tưởng đã siết IP thành công, nhưng thực tế nhóm luật mặc định cũng đã bật song song. Không có cách nào phát hiện điều này bằng mắt thường — bắt buộc kiểm lại bằng lệnh sau khi gỡ lưới, không chỉ dựa vào việc "RDP vẫn vào được":
# SAU KHI gỡ lưới an toàn, BẮT BUỘC kiểm lại — TÌM LẠI theo tên gần đúng, ĐỪNG dựa
# vào biến $tenTask: bước này thường chạy ở một PHIÊN PowerShell KHÁC (vd sau khi
# máy vừa khởi động lại), biến đã mất thì lệnh với $tenTask rỗng sẽ không gỡ được gì.
Get-ScheduledTask -TaskName "TAM-CUU-HO-RDP-*" | Select-Object TaskName, State
Get-ScheduledTask -TaskName "TAM-CUU-HO-RDP-*" | Unregister-ScheduledTask -Confirm:$false
Get-NetFirewallRule -DisplayGroup "Remote Desktop" | Select-Object DisplayName, Enabled
# Mọi luật trong nhóm này PHẢI là Enabled=False. Nếu thấy True -> lưới an toàn đã kích hoạt,
# chạy lại: Disable-NetFirewallRule -DisplayGroup "Remote Desktop"
Get-NetFirewallRule -DisplayName "RDP - Chi IP van phong" |
Get-NetFirewallAddressFilter | Select-Object RemoteAddress # phải đúng IP đã khai ở bước 2Lưới an toàn là tạm thời — gỡ ngay khi xong, đừng để qua đêm. Nó chạy dưới quyền SYSTEM và hành động của nó là mở RDP cho mọi IP; để quên trên máy chủ tương đương để sẵn một công tắc mở cửa, kể cả sau khi bạn đã xong việc.
Mở một phiên RDP mới từ máy đã cho phép, xác nhận vào được, rồi làm đủ ba bước kiểm lại ở trên trước khi coi việc siết RDP đã xong.
RD Gateway + MFA (khi cần cho nhiều người dùng ngoài văn phòng truy cập, không expose RDP trực tiếp): cài role bằng PowerShell được, nhưng cấu hình chi tiết Connection Authorization Policy (CAP)/Resource Authorization Policy (RAP) và tích hợp MFA qua NPS Extension của Microsoft Entra thì phải làm qua console chuyên dụng (RD Gateway Manager, Network Policy Server) — Microsoft không đóng gói bước này thành một chuỗi PowerShell duy nhất vì đây là cấu hình liên-role (RD Gateway + NPS + Entra MFA):
Install-WindowsFeature RDS-Gateway -IncludeManagementToolsSau khi cài, cấu hình CAP/RAP trong RD Gateway Manager, rồi đăng ký NPS Extension theo hướng dẫn tích hợp MFA của Microsoft Entra.
Cách xác nhận:
Get-ItemProperty -Path 'HKLM:\SYSTEM\CurrentControlSet\Control\Terminal Server\WinStations\RDP-Tcp' -Name UserAuthentication
Get-NetFirewallRule -DisplayName "RDP - Chi IP van phong" | Select-Object DisplayName, Enabled, Action2.6 Just Enough Administration (JEA) — giới hạn đúng lệnh cần, không cấp full admin qua remoting
Việc cần làm: Với những người chỉ cần thực hiện một số thao tác quản trị cụ thể (khởi động lại dịch vụ DNS, xem log, restart một app pool...), cấp một endpoint PowerShell Remoting chỉ chạy được đúng tập lệnh đó — thay vì thêm họ vào nhóm Administrators/Domain Admins.
Vì sao: Microsoft nêu ví dụ điển hình trong tài liệu JEA: DNS Admin cần quyền cục bộ để sửa DNS trên Domain Controller, nhưng cách làm phổ biến là thêm họ vào Domain Admins — vô tình cấp quyền kiểm soát toàn miền chỉ để sửa DNS. JEA giải quyết đúng vấn đề này bằng nguyên tắc Least Privilege, và có thể chạy dưới virtual account tạm thời có quyền admin cục bộ mà người dùng không cần tài khoản admin thật.
Cách thực thi:
Đây là lỗi cấu hình JEA nguy hiểm nhất: một file .psrc viết lỏng biến JEA thành đường LEO THANG ĐẶC QUYỀN, vì -RunAsVirtualAccount khiến mọi lệnh người dùng chạy trong endpoint chạy với quyền Administrator cục bộ (trên Domain Controller là quyền Domain Admin). Người trong nhóm không phải admin (ví dụ DNSAdmins) kết nối vào endpoint — nếu .psrc lỡ cho phép bất kỳ cmdlet nào dưới đây, họ thành admin/SYSTEM ngay lập tức: TUYỆT ĐỐI CẤM khai trong .psrc: Start-Process, Invoke-Expression, Add-Type, New-Object, Invoke-Command, Start-Job, New-Service, Set-Service, Register-ScheduledTask, và mọi cmdlet có tham số -ScriptBlock / -FilePath / -Command. Cũng cấm khai nguyên cả module vào VisibleModules (ví dụ VisibleModules = 'ActiveDirectory') — làm vậy cấp luôn toàn bộ cmdlet của module đó, kể cả Set-ADAccountPassword trên Domain Admins. Chỉ khai đúng cmdlet lẻ, và giới hạn cả tham số bằng ValidateSet khi có thể.
1. Viết Role Capability file (.psrc) liệt kê đúng cmdlet được phép, giới hạn cả tham số — không khai nguyên module. Ví dụ chỉ cho khởi động lại đúng dịch vụ DNS, không phải mọi dịch vụ:
VisibleCmdlets = @{ Name = 'Restart-Service'; Parameters = @{ Name = 'Name'; ValidateSet = 'DNS' } },
'Get-DnsServerResourceRecord',
'Get-WinEvent'đặt file .psrc trong thư mục RoleCapabilities của một module PowerShell.
2. Tạo file cấu hình phiên — bật ghi hình phiên (transcript) ngay từ đầu, nếu không mọi hành động đều hiện ra dưới tên "virtual account" giống hệt nhau, không truy được ai làm gì:
New-PSSessionConfigurationFile -Path .\JEA-DNSAdmin.pssc `
-SessionType RestrictedRemoteServer `
-RunAsVirtualAccount `
-TranscriptDirectory "D:\PSLogs\JEA" `
-RoleDefinitions @{ 'CONGTY\DNSAdmins' = @{ RoleCapabilities = 'DNSAdmin' } }3. Kiểm file cấu hình TRƯỚC khi đăng ký — đừng đăng ký rồi mới phát hiện lỗi:
Test-PSSessionConfigurationFile -Path .\JEA-DNSAdmin.pssc4. Đăng ký endpoint:
Register-PSSessionConfiguration -Path .\JEA-DNSAdmin.pssc -Name 'JEA-DNSAdmin' -ForceLệnh Register-PSSessionConfiguration khởi động lại dịch vụ WinRM ngay lập tức — cắt ngang mọi phiên PowerShell Remoting đang mở trên máy đó, kể cả cấu hình DSC đang chạy dở. Chỉ chạy trong khung giờ bảo trì, không chạy khi đang có phiên remoting production khác đang hoạt động. Unregister-PSSessionConfiguration (đường lùi, xem cuối mục) cũng gây y hệt hiệu ứng này.
Người dùng kết nối vào endpoint đã giới hạn:
Enter-PSSession -ComputerName "MAYCHU-DNS" -ConfigurationName "JEA-DNSAdmin"Cách xác nhận — kiểm NGƯỢC sau khi đăng ký, bằng chính tài khoản người dùng thật (không phải tài khoản admin của bạn):
Get-PSSessionConfiguration | Select-Object Name, EnabledSau đó đăng nhập thử bằng tài khoản trong nhóm DNSAdmins và xác nhận cả ba điều:
Get-Command— chỉ thấy đúng tập lệnh đã khai trong Role Capability, không thấy gì khác.$ExecutionContext.SessionState.LanguageMode— phải làNoLanguage.- Thử
Start-Process notepad.exe(hoặc bất kỳ cmdlet nào không nằm trong danh sách cho phép) — phải bị từ chối. Nếu chạy được,.psrcđang bị hở, gỡ đăng ký ngay.
Đường lùi (lưu ý: cũng khởi động lại WinRM, cắt mọi phiên remoting đang mở):
Unregister-PSSessionConfiguration -Name 'JEA-DNSAdmin' -Force3. Bảo mật chính PowerShell
PowerShell là công cụ dùng để làm mọi việc trong ba mục trên — nhưng chính nó cũng là công cụ hàng đầu kẻ tấn công dùng sau khi xâm nhập (gọi là "living-off-the-land"), vì nó có sẵn trên mọi máy Windows và không bị antivirus truyền thống coi là đáng ngờ. Mục này bảo vệ chính công cụ đó.
3.1 Script Block Logging & Transcription — ghi lại chính xác lệnh gì đã chạy
Việc cần làm: Bật ghi log toàn bộ nội dung script block được thực thi (kể cả script bị obfuscate/mã hoá — PowerShell giải mã ra trước khi log) và bật transcription để có bản ghi đầy đủ input/output từng phiên.
Vì sao: Không có hai lớp log này, khi có sự cố bạn chỉ còn Get-History của phiên hiện tại — biến mất ngay khi đóng cửa sổ. Đây là nguồn bằng chứng gần như duy nhất trả lời được câu hỏi "kẻ tấn công đã chạy lệnh gì trên máy chủ này".
Cách thực thi — qua GPO (Computer Configuration → Administrative Templates → Windows Components → Windows PowerShell, với PowerShell 7.x là nhánh PowerShell Core riêng) hoặc trực tiếp bằng registry:
# Script Block Logging — Windows PowerShell 5.1
$sblPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging"
New-Item -Path $sblPath -Force | Out-Null
Set-ItemProperty -Path $sblPath -Name "EnableScriptBlockLogging" -Value 1 -Type DWord
# Module Logging — Event ID 4103, chân thứ ba của bộ ba log PowerShell tiêu chuẩn
$modPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ModuleLogging"
New-Item -Path $modPath -Force | Out-Null
Set-ItemProperty -Path $modPath -Name "EnableModuleLogging" -Value 1 -Type DWord
New-Item -Path "$modPath\ModuleNames" -Force | Out-Null
Set-ItemProperty -Path "$modPath\ModuleNames" -Name "*" -Value "*" -Type String
# Transcription — ghi lại toàn bộ input/output của mỗi phiên ra file
$transPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\Transcription"
New-Item -Path $transPath -Force | Out-Null
Set-ItemProperty -Path $transPath -Name "EnableTranscripting" -Value 1 -Type DWord
Set-ItemProperty -Path $transPath -Name "IncludeInvocationHeader" -Value 1 -Type DWord
Set-ItemProperty -Path $transPath -Name "OutputDirectory" -Value "D:\PSLogs\Transcripts" -Type StringNếu chạy PowerShell 7.x (PowerShell Core) song song, đường dẫn registry là HKLM:\SOFTWARE\Policies\Microsoft\PowerShellCore\ScriptBlockLogging — hai engine dùng hai khoá riêng, phải bật cả hai nếu máy chạy cả hai bản.
Nới dung lượng log — nếu bỏ qua bước này, hai lớp log trên gần như vô dụng khi cần tra cứu: log Microsoft-Windows-PowerShell/Operational mặc định chỉ khoảng 15 MB, máy chủ bận có thể cuộn vòng ghi đè trong vài giờ — đúng lúc bạn cần tra lại thì bằng chứng đã mất. Nới lên tối thiểu 1 GB, và nếu công ty có hệ thống log tập trung (WEF/SIEM) thì chuyển tiếp log ra ngoài máy:
wevtutil sl Microsoft-Windows-PowerShell/Operational /ms:1073741824Thư mục OutputDirectory của Transcription chứa toàn bộ nội dung lệnh đã chạy — không chỉ mật khẩu gõ nhầm vào dòng lệnh, mà còn mật khẩu LAPS bạn tra bằng -AsPlainText ở mục 2.2, chuỗi kết nối, token API. Đây phải được đối xử như kho bí mật, không phải log thông thường. Đặt quyền NTFS giới hạn chỉ admin/SIEM đọc được, và đẩy về hệ thống giám sát log tập trung (SIEM) ngay trong ngày nếu công ty có — đừng để tích tụ trên chính máy chủ đó, vì máy bị xâm nhập thì log trên đó cũng mất giá trị làm bằng chứng.
Cách xác nhận:
Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging" -Name EnableScriptBlockLogging
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" -MaxEvents 5 |
Where-Object Id -eq 4104 | Select-Object TimeCreated, Message
Get-WinEvent -LogName "Microsoft-Windows-PowerShell/Operational" -MaxEvents 5 |
Where-Object Id -eq 4103 | Select-Object TimeCreated, Message # xác nhận Module Logging đang hoạt độngEvent ID 4104 xuất hiện nghĩa là Script Block Logging đang hoạt động thật, không chỉ bật registry mà không sinh log; 4103 xác nhận Module Logging.
3.2 Constrained Language Mode — giới hạn API nguy hiểm ngay cả khi có người chạy được script
Việc cần làm: Đưa PowerShell về chế độ chỉ cho phép các kiểu dữ liệu/API an toàn, chặn truy cập trực tiếp .NET/COM/Win32 API tuỳ ý.
Vì sao: Ngay cả khi Execution Policy (mục 3.3) chặn chạy script không rõ nguồn gốc, kẻ tấn công vẫn có thể gõ lệnh trực tiếp vào một phiên PowerShell tương tác. ConstrainedLanguage mode giới hạn: Add-Type không load được C# tuỳ ý, New-Object chỉ tạo được các kiểu dữ liệu nằm trong danh sách cho phép — thu hẹp đáng kể những gì một kẻ tấn công (hoặc một script độc hại) làm được dù đã chạy được lệnh.
Cách thực thi — theo đúng tài liệu chính thức, đây KHÔNG phải setting bật tay đơn lẻ mà là hệ quả của một cơ chế khác:
Theo about_Language_Modes của Microsoft: "PowerShell automatically runs in ConstrainedLanguage mode when it's running under a system application control policy" — nghĩa là cách chính thức để bật ConstrainedLanguage trên toàn hệ thống là áp một chính sách kiểm soát ứng dụng (AppLocker hoặc Windows Defender Application Control — WDAC) ở chế độ Enforce. Khi máy chạy dưới chính sách đó, PowerShell tự động vào ConstrainedLanguage cho mọi script không được chính sách tin cậy, và tự động chạy FullLanguage không giới hạn cho script đã được chính sách đó cho phép rõ ràng.
Có một cách khác — đặt biến môi trường hệ thống __PSLockdownPolicy — nhưng Microsoft xác nhận đây là cơ chế không tài liệu hoá chính thức (undocumented), tương đương không được hỗ trợ. Bài này không khuyến nghị dùng nó cho production; AppLocker/WDAC là đường chính thức. Phần thiết lập AppLocker/WDAC chi tiết nằm ở Bài 2 của loạt này (phần Nâng cao).
Cách xác nhận language mode hiện tại của một phiên:
$ExecutionContext.SessionState.LanguageModeKết quả ConstrainedLanguage nghĩa là đang bị giới hạn; FullLanguage là mặc định không giới hạn.
3.3 Execution Policy — chặn chạy script không mong muốn ở lớp ngoài cùng
Việc cần làm: Đặt execution policy tối thiểu là RemoteSigned (chỉ chạy không cần chữ ký với script viết tại chỗ, script tải từ Internet/mạng phải có chữ ký số hợp lệ).
Vì sao: Đây là lớp phòng thủ ngoài cùng, dễ vượt qua nhất (không phải cơ chế bảo mật tuyệt đối — PowerShell tài liệu hoá rõ nó không phải security boundary), nhưng vẫn chặn được phần lớn trường hợp double-click nhầm một file .ps1 tải về từ email/web.
Cách thực thi — qua GPO (áp dụng toàn domain, override mọi cấu hình cục bộ):
Computer Configuration → Administrative Templates → Windows Components → Windows PowerShell → Turn on Script Execution → Enabled, chọn Allow only signed scripts (tương đương AllSigned) hoặc Allow local scripts and remote signed scripts (tương đương RemoteSigned).
Nếu chọn Allow only signed scripts (AllSigned) và áp qua GPO cho toàn domain: mức này chặn mọi script .ps1 chưa ký số, kể cả script vận hành nội bộ của chính bạn — script backup, script giám sát, agent, tác vụ đã lên lịch. Chỉ chọn AllSigned khi công ty đã có hạ tầng ký số nội bộ (chứng chỉ code signing + quy trình ký script trước khi triển khai). Mặc định an toàn và đủ dùng cho phần lớn môi trường là RemoteSigned (script viết tại chỗ chạy không cần ký, script tải từ ngoài mới cần ký). Đường lùi nếu AllSigned làm gãy script nội bộ: đặt GPO Turn on Script Execution về Not Configured rồi gpupdate /force.
Hoặc bằng PowerShell cho một máy đơn lẻ:
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope LocalMachineKhi GPO Turn on Script Execution đã được cấu hình, nó ghi đè mọi execution policy đặt bằng Set-ExecutionPolicy ở toàn bộ scope cục bộ — đừng ngạc nhiên nếu đổi bằng PowerShell không có tác dụng, kiểm tra GPO trước.
Cách xác nhận:
Get-ExecutionPolicy -List3.4 PowerShell Remoting hardening — WinRM qua HTTPS, không để endpoint mặc định mở toang
Việc cần làm: Chuyển WinRM sang chạy trên HTTPS (mã hoá thật ở tầng transport, không chỉ dựa vào Kerberos encryption mặc định), tắt Basic Auth, và giới hạn ai được kết nối vào endpoint PowerShell full quyền mặc định — kết hợp với JEA (mục 2.6) cho các nhóm người dùng không cần full quyền.
Vì sao: Mặc định, WinRM dùng Kerberos để xác thực nên mật khẩu không truyền đi dạng rõ — nhưng bản thân dữ liệu trao đổi trong phiên không được mã hoá bằng một kênh TLS độc lập trừ khi bạn cấu hình thêm. Chuyển sang HTTPS mã hoá toàn bộ kênh, độc lập với cơ chế xác thực.
Cách thực thi:
# 1. Yêu cầu: đã có certificate Server Authentication hợp lệ, CN trùng hostname, chưa hết hạn, KHÔNG self-signed
winrm quickconfig -transport:https
# 2. Xác nhận listener HTTPS đã lên đúng cổng 5986
winrm enumerate winrm/config/listener
# 3. Tắt Basic Auth và cấm truyền dữ liệu không mã hoá
Set-Item WSMan:\localhost\Service\Auth\Basic -Value $false
Set-Item WSMan:\localhost\Service\AllowUnencrypted -Value $false:: 4. Gỡ listener HTTP cũ sau khi đã xác nhận HTTPS hoạt động — KHÔNG làm bước này trước khi kiểm tra bước 2 và 3
winrm delete winrm/config/Listener?Address=*+Transport=HTTP# 5. Mở firewall đúng cổng HTTPS — CHỈ cho dải mạng quản trị, ĐỔI dải cho đúng môi trường thật.
# Nguyên tắc chung: MỌI cổng quản trị (3389, 5985, 5986, 445) đều phải giới hạn nguồn —
# siết RDP (mục 2.5) mà để WinRM mở cho cả Internet là không siết gì cả.
New-NetFirewallRule -DisplayName "WinRM-HTTPS" -Direction Inbound -Protocol TCP -LocalPort 5986 `
-RemoteAddress "10.10.10.0/24" -Action Allow
Disable-NetFirewallRule -DisplayName "Windows Remote Management (HTTP-In)"
# 6. Khoá thêm ở tầng WinRM (lớp 2, độc lập với firewall)
Set-Item WSMan:\localhost\Service\IPv4Filter -Value "10.10.10.0-10.10.10.255"Nếu chưa xác nhận chắc chắn listener HTTPS hoạt động (bước 2, 3) mà đã gỡ listener HTTP (bước 4) hoặc đóng firewall cổng cũ, bạn có thể mất hoàn toàn khả năng PowerShell Remoting vào máy này từ xa cho tới khi ra tận nơi sửa qua console. Làm đúng thứ tự, kiểm tra từng bước, đừng gộp lại chạy một lần. Đường lùi nếu HTTPS không hoạt động và bạn còn console/iDRAC:
winrm quickconfig -force # dựng lại listener HTTP cục bộ
Enable-NetFirewallRule -DisplayName "Windows Remote Management (HTTP-In)"
Set-Item WSMan:\localhost\Service\IPv4Filter -Value "*" # gỡ bộ lọc IP đặt ở bước 6 —
# thiếu dòng này thì dù mở lại
# HTTP vẫn không vào được nếu
# dải IP ở bước 6 khai saiCách xác nhận cuối cùng — thử kết nối thật từ một máy khác trước khi coi là xong:
Test-WSMan -ComputerName "TENMAYCHU" -UseSSL4. Mạng
4.1 SMB Signing — chặn giả mạo gói tin SMB giữa client và server
Việc cần làm: Bắt buộc ký số (signing) cho mọi phiên SMB, cả chiều server lẫn client.
Vì sao: SMB signing xác minh dữ liệu không bị chỉnh sửa trên đường truyền và xác thực đúng danh tính hai đầu — chặn được kiểu tấn công adversary-in-the-middle nhắm vào chính giao thức SMB, cụ thể là NTLM relay vào dịch vụ SMB. Cần nói rõ giới hạn: SMB signing không chặn relay NTLM sang các dịch vụ khác — LDAP, HTTP (ví dụ ADCS), MSSQL — và không chặn việc thu thập/bẻ khoá hash NTLM ở bước trước đó. Muốn bịt các hướng relay khác cần LDAP signing + channel binding (mục 4.3) và tắt LLMNR/NBT-NS (mục 4.6). Đây là control khác với SMB Encryption (mã hoá nội dung) — signing chống giả mạo/toàn vẹn, không nhất thiết mã hoá nội dung.
Cách thực thi:
Set-SmbServerConfiguration -RequireSecuritySignature $true -Force
Set-SmbClientConfiguration -RequireSecuritySignature $true -ForceMáy khách SMB cũ, thiết bị NAS đời cũ, hoặc máy Linux dùng Samba cấu hình cũ không hỗ trợ hoặc không bật sẵn signing sẽ mất kết nối SMB ngay khi bật bắt buộc. Kiểm tra danh sách thiết bị/ứng dụng đang map ổ mạng/dùng file share trên máy chủ này trước khi ép buộc toàn server — giống cách đã làm với SMB Encryption ở bài trước của loạt.
Cách xác nhận:
Get-SmbServerConfiguration | Select-Object RequireSecuritySignature
Get-SmbClientConfiguration | Select-Object RequireSecuritySignatureĐường lùi:
Set-SmbServerConfiguration -RequireSecuritySignature $false -Force4.2 Tắt SMB 1.0
Việc cần làm: Gỡ hẳn giao thức SMB 1.0 — giao thức đứng sau khai thác EternalBlue/WannaCry.
Vì sao: SMB 1.0 không có cơ chế signing/encryption hiện đại và là mục tiêu của nhiều họ mã độc lây lan qua mạng nội bộ. Windows Server hiện đại không cài SMB 1.0 theo mặc định, nhưng máy đã nâng cấp qua nhiều đời hệ điều hành có thể vẫn còn.
Cách thực thi — kiểm tra trước khi gỡ, đừng gỡ mù:
Get-WindowsFeature FS-SMB1
Set-SmbServerConfiguration -AuditSmb1Access $true -Force
Get-WinEvent -LogName Microsoft-Windows-SMBServer/Audit -MaxEvents 50
# Không còn thiết bị nào dùng sau 1-2 tuần theo dõi thì mới gỡ:
Uninstall-WindowsFeature FS-SMB1 -RestartCách xác nhận:
Get-SmbServerConfiguration | Select-Object EnableSMB1Protocol4.3 LDAP signing + channel binding — bảo vệ giao tiếp với Active Directory
Việc cần làm: Bắt buộc ký số cho phiên LDAP (LDAPServerIntegrity) và bật Channel Binding Token cho LDAP qua TLS (LdapEnforceChannelBinding) trên Domain Controller.
Vì sao: Theo khuyến cáo bảo mật chính thức ADV190023 của Microsoft, LDAP không ký số dễ bị replay attack (chặn phiên xác thực rồi dùng lại) và man-in-the-middle. Đáng chú ý: từ Windows Server 2025, LDAP signing được bật bắt buộc theo mặc định cho mọi domain mới dựng — với domain nâng cấp từ bản cũ, cấu hình cũ được giữ nguyên để không gây gián đoạn, nghĩa là bạn vẫn phải tự bật nếu domain đã tồn tại từ trước.
Cách thực thi — LDAP signing, qua GPO (không có cmdlet PowerShell riêng cho setting Security Options này, đường chính thức là GPO):
Trên Default Domain Controllers Policy: Computer Configuration → Policies → Windows Settings → Security Settings → Local Policies → Security Options → Domain controller: LDAP server signing requirements → Require signing.
Với Windows Server 2025, có thêm policy riêng ghi đè: Domain controller: LDAP server signing requirements enforcement — mặc định (Not Configured) đã tương đương Enabled cho domain mới; domain nâng cấp cần bật rõ ràng nếu muốn áp dụng.
Phía client, cấu hình tương ứng tại Network security: LDAP client signing requirements → Require signing.
Cách thực thi — LDAP channel binding, qua registry trên từng Domain Controller (Microsoft xác nhận đây là setting cấu hình qua registry, không có GPO ADMX riêng cho AD DS classic):
$ldapPath = "HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Parameters"
Set-ItemProperty -Path $ldapPath -Name "LdapEnforceChannelBinding" -Value 2 -Type DWordGiá trị 2 = bắt buộc CBT (yêu cầu passed verification cho phiên LDAP qua TLS dùng SASL); 1 = chấp nhận nếu client gửi CBT nhưng không bắt buộc; 0 = tắt.
Bật Require signing hoặc LdapEnforceChannelBinding = 2 ngay lập tức mà chưa rà soát trước sẽ làm mọi client/ứng dụng đang bind LDAP không hỗ trợ signing/CBT mất kết nối tới Active Directory — bao gồm khả năng cả những ứng dụng nghiệp vụ cũ dùng LDAP simple bind. Trước khi bật bắt buộc: theo dõi Event ID 2887 trên Domain Controller trong vài tuần (thống kê số lượng bind không ký mỗi 24 giờ); muốn biết chi tiết IP/tài khoản nào đang bind không ký, bật thêm log chi tiết bằng cách đặt 16 LDAP Interface Events trong HKLM:\SYSTEM\CurrentControlSet\Services\NTDS\Diagnostics lên giá trị 2, sẽ thấy Event ID 2889 liệt kê từng client. Chỉ bật bắt buộc sau khi không còn client nào cần cập nhật, hoặc đã cập nhật xong.
Cách xác nhận: thử bind LDAP đơn giản không ký bằng ldp.exe (Connection → Bind → Simple bind) — nếu cấu hình đúng, sẽ nhận lỗi Ldap_simple_bind_s() failed: Strong Authentication Required, xác nhận domain controller đã từ chối bind không ký. Theo dõi Event ID 2888 để biết số lượng bind bị từ chối mỗi 24 giờ.
4.4 IPsec cho lưu lượng nội bộ — domain isolation / server isolation
Việc cần làm: Bắt buộc xác thực IPsec cho lưu lượng giữa các máy trong domain, để máy không xác thực được (không phải thành viên domain, hoặc không đúng nhóm được phép) không kết nối được vào máy chủ nhạy cảm — dù đã lọt được vào cùng mạng.
Vì sao: Firewall theo port/IP không phân biệt được "máy này có phải domain member hợp lệ không" — một máy lạ cắm vào cùng dải mạng vẫn gửi được gói tin tới đúng port mở. IPsec domain/server isolation thêm một lớp xác thực ở tầng mạng: máy phải chứng minh được danh tính (thường qua Kerberos) trước khi gói tin được chấp nhận, độc lập với việc firewall có cho qua port đó hay không.
Đây là thay đổi có bán kính phá hoại lớn nhất Mục 4 — IPsec hoạt động ở tầng mạng, chặn TẤT CẢ giao thức cùng lúc, không riêng SMB hay LDAP: RDP, WinRM, HTTP, SQL, cả ping. -PolicyStore "congty.local\Domain_Isolation" là một GPO, không phải cấu hình cục bộ — chạy lệnh này là đẩy luật xuống mọi máy mà GPO đó áp tới, không chỉ máy bạn đang gõ lệnh. Cái bẫy nguy hiểm nhất: thiếu danh sách miễn trừ cho DC/DNS/DHCP. Máy phải lấy được vé Kerberos từ Domain Controller để chứng minh danh tính — nhưng nếu luật IPsec đã bắt buộc xác thực cho cả lưu lượng tới DC/DNS/DHCP thì máy không thể nói chuyện với DC để lấy vé, tạo thành vòng lặp chết. Bỏ qua bước miễn trừ này có thể khiến cả domain ngừng đăng nhập được cùng lúc, và gpupdate để sửa cũng không tới được máy nào vì chính kết nối đó cũng đang bị luật IPsec chặn.
Cách thực thi — BẮT BUỘC làm bước miễn trừ TRƯỚC rule chính, rồi triển khai theo giai đoạn:
# BƯỚC 1 - BẮT BUỘC TRƯỚC TIÊN: miễn trừ hạ tầng cốt lõi, nếu không sẽ khoá cả domain
# ĐỔI: IP thật của DC/DNS/DHCP trong môi trường bạn
New-NetIPsecRule -DisplayName "Exempt - DC/DNS/DHCP" -InboundSecurity None -OutboundSecurity None `
-RemoteAddress "10.10.10.5","10.10.10.6" `
-PolicyStore "congty.local\Domain_Isolation"
# BƯỚC 2 - GIAI ĐOẠN 1 (bắt buộc, tối thiểu 1-2 tuần): chỉ ĐỀ NGHỊ, không ép, để đo phạm vi ảnh hưởng
New-NetIPsecRule -DisplayName "Domain Isolation Rule" `
-InboundSecurity Request -OutboundSecurity Request `
-PolicyStore "congty.local\Domain_Isolation"
# BƯỚC 3 - GIAI ĐOẠN 2: chỉ sau khi giai đoạn 1 sạch (không còn máy nào lỗi kết nối), nâng Inbound lên Require:
# New-NetIPsecRule -DisplayName "Domain Isolation Rule" -InboundSecurity Require -OutboundSecurity Request `
# -PolicyStore "congty.local\Domain_Isolation"Request nghĩa là cố gắng xác thực nhưng vẫn cho qua nếu đầu kia không hỗ trợ; Require nghĩa là bắt buộc, không xác thực được thì gói tin bị huỷ. Main mode negotiation mặc định dùng Kerberos v5 để xác thực máy/người dùng, không cần cấp phát certificate riêng nếu đã trong cùng domain. Hướng dẫn chính thức của Microsoft cho domain isolation ghi rõ: "begin operations by using request in and request out behavior until you are sure that all the computers in your IPsec environment are communicating successfully".
Đường lùi:
Remove-NetIPsecRule -DisplayName "Domain Isolation Rule" -PolicyStore "congty.local\Domain_Isolation"Khẩn cấp hơn (nếu không còn gõ lệnh được vào máy nào): gỡ link GPO "Domain_Isolation" khỏi OU trong Group Policy Management Console — tương tự đường lùi cấp domain đã nói ở Bước 0.
Cách xác nhận:
Get-NetIPsecRule -PolicyStore "congty.local\Domain_Isolation" | Select-Object DisplayName, Enabled4.5 Network segmentation cơ bản
Việc cần làm: Đặt máy chủ vào đúng vùng mạng theo mức độ nhạy cảm, và dùng đúng Windows Firewall profile (Domain/Private/Public) tương ứng với vùng đó — không để mọi máy chủ nằm chung một dải mạng phẳng.
Vì sao: Windows Firewall tự nhận diện và áp profile khác nhau tuỳ mạng máy đang kết nối — đây là cơ chế native để một máy chủ có chính sách lỏng hơn khi ở mạng nội bộ tin cậy (Domain) và chặt hơn khi ở mạng ngoài (Public). Kết hợp với IPsec isolation zones (mục 4.4), Microsoft mô tả mô hình phân vùng theo mức độ tin cậy: Boundary Zone (máy cần giao tiếp cả với máy ngoài isolation lẫn trong, chấp nhận cả kết nối không xác thực), Encryption Zone (dữ liệu nhạy cảm nhất, bắt buộc mã hoá IPsec, không chỉ xác thực), và vùng isolation domain thông thường ở giữa.
Cách thực thi: Đây là quyết định kiến trúc (VLAN/subnet nào chứa máy nào) kết hợp thực thi bằng chính hai công cụ đã nói ở trên — Windows Firewall profile (mục 1.2) và IPsec rules (mục 4.4), gán theo đúng OU/nhóm máy tương ứng từng vùng qua GPO riêng cho từng OU.
Cách xác nhận:
Get-NetConnectionProfile | Select-Object InterfaceAlias, NetworkCategoryNetworkCategory phải khớp đúng loại mạng máy chủ đang thực sự kết nối — máy chủ trong mạng nội bộ domain hiện DomainAuthenticated, không phải Public.
4.6 Tắt LLMNR và NetBIOS Name Service (NBT-NS) — bịt nguồn cấp thông tin xác thực cho NTLM relay
Việc cần làm: Tắt hai giao thức phân giải tên dự phòng cũ — Link-Local Multicast Name Resolution (LLMNR) và NetBIOS Name Service (NBT-NS) — trên máy chủ, và lý tưởng là trên toàn bộ máy trạm trong mạng.
Vì sao: Mục 4.1 vừa nhắc tới NTLM relay, và SMB signing chỉ chặn được cú đánh cuối (relay vào SMB) — không chặn được bước ĐẦU của kiểu tấn công này. Khi một máy Windows tìm một tên không phân giải được bằng DNS, nó tự động "hỏi lớn" ra cả mạng nội bộ qua LLMNR (UDP/5355) và NBT-NS (UDP/137) xem có máy nào biết tên đó không. Bất kỳ máy nào trên cùng mạng cũng trả lời được — kể cả máy của kẻ tấn công (công cụ phổ biến nhất cho kiểu tấn công này là Responder). Máy hỏi tin máy trả lời, gửi thẳng thông tin xác thực (NTLM hash) sang máy kẻ tấn công, người đó dùng ngay hash đó để relay sang SMB/LDAP/HTTP khác. Đây là kỹ thuật thu thập thông tin xác thực phổ biến bậc nhất trong mạng nội bộ Windows, chi phí triển khai gần bằng 0, rủi ro gián đoạn thấp — Microsoft Security Baseline cho Windows Server 2025 (mục 1.5 của bài này) cũng tắt sẵn cả hai giao thức này theo mặc định, xác nhận đây không phải khuyến nghị ngoài luồng.
Đã áp Security Baseline WS2025 ở mục 1.5? Có thể không cần làm lại mục này. Baseline đó tắt sẵn cả LLMNR lẫn NBT-NS theo mặc định — nhưng qua cơ chế OSConfig riêng của baseline, không phải qua GPO/registry cổ điển ở dưới đây (đó là lý do "Windows không có administrative template GPO cho NetBIOS" vẫn đúng — hai câu không mâu thuẫn, chỉ là hai cơ chế khác nhau). Kiểm trước khi làm lại: Get-ItemProperty "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" -Name EnableMulticast và Get-CimInstance Win32_NetworkAdapterConfiguration -Filter "IPEnabled = TRUE" | Select TcpipNetbiosOptions — nếu đã đúng giá trị mong muốn thì bỏ qua các lệnh dưới, chỉ cần lệnh xác nhận cuối mục.
Cách thực thi — LLMNR, qua GPO (đường chính thức, có administrative template riêng):
Computer Configuration → Administrative Templates → Network → DNS Client → Turn off Multicast Name Resolution → Enabled.
Hoặc bằng registry cho một máy đơn lẻ:
$dnsClientPath = "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient"
New-Item -Path $dnsClientPath -Force | Out-Null
Set-ItemProperty -Path $dnsClientPath -Name "EnableMulticast" -Value 0 -Type DWordThiết lập có hiệu lực từ chu kỳ đồng bộ GPO tiếp theo (khoảng 90 phút với computer policy) — không cần khởi động lại.
Cách thực thi — NBT-NS: KHÔNG có administrative template GPO riêng, tắt qua WMI hoặc từng adapter:
Khác với LLMNR, Windows không có sẵn setting ADMX để tắt NetBIOS over TCP/IP qua GPO. Cách chính thức của Microsoft là gọi phương thức SetTcpipNetbios của lớp Win32_NetworkAdapterConfiguration (giá trị 2 = DisableNetbios):
Get-CimInstance Win32_NetworkAdapterConfiguration -Filter "IPEnabled = TRUE" | ForEach-Object {
Invoke-CimMethod -InputObject $_ -MethodName SetTcpipNetbios -Arguments @{ TcpipNetbiosOptions = 2 }
}Khác với LLMNR ở trên, bước này CẦN khởi động lại máy mới có hiệu lực — SetTcpipNetbios trả về ReturnValue = 1 ("Reboot required") khi chạy thành công, đừng nhầm đó là lỗi.
Đóng gói thành Startup Script để áp lại mỗi lần khởi động — đây KHÔNG chỉ để "áp hàng loạt", mà bắt buộc vì lý do khác: thiết lập SetTcpipNetbios ghi theo từng adapter, và một card mạng mới lắp, một card ảo sinh ra sau khi chuyển máy ảo, hoặc một card VPN kết nối sau — đều tự bật lại NetBIOS vì các adapter đó chưa từng được lệnh trên chạm tới. Đóng gói qua Startup Script (Computer Configuration → Policies → Windows Settings → Scripts → Startup, vì không có setting ADMX tương đương cho NetBIOS) để lệnh chạy lại mỗi lần khởi động, bắt đúng mọi adapter mới.
Một số ứng dụng/máy in/thiết bị cũ vẫn phụ thuộc phân giải tên qua NetBIOS (đặc biệt máy chưa gia nhập domain, hoặc mạng còn thiết bị Windows rất cũ). Không có cách đo trực tiếp lưu lượng UDP/137 và UDP/5355 nhanh gọn bằng một lệnh PowerShell có sẵn — thay vào đó, làm theo hai bước để giảm rủi ro: (1) tắt LLMNR trước (rủi ro gián đoạn rất thấp, phần lớn mạng hiện đại không còn phụ thuộc), chờ và quan sát; (2) tắt NetBIOS sau, thử trên một nhóm máy nhỏ qua một GPO/Startup Script riêng trước khi áp toàn mạng — cùng tinh thần "đo trước, ép sau" đã áp dụng ở mục 4.2 và 4.3, chỉ khác công cụ đo vì NetBIOS không có audit mode sẵn như SMB1/LDAP.
Đường lùi nếu gây gián đoạn (về lại mặc định):
Set-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" -Name "EnableMulticast" -Value 1 -Type DWord # bật lại LLMNR
Get-CimInstance Win32_NetworkAdapterConfiguration -Filter "IPEnabled = TRUE" | ForEach-Object {
Invoke-CimMethod -InputObject $_ -MethodName SetTcpipNetbios -Arguments @{ TcpipNetbiosOptions = 0 }
} # trả NetBIOS về mặc định (theo cấu hình DHCP)Cách xác nhận:
Get-ItemProperty -Path "HKLM:\SOFTWARE\Policies\Microsoft\Windows NT\DNSClient" -Name EnableMulticast
Get-CimInstance Win32_NetworkAdapterConfiguration -Filter "IPEnabled = TRUE" | Select-Object Description, TcpipNetbiosOptionsTcpipNetbiosOptions = 2 nghĩa là NetBIOS đã tắt trên adapter đó.
Kết Phần 1
Bốn mảng trên — baseline, danh tính/truy cập, bảo vệ chính PowerShell, và hạ tầng mạng — là lớp nền để mọi lớp phòng thủ khác (giám sát, mã hoá, chống ransomware ở Phần 2) có chỗ đứng vững. Nói cho đúng: làm xong Phần 1 không có nghĩa máy chủ đã an toàn tuyệt đối — đây là điều kiện cần, không phải điều kiện đủ. Attacker hiện đại không dừng ở việc dò mật khẩu RDP; chúng khai thác ứng dụng, giả mạo nội bộ, và tồn tại lâu dài sau khi đã vào được. Đó là lý do Phần 2 sẽ nói về cách phát hiện khi những lớp phòng thủ này bị vượt qua.
Bài viết liên quan
STEP Có Thể Hỗ Trợ Gì
Không phải công ty nào cũng có đội IT đủ người để tự làm hết từng bước trên — nhất là khi nhiều lệnh trong bài đổi cấu hình cả domain cùng lúc, mỗi bước đều cần rà soát tác động trước khi chạy để không làm gián đoạn hệ thống đang chạy. Dịch vụ CIO/quản trị hạ tầng thuê ngoài của STEP nhận làm trọn phần hardening này cho khách hàng — từ rà soát hiện trạng, lên kế hoạch triển khai đúng thứ tự an toàn, tới vận hành và theo dõi liên tục sau đó, không cần công ty bạn tuyển thêm người cho việc này.
Thường phản hồi trong vòng vài phút trong giờ làm việc.