1. 从老漏洞到新思路为什么这个SQL注入值得深挖上次咱们聊了PbootCMS那个老漏洞DVB-2021-2510很多朋友试了之后发现在最新版里虽然还能用但限制挺多玩起来有点束手束脚。我当时就在想这套模板系统这么复杂肯定还有别的路子能走通。果不其然最近我又摸到了一条新的利用链而且这条链子更隐蔽威力也更大。说白了这次我们不用再跟那些烦人的字符过滤较劲了直接在前台就能搞出有回显的联合查询数据拿到手软。你可能要问PbootCMS用户量这么大官方就没修吗我实测下来发现问题出在它那个强大的模板标签解析机制上。PbootCMS为了让站长能灵活定制页面设计了一套非常复杂的标签语法什么{pboot:list}、{pboot:position}功能是强了但安全边界一不小心就模糊了。这次我们要钻的空子就在TagController.php文件里处理get(tag)参数的地方。跟上次那个带vars参数过滤的request方法不同这里的get方法如果没传第二个参数默认就是null进了filter方法后因为类型检查没命中任何条件导致传入的数据几乎没受到实质性的内容限制。这就有意思了。虽然filter方法里还是会替换掉一些敏感的pboot标签内容比如{pboot:sitetitle}这些但它有个名单只防它认为“危险”的。只要我们用的标签不在那个黑名单里我们的payload就能一路畅通进入核心的解析流程。这就像保安只拦他认识的小偷你换身衣服大摇大摆进去他根本不管。所以我们的核心思路就是找一个合法的、功能强大的PbootCMS标签然后通过它的参数把我们的SQL注入代码“夹带”进去。我试了一圈{pboot:list}这个用来展示数据列表的标签就是最完美的载体。2. 漏洞原理拆解标签是怎么变成注入点的光说思路可能有点抽象咱们把代码掰开揉碎了看。当你访问一个PbootCMS站点在URL里传入类似?tag{pboot:list filterxxx}这样的参数时程序是怎么一步步把filterxxx变成SQL语句的一部分的呢这个过程就像一场精心设计的传球游戏我们的恶意数据就是那个球。首先在TagController.php里get(tag)拿到我们传入的字符串。然后它会调用一个叫parserPositionLabel的方法这个方法原本是处理导航位置标签的但它有个副作用会把我们标签里的一部分内容放到页面链接a标签的href属性里。不过这里还不是重点它只是帮我们过了第一道安检。真正的重头戏在接下来的parserAfter方法里。这个方法是个“大管家”负责解析一大堆PbootCMS标签。当它遇到我们的{pboot:list}时就会把它交给专门的parserListLabel方法去处理。这个方法会用一个正则表达式把标签里的属性名和值提取出来存到一个叫$params的数组里。比如filterxxx就会变成$params[filter] xxx。关键转折点来了在后续处理$params数组的代码里如果发现键名是filter就会把这个值取出来然后用竖线|分割。分割后的第一部分$filter[0]会被直接拼接到一个叫$where1的SQL条件数组里。注意这里是直接拼接没有用预编译语句也没有进行充分的转义或过滤。随后这个$where1数组被传入底层的getList方法并最终进入where子句和数据库查询语句结合在一起。由于$where1的内容我们完全可控一个标准的SQL注入漏洞就这么产生了。我画个简单的流程图帮你理解用户请求?tag{pboot:list filter恶意SQL代码}程序提取$params[filter] 恶意SQL代码程序拼接$where1数组中加入恶意SQL代码数据库执行SELECT ... FROM ... WHERE 恶意SQL代码和上次那个漏洞比这次的优势太明显了第一没有对空格等特殊字符进行严格过滤我们构造Payload的自由度大大提升第二查询结果会直接反映在生成的网页源代码里属于有回显的注入我们不用再盲猜了数据可以直接看到。3. 构造基础Payload手把手实现联合查询注入原理懂了咱们就来点实在的看看怎么构造一个能用的Payload。假设目标网站是http://target.com/我们想查询管理员的密码假设用户表是ay_user密码字段是password。一个最基础的、能绕过标签解析的Payload长这样为了在URL里传输需要经过URL编码http://target.com/?tagxxx:{pboot:list filter12)UNION/**/SELECT/**/1,2,3,4,5,(select/**/password/**/from/**/ay_user/**/limit/**/0,1),7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29/**/#/**/|123 scode123}[list:link linkasd]{/pboot:list}看起来有点乱别急我帮你拆解一下每个部分的作用xxx:这是一个简单的占位满足parserPositionLabel方法的一些格式要求。{pboot:list filter这是利用的核心声明我们要使用列表标签并将注入代码放在filter参数里。12)这是一个技巧让原查询不返回结果这样页面上就只会显示我们UNION查询的结果。UNION/**/SELECT联合查询语句。注意这里用/**/代替了空格。因为解析{pboot:list}标签的parserParam函数是用空格来分割属性的如果我们直接用空格filter参数的值在提取时就会被截断。所以整个Payload里都不能出现真正的空格全部用/**/MySQL中的注释符但在这里被当作空白符使用替代。(select/**/password...子查询用于获取我们想要的数据。这里查询ay_user表的第一个密码。/**/#/**/注释掉原SQL语句后面的部分。这里有个巨坑的细节对于MySQL数据库你必须用#来注释不能用--。因为--注释符后面必须跟一个空白符空格或制表符而我们的Payload里不允许有真空格所以--会报语法错误。相反#后面可以直接跟字符。如果你的目标用的是SQLite数据库那情况就反过来了SQLite只认--不认#。所以打之前最好先摸清对方数据库类型。|123 scode123filter参数解析时按|分割我们只需要第一部分后面跟一个合法的scode参数值以满足其他校验。[list:link linkasd]{/pboot:list}闭合标签的固定格式。把上面这个Payload扔到目标地址然后查看网页源代码你会在对应的HTML位置发现一个数字比如5被替换成了数据库里查询出来的密码哈希值。这就说明注入成功了4. 高级WAF绕过实战让安全设备变成“睁眼瞎”上面那个Payload虽然能用但有个致命问题特征太明显了。UNION SELECT、from、where这些关键字就像黑夜里的探照灯任何一款像样的WAFWeb应用防火墙都会立刻报警并拦截。直接拿这种Payload去打有防护的站点基本就是送人头。那怎么办放弃吗当然不。还记得我们最开始看filter方法时提到它会替换一些标签内容吗仔细看图4的代码你会发现它会把字符串x3e和x3c替换成空这不是给我们开了一扇后门吗我们可以把敏感关键字拆散中间插入大量的x3e等WAF检查完之后程序自己又会把这些x3e删掉还原出完整的SQL语句。举个例子我们要绕过对SELECT的检测原始关键字SELECT插入干扰符SELx3eECTWAF看到的是SELx3eECT可能不认为这是SELECT程序处理时把x3e替换为空变成SELECT数据库执行的最终是SELECT基于这个思路我们可以构造一个“面目全非”的Payloadhttp://target.com/?tagxxx:{pboot:list filter12)UNIx3eON/**/SELx3eECT/**/1,2,3,(selx3eect/**/column_name/**/frx3eom/**/information_schema.columns/**/where/**/table_name0x61795f75736572),5,6,7,8/**/#/**/|123 scode123}[list:link linkasd]{/pboot:list}这个Payload做了几件事混淆关键字UNION变成了UNIx3eONSELECT变成了SELx3eECTfrom变成了frx3eom。查询系统表这里我演示了查询information_schema.columns来获取ay_user表的所有列名0x61795f75736572是ay_user的十六进制避免引号。在实际渗透测试中你需要先获取表名和列名才能精准窃取数据。保持结构/**/替代空格、#注释等技巧依然保留。这种手法的好处是它完全依赖于PbootCMS自身的功能逻辑来“消毒”而不是依赖WAF的规则遗漏。很多WAF是基于正则表达式匹配关键字的它们很难判断SELx3eECT在经过系统处理后会不会变成SELECT。这就叫“借力打力”用系统的特性来绕过外部的防护。5. 实战场景演练从信息搜集到数据拖库掌握了绕过技巧我们把它放到一个完整的实战场景里过一遍。假设你现在要对一个采用PbootCMS的企业站进行授权渗透测试并且已经通过扫描或别的途径怀疑其存在此漏洞。第一步漏洞确认与指纹识别不要一上来就扔复杂的Payload。先发一个简单的探测请求看看响应是否异常。http://target.com/?tagxxx:{pboot:list filter1}[list:link linkasd]{/pboot:list}然后访问一个正常列表页比如http://target.com/list/?id1。 对比两个页面的源代码结构特别是查看{pboot:list}标签渲染区域附近的内容。如果探测请求的页面结构发生了变化或者出现了非预期的内容就初步证实标签解析被执行漏洞可能存在。同时通过报错信息或其他方式尽量判断后端数据库是MySQL还是SQLite这决定了你用#还是--注释。第二步构造绕过Payload获取数据库信息确认漏洞后使用混淆过的Payload来获取基础信息避免触发WAF。例如获取当前数据库用户名和版本...filter12)UNIx3eON/**/SELx3eECT/**/1,user(),version(),4,5...这里user()和version()是MySQL函数如果目标是SQLite则需要用sqlite_version()等函数。通过回显位置你就能看到数据库用户和版本号这对后续判断权限很重要。第三步枚举表名与列名知道数据库类型后就可以查询系统表来获取业务数据表的信息。对于MySQL就是查询information_schema.tables和information_schema.columns。记得把表名和列名都进行十六进制编码如0x61795f75736572避免使用引号减少特征。...filter... (selx3eect/**/group_concat(table_name)/**/frx3eom/**/information_schema.tables/**/where/**/table_schemadatabase()) ...这个Payload会把当前数据库的所有表名拼接起来回显。找到像ay_user、ay_member这类可能存放用户凭证的表。第四步提取核心数据找到目标表和列后就可以直接拖库了。比如提取管理员账号密码...filter... (selx3eect/**/concat(username,0x3a,password)/**/frx3eom/**/ay_user/**/limit/**/0,1) ...这里用concat把用户名和密码用冒号连接起来回显。limit 0,1一次取一条你可以通过改变这个值来遍历所有用户。第五步清理痕迹与深入利用拿到密码哈希通常是MD5后可以尝试破解。如果破解成功结合PbootCMS的后台地址通常是/admin.php就能登录后台获取Webshell进一步控制服务器。在整个过程中你的请求Payload因为经过了混淆在WAF日志里可能只是一堆看起来杂乱无章的请求极大地增加了防守方的分析难度。6. 防御视角与安全加固建议讲了这么多攻击手法咱们也得站在防守方想想怎么防。这个漏洞的根源在于用户输入tag参数经过了多层标签解析后最终在拼接SQL语句时失去了控制。对于使用PbootCMS的站长或开发人员来说我分享几个立刻就能做的加固点1. 紧急临时方案输入过滤在apps/home/controller/TagController.php文件的get方法调用处或者在其filter方法中增加对tag参数内容的严格过滤。特别是要检查传入的内容是否包含未授权的pboot:标签以及SQL敏感字符。虽然这不是根本解决办法但能挡掉大部分自动化扫描和简单攻击。2. 根本解决之道升级与修改核心关注官方更新。虽然这个漏洞在多个版本中存在但官方可能在后续补丁中修复。最核心的修复方案是修改parserListLabel方法中处理filter参数的逻辑禁止将其直接拼接进where条件而应该采用白名单机制只允许特定的、安全的字段名和运算符或者强制使用参数化查询。3. 架构层面使用预编译语句Prepared Statements这是防止SQL注入的银弹。PbootCMS的ORM或数据库操作层应该彻底摒弃字符串拼接SQL的方式所有用户输入都通过参数绑定的形式传递给数据库引擎。这样即使攻击者注入了恶意代码数据库也会将其视为数据而非指令执行。4. WAF规则定制对于运维人员可以在WAF上定制针对PbootCMS的防护规则。不仅要检测常见的UNION SELECT更要关注{pboot:list filter这样的特殊参数以及x3e、x3c这种在PbootCMS上下文中有特殊意义的字符被频繁使用的情况。可以设置规则对短时间内大量访问带tag参数的请求进行频率限制或深入检查。说实话挖到这个漏洞并研究出绕过方法的过程让我再次深刻体会到“功能越复杂攻击面越广”这个道理。PbootCMS的模板引擎本意是提供灵活性却因为一处小小的逻辑疏忽导致了全版本的通杀。对于安全研究者这是一个绝佳的学习案例它展示了如何从框架特性中寻找突破点对于开发者则是一个警钟提醒我们在追求功能强大的同时必须对用户输入的每一处处理保持敬畏。在实战中利用这个漏洞时那种通过精巧构造绕过层层防护、最终拿到数据的成就感确实让人着迷但我也时刻提醒自己这些技术只应用于授权的测试场景去帮助厂商修复问题让网络环境更安全。