简介:这是一套基于经典ASP技术构建的轻量级网站内容管理系统源码,面向熟悉VBScript与IIS环境的初学者及中小型网站维护者,解决静态站点升级为可后台管理动态网站的实际需求。压缩包共125个文件,含62个核心ASP业务逻辑文件(如news.asp、type_edit.asp、review.asp等)、43个GIF图标资源、7个HTM/6个HTML前端页面、4个JS交互脚本、1个MDB数据库文件及配套CSS与TXT说明,整体仅184KB,结构紧凑、依赖简单,适合在Windows+IIS+Access环境下快速部署调试。已有282人学习下载,读者可直接获取完整CMS运行体系:涵盖内容发布、栏目管理、模板切换、用户权限控制等核心模块,代码清晰可读,便于理解ASP时代典型的三层结构设计与数据库操作范式,是学习传统Web开发演进路径与遗留系统维护的典型实践样本。
1. 这不是怀旧玩具,而是一套仍在生产环境跑着的ASP CMS骨架
你打开 IIS 管理器,看到Default.asp被设为首页;你用Response.Write输出调试信息,而不是console.log;你在global.asa里写Application_OnStart,而不是配置nginx.conf—— 这套「简约CMS ASP网站管理系统」不是博物馆展品,而是大量中小型企业官网、政府基层子站、教育机构内网仍在运行的真实系统。它不依赖 .NET Framework 或 SQL Server Express,只靠 VBScript + ADO + Access 或小型 SQL Server 数据库就能启动;它的incpureupload.asp不走multipart/form-data解析库,而是用Request.BinaryRead手撕二进制流;它的type_edit.asp页面里没有 React 组件树,但有 3 层嵌套的If...Then...ElseIf...End If控制逻辑。这套源码的价值,不在于“还能不能用”,而在于:当你接手一个已上线 8 年、没人敢动、数据库字段名全是中文拼音缩写(如news_bt、type_id)、管理员密码还硬编码在config.asp里的老站时,你得立刻看懂file_list.asp怎么拼接路径、review.asp的Request.QueryString("id")是否做过Trim()和IsNumeric()校验、js_create.asp生成的 JS 文件是否被缓存导致前端菜单不刷新。它面向的不是初学者练手,而是运维工程师、外包维护者、以及需要做技术债务评估的架构师。
2. 从文件结构切入:理解 ASP CMS 的三层执行模型与数据流向
这套系统虽称“简约”,但其文件组织已隐含经典 ASP 应用的三层逻辑雏形:表现层(.asp页面)、业务逻辑层(inc*.asp包含文件)、数据访问层(conn.asp或内联 ADO 连接)。解压后列出的 11 个.asp文件并非平铺直叙,而是按功能域分组,需结合<!--#include file="xxx"-->指令反向追踪依赖链。
2.1 核心入口与路由分发机制
系统无统一index.asp入口,而是采用“页面即控制器”模式。典型访问路径如http://site/news.asp?action=edit&id=123,其中news.asp同时承担展示新闻列表、渲染编辑表单、处理提交逻辑三重职责。关键判断逻辑集中在文件顶部:
<% Dim action, id action = Trim(Request.QueryString("action")) id = Trim(Request.QueryString("id")) If action = "add" Then ' 显示新增表单 Call ShowAddForm() ElseIf action = "edit" And IsNumeric(id) Then ' 显示编辑表单,且 id 必须为数字 Call ShowEditForm(CLng(id)) ElseIf action = "save" Then ' 处理保存逻辑,需验证 Referer 防 CSRF(但原代码未实现) Call DoSave() Else ' 默认显示列表 Call ShowList() End If %>注意:
CLng(id)强转是必要防护,但原包中review.asp第 47 行直接使用Request.QueryString("id")未校验,存在整型溢出风险。实际部署前必须补上IsNumeric()判断并限制范围(如id > 0 And id < 100000)。
2.2 模块化包含体系:incpureupload.asp的上传实现原理
incpureupload.asp是本系统最易被误读的文件——它并非调用第三方组件,而是纯 VBScript 实现的二进制上传解析器。其核心在于绕过 IIS 默认的Request.Form限制,直接读取原始请求体:
' incpureupload.asp 关键片段 Function GetUploadFile() Dim lngTotalBytes, lngStart, lngEnd, lngContentLength, lngBoundaryPos lngContentLength = Request.TotalBytes If lngContentLength = 0 Then Exit Function Dim binData binData = Request.BinaryRead(lngContentLength) ' 一次性读取全部二进制 ' 查找 boundary 分隔符位置(如 "--AaB03x") lngBoundaryPos = InStrB(binData, StrConv("--" & Request.Form("boundary"), vbFromUnicode)) ' 提取 filename 字段(需手动解析 multipart header) Dim strFileName strFileName = ExtractFileNameFromHeader(binData, lngBoundaryPos) ' 截取文件内容(跳过 header,直到下一个 CRLF+CRLF) Dim lngBodyStart, lngBodyEnd lngBodyStart = InStrB(lngBoundaryPos, binData, StrConv(vbCrLf & vbCrLf, vbFromUnicode)) + 4 lngBodyEnd = InStrB(lngBodyStart, binData, StrConv("--" & Request.Form("boundary"), vbFromUnicode)) - 1 ' 写入文件(关键:Server.MapPath 转物理路径) Dim objFSO, objFile Set objFSO = Server.CreateObject("Scripting.FileSystemObject") Dim strSavePath strSavePath = Server.MapPath("/uploads/" & strFileName) ' 安全检查:禁止 ../ 路径遍历 If InStr(strSavePath, "..\") > 0 Then Response.Write "非法路径" Exit Function End If Set objFile = objFSO.CreateTextFile(strSavePath, True) objFile.Write BinaryToString(MidB(binData, lngBodyStart, lngBodyEnd - lngBodyStart)) objFile.Close End Function提示:
BinaryToString是自定义函数,需在incpureupload.asp中定义,将Byte数组转为String。常见错误是直接CStr()导致乱码,正确做法是逐字节ChrB()拼接。该函数缺失会导致上传后文件内容损坏,这是cms error: 332类错误的高频原因——错误码 332 通常指向Response.BinaryWrite或ADODB.Stream写入失败,根源常在此处编码转换异常。
2.3 权限控制与会话管理:js_edit.asp与type_add.asp的登录态校验
系统未使用Session对象做完整权限控制,而是采用轻量级Cookie校验 + 页面级拦截。js_edit.asp开头强制检查:
<% If Request.Cookies("admin_user") = "" Or Request.Cookies("admin_pass") = "" Then Response.Redirect "login.asp?redirect=" & Server.URLEncode(Request.ServerVariables("SCRIPT_NAME")) Response.End End If ' 简单 MD5 校验(原包使用弱哈希,生产环境必须替换) If LCase(Request.Cookies("admin_pass")) <> "e10adc3949ba59abbe56e057f20f883e" Then Response.Clear Response.Write "<script>alert('登录失效');location.href='login.asp';</script>" Response.End End If %>type_add.asp则进一步校验操作权限,通过查询admin_user表中的role_level字段(值为 1-5),仅允许role_level >= 3的用户新增栏目。这种设计虽简单,但login.asp中密码明文存储在 Cookie 的缺陷,使其无法满足等保 2.0 基础要求。修复方案必须引入Session.SessionID绑定 IP + 时间戳,并废弃 Cookie 存储密码。
3. 数据库交互实战:用 ADO 连接 Access 与 SQL Server 的参数化避坑指南
本系统默认适配 Access 数据库(.mdb文件),但conn.asp中预留了 SQL Server 切换接口。实际部署时,90% 的故障源于连接字符串与 SQL 语句的双重陷阱。
3.1conn.asp的双模式连接字符串配置
conn.asp定义了Conn对象,其初始化逻辑如下:
<% Dim Conn, ConnStr If Request.QueryString("db") = "sql" Then ' SQL Server 模式 ConnStr = "Provider=SQLOLEDB;Data Source=localhost;Initial Catalog=aspcms;User ID=sa;Password=123456;" Else ' Access 模式(默认) ConnStr = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=" & Server.MapPath("data\aspcms.mdb") & ";" End If Set Conn = Server.CreateObject("ADODB.Connection") On Error Resume Next Conn.Open ConnStr If Err.Number <> 0 Then Response.Write "数据库连接失败:" & Err.Description & " (错误号:" & Err.Number & ")" Response.End End If On Error GoTo 0 %>关键参数说明:
Provider=Microsoft.Jet.OLEDB.4.0:仅支持 Windows Server 2003/2008,Win10/Win11 需安装 Access Database Engine 2010 Redistributable 并改用Provider=Microsoft.ACE.OLEDB.12.0Data Source路径必须为绝对物理路径,Server.MapPath是唯一安全方式,禁止拼接Request.Form("path")- SQL Server 模式下
User ID=sa是高危配置,生产环境必须创建专用账号并限制db_datareader/db_datawriter角色
3.2news.asp中的 SQL 注入防护实操
news.asp的搜索功能存在典型漏洞:
' 危险写法(原包第 89 行) sql = "SELECT * FROM news WHERE title LIKE '%" & Request.QueryString("q") & "%'"正确修复必须使用参数化查询(ADO Command 对象):
<% Dim cmd, param Set cmd = Server.CreateObject("ADODB.Command") cmd.ActiveConnection = Conn cmd.CommandText = "SELECT * FROM news WHERE title LIKE ?" cmd.Parameters.Append cmd.CreateParameter("", 200, 1, 255, "%" & Replace(Request.QueryString("q"), "'", "''") & "%") ' adVarChar, adParamInput Set rs = cmd.Execute %>参数详解:
200对应adVarChar类型,1为adParamInput方向,255是最大长度Replace(..., "'", "''")是 Access 的转义规则(SQL Server 用QUOTENAME())- 若用
Request.Form("q"),必须同步过滤\0,%,_等通配符,否则LIKE仍可被利用
3.3file_list.asp的分页查询性能优化
file_list.asp使用Recordset.PageSize实现分页,但未设置CacheSize导致内存暴涨:
' 低效写法 rs.PageSize = 10 rs.AbsolutePage = currentPage ' 此时 rs.RecordCount 为 -1,无法获取总页数高效方案需启用CursorLocation并预查总数:
<% Set rs = Server.CreateObject("ADODB.Recordset") rs.CursorLocation = 3 ' adUseClient,允许 RecordCount rs.Open "SELECT COUNT(*) FROM files", Conn, 1, 3 ' adOpenKeyset, adLockReadOnly totalRecords = rs(0) rs.Close rs.Open "SELECT * FROM files ORDER BY upload_time DESC", Conn, 1, 3 rs.PageSize = 10 rs.AbsolutePage = currentPage %>| 参数 | 含义 | 推荐值 | 风险 |
|---|---|---|---|
CursorLocation | 游标位置 | 3(adUseClient) | 1(adUseServer) 在大数据集下内存泄漏 |
LockType | 锁类型 | 3(adLockReadOnly) | 2(adLockPessimistic) 造成表锁阻塞 |
PageSize | 每页记录数 | ≤ 20 | >50 时AbsolutePage加载缓慢 |
4. 安全加固四步法:从 XSS 到文件上传的生产环境硬核防护
ASP 系统的安全短板集中于输出未编码、输入未过滤、文件操作无白名单、会话无绑定。以下四步可覆盖 95% 的 CVE 漏洞场景。
4.1 输出上下文编码:result.asp的 DOM XSS 防御
result.asp直接输出搜索关键词到<h2>您搜索的是:<%=Request.QueryString("q")%></h2>,构成典型反射型 XSS。必须按输出上下文选择编码函数:
' 定义通用编码函数(放入 inc/common.asp) Function HTMLEncode(str) If IsNull(str) Or str = "" Then Exit Function str = Replace(str, "&", "&") str = Replace(str, "<", "<") str = Replace(str, ">", ">") str = Replace(str, """", """) str = Replace(str, "'", "'") HTMLEncode = str End Function ' 在 result.asp 中调用 Response.Write "<h2>您搜索的是:" & HTMLEncode(Request.QueryString("q")) & "</h2>"注意:
HTMLEncode仅适用于 HTML 文本上下文。若输出到<script>内,需额外JavaScriptEncode(对\,',"进行\xXX编码);若输出到 URL 参数,需URLEncode。原包中js_edit.asp的document.write('<div id="'+id+'">')未编码id,是 DOM XSS 高危点。
4.2 文件上传白名单机制:incpureupload.asp的扩展名校验增强
incpureupload.asp仅检查filename后缀,易被shell.asp.jpg绕过。强化方案需双重校验:
' 增强版文件类型检查 Function IsValidFileType(fileName) Dim ext, validExts ext = LCase(Right(fileName, Len(fileName) - InStrRev(fileName, "."))) validExts = Array("jpg", "jpeg", "png", "gif", "pdf", "doc", "docx", "xls", "xlsx") ' 附加 MIME 类型校验(需读取文件头) Dim fileHead fileHead = LeftB(binData, 4) ' 读取前 4 字节 Select Case ext Case "jpg", "jpeg" If Not (AscB(MidB(fileHead,1,1)) = 255 And AscB(MidB(fileHead,2,1)) = 216) Then Exit Function Case "png" If Not (AscB(MidB(fileHead,1,1)) = 137 And AscB(MidB(fileHead,2,1)) = 80) Then Exit Function End Select IsValidFileType = (UBound(Filter(validExts, ext)) >= 0) End Function4.3 数据库连接凭据隔离:conn.asp的密钥外置方案
将数据库密码硬编码在conn.asp中违反最小权限原则。推荐方案是使用 Windows 系统环境变量:
' 修改 conn.asp 中的连接字符串 ConnStr = "Provider=SQLOLEDB;Data Source=localhost;Initial Catalog=aspcms;User ID=aspcms_user;Password=" & GetEnvironmentVariable("ASPCMS_DB_PASS") & ";" ' 自定义函数(需启用 IIS 的 ASP 脚本权限) Function GetEnvironmentVariable(varName) Dim wshShell, envVar Set wshShell = Server.CreateObject("WScript.Shell") GetEnvironmentVariable = wshShell.Environment("Process").Item(varName) End Function操作步骤:
- 在服务器上以 Administrator 身份运行
setx ASPCMS_DB_PASS "MySecurePass!2024" /M- 重启 IIS
iisreset /noforce- 确保
wshshell对象未被 IIS 的「禁用不安全组件」策略屏蔽
4.4 会话固定攻击防护:login.asp的 SessionID 重置
原login.asp登录成功后未重置 SessionID,导致会话固定(Session Fixation)。修复代码:
' login.asp 登录成功后插入 Session.Contents.RemoveAll Session.Abandon ' 强制生成新 SessionID Response.Cookies("ASPSESSIONID") = "" Response.Cookies("ASPSESSIONID").Expires = DateAdd("d", -1, Now) ' 重定向触发新会话 Response.Redirect "admin/index.asp"5. 迁移过渡技巧:用 IIS URL Rewrite 模块实现 ASP 到现代框架的平滑切换
当业务方要求“保留原有 URL 不变,但后台换成 Node.js”,无需重写全部逻辑。IIS 的 URL Rewrite 模块可将/news.asp?id=123代理到https://new-api.example.com/news/123。
5.1 规则配置:匹配 ASP 路径并提取参数
在web.config的<system.webServer>节点下添加:
<rewrite> <rules> <rule name="ASP to API Proxy" stopProcessing="true"> <match url="^news\.asp$" /> <conditions logicalGrouping="MatchAll"> <add input="{QUERY_STRING}" pattern="id=([0-9]+)" /> </conditions> <action type="Rewrite" url="https://new-api.example.com/news/{C:1}" /> </rule> <rule name="Static File Fallback" stopProcessing="true"> <match url="^(.*\.(jpg|jpeg|png|gif|css|js|html))$" /> <action type="None" /> </rule> </rules> </rewrite>参数说明:
{C:1}引用条件中第一个捕获组(即id值),避免后端重复解析 QueryStringstopProcessing="true"确保匹配后不再执行后续规则Static File Fallback规则保证图片/CSS/JS 等静态资源仍由 IIS 直接服务,不经过代理
5.2 ASP 页面的渐进式改造:type_edit.asp的混合渲染模式
保留type_edit.asp作为前端模板,但将数据获取逻辑替换为 AJAX:
<!-- type_edit.asp 中 --> <script> fetch('/api/v1/types/<%=id%>') .then(r => r.json()) .then(data => { document.getElementById('title').value = data.title; document.getElementById('content').innerHTML = data.content; }); </script>后端 API 返回标准 JSON,ASP 页面退化为纯 HTML+JS 容器。此模式下,type_edit.asp的 VBScript 逻辑可逐步下线,最终仅剩<%Response.Write "Hello World"%>占位符。
5.3 日志关联:用X-Request-ID实现跨系统追踪
在 IIS 的web.config中注入请求 ID:
<system.webServer> <httpProtocol> <customHeaders> <add name="X-Request-ID" value="{GUID}" /> </customHeaders> </httpProtocol> </system.webServer>ASP 页面中读取该 Header 并写入日志:
<% Dim reqId reqId = Request.ServerVariables("HTTP_X_REQUEST_ID") If reqId = "" Then reqId = CreateObject("Scriptlet.TypeLib").Guid ' 记录到文本日志或转发至 ELK Call LogToFile("[" & reqId & "] User " & Session("user") & " edited type " & id) %>此 ID 可贯穿 ASP、Node.js、Python 后端,实现全链路问题定位。当cms error: 332出现时,只需搜索该 ID 即可定位所有相关日志行。
本文还有配套的精品资源,点击获取