Skip to content

fix: record real response status for IIS audit log F part #201

fix: record real response status for IIS audit log F part

fix: record real response status for IIS audit log F part #201

Workflow file for this run

name: CI/CD for IIS Module
on:
push:
pull_request:
jobs:
build:
strategy:
matrix:
arch: [x64, x86]
config: [Release, RelWithDebInfo]
runs-on: windows-latest
# For Caching
permissions:
actions: read
contents: read
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
submodules: recursive
fetch-depth: 0
- name: Install Apache for x86
if: matrix.arch == 'x86'
shell: pwsh
run: |
$apachePath = "${{ github.workspace }}\apache-x86"
New-Item -ItemType Directory -Path $apachePath -Force
choco install apache-httpd -y --force --forcex86 --no-progress -r --params="'/installLocation:$apachePath /noService'"
echo "APACHE_ROOT=$apachePath\Apache24" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Set Apache path for x64
if: matrix.arch == 'x64'
shell: pwsh
run: |
echo "APACHE_ROOT=C:\tools\Apache24" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
- name: Setup MSYS2
uses: msys2/setup-msys2@fb197b72ce45fb24f17bf3f807a388985654d1f2
with:
msystem: ${{ matrix.arch == 'x86' && 'MINGW32' || 'UCRT64' }}
update: true
install: >
git
make
autoconf
automake
libtool
${{ matrix.arch == 'x86' && 'mingw-w64-i686-gcc' || 'mingw-w64-ucrt-x86_64-gcc' }}
${{ matrix.arch == 'x86' && 'mingw-w64-i686-pkg-config' || 'mingw-w64-ucrt-x86_64-pkg-config' }}
- name: Clone and build ssdeep
shell: msys2 {0}
run: |
MSYS2_WORKSPACE=$(cygpath -u '${{ github.workspace }}')
git clone https://github.com/ssdeep-project/ssdeep.git --depth 1
cd ssdeep
autoreconf -i
if [ "${{ matrix.arch }}" = "x86" ]; then
./configure --enable-shared --disable-static CFLAGS="-O3" CXXFLAGS="-O3" --build=i686-pc-mingw32
else
./configure --enable-shared --disable-static CFLAGS="-O3" CXXFLAGS="-O3"
fi
make dll
mkdir -p "${MSYS2_WORKSPACE}/ssdeep-install/"
cp -v fuzzy.dll "${MSYS2_WORKSPACE}/ssdeep-install/"
cp -v fuzzy.h "${MSYS2_WORKSPACE}/ssdeep-install/"
cp -v fuzzy.def "${MSYS2_WORKSPACE}/ssdeep-install/"
- name: Restore vcpkg cache
id: vcpkg-cache
uses: TAServers/vcpkg-cache@e848939f754daf406a06006be2e05eb5b17cc481
with:
token: ${{ secrets.GITHUB_TOKEN }}
prefix: vcpkg-iis-module-${{ matrix.arch }}/
- uses: ammaraskar/msvc-problem-matcher@1ebcb382869bfdc2cc645e8a2a43b6d319ea1cc0
- name: Configure CMake for IIS Module
env:
VCPKG_FEATURE_FLAGS: "binarycaching"
VCPKG_BINARY_SOURCES: "clear;files,${{ steps.vcpkg-cache.outputs.path }},readwrite"
VCPKG_DEFAULT_TRIPLET: ${{ matrix.arch }}-windows
run: |
$archFlag = "${{ matrix.arch }}"
$cmakeArch = if ($archFlag -eq "x86") { "Win32" } else { "x64" }
$installDir = if ($archFlag -eq "x86") { "x86" } else { "amd64" }
cmake `
-DAPACHE_ROOT="$env:APACHE_ROOT" `
-DCMAKE_INSTALL_PREFIX="${{ github.workspace }}\iis\release\$installDir" `
-DCMAKE_TOOLCHAIN_FILE="$env:VCPKG_INSTALLATION_ROOT\scripts\buildsystems\vcpkg.cmake" `
-DSSDEEP_ROOT="${{ github.workspace }}\ssdeep-install" `
-DWITH_SSDEEP=ON `
-A $cmakeArch `
-DWITH_LUA=ON `
-DWITH_YAJL=ON `
-S IIS -B "iis\build"
- name: Build IIS Module
shell: pwsh
run: |
cmake --build "iis\build" --config ${{ matrix.config }}
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: iis-module-${{ matrix.arch }}-${{ matrix.config }}
path: iis/build/${{ matrix.config }}/
package:
needs: build
runs-on: windows-latest
strategy:
matrix:
config: [Release, RelWithDebInfo]
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
submodules: recursive
fetch-depth: 0
- name: Download x64 artifacts
uses: actions/download-artifact@v4
with:
name: iis-module-x64-${{ matrix.config }}
path: iis/release/amd64/
- name: Download x86 artifacts
uses: actions/download-artifact@v4
with:
name: iis-module-x86-${{ matrix.config }}
path: iis/release/x86/
- name: Generate MSI files
shell: pwsh
run: |
heat dir "iis\release\amd64" -cg ModSec64Components -dr inetsrv64 -gg -sreg -srd -var var.ModSecurityIISRelease64 -out "iis\ModSec64.wxs"
heat dir "iis\release\x86" -cg ModSec32Components -dr inetsrv32 -gg -sreg -srd -var var.ModSecurityIISRelease32 -out "iis\ModSec32.wxs"
candle.exe -ext WixUtilExtension -ext WixUIExtension "iis\installer.wxs" "iis\ModSec64.wxs" -arch x64 -dModSecurityIISRelease64="iis\release\amd64\" -out iis\
candle.exe -ext WixUtilExtension -ext WixUIExtension "iis\ModSec32.wxs" -arch x86 -dModSecurityIISRelease32="iis\release\x86\" -out iis\
light.exe -ext WixUtilExtension -ext WixUIExtension "iis\installer.wixobj" "iis\ModSec32.wixobj" "iis\ModSec64.wixobj" -out "iis\modsecurityiis.msi"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: modsecurityiis-installers-${{ matrix.config }}
path: iis/modsecurityiis.msi
test:
needs: package
runs-on: windows-latest
strategy:
matrix:
config: [Release, RelWithDebInfo]
steps:
- name: Checkout code
uses: actions/checkout@v5
with:
submodules: recursive
fetch-depth: 0
- name: Download MSI files
uses: actions/download-artifact@v4
with:
name: modsecurityiis-installers-${{ matrix.config }}
path: ${{ github.workspace }}/
- name: Install MSI
shell: pwsh
run: |
$msiPath = "${{ github.workspace }}\modsecurityiis.msi"
if (-not (Test-Path $msiPath)) {
Write-Error "MSI file not found at $msiPath"
exit 1
}
# Install with logging for debugging
$installLog = "${{ github.workspace }}\install.log"
$installResult = Start-Process -FilePath "msiexec.exe" -ArgumentList @(
"/i", "`"$msiPath`"",
"/qn",
"/norestart",
"/l*", "`"$installLog`""
) -Wait -PassThru
if ($installResult.ExitCode -ne 0) {
Write-Error "MSI installation failed with exit code $($installResult.ExitCode)"
Get-Content $installLog | Write-Host
exit 1
}
$installDir = "C:\Program Files\ModSecurity IIS"
$requiredFiles = @(
"modsecurity.conf",
"modsecurity_iis.conf"
)
foreach ($file in $requiredFiles) {
$filePath = Join-Path $installDir $file
if (-not (Test-Path $filePath)) {
Write-Error "Required file $file not found in installation directory"
exit 1
}
}
- name: Install OWASP Core Rules
shell: pwsh
run: |
$crsVersion = "v4.18.0"
$crsUrl = "https://github.com/coreruleset/coreruleset/archive/refs/tags/$crsVersion.tar.gz"
$crsDir = "C:\Program Files\ModSecurity IIS\coreruleset"
$modSecurityConfigDir = "C:\Program Files\ModSecurity IIS"
try {
New-Item -ItemType Directory -Path $crsDir -Force
Invoke-WebRequest -Uri $crsUrl -OutFile "$crsDir\$crsVersion.tar.gz"
tar -xzf "$crsDir\$crsVersion.tar.gz" -C $crsDir --strip-components=1
Get-ChildItem "$crsDir" -Recurse -Filter "*.example" | ForEach-Object {
$newName = $_.Name.Replace(".example", "")
Rename-Item -Path $_.FullName -NewName $newName
}
$testConfigDir = "${{ github.workspace }}\iis\tests"
$modSecurityConfigFile = "$modSecurityConfigDir\modsecurity_iis.conf"
Add-Content -Path "$crsDir\crs-setup.conf" -Value (Get-Content -Path "$testConfigDir\crs-900005.conf")
$crsRules = @(
"Include coreruleset/crs-setup.conf",
"Include coreruleset/plugins/*-config.conf",
"Include coreruleset/plugins/*-before.conf",
"Include coreruleset/rules/*.conf",
"Include coreruleset/plugins/*-after.conf"
)
Add-Content -Path $modSecurityConfigFile -Value $crsRules
(Get-Content -Path $modSecurityConfigDir\modsecurity.conf) -replace 'SecRuleEngine DetectionOnly', 'SecRuleEngine On' | Set-Content -Path $modSecurityConfigDir\modsecurity.conf
$auditLogDir = "c:\inetpub\logs"
New-Item -ItemType Directory -Path $auditLogDir -Force | Out-Null
$acl = Get-Acl -LiteralPath $auditLogDir
foreach ($identity in @("NETWORK SERVICE", "IIS_IUSRS")) {
$rule = New-Object System.Security.AccessControl.FileSystemAccessRule($identity, "Modify", "ContainerInherit,ObjectInherit", "None", "Allow")
$acl.AddAccessRule($rule)
}
Set-Acl -LiteralPath $auditLogDir -AclObject $acl
Add-Content -Path $modSecurityConfigFile -Value (Get-Content -Path "$testConfigDir\audit-logging.conf")
}
catch {
Write-Error "Failed to install OWASP Core Rules: $($_.Exception.Message)"
exit 1
}
- name: Test IIS Module
shell: pwsh
run: |
$iisConfigDir = "C:\Program Files\ModSecurity IIS\"
Restart-Service W3SVC -Force
$modules = & "$env:SystemRoot\system32\inetsrv\appcmd.exe" list modules
Write-Host "IIS modules: $modules"
if ($LASTEXITCODE -ne 0) {
Write-Error "appcmd failed with exit code $LASTEXITCODE"
exit 1
}
if (-not ($modules -match "ModSecurity")) {
Write-Error "ModSecurity module not found in IIS modules"
Write-Host "IIS modules: $modules"
exit 1
}
$testCases = @(
@{Url = "http://localhost/"; Description = "Normal request"; ExpectedCode = 200},
@{Url = "http://localhost/?id=1' OR '1'='1"; Description = "SQL injection attempt (logged, not blocked in DetectionOnly)"; ExpectedCode = 200},
@{Url = "http://localhost/?q=<script>alert('test')</script>"; Description = "XSS attempt (logged, not blocked in DetectionOnly)"; ExpectedCode = 200}
)
foreach ($test in $testCases) {
try {
$response = Invoke-WebRequest $test.Url -UseBasicParsing -SkipHttpErrorCheck -TimeoutSec 30
if ($response.StatusCode -eq $test.ExpectedCode) {
Write-Host "PASS: $($test.Description) - returned $($response.StatusCode)"
}
else {
Write-Host "FAIL: $($test.Description) - expected $($test.ExpectedCode) but got $($response.StatusCode)"
}
}
catch {
Write-Host "ERROR: $($test.Description) - request failed: $($_.Exception.Message)"
}
}
$badMessagePattern = 'Failed to find the RegisterModule entrypoint|The description for Event ID|The data is the error|dll failed to load'
$events = Get-EventLog -LogName Application -Newest 100 |
Where-Object { $_.Message -match $badMessagePattern } |
Where-Object { $_.Source -match 'IIS|W3SVC|mscor|IIS-W3SVC|IIS-W3WP|ModSecurity' }
if ($events -and $events.Count -gt 0) {
Write-Host '::error:: Found errors in event log'
$events | Select-Object TimeGenerated, Source, EntryType, EventID, Message | Format-List
Exit 1
}
Get-EventLog -LogName Application -Source ModSecurity | Format-List
- name: Diagnose audit log
shell: pwsh
run: |
$auditLogPath = "c:\inetpub\logs\modsec_audit.log"
Write-Host "Audit log exists: $(Test-Path -LiteralPath $auditLogPath)"
if (Test-Path -LiteralPath $auditLogPath) {
Write-Host "Audit log size before: $((Get-Item -LiteralPath $auditLogPath).Length) bytes"
}
$marker = "hello-marker-$(Get-Random)"
try {
$resp = Invoke-WebRequest "http://localhost/?probe=1" -Headers @{ "X-CRS-Test" = $marker } -UseBasicParsing -SkipHttpErrorCheck -TimeoutSec 30
Write-Host "Probe request status: $($resp.StatusCode)"
} catch {
Write-Host "Probe request error: $($_.Exception.Message)"
}
Start-Sleep -Seconds 2
if (-not (Test-Path -LiteralPath $auditLogPath)) {
Write-Error "Audit log not found: $auditLogPath"
exit 1
}
Write-Host "Audit log size after: $((Get-Item -LiteralPath $auditLogPath).Length) bytes"
Write-Host "--- TAIL OF AUDIT LOG ---"
Get-Content -LiteralPath $auditLogPath -Tail 60
$markerLine = Select-String -Path $auditLogPath -Pattern "X-CRS-Test $marker" | Select-Object -First 1
if (-not $markerLine) {
Write-Error "Marker '$marker' not found in audit log"
exit 1
}
$fPart = Get-Content -LiteralPath $auditLogPath |
Select-Object -Skip $markerLine.LineNumber |
Select-String -Pattern "^HTTP/1\.1 \d{3}" | Select-Object -First 1
if (-not $fPart) {
Write-Error "No F-part status line found after marker"
exit 1
}
Write-Host "OK: probe F part = $($fPart.Line)"
if ($fPart.Line -notmatch "^HTTP/1\.1 200") {
Write-Error "Probe F part is not 200 (bogus status): $($fPart.Line)"
exit 1
}
$bogus = Get-Content -LiteralPath $auditLogPath |
Select-String -Pattern "HTTP/1\.1 500 Internal Server Error"
if ($bogus) {
Write-Error "Found bogus 500 in audit log F parts: $($bogus.Count)"
exit 1
}
Write-Host "OK: no bogus 500 entries in audit log"
- name: Install go-ftw and albedo
shell: pwsh
run: |
go install github.com/coreruleset/go-ftw@latest
go install github.com/coreruleset/albedo@latest
- name: Configure IIS reverse proxy to albedo backend
shell: pwsh
run: |
$ErrorActionPreference = "Stop"
$goBinPath = ""
if ($env:GOBIN) {
$goBinPath = $env:GOBIN
} elseif ($env:GOPATH) {
$goBinPath = Join-Path $env:GOPATH "bin"
} else {
$goBinPath = Join-Path $env:USERPROFILE "go\bin"
}
# Install URL Rewrite 2.0 and Application Request Routing (ARR)
choco install urlrewrite iis-arr -y --no-progress
& "$env:SystemRoot\system32\inetsrv\appcmd.exe" set config /section:system.webServer/proxy /enabled:true
if ($LASTEXITCODE -ne 0) { Write-Error "Failed to enable ARR proxy"; exit 1 }
Copy-Item -LiteralPath "${{ github.workspace }}\iis\tests\web.config" -Destination "C:\inetpub\wwwroot\web.config" -Force
$albedoExe = Join-Path $goBinPath "albedo.exe"
if (-not (Test-Path -LiteralPath $albedoExe)) {
Write-Error "albedo.exe not found at $albedoExe"
exit 1
}
Start-Process -FilePath $albedoExe -ArgumentList "-p", "8080" -WindowStyle Hidden
$ready = $false
for ($i = 0; $i -lt 30; $i++) {
try {
$resp = Invoke-WebRequest "http://localhost:8080/capabilities?quiet=true" -UseBasicParsing -TimeoutSec 3
if ($resp.StatusCode -eq 200) { $ready = $true; break }
} catch {
Start-Sleep -Seconds 1
}
}
if (-not $ready) { Write-Error "albedo did not become ready on port 8080"; exit 1 }
Write-Host "albedo backend is ready on http://localhost:8080"
Restart-Service W3SVC -Force
try {
$probe = Invoke-WebRequest "http://localhost/status/200" -UseBasicParsing -SkipHttpErrorCheck -TimeoutSec 15
Write-Host "Reverse proxy probe /status/200 -> $($probe.StatusCode)"
} catch {
Write-Host "Reverse proxy probe error: $($_.Exception.Message)"
}
- name: Test ModSecurity Rules
shell: pwsh
run: |
$testRuleDir = "C:\Program Files\ModSecurity IIS\coreruleset\tests\regression\tests"
$goBinPath = ""
if ($env:GOBIN) {
$goBinPath = $env:GOBIN
} elseif ($env:GOPATH) {
$goBinPath = Join-Path $env:GOPATH "bin"
} else {
$goBinPath = Join-Path $env:USERPROFILE "go\bin"
}
& "$goBinPath\go-ftw.exe" run -d $testRuleDir --config "${{ github.workspace }}\iis\tests\ftw.yaml" --show-failures-only
$exitCode = $LASTEXITCODE
Write-Host "go-ftw exit code: $exitCode"
exit $exitCode