Vá Bảo Mật Windows Server Trong 20 Phút — 1 Kịch Bản PowerShell
Một kịch bản PowerShell: kiểm tra trước, vá sau, hoàn tác được bằng một dòng lệnh. Kèm cách mở và ĐÓNG cổng quản lý từ xa đúng chuẩn cho máy chủ Windows.
STEP Technology
Đội kỹ thuật
Hai bài trước nói phải làm gì. Bài này làm luôn.
Loạt Bảo Mật Windows Server Từ Cơ Bản Đến Nâng Cao đi qua tám mảng, hơn sáu mươi mục, mỗi mục có lệnh riêng. Đọc hết là một buổi chiều. Làm hết là vài ngày.
Thực tế thì phần lớn máy chủ Windows của doanh nghiệp Việt Nam đang thiếu đúng một nhóm nhỏ trong số đó — nhóm vừa dễ vá, vừa chặn được phần lớn kiểu tấn công phổ thông. Bài này gom đúng nhóm ấy vào một kịch bản PowerShell duy nhất, chạy xong trong khoảng 20 phút kể cả lúc ngồi đọc kết quả.
- Cách mở cổng quản lý từ xa đúng chuẩn — và đóng lại sau khi dùng. Đây là việc phải làm trước nếu bạn quản máy chủ từ xa, và cũng là lỗi phổ biến nhất ngoài thực tế: mở cho nhanh rồi quên đóng.
- Kịch bản
Invoke-StepHardening.ps1— mặc định chỉ kiểm tra và báo cáo, không đổi gì. Muốn đổi thật phải thêm một tham số. Đổi rồi vẫn hoàn tác được bằng một dòng lệnh. - Sáu mục rủi ro cao để riêng, mỗi mục một công tắc, có cảnh báo và đường lùi — trong đó có AppLocker, kèm hai con số STEP tự đo được trên máy thật mà tài liệu Microsoft không ghi.
Nói trước cho rõ
Kịch bản này không biến máy chủ của bạn thành bất khả xâm phạm, và không thay thế được một đợt rà soát hạ tầng đúng nghĩa. Nó làm đúng một việc: đóng nhanh những khe hở phổ thông nhất, thứ mà mã độc hàng loạt và công cụ dò quét tự động nhắm vào đầu tiên. Máy chủ đang giữ dữ liệu khách hàng, đang chạy phần mềm nghiệp vụ đặc thù, hoặc đã từng bị xâm nhập — cần người xem tận nơi. Kịch bản được phát hành không kèm bảo hành; bạn tự chịu trách nhiệm khi chạy trên máy chủ của mình.
Bước 0 — Làm trước khi chạy bất cứ thứ gì
Kịch bản tự sao lưu trạng thái cũ trước khi đổi bất cứ giá trị nào, và ghi nhật ký hoàn tác ngay sau từng mục chứ không đợi tới cuối. Nhưng bản sao lưu ấy chỉ cứu được những gì chính nó đã đổi. Nó không cứu được máy nếu có chuyện khác xảy ra giữa chừng.
- Máy ảo: chụp một bản ảnh (snapshot) trước khi chạy.
- Máy vật lý: có bản sao lưu toàn máy còn dùng được, và đã thử phục hồi ít nhất một lần.
- Luôn giữ một đường vào thứ hai không phụ thuộc mạng: console của hệ thống ảo hoá, iDRAC/iLO, hoặc KVM của nhà cung cấp. Mọi lệnh siết bảo mật đều có khả năng cắt mất đường vào chính.
- Chạy vào giờ thấp điểm. Vài mục làm thiết bị cũ — máy quét, máy chấm công, NAS đời cũ — mất kết nối tới thư mục chia sẻ, và bốn mục chỉ có hiệu lực sau khi khởi động lại.
- Máy nằm trong hệ thống domain thì làm qua Group Policy, đừng chạy cục bộ: năm mục trong kịch bản ghi vào nhánh chính sách, và chu kỳ đồng bộ sau đó sẽ ghi đè lại. Kịch bản tự phát hiện và ghi rõ
(TẠM THỜI - GPO sẽ ghi đè)vào cột kết quả, nhưng biết trước vẫn hơn.
1. Mở cổng quản lý từ xa đúng cách, và đóng lại
Muốn chạy kịch bản trên nhiều máy chủ từ một chỗ, bạn cần cổng quản lý từ xa (WinRM). Muốn chạy trên đúng một máy, bỏ qua phần này, xuống thẳng mục 2.
Điều cần nói trước: cửa đầu tiên luôn phải mở từ màn hình máy chủ, không mở từ xa được. Chuyện hiển nhiên nhưng hay bị quên lúc lên kế hoạch — bạn cần console, KVM, hoặc một phiên màn hình từ xa đang mở sẵn.
1.1 Mở — bốn bước, làm trên máy chủ
# 1. Bat dich vu chay lenh tu xa
Enable-PSRemoting -Force -SkipNetworkProfileCheckChi tiết ít người để ý nằm ở đây, và nó giải thích vì sao phải có bước 2. Theo đúng tài liệu Microsoft: trên bản Windows Server, lệnh trên chạy thành công ở mọi hồ sơ mạng — nó tạo luật tường lửa mở cho mạng Domain và Private, còn với hồ sơ Public thì "it creates firewall rules that allows remote access from the same local subnet" — chỉ mở cho máy cùng lớp mạng nội bộ.
Máy chủ thuê ngoài gần như luôn nằm ở hồ sơ Public. Nghĩa là dịch vụ đã bật, cổng đã mở, mà bạn gọi vào từ văn phòng vẫn không vào được — và nhiều người lúc đó gỡ rối bằng cách mở toang cho mọi nguồn. Bước 2 mở đúng một địa chỉ thay vì mở toang. (Tham số -SkipNetworkProfileCheck chỉ bắt buộc trên bản Windows dành cho máy trạm; để lại trên máy chủ cũng không hại gì.)
# 2. Gioi han nguon - LOP 1: tuong lua, chi mot dia chi duoc goi vao.
# DOI 203.0.113.25 thanh dia chi that cua may ban dang ngoi.
Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP" -RemoteAddress "203.0.113.25"
Set-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" -RemoteAddress "203.0.113.25" -ErrorAction SilentlyContinue
# 3. Gioi han nguon - LOP 2: ngay trong WinRM, doc lap voi tuong lua
Set-Item WSMan:\localhost\Service\IPv4Filter -Value "203.0.113.25"Hai lớp, không phải một. Tường lửa có thể bị một luật khác ghi đè, hoặc bị tắt cả hồ sơ trong lúc xử lý sự cố; bộ lọc trong WinRM vẫn còn đó.
# 4. Tat kieu xac thuc yeu va cam truyen du lieu khong ma hoa
Set-Item WSMan:\localhost\Service\Auth\Basic -Value $false
Set-Item WSMan:\localhost\Service\AllowUnencrypted -Value $falseBước 4 là chỗ cần hiểu cho đúng, vì có một hiểu lầm rất phổ biến: "cổng 5985 là HTTP nên dữ liệu đi dạng thô". Không đúng. Tài liệu Microsoft nói thẳng về phiên làm việc từ xa: "The credentials and the rest of the transmission are encrypted" — nội dung phiên được mã hoá sẵn bằng chính cơ chế xác thực (Kerberos hoặc NTLM), và chuyển sang HTTPS được mô tả là "to increase protection", tức tăng thêm chứ không phải vá một lỗ hổng. Thứ thật sự làm dữ liệu đi dạng thô là Basic Auth bật cùng với AllowUnencrypted = $true — đúng cặp mà các hướng dẫn "mở nhanh cho tiện" trên mạng hay bảo bạn bật. Hai dòng ở bước 4 khoá đúng cặp đó lại.
Có chứng chỉ số hợp lệ thì tốt hơn nữa: chuyển hẳn sang HTTPS (cổng 5986) để mã hoá cả kênh truyền, độc lập với cơ chế xác thực. Quy trình đầy đủ nằm ở mục 3.4 của Phần 1 trong loạt bài này — đó là cấu hình nên dùng cho máy chủ cần quản trị từ xa thường trực. Phần đang đọc ở đây là cách mở tạm cho một đợt việc rồi đóng lại.
1.2 Phía máy bạn đang ngồi
Máy chủ lẻ (không nằm trong hệ thống domain chung) thì không dùng được Kerberos — Microsoft ghi rõ "Kerberos authentication doesn't support IP addresses", và khi bạn gõ địa chỉ IP thì hệ thống chuyển sang NTLM. Lúc đó Windows bắt bạn khai trước là "tôi tin máy này":
# Khai DUNG dia chi may chu can noi toi. Tuyet doi khong dung "*".
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "198.51.100.10" -Concatenate -Force
Test-WSMan -ComputerName "198.51.100.10"
$cred = Get-Credential
Enter-PSSession -ComputerName "198.51.100.10" -Credential $cred(Tham số -Concatenate chạy được cả khi danh sách đang rỗng — chúng tôi đã thử trên Windows Server 2022, không cần viết hai đường code cho hai trường hợp.)
TrustedHosts = "*" là cái bẫy số một của phần này. Nó bảo máy bạn chấp nhận xác thực với bất kỳ máy chủ nào — kể cả máy giả mạo chen vào giữa. Đặt xong thì nó nằm đó vĩnh viễn, không ai nhớ để gỡ. Khai đúng địa chỉ cần dùng, xong việc thì xoá về rỗng.
1.3 Đóng lại — phần hay bị bỏ quên nhất
Thứ tự trong khối lệnh dưới đây quan trọng, và chúng tôi biết điều đó vì đã làm sai một lần trên máy thử.
# Tren MAY CHU - chay tu console, hoac hen chay sau vai giay neu ban dang o xa:
# dong thu hai tro di se cat dut chinh phien ban dang dung.
Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP"
Disable-NetFirewallRule -Name "WINRM-HTTP-In-TCP-PUBLIC" -ErrorAction SilentlyContinue
Get-ChildItem WSMan:\localhost\Listener | Remove-Item -Recurse -Force
Set-Item WSMan:\localhost\Service\IPv4Filter -Value "" -Force
# Tra ve mac dinh an toan - buoc nay rat hay bi bo sot
Remove-ItemProperty "HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Policies\System" `
-Name LocalAccountTokenFilterPolicy -ErrorAction SilentlyContinue
Stop-Service WinRM -Force
Set-Service WinRM -StartupType Manual# Tren MAY BAN DANG NGOI - don dau vet
Set-Item WSMan:\localhost\Client\TrustedHosts -Value "" -ForceDisable-PSRemoting KHÔNG đóng cửa — đã đo trên máy thật
Nghe tên thì tưởng là lệnh đảo ngược của Enable-PSRemoting. Chúng tôi chạy nó trên một máy Windows Server 2022 rồi dò cổng từ máy khác: cổng 5985 vẫn mở. Chính lệnh đó in ra cảnh báo của Microsoft liệt kê bốn việc bạn vẫn phải tự làm: dừng và tắt dịch vụ WinRM, xoá trình nghe, đóng luật tường lửa, và trả LocalAccountTokenFilterPolicy về 0. Tệ hơn nữa: nó chặn luôn phiên làm việc từ xa của chính bạn — chạy nó đầu tiên khi đang ngồi ở xa là tự cắt mất khả năng hoàn tất việc đóng cửa, máy nằm lại ở trạng thái nửa vời. Chạy đúng khối lệnh ở trên, theo đúng thứ tự đó.
Và kiểm lại từ bên ngoài, đừng tin lệnh tự báo thành công. Từ một máy khác:
Test-NetConnection -ComputerName "198.51.100.10" -Port 5985Cột TcpTestSucceeded phải là False. Còn True nghĩa là còn một đường nào đó bạn chưa đóng.
1.4 Ba cái bẫy, gộp lại
| Bẫy | Hậu quả | Cách tránh |
|---|---|---|
TrustedHosts = "*" | Máy quản trị của bạn chấp nhận xác thực với mọi máy chủ, kể cả máy giả mạo | Khai đúng địa chỉ, xong việc xoá về rỗng |
Tưởng Disable-PSRemoting là đã đóng | Cổng vẫn mở, dịch vụ vẫn chạy — mà bạn thì mất đường vào để đóng nốt | Dùng đủ khối lệnh 1.3, đúng thứ tự, rồi kiểm từ ngoài |
| Mở cổng cho mọi nguồn "cho tiện, mai siết sau" | Bị dò quét tự động trong vài giờ | Giới hạn nguồn ngay từ lệnh đầu, hai lớp |
Cần vào thường xuyên chứ không phải một đợt? Đừng mở cổng quản lý ra internet — dựng VPN hoặc một máy trung gian (jump host), rồi chỉ cho phép máy đó gọi vào.
2. Kịch bản: kiểm tra trước, vá sau, hoàn tác được
2.1 Ba chế độ
# 1. CHI KIEM TRA - khong doi bat cu thu gi. Day la che do mac dinh.
.\Invoke-StepHardening.ps1
# 2. AP DUNG 14 muc an toan. Tu sao luu truoc khi doi.
.\Invoke-StepHardening.ps1 -ApDung
# 3. HOAN TAC toan bo mot lan chay truoc do
.\Invoke-StepHardening.ps1 -HoanTac "C:\STEP-Hardening\20260922-153000"Chạy phát đầu không đổi gì là cố ý. Bạn đọc bảng kết quả, thấy máy mình đang thiếu đúng những gì, rồi mới quyết định. Không ai nên chạy một kịch bản lạ ở chế độ ghi ngay lần đầu — kể cả kịch bản này.
2.2 Mười bốn mục trong nhóm an toàn
| # | Mục | Chặn được gì | Khởi động lại? |
|---|---|---|---|
| 1 | Tắt giao thức chia sẻ file đời cũ (SMBv1) | Họ mã độc lây lan kiểu WannaCry/EternalBlue | Có |
| 2 | Bắt buộc ký số gói tin SMB (phía máy chủ) | Chen giữa, giả mạo phiên truy cập thư mục chia sẻ | Không |
| 3 | Tắt LLMNR | Bẫy hỏi-tên-máy để lừa máy gửi thông tin đăng nhập | Không |
| 4 | Tắt NetBIOS over TCP/IP trên mọi card mạng | Như trên, đường thứ hai của cùng kiểu tấn công | Có |
| 5 | Không giữ mật khẩu dạng đọc được trong bộ nhớ (WDigest) | Công cụ kiểu Mimikatz moi mật khẩu từ bộ nhớ | Không |
| 6 | Tắt tự động chạy khi cắm USB | Đường lây mã độc cổ điển nhất qua USB | Không |
| 7 | Bật đủ lớp Microsoft Defender | Phần mềm rác, mã độc chưa có mẫu, trang lừa đảo | Không |
| 8 | Ba luật ASR nhóm chuẩn, chế độ theo dõi | Xem trước sẽ chặn nhầm gì, chưa chặn thật | Không |
| 9 | Ghi nhật ký lệnh PowerShell đã chạy | Không chặn, nhưng cho biết kẻ tấn công đã gõ gì | Không |
| 10 | Ghi biên bản phiên PowerShell ra thư mục khoá quyền riêng | Như trên, đầy đủ hơn | Không |
| 11 | Bật các mục nhật ký an ninh quan trọng + ghi cả dòng lệnh | Điều tra sau sự cố có dữ liệu mà lần | Không |
| 12 | Mở rộng nhật ký an ninh lên 1 GB | Nhật ký không bị ghi đè mất trước khi kịp đọc | Không |
| 13 | Siết đăng nhập màn hình từ xa (xác thực trước, kênh TLS, mã hoá cao) | Dò mật khẩu RDP, nghe lén phiên | Không |
| 14 | Chặn dò danh sách tài khoản qua phiên ẩn danh + tắt tài khoản Guest | Bước đầu của mọi đợt dò quét: lấy danh sách tài khoản | Không |
Cộng thêm một mục chỉ báo cáo, không bao giờ tự sửa: kịch bản dò xem có luật tường lửa nào đang mở cổng quản trị (3389, 5985, 5986, 445, 135) cho mọi nguồn hay không, có luật nào mở mọi cổng cho một chương trình không, và tường lửa có thật sự đang chặn không. Nó chỉ in ra cho bạn biết. Tự tay sửa tường lửa trên máy đang có người kết nối vào là cách nhanh nhất để tự khoá mình ở ngoài — việc đó phải do người quyết định, không phải kịch bản.
Hai đánh đổi có chủ ý, nói thẳng
- Windows Update: kịch bản đặt chế độ tự TẢI về và thông báo, KHÔNG tự cài. Máy chủ tự khởi động lại giữa giờ làm việc là rủi ro lớn hơn rủi ro nó định tránh. Đổi lại, vẫn cần người bấm cài bản vá hằng tháng — hãy đặt lịch nhắc, đừng nghĩ là xong.
- Nếu Defender đang bị tắt quét thời gian thực, kịch bản KHÔNG tự bật lại. Người quản trị trước có thể đã tắt có chủ ý (máy SQL/Exchange chưa khai thư mục loại trừ) — bật lại mà chưa khai loại trừ là cách quen thuộc để làm treo ổ đĩa. Kịch bản báo cho bạn biết và để bạn quyết.
2.3 Hoàn tác — thứ quan trọng hơn cả danh sách trên
Trước khi đổi bất cứ giá trị nào, kịch bản ghi lại giá trị cũ, đúng như nó đang là — kể cả trường hợp "giá trị này trước đó chưa từng tồn tại", vì trả về nguyên trạng trong trường hợp đó nghĩa là xoá hẳn, chứ không phải đặt về 0. Đây là chỗ đa số kịch bản hardening trôi nổi trên mạng làm sai, và người dùng chỉ phát hiện lúc cần lùi lại.
Quan trọng không kém: nhật ký hoàn tác được ghi ra đĩa ngay sau từng mục, không đợi tới cuối. Nghe như chi tiết vụn, nhưng đây là khác biệt giữa "lùi lại được" và "mất trắng": nếu nhật ký chỉ ghi ở dòng cuối, thì mọi sự cố giữa chừng — bạn bấm Ctrl+C vì thấy dòng cảnh báo đỏ, rớt phiên từ xa, một lệnh treo — đều để lại hai mươi thay đổi trên máy mà không có gì để lùi. Chúng tôi đã thử đúng tình huống đó trên máy ảo: giết kịch bản sau 2,5 giây, nhật ký vẫn có đủ bốn mục nó kịp đổi, và lệnh hoàn tác trả cả bốn về nguyên trạng.
function Luu-NhatKyHoanTac {
if (-not $script:TepTrangThai) { return }
try {
([ordered]@{
ThoiDiem = $script:ThoiDiemChay
MayChu = $env:COMPUTERNAME
PhienBan = $script:PhienBanScript
Muc = $script:TrangThaiCu
}) | ConvertTo-Json -Depth 8 | Out-File $script:TepTrangThai -Encoding UTF8 -ErrorAction Stop
} catch {
Ghi-Dong " [X] KHONG ghi duoc nhat ky hoan tac: $($_.Exception.Message)" 'Red'
}
}
function Ghi-Nho {
param([string]$Ma, [hashtable]$NoiDung)
$script:TrangThaiCu[$Ma] = $NoiDung
Luu-NhatKyHoanTac
}Mỗi lần chạy sinh ra một thư mục riêng, khoá quyền chỉ Administrators và SYSTEM đọc được — vì nội dung bên trong chính là bản đồ những lớp bảo vệ đang tắt trên máy chủ này:
C:\STEP-Hardening\20260922-153000\
trang-thai-truoc.json <- gia tri cu cua tung muc, ghi ngay sau moi thay doi
chinh-sach-cuc-bo.inf <- ban xuat chinh sach bao mat cuc bo
tuong-lua.wfw <- ban xuat cau hinh tuong lua
auditpol-truoc.csv <- cau hinh nhat ky an ninh truoc khi doi
applocker-truoc.xml <- chinh sach AppLocker truoc khi doi
bao-cao.csv <- bang ket qua lan chay do
CACH-HOAN-TAC.txt <- dong lenh lui lai, dan sanBốn mục chỉ có hiệu lực đầy đủ sau khi khởi động lại (tắt SMBv1, tắt NetBIOS, tắt TLS đời cũ, bảo vệ LSA) thì lùi lại cũng cần khởi động lại — kịch bản nói rõ điều đó ở cuối mỗi lần chạy. Riêng mục bảo vệ LSA có một ngoại lệ, đọc kỹ ở mục 3.1 bên dưới.
2.4 Toàn bộ công tắc, không có gì giấu
Đây là nguyên văn phần khai báo tham số của kịch bản. Đọc hết chỗ này là biết nó có thể làm gì — không có hành động nào nằm ngoài danh sách này:
[CmdletBinding(DefaultParameterSetName = 'KiemTra')]
param(
# --- Che do ---
[Parameter(ParameterSetName = 'ApDung')]
[switch]$ApDung,
[Parameter(ParameterSetName = 'HoanTac', Mandatory = $true)]
[string]$HoanTac,
[Parameter(ParameterSetName = 'KiemTra')]
[Parameter(ParameterSetName = 'ApDung')]
[string]$ThuMucLuu = 'C:\STEP-Hardening',
# --- 6 muc RUI RO CAO: phai tu bat, KHONG nam trong -ApDung ---
[Parameter(ParameterSetName = 'ApDung')]
[switch]$AsrChan, # 3 luat ASR + bao ve mang: THEO DOI -> CHAN THAT
[Parameter(ParameterSetName = 'ApDung')]
[switch]$BatBaoVeLsa, # RunAsPPL - KHONG hoan tac duoc tren may Secure Boot
[Parameter(ParameterSetName = 'ApDung')]
[switch]$KySoSmbPhiaMayKhach, # May chu nay se tu choi noi toi NAS khong ho tro ky so
[Parameter(ParameterSetName = 'ApDung')]
[switch]$TatPrintSpooler, # Tat dich vu in (chan ho lo hong in an)
[Parameter(ParameterSetName = 'ApDung')]
[switch]$TatTlsCu, # Tat SSL 2.0/3.0 / TLS 1.0 / TLS 1.1
[Parameter(ParameterSetName = 'ApDung')]
[switch]$AppLockerTheoDoi, # AppLocker o che do THEO DOI (khong chan)
[Parameter(ParameterSetName = 'ApDung')]
[switch]$AppLockerChanThat, # CHAN THAT - nguy hiem nhat, doc ky bai viet truoc
# Thay cho cau hoi go tay "TOI HIEU". CHI dung khi chay tu dong qua cong cu
# quan tri va da doc het canh bao. Bai viet KHONG in tham so nay trong lenh mau.
[Parameter(ParameterSetName = 'ApDung')]
[switch]$ToiHieu
)3. Sáu mục rủi ro cao, để riêng
Sáu mục dưới đây không nằm trong -ApDung. Phải tự bật từng cái bằng công tắc riêng. Lý do đơn giản: chúng có thể làm phần mềm đang chạy ngừng hoạt động, và bạn phải biết trước mình đang bật cái gì.
3.1 Bảo vệ LSA — mục duy nhất KHÔNG hoàn tác được bằng phần mềm
Bảo vệ tiến trình LSA (RunAsPPL) chặn công cụ đọc trộm vùng nhớ chứa thông tin đăng nhập. Rất đáng bật. Nhưng nó có một tính chất mà hầu hết hướng dẫn trên mạng không nói:
Theo đúng tài liệu Microsoft, đặt giá trị 1 là bật kèm ghi vào biến UEFI của máy: "the UEFI variable can't be deleted or changed to configure added LSA protection by modifying the registry or by policy", và "disabling the ... Lsa registry key has no effect". Nghĩa là trên máy chủ bật Secure Boot — gần như mọi máy đời mới — lệnh hoàn tác sẽ xoá giá trị trong registry, báo thành công, và LSA vẫn được bảo vệ sau khi khởi động lại. Gỡ thật phải dùng công cụ LsaPplConfig.efi của Microsoft, khởi động từ firmware. Vì vậy kịch bản tách mục này ra công tắc riêng, in cảnh báo đỏ, cho biết máy bạn có bật Secure Boot không, và bắt gõ đúng chữ TOI HIEU mới đi tiếp.
Microsoft còn yêu cầu chạy chế độ kiểm toán trước khi bật: đặt AuditLevel = 8 cho LSASS.exe rồi đọc sự kiện 3065/3066 trong nhật ký CodeIntegrity khoảng một tuần, để tìm những thứ sẽ không nạp được nữa — driver thẻ từ, bộ lọc mật khẩu của phần mềm nhân sự, agent đăng nhập một lần. Bỏ qua bước này thì sự cố sẽ xuất hiện ở lần khởi động lại sau, có thể là vài tuần sau, và không ai nối nó với kịch bản đã chạy từ lâu.
3.2 AppLocker — và hai con số không có trong tài liệu Microsoft
Mục nguy hiểm nhất. AppLocker chỉ cho phép phần mềm đã duyệt được chạy; làm sai thì chặn luôn công cụ bạn cần để sửa lỗi.
Cách làm phổ biến trên mạng là quét toàn bộ máy rồi sinh một luật cho từng file. Khi rà soát Phần 2 của loạt bài này, STEP thử đúng cách đó trên Windows Server 2022 thật và đo được điều này:
Con số thứ nhất: 571 và 1005
Chính sách AppLocker cục bộ với 571 luật vẫn khớp đúng. Với 1005 luật thì ngừng khớp đúng — Get-AppLockerPolicy -Local vẫn báo đủ số luật, nhưng Test-AppLockerPolicy trả về DeniedByDefault cho chính những file có luật hợp lệ, kể cả cmd.exe. Ngưỡng chính xác nằm đâu đó giữa hai con số này. Đây là quan sát riêng của STEP trên máy thật — chúng tôi không tìm thấy trang tài liệu nào của Microsoft công bố giới hạn số luật AppLocker, nên đừng trích dẫn lại như thông tin chính hãng.
Nghĩa là cách "quét hết rồi sinh luật cho từng file" không chỉ chậm — ở quy mô đủ lớn nó hỏng âm thầm. Kịch bản đi đường khác, đúng theo khuyến nghị chính thức của Microsoft cho luật Publisher: "A single rule can be used to allow an entire product suite." Một luật duy nhất, bám theo chữ ký số của sản phẩm thay vì tên từng file. Tên nhà phát hành và tên sản phẩm vẫn giữ nguyên, không thay bằng dấu sao — nên luật vẫn xác thực đúng chữ ký, không mở cửa cho phần mềm giả mạo:
# MOT luat Publisher pham vi rong cho toan bo san pham "Windows Operating
# System", thay cho hang nghin luat tung file.
$thamChieu = Get-AppLockerFileInformation -Path 'C:\Windows\System32\cmd.exe' -ErrorAction Stop
[xml]$xml = New-AppLockerPolicy -FileInformation $thamChieu -RuleType Publisher -User Everyone -Xml
$dk = $xml.SelectSingleNode('//FilePublisherCondition')
if (-not $dk) { throw 'Khong doc duoc chu ky so cua cmd.exe - may nay co the da bi thay file he thong' }
# Giu nguyen PublisherName VA ProductName (van xac thuc dung chu ky so cua
# dung san pham), chi mo rong ten file + phien ban.
$dk.BinaryName = '*'
$dk.BinaryVersionRange.LowSection = '*'
$dk.BinaryVersionRange.HighSection = '*'
$luatGoc = $xml.SelectSingleNode('//FilePublisherRule')
if ($luatGoc) {
$luatGoc.SetAttribute('Name', 'STEP - toan bo file cua san pham Microsoft Windows Operating System')
$luatGoc.SetAttribute('Description', 'Mot luat Publisher pham vi rong thay cho hang nghin luat tung file')
}
$boExe = $xml.SelectSingleNode("//RuleCollection[@Type='Exe']")
$boExe.SetAttribute('EnforcementMode', 'AuditOnly')Phần mềm đã cài trong Program Files thì sinh luật bằng New-AppLockerPolicy có tham số -Optimize để Windows tự gộp các luật giống nhau. Số đo trên máy thử: 137 file .exe cho ra đúng 20 luật — thay vì 137 luật nếu liệt kê từng file.
Con số thứ hai: 3 file của chính Windows mà luật trên KHÔNG phủ
Đây là phát hiện chỉ máy thật mới cho ra, và nó đánh sập câu "một luật phủ cả hệ điều hành". Trên Windows Server 2022 mới cài, có ba file nằm ngay trong C:\Windows, có chữ ký số của Microsoft, nhưng để TRỐNG tên sản phẩm — nên chúng không khớp luật theo sản phẩm "Microsoft Windows Operating System": AggregatorHost.exe, AzureArcSysTray.exe, và StartMenuExperienceHost.exe — tức là nút Start. Tệ hơn: New-AppLockerPolicy không tạo nổi luật Publisher cho chúng, báo thẳng "Required file information is missing", vì luật Publisher cần đủ cả nhà phát hành lẫn tên sản phẩm. Chặn thật mà bỏ sót ba file này thì người dùng thường mất nút Start ngay sáng hôm sau.
Vì vậy cổng chặn của kịch bản không kiểm mấy file Windows quen thuộc rồi kết luận an toàn — nó quét phần mềm thật của chính máy đó: mọi tiến trình đang chạy, phần mềm trong Program Files, và cả các thư mục ngoài chuẩn kiểu C:\BRAVO, D:\PHANMEM nơi phần mềm nghiệp vụ Việt Nam hay nằm. Rồi kiểm với hai nhân thân người dùng thường — không phải tài khoản quản trị, vì dây bảo hiểm cho Administrators làm mọi thứ đều xanh:
# --- B8: CONG CHAN - kiem PHAN MEM THAT cua may nay, khong phai 6 file Windows ---
# 6 file cua Windows luon dau vi luat Publisher phu dung san pham do. Kiem chung
# roi ket luan "an toan de chan that" la kiem sai thu can kiem.
$fileThu = New-Object System.Collections.Generic.HashSet[string]
foreach ($f in 'C:\Windows\System32\cmd.exe',
'C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe',
'C:\Windows\explorer.exe', 'C:\Windows\regedit.exe',
'C:\Windows\System32\mmc.exe', 'C:\Windows\System32\mstsc.exe') {
if (Test-Path $f) { [void]$fileThu.Add($f) }
}
# Moi tien trinh DANG CHAY that tren may - day moi la phan mem cua khach
foreach ($p in (Get-Process -ErrorAction SilentlyContinue | Where-Object { $_.Path })) {
[void]$fileThu.Add($p.Path)
}
# Va mot phan phan mem da cai
foreach ($f in (Get-ChildItem -Path @('C:\Program Files', 'C:\Program Files (x86)') -Recurse -Include *.exe `
-ErrorAction SilentlyContinue | Select-Object -First 200)) {
[void]$fileThu.Add($f.FullName)
}
# VA phan mem cai NGOAI cac thu muc chuan (D:\PHANMEM, C:\BRAVO, C:\MISA...).
# Day moi la cho phan mem nghiep vu Viet Nam hay nam, va la cho chinh sach nay
# KHONG co luat nao - bo qua thi cong chan van xanh trong khi ca phong mat
# phan mem ngay hom sau.
foreach ($o in (Get-PSDrive -PSProvider FileSystem -ErrorAction SilentlyContinue |
Where-Object { $_.Root -match '^[A-Z]:\\$' })) {
$thuMucLa = @(Get-ChildItem -Path $o.Root -Directory -ErrorAction SilentlyContinue |
Where-Object { $_.FullName -notmatch '^[A-Z]:\\(Windows|Program Files|Program Files \(x86\)|ProgramData|Users|\$Recycle|System Volume)' })
foreach ($t in $thuMucLa) {
foreach ($f in (Get-ChildItem -Path $t.FullName -Recurse -Include *.exe `
-ErrorAction SilentlyContinue | Select-Object -First 30)) {
[void]$fileThu.Add($f.FullName)
}
}
}
$dsThu = @($fileThu)
# Kiem voi NGUOI DUNG THUONG, khong phai Administrators: day bao hiem lam moi
# thu deu Allowed neu kiem bang tai khoan quan tri.
$nhanThan = @('Everyone')
try {
$nhomUsers = (New-Object System.Security.Principal.SecurityIdentifier('S-1-5-32-545')).Translate([System.Security.Principal.NTAccount]).Value
$nhanThan += $nhomUsers
} catch { }
$biChan = @()
foreach ($nt in $nhanThan) {
$ketQuaThu = $null
try {
$ketQuaThu = @(Get-AppLockerPolicy -Local |
Test-AppLockerPolicy -Path $dsThu -User $nt -ErrorAction Stop)
} catch {
# Khong do duoc thi KHONG duoc coi la sach. Loi = dung lai.
Them-KetQua 'APPLOCKER' 'Chi cho phep phan mem da duyet duoc chay' "$soLuat luat" 'DUNG O THEO DOI' `
"Khong kiem duoc chinh sach cho '$nt' ($($_.Exception.Message)) - khong dam chuyen chan that"
return
}
if ($null -eq $ketQuaThu -or $ketQuaThu.Count -ne $dsThu.Count) {
Them-KetQua 'APPLOCKER' 'Chi cho phep phan mem da duyet duoc chay' "$soLuat luat" 'DUNG O THEO DOI' `
"Kiem tra tra ve thieu ket qua ($($ketQuaThu.Count)/$($dsThu.Count)) - khong dam chuyen chan that"
return
}
$biChan += @($ketQuaThu | Where-Object { $_.PolicyDecision -ne 'Allowed' } |
ForEach-Object { "$nt : $($_.FilePath)" })
}File nào chưa được phủ mà có chữ ký số thì kịch bản tự sinh luật bổ sung rồi kiểm lại một lượt nữa trước khi cho phép chặn thật. File nào không có chữ ký số thì nó dừng hẳn ở chế độ theo dõi và in tên ra — tự động cho phép một file không ai ký tên chính là cách biến AppLocker thành đồ trang trí.
Kết quả đo trên máy thử, đúng cả hai chiều: máy sạch thì kịch bản tự vá ba file kia rồi chuyển sang chặn thật (26 luật), phiên đăng nhập mới vẫn vào được bình thường, người dùng thường bị chặn đúng file lạ. Còn khi chúng tôi cố tình đặt một file ketoan.exe không chữ ký vào C:\PHANMEM-KETOAN, kịch bản từ chối chặn thật, in tên file ra, và giữ nguyên chế độ theo dõi.
Đường lùi của AppLocker, đọc trước khi bật
Chính sách cũ được lưu ở applocker-truoc.xml ngay trước khi đổi, và -HoanTac sẽ áp lại chính sách đó. Máy đã có sẵn chính sách AppLocker thì kịch bản dừng lại, không ghi đè — vì Set-AppLockerPolicy không kèm tham số gộp sẽ xoá sạch bộ luật bạn đã dày công dựng. Một thay đổi không lùi được: dịch vụ Application Identity chuyển sang tự khởi động, và Microsoft ghi rõ không đặt lại về thủ công bằng sc.exe được. Nếu máy đã chặn tới mức không gõ được lệnh nào nữa, bạn cần console/iDRAC. Đừng bật mục này lần đầu trên máy chủ đang chạy việc thật — bật trên một máy ảo giống nó trước, và chạy chế độ theo dõi một đến hai tuần.
3.3 Bốn mục còn lại
| Công tắc | Làm gì | Rủi ro phải biết trước |
|---|---|---|
-AsrChan | Ba luật ASR và bảo vệ mạng chuyển từ theo dõi sang chặn thật | Chỉ làm sau khi đã chạy theo dõi một hai tuần và đọc nhật ký Defender. Một trong ba luật xung khắc với máy dùng Microsoft Configuration Manager |
-KySoSmbPhiaMayKhach | Bắt buộc ký số gói tin SMB ở chiều máy chủ này gọi đi | Máy chủ sẽ từ chối kết nối tới NAS/Samba không hỗ trợ ký số — đích sao lưu là nạn nhân điển hình |
-TatPrintSpooler | Tắt dịch vụ in, đóng cả một họ lỗ hổng in ấn | Kịch bản tự từ chối nếu thấy máy đang chia sẻ máy in, hoặc nếu không đọc được danh sách máy in |
-TatTlsCu | Tắt SSL 2.0/3.0 và TLS 1.0/1.1, bật TLS 1.2, kèm cờ mã hoá mạnh cho .NET Framework | Phần mềm cũ có thể mất kết nối ngay. Cần khởi động lại. Thiếu cặp cờ .NET là ứng dụng .NET đứt kết nối mà không báo gì rõ ràng |
4. Chạy xong thì kiểm lại gì
- Đọc lại bảng kết quả — cột hành động còn dòng
LOInào không. - Vẫn vào được máy không? Mở một phiên đăng nhập mới (đừng dùng lại phiên đang mở) để chắc chắn phần siết đăng nhập từ xa không chặn nhầm chính bạn.
- Thư mục chia sẻ còn truy cập được không — từ một máy người dùng thật, không phải từ chính máy chủ.
- Phần mềm nghiệp vụ còn chạy không — mở đúng phần mềm khách đang dùng hằng ngày, bằng tài khoản người dùng thường.
- Lên lịch khởi động lại vào giờ thấp điểm để bốn mục cần khởi động lại có hiệu lực.
- Một tuần sau, đọc nhật ký ASR và bảo vệ mạng ở chế độ theo dõi trước khi nghĩ tới chuyện chặn thật.
Nếu một bước nào trong số đó hỏng: chạy lại kịch bản với -HoanTac và đúng thư mục của lần chạy, rồi làm lại từng mục một để biết mục nào gây ra.
Tải kịch bản
Bài này dán những đoạn đáng đọc nhất. Bản đầy đủ 1.717 dòng — gồm cả phần chú thích giải thích từng quyết định — nằm trong tệp tải về:
https://step.com.vn/tools/Invoke-StepHardening.ps1Kiểm tệp tải về đúng bản gốc trước khi chạy. Mã kiểm tra SHA256 của bản 1.1:
861265624E63F5402B7B46C271202F845AD08ECFEF7E099AF30024BBD89372DDGet-FileHash .\Invoke-StepHardening.ps1 -Algorithm SHA256 | Format-ListChuỗi in ra phải trùng với mã ở trên. Không trùng nghĩa là tệp đã bị sửa trên đường truyền — đừng chạy.
Vì sao kịch bản không có dấu tiếng Việt
Tệp .ps1 không kèm dấu nhận dạng bảng mã sẽ bị Windows PowerShell 5.1 đọc theo bảng mã hệ thống, làm chữ có dấu vỡ thành ký tự lạ — và nếu chữ vỡ nằm trong một phép so khớp thì kịch bản chạy sai chứ không báo lỗi. Viết không dấu là cách chắc chắn nhất để tệp chạy giống nhau trên mọi máy, mọi bản Windows.
Kết
Hai mươi phút không làm máy chủ của bạn an toàn tuyệt đối. Nhưng nó đóng được những khe hở mà công cụ dò quét tự động nhắm vào trước tiên — và quan trọng hơn, nó cho bạn một bản ghi rõ ràng về việc máy chủ đang thiếu gì, kèm đường lùi cho mọi thứ vừa đổi.
Muốn hiểu tại sao từng mục lại quan trọng, và còn những mảng nào nữa chưa nằm trong kịch bản này, đọc hai bài còn lại của loạt: Phần 1 (baseline, danh tính & truy cập, bảo vệ chính PowerShell, mạng) và Phần 2 (giám sát, kiểm soát ứng dụng, sao lưu chống ransomware).
Bài viết liên quan
STEP Có Thể Hỗ Trợ Gì
Kịch bản này đóng nhanh những khe hở phổ thông nhất, nhưng nó không biết máy chủ của bạn đang chạy phần mềm gì, ai đang dùng, và điều gì sẽ gãy nếu siết sai chỗ — đó là phần phải có người nhìn tận nơi. Dịch vụ CIO/quản trị hạ tầng thuê ngoài của STEP nhận làm trọn: rà soát hiện trạng, lên kế hoạch siết theo đúng thứ tự an toàn cho từng máy chủ, triển khai ngoài giờ làm việc, rồi 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.