简介:这是一套基于经典ASP技术开发的公安行业网站管理系统实战案例,面向Web开发初学者及政务系统维护人员,聚焦IP白名单访问控制这一典型安全需求,帮助理解传统IIS+ASP架构下的权限管控实现逻辑。资源包共1195个文件,主体为298个ASP核心业务脚本(含manage_web.asp、zhiban.asp等管理模块)、467个GIF/JPG/PNG图形资源、69个JS交互脚本与51个HTML前端页面,辅以CSS样式、PSD设计源稿及INI配置文件,完整呈现了从登录鉴权、值班管理到内容发布的功能链路,压缩包体积25.05MB。已有79人学习下载,可直接部署调试,深入掌握Request.ServerVariables('REMOTE_ADDR')获取客户端IP、服务端条件跳转、ASP内置对象(Session/Server/Response)协同使用等关键技能,并通过nzcms系列ASP文件结构理解国产化CMS早期演进路径。
1. 这不是普通ASP网站,而是一套带IP白名单硬隔离的公安内网管理前端
宁志公安局网站管理系统 IP限制签收版 v2022.4.30.zip 看似是常规ASP站点压缩包,实则在架构层就做了强边界控制——它不依赖IIS权限配置或防火墙策略,而是把IP校验逻辑直接嵌入到每个核心页面(manage_web.asp、zhiban.asp、list.asp)的顶部执行流中。这意味着:即使攻击者绕过登录页、伪造Session、甚至拿到管理员Cookie,只要IP不在白名单内,连index.asp的HTML骨架都吐不出来。这种“请求即拦截”的设计,比单纯在登录环节做IP判断更彻底。它面向的是公安系统内部网络环境:IP段固定(如10.128.0.0/16)、终端可控、无公网暴露面,因此不追求OAuth或JWT等现代鉴权模型,而是用最轻量、最确定的Request.ServerVariables("REMOTE_ADDR")做第一道闸机。如果你正在维护一个局域网内的政务类ASP老系统,且需要快速落地可审计的访问控制,这个版本的实现思路比重写整套后端更务实。
2. IP限制机制的三层嵌套实现与VBScript硬编码逻辑解析
2.1 白名单存储结构:从data.asax到内存缓存的加载链路
该系统未使用数据库存储IP白名单,而是将规则固化在data.asax文件中——这是ASP时代典型的配置即代码实践。打开data.asax可见如下结构:
<!-- data.asax --> <% ' 公安局内网白名单(IPv4) Dim g_IPList g_IPList = Array( _ "10.128.1.5", _ "10.128.1.12", _ "10.128.2.33", _ "10.128.2.201", _ "10.128.3.0/24", _ ' 支持CIDR网段 "10.128.4.0/24" _ ) %>提示:
data.asax在ASP中属于Application级全局文件,IIS启动时自动加载一次,所有会话共享g_IPList数组。这种设计避免了每次请求都读取文件或查库,但修改后需重启IIS或等待Application超时回收。
关键点在于10.128.3.0/24这类CIDR写法——系统并未调用第三方组件,而是用纯VBScript实现网段匹配。其核心函数位于nzcms_dna.asp中:
' nzcms_dna.asp 中的 IPInSubnet 函数 Function IPInSubnet(ip, subnet) Dim ipParts, netParts, maskBits, i If InStr(subnet, "/") = 0 Then IPInSubnet = (ip = subnet) : Exit Function End If ipParts = Split(ip, ".") netParts = Split(Split(subnet, "/")(0), ".") maskBits = CInt(Split(subnet, "/")(1)) ' 将IP和网段转为32位整数 Dim ipNum, netNum ipNum = CLng(ipParts(0)) * 2^24 + CLng(ipParts(1)) * 2^16 + CLng(ipParts(2)) * 2^8 + CLng(ipParts(3)) netNum = CLng(netParts(0)) * 2^24 + CLng(netParts(1)) * 2^16 + CLng(netParts(2)) * 2^8 + CLng(netParts(3)) ' 计算子网掩码 Dim mask mask = (2^maskBits - 1) * 2^(32 - maskBits) IPInSubnet = ((ipNum And mask) = (netNum And mask)) End Function这段代码逻辑清晰:先将IP和网段地址转为32位整数,再通过位运算(And)比对是否落在同一子网。它规避了正则表达式或字符串分割的性能损耗,在IIS 5.1+环境下毫秒级完成判断。注意maskBits必须是整数,若传入/25以外的值(如/33)会导致2^33溢出,此时函数返回False——这反而是安全兜底行为。
2.2 全局拦截入口:nzcms_2.asp中的强制校验链
所有业务页面(manage_web.asp,zhiban.asp等)顶部均包含:
<!--#include file="nzcms_2.asp"-->而nzcms_2.asp的核心逻辑是:
' nzcms_2.asp Dim clientIP, isAllowed, i clientIP = Request.ServerVariables("REMOTE_ADDR") ' 处理代理转发场景(X-Forwarded-For) If Request.ServerVariables("HTTP_X_FORWARDED_FOR") <> "" Then clientIP = Split(Request.ServerVariables("HTTP_X_FORWARDED_FOR"), ",")(0) End If isAllowed = False For i = 0 To UBound(g_IPList) If InStr(g_IPList(i), "/") > 0 Then If IPInSubnet(clientIP, g_IPList(i)) Then isAllowed = True : Exit For End If Else If clientIP = g_IPList(i) Then isAllowed = True : Exit For End If End If Next If Not isAllowed Then Response.Status = "403 Forbidden" Response.Write "<h1>访问被拒绝</h1><p>您的IP地址 " & clientIP & " 不在授权列表中。</p>" Response.End End If注意:
HTTP_X_FORWARDED_FOR的处理仅取第一个IP,防止客户端伪造长链。Response.End是关键——它立即终止响应流,后续ASP代码(包括数据库连接、HTML渲染)完全不执行。这比跳转到错误页更安全,杜绝了任何信息泄露可能。
2.3 动态IP适配:zhiban.asp中的值班员临时放行机制
zhiban.asp作为值班管理页面,实现了特殊放行逻辑:当值班员使用工号+密码登录后,其当前IP会被临时加入Application("tempIPList")集合(有效期2小时)。该机制在zhiban.asp中体现为:
' zhiban.asp 登录成功后追加 If loginSuccess Then Dim tempIPs If IsEmpty(Application("tempIPList")) Then tempIPs = Array(clientIP) Else tempIPs = Application("tempIPList") ReDim Preserve tempIPs(UBound(tempIPs) + 1) tempIPs(UBound(tempIPs)) = clientIP End If Application.Lock Application("tempIPList") = tempIPs Application("tempIPExpire") = DateAdd("h", 2, Now()) Application.UnLock End If而nzcms_2.asp的校验逻辑随后扩展为:
' 在原有 isAllowed 判断前插入 If Not IsEmpty(Application("tempIPList")) Then If Application("tempIPExpire") > Now() Then For i = 0 To UBound(Application("tempIPList")) If clientIP = Application("tempIPList")(i) Then isAllowed = True : Exit For End If Next Else Application.Lock Application("tempIPList") = Empty Application("tempIPExpire") = Empty Application.UnLock End If End If这种“静态白名单+动态临时授权”双轨制,既保障了日常访问的确定性,又满足了值班场景的灵活性。Application对象的锁机制(Lock/UnLock)确保了多用户并发写入的安全性。
3. 关键页面功能拆解与<% %>嵌入式开发模式实战
3.1manage_web.asp:基于ADO的SQL Server数据操作模板
manage_web.asp是后台管理主入口,其数据库操作严格遵循ASP经典模式:Connection→Recordset→Close。以用户列表查询为例:
<!-- manage_web.asp 片段 --> <% Dim conn, rs, sql Set conn = Server.CreateObject("ADODB.Connection") conn.Open "Provider=SQLOLEDB;Data Source=10.128.1.10;Initial Catalog=nzcms_db;User ID=sa;Password=Admin@2022;" sql = "SELECT id, username, dept, last_login FROM users WHERE status=1 ORDER BY last_login DESC" Set rs = Server.CreateObject("ADODB.Recordset") rs.Open sql, conn, 1, 3 ' adOpenKeyset, adLockOptimistic If Not rs.EOF Then %> <table border="1"> <tr><th>ID</th><th>用户名</th><th>部门</th><th>最后登录</th></tr> <% Do While Not rs.EOF %> <tr> <td><%= rs("id") %></td> <td><%= Server.HTMLEncode(rs("username")) %></td> <td><%= rs("dept") %></td> <td><%= FormatDateTime(rs("last_login"), 2) %></td> </tr> <% rs.MoveNext Loop %> </table> <% Else Response.Write "<p>暂无活跃用户</p>" End If rs.Close Set rs = Nothing conn.Close Set conn = Nothing %>提示:
Server.HTMLEncode()是必备防护,防止XSS;FormatDateTime(rs("last_login"), 2)中参数2对应vbLongDate,输出如2022年4月30日,符合国内政务系统日期习惯。连接字符串明文写死在代码中,虽不符合现代安全规范,但在内网隔离环境中属常见做法。
3.2list.asp:分页与<%=短标记的高效渲染
list.asp负责案件列表展示,采用客户端分页(因数据量小),但<%=短标记的使用极大提升了HTML生成效率:
<!-- list.asp --> <% Dim pageSize, pageNum, totalRecords, totalPages pageSize = 15 pageNum = CInt(Request.QueryString("page")) If pageNum < 1 Then pageNum = 1 ' 获取总记录数(单次查询) Set rs = Server.CreateObject("ADODB.Recordset") rs.Open "SELECT COUNT(*) FROM cases WHERE status IN (1,2)", conn, 1, 1 totalRecords = rs(0) rs.Close totalPages = Int((totalRecords - 1) / pageSize) + 1 If pageNum > totalPages Then pageNum = totalPages ' 获取当前页数据 sql = "SELECT TOP " & pageSize & " * FROM cases WHERE id NOT IN " & _ "(SELECT TOP " & ((pageNum-1)*pageSize) & " id FROM cases ORDER BY create_time DESC) " & _ "ORDER BY create_time DESC" rs.Open sql, conn, 1, 1 %> <div class="case-list"> <% Do While Not rs.EOF %> <div class="case-item"> <h3><%= Server.HTMLEncode(rs("title")) %></h3> <p><strong>编号:</strong><%= rs("case_no") %> | <strong>状态:</strong><%= IIf(rs("status")=1,"待处理","已归档") %> | <strong>时间:</strong><%= FormatDateTime(rs("create_time"), 2) %></p> </div> <% rs.MoveNext : Loop %> </div> <!-- 分页导航 --> <div class="pagination"> <% For i = 1 To totalPages %> <a href="?page=<%= i %>" class="<%= IIf(i=pageNum,"active","") %>"><%= i %></a> <% Next %> </div><%=是<% Response.Write的简写,减少字符输入,提升可读性。IIf()函数用于三元表达式,IIf(rs("status")=1,"待处理","已归档")比写完整If...Else更紧凑。分页SQL采用NOT IN子查询,虽在大数据量下有性能瓶颈,但对公安系统日均百条案件的场景足够高效。
3.3nzcms_bq.asp:BBS模块中的防注入参数化查询实践
nzcms_bq.asp是内部论坛模块,其发帖功能展示了ASP时代较先进的防注入手段——参数化查询(通过Command对象):
' nzcms_bq.asp 发帖处理 If Request.Form("action") = "post" Then Dim cmd Set cmd = Server.CreateObject("ADODB.Command") cmd.ActiveConnection = conn cmd.CommandText = "INSERT INTO posts (title, content, author_id, create_time) VALUES (?, ?, ?, GETDATE())" cmd.Parameters.Append cmd.CreateParameter("@title", 200, 1, 255, Request.Form("title")) cmd.Parameters.Append cmd.CreateParameter("@content", 201, 1, 10000, Request.Form("content")) cmd.Parameters.Append cmd.CreateParameter("@author_id", 3, 1, , Session("user_id")) cmd.Execute Response.Redirect "nzcms_bq.asp?msg=success" End If注意:
cmd.Parameters.Append显式声明参数类型(200=adVarChar,201=adLongVarChar,3=adInteger)和长度,彻底阻断SQL注入。GETDATE()由SQL Server执行,避免客户端时间篡改。这种写法在ASP项目中并不普遍,说明该系统开发者具备较强的安全意识。
4. 部署调试与IIS 6.0兼容性关键配置
4.1 IIS 6.0虚拟目录设置与脚本映射验证
该系统针对Windows Server 2003 + IIS 6.0环境优化,部署时需确认以下三项:
| 配置项 | 正确值 | 错误表现 | 验证命令 |
|---|---|---|---|
| 主目录执行权限 | “脚本和可执行文件” | 403.14错误(目录浏览被禁) | cacls "C:\Inetpub\wwwroot\nzcms" /T /E /G "IIS_WPG":R |
| ASP脚本映射 | .asp→C:\WINDOWS\system32\inetsrv\asp.dll | 404.2错误(MIME类型未注册) | cscript adsutil.vbs get w3svc/1/root/ScriptMaps |
| 默认文档 | index.asp必须在首位 | 显示IIS默认欢迎页而非系统首页 | cscript adsutil.vbs get w3svc/1/root/DefaultDoc |
提示:
adsutil.vbs位于C:\Inetpub\AdminScripts\,是IIS 6.0原生管理工具。执行get w3svc/1/root/ScriptMaps后,检查输出中是否存在.asp映射行,且路径指向asp.dll。若缺失,需手动添加:cscript adsutil.vbs set w3svc/1/root/ScriptMaps ".asp,C:\WINDOWS\system32\inetsrv\asp.dll,5,GET,HEAD,POST,TRACE"。
4.2 常见报错定位与On Error Resume Next的双刃剑用法
系统大量使用On Error Resume Next(见nzcms_sql.asax),这是ASP时代处理COM组件异常的惯用手法,但会掩盖真实错误。调试时需临时注释:
' nzcms_sql.asax 原始片段 On Error Resume Next Set conn = Server.CreateObject("ADODB.Connection") conn.Open connStr If Err.Number <> 0 Then Response.Write "数据库连接失败:" & Err.Description Response.End End If On Error GoTo 0 ' 恢复错误中断关键调试步骤:
- 在
global.asa中启用Application("debug") = True - 修改
nzcms_2.asp,在IP校验失败时追加日志:If Not isAllowed Then Dim fso, logFile Set fso = Server.CreateObject("Scripting.FileSystemObject") Set logFile = fso.OpenTextFile("C:\Inetpub\wwwroot\nzcms\logs\access.log", 8, True) logFile.WriteLine Now() & " | BLOCKED | " & clientIP & " | " & Request.ServerVariables("URL") logFile.Close Response.Status = "403 Forbidden" Response.Write "<h1>访问被拒绝</h1>..." Response.End End If - 检查IIS日志
%SystemDrive%\inetpub\logs\LogFiles\W3SVC1\,比对cs-uri-stem与sc-status字段,确认是403还是500错误。
4.3asp:gridview列中的数据怎么换行问题的本地化解决方案
虽然该系统未使用.NET的GridView,但list.asp中表格单元格内容过长时同样面临换行需求。标准做法是在CSS中设置:
<style> .case-item h3 { word-break: break-all; white-space: pre-line; } </style>但ASP环境常需服务端预处理。nzcms_dna.asp提供WrapText函数:
Function WrapText(text, maxLength) Dim result, i, lenText result = "" lenText = Len(text) For i = 1 To lenText If i Mod maxLength = 1 And i > 1 Then result = result & "<br>" result = result & Mid(text, i, 1) Next WrapText = result End Function在list.asp中调用:
<td><%= WrapText(Server.HTMLEncode(rs("title")), 20) %></td>此函数按字符数强制换行,比CSS更可控,适用于IE6等老旧浏览器——这正是公安内网环境的真实约束。
5. 安全加固与IP限制策略的演进式优化技巧
5.1 从硬编码白名单到XML配置的平滑迁移
data.asax的硬编码方式不利于运维。可创建ip_whitelist.xml:
<?xml version="1.0" encoding="GB2312"?> <whitelist> <ip>10.128.1.5</ip> <ip>10.128.1.12</ip> <subnet>10.128.3.0/24</subnet> <subnet>10.128.4.0/24</subnet> </whitelist>并在nzcms_dna.asp中添加加载函数:
Function LoadIPListFromXML() Dim xmlDoc, ipNodes, subnetNodes, i, list Set xmlDoc = Server.CreateObject("Microsoft.XMLDOM") xmlDoc.Async = False xmlDoc.Load Server.MapPath("ip_whitelist.xml") Set ipNodes = xmlDoc.SelectNodes("//ip") Set subnetNodes = xmlDoc.SelectNodes("//subnet") ReDim list(ipNodes.Length + subnetNodes.Length - 1) For i = 0 To ipNodes.Length - 1 list(i) = ipNodes(i).Text Next For i = 0 To subnetNodes.Length - 1 list(ipNodes.Length + i) = subnetNodes(i).Text Next LoadIPListFromXML = list End Function然后在data.asax中替换为:
<% Dim g_IPList g_IPList = LoadIPListFromXML() %>提示:
Microsoft.XMLDOM在IIS 6.0默认可用,无需额外安装。GB2312编码确保中文注释正常读取。此改造零侵入现有逻辑,仅替换数据源。
5.2 日志审计增强:记录IP校验全过程
在nzcms_2.asp的校验逻辑末尾添加审计日志:
' 校验完成后追加 Dim auditLog auditLog = Now() & "|" & clientIP & "|" & isAllowed & "|" & Request.ServerVariables("HTTP_USER_AGENT") & "|" & Request.ServerVariables("URL") & vbCrLf Dim fso, logFile Set fso = Server.CreateObject("Scripting.FileSystemObject") Set logFile = fso.OpenTextFile(Server.MapPath("logs\ip_audit.log"), 8, True) logFile.Write auditLog logFile.Close生成的日志格式为:
2022-04-30 14:22:18|10.128.2.33|True|Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1)|/nzcms/manage_web.asp 2022-04-30 14:23:05|192.168.1.100|False|curl/7.58.0|/nzcms/index.asp配合Windows自带的findstr命令可快速分析:
findstr "False" C:\Inetpub\wwwroot\nzcms\logs\ip_audit.log | findstr "192.168"精准定位非法扫描源。
5.3 应急熔断:nzcms_sql.asax中的数据库连接池保护
nzcms_sql.asax定义了连接池超时与重试机制:
' nzcms_sql.asax Const MAX_CONN_ATTEMPTS = 3 Const CONN_TIMEOUT = 15 ' 秒 Function GetDBConnection() Dim conn, attempts, startTime Set conn = Server.CreateObject("ADODB.Connection") attempts = 0 Do While attempts < MAX_CONN_ATTEMPTS On Error Resume Next conn.Open connStr, "", "", 64 ' adConnectUnspecified If Err.Number = 0 Then GetDBConnection = conn Exit Function End If attempts = attempts + 1 If attempts < MAX_CONN_ATTEMPTS Then WScript.Sleep 1000 ' 等待1秒后重试 End If Loop ' 三次失败后返回空连接,业务层需处理 Set GetDBConnection = Nothing End Function64标志位(adConnectUnspecified)启用连接池,WScript.Sleep避免雪崩式重连。当数据库短暂不可用时,系统降级为只读(IP校验仍生效),保障核心访问控制不中断。
本文还有配套的精品资源,点击获取