php连接mysql的三种方式和预处理下的sql注入

0x00 前言

学习了一下堆叠注入和这三种连接方式预处理下的SQL注入问题。

0x01 基础知识

参考:

https://www.cnblogs.com/joshua317/articles/5989781.html

https://www.cnblogs.com/geaozhang/p/9891338.html

1、即时 SQL

一条 SQL 在 DB 接收到最终执行完毕返回,大致的过程如下:

  1. 词法和语义解析;

  2. 优化 SQL 语句,制定执行计划;

  3. 执行并返回结果;

  如上,一条 SQL 直接是走流程处理,一次编译,单次运行,此类普通语句被称作 Immediate Statements (即时 SQL)。

2、预处理 SQL

但是,绝大多数情况下,某需求某一条 SQL 语句可能会被反复调用执行,或者每次执行的时候只有个别的值不同(比如 select 的 where 子句值不同,update 的 set 子句值不同,insert 的 values 值不同)。如果每次都需要经过上面的词法语义解析、语句优化、制定执行计划等,则效率就明显不行了。

  所谓预编译语句就是将此类 SQL 语句中的值用占位符替代,可以视为将 SQL 语句模板化或者说参数化,一般称这类语句叫Prepared Statements。

  预编译语句的优势在于归纳为:一次编译、多次运行,省去了解析优化等过程;此外预编译语句能防止 SQL 注入。

3:PHP与MySQL的连接有三种API接口,分别是:PHP的MySQL扩展 、PHP的mysqli扩展 、PHP数据对象(PDO)

我们对比一下看看两者的区别,可以看到Mysqli和PDO是都是支持多语句执行的,这就为我要在后面写的注入埋下伏笔。

MySQL扩展

PHP的MySQL扩展是设计开发允许php应用与MySQL数据库交互的早期扩展。MySQL扩展提供了一个面向过程的接口,并且是针对MySQL4.1.3或者更早版本设计的。因此这个扩展虽然可以与MySQL4.1.3或更新的数据库服务端进行交互,但并不支持后期MySQL服务端提供的一些特性。由于太古老,又不安全,所以已被后来的mysqli完全取代;

mysqli扩展

PHP的mysqli扩展,我们有时称之为MySQL增强扩展,可以用于使用 MySQL4.1.3或更新版本中新的高级特性。其特点为:面向对象接口 、prepared语句支持、多语句执行支持、事务支持 、增强的调试能力、嵌入式服务支持 、预处理方式完全解决了sql注入的问题。不过其也有缺点,就是只支持mysql数据库。如果你要是不操作其他的数据库,这无疑是最好的选择。

Mysqli通过multi_query()函数来进行多语句执行

<?php
$host=‘127.0.0.1‘;
$dbName=‘mysqli‘;
$user=‘root‘;
$pass=‘root‘;
$mysqli = mysqli_connect($host,$user,$pass,$dbName);
if(mysqli_connect_errno())
{
   echo mysqli_connect_error();
}
$sql = "select * from user where id=1;";
$sql .= "create table test2 like user";
$mysqli->multi_query($sql);
$data = $mysqli->store_result();
print_r($data->fetch_row());
mysqli_close($mysqli);

我们在本地建立一个user表,然后请求我们的php

发现数据库中成功创建了test2表,说明多语句成功执行

PDO

PDO是PHP Data Objects的缩写,是PHP应用中的一个数据库抽象层规范。PDO提供了一个统一的API接口可以使得你的PHP应用不去关心具体要连接的数据库服务器系统类型,也就是说,如果你使用PDO的API,可以在任何需要的时候无缝切换数据库服务器,比如从Oracle 到MySQL,仅仅需要修改很少的PHP代码。其功能类似于JDBC、ODBC、DBI之类接口。同样,其也解决了sql注入问题,有很好的安全性。不过他也有缺点,某些多语句执行查询不支持(不过该情况很少)。

同样的,我们使用PDO中的query()函数同数据库交互

先新建一个数据库

<?php
$dbms=‘mysql‘;
$host=‘127.0.0.1‘;
$dbName=‘pdo‘;
$user=‘root‘;
$pass=‘root‘;
$dsn="$dbms:host=$host;dbname=$dbName";
try {
     $pdo = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
     echo $e;
}
$sql = "select * from user where id=1;";
$sql .= "create table test2 like user";
$stmt = $pdo->query($sql);
while($row=$stmt->fetch(PDO::FETCH_ASSOC))
{
    var_dump($row);
    echo "<br>";
}

运行我们的php文件后发现数据库中成功创建了test2表,说明多语句成功执行

再来看一个:

PDO默认支持多语句查询,如果php版本小于5.5.21或者创建PDO实例时未设置PDO::MYSQL_ATTR_MULTI_STATEMENTS为false时可能会造成堆叠注入

$dbms=‘mysql‘;
$host=‘127.0.0.1‘;
$dbName=‘pdo‘;
$user=‘root‘;
$pass=‘root‘;
$dsn="$dbms:host=$host;dbname=$dbName";
try {
     $pdo = new PDO($dsn, $user, $pass);
} catch (PDOException $e) {
     echo $e;
}
$id = $_GET[‘id‘];
$sql = "SELECT * from user where id =".$id;
$stmt = $pdo->query($sql);
while($row=$stmt->fetch(PDO::FETCH_ASSOC))
{
    var_dump($row);
    echo "<br>";
}

执行:

http://127.0.0.1:88/pdo2.php?id=1;create table wtz like user

成功创建wtz数据表

如果想禁止多语句执行,可在创建PDO实例时将PDO::MYSQL_ATTR_MULTI_STATEMENTS设置为false

new PDO($dsn, $user, $pass, array( PDO::MYSQL_ATTR_MULTI_STATEMENTS => false))

mysql预处理和问题

MySQL数据库支持预处理,预处理或者说是可传参的语句用来高效的执行重复的语句。

MySQL官方将prepare、execute、deallocate统称为PREPARE STATEMENT

预制语句的SQL语法基于三个SQL语句:

// 定义预处理语句
PREPARE stmt_name FROM preparable_stmt;
// 执行预处理语句
EXECUTE stmt_name [USING @var_name [, @var_name] ...];
//删除(释放)定义
{DEALLOCATE | DROP} PREPARE stmt_name;

我们先来看一个正常的预处理:

解释:

set用于设置变量名和值

prepare用于预备一个语句,并赋予名称,以后可以引用该语句

execute执行语句

deallocate prepare用来释放掉预处理的语句

这里我用16进制来表示user表赋值给变量a

然后预备一个名叫execsql的语句。

然后执行。

而如果想要执行这么多语句,我们必须要能够多语句执行,因此这个想法也就造就了堆叠注入。

堆叠注入的使用条件十分有限,其可能受到API或者数据库引擎,又或者权限的限制只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行,但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_ query()函数,其只能执行一条语句,分号后面的内容将不会被执行,所以可以说堆叠注入的使用条件十分有限,一旦能够被使用,将可能对网站造成十分大的威胁。

想了解的可以去看一个ctf题目

[强网杯 2019]随便注,以前也写过

https://www.cnblogs.com/wangtanzhi/p/11845760.html

PDO预处理和问题

PDO默认支持多语句查询,如果php版本小于5.5.21或者创建PDO实例时未设置PDO::MYSQL_ATTR_MULTI_STATEMENTS为false时可能会造成堆叠注入

PDO分为模拟预处理和非模拟预处理。

模拟预处理是防止某些数据库不支持预处理而设置的,在初始化PDO驱动时,可以设置一项参数,PDO::ATTR_EMULATE_PREPARES,作用是打开模拟预处理(true)或者关闭(false),默认为true。PDO内部会模拟参数绑定的过程,SQL语句是在最后execute()的时候才发送给数据库执行。

非模拟预处理则是通过数据库服务器来进行预处理动作,主要分为两步:第一步是prepare阶段,发送SQL语句模板到数据库服务器;第二步通过execute()函数发送占位符参数给数据库服务器进行执行。

PDO与安全问题相关的主要的设置有下面三个:

PDO::ATTR_EMULATE_PREPARES

PDO::ATTR_ERRMODE

PDO::MYSQL_ATTR_MULTI_STATEMENTS

在分别开启的情况下分别与模拟预编译、报错和多句执行有关。

PDO默认是允许多句执行和模拟预编译的,上面也提过了,在参数可控的情况下,会导致堆叠注入。

问题很大的模拟预编译

在模拟预编译的情况下,PDO对于SQL注入的防范(PDO::queto()),无非就是将数字型的注入转变为字符型的注入,又用类似mysql_real_escape_string()的方法将单引号、双引号、反斜杠等字符进行了转义。

这种防范方法在GBK编码的情况下便可用宽字节进行绕过。

非GBK编码的情况下,若存在二次注入的情况,是否能利用呢?

二次注入是由于对添加进数据库中的数据没有再次处理和转义而导致的,而预编译对每次查询都进行转义,则不存在二次注入的情况。

上述安全隐患,是由于未正确设置PDO造成的,在PDO的默认设置中,PDO::ATTR_EMULATE_PREPARES和PDO::MYSQL_ATTR_MULTI_STATEMENTS都是true,意味着模拟预编译和多句执行是默认开启的。

而在非模拟预编译的情况下,若语句中没有可控参数,则不可以注入。

结论

非模拟预编译的情况下,若语句中没有可控参数

如果不是GBK编码,如上面所说,也不存在二次注入的情况,故可以避免SQL注入漏洞。

PDO中的的Prepare Statement方法

PDO的原理,与Mysql中prepare语句是一样的。上面我已经演示过了。

这样设置不用担心没有合理地设置PDO,或是用了GBK编码等情况。

Prepare Statement在SQL注入中的利用

Prepare语句最大的特点就是它可以将16进制串转为语句字符串并执行。如果我们发现了一个存在堆叠注入的场景,但过滤非常严格,便可以使用prepare语句进行绕过。

利用方式同上

总结

  1. 合理、安全地使用gbk编码。即使采用PDO预编译的方式,如若配置不当,依然可造成宽字节注入
  2. 使用PDO时,一定要将模拟预编译设为false
  3. 可采用使用Prepare Statement手动预编译,杜绝SQL注入
  4. 创建PDO实例时将PDO::MYSQL_ATTR_MULTI_STATEMENTS设置为false,禁止多语句查询。

参考

https://xz.aliyun.com/t/3950#toc-3

https://www.freebuf.com/articles/web/216336.html

原文地址:https://www.cnblogs.com/wangtanzhi/p/12588840.html

时间: 2024-11-04 15:52:30

php连接mysql的三种方式和预处理下的sql注入的相关文章

JDBC 创建连接对象的三种方式

创建连接对象的三种方式 //第一种方式 Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mydb?user=root&password=root") ; //第二种方式 //读取properties文件 Properties pro = new Properties() ; InputStream in = JdbcDemo3.class.getClassLoader().ge

sqlplus连接oracle数据库三种方式

方式一:命令提示符下敲入sqlplus,然后键入用户名和口令,测试查询 方式二:命令提示符下 敲入sqlplus 用户名/口令@数据库实例 方式三:命令提示符下敲入sqlplus /nolog,然后连接数据库connect scott/[email protected] sqlplus连接oracle数据库三种方式

Java连接MySQL数据库三种方法

好久没有更新博客了!今天利用周目时学习了一下数据库mysql.介绍一下数据库的三种连接方式! 开发工具:Myeclipse MySQL5.6 MySQL连接驱动:mysql-connector-java-5.1.27.jar 加载驱动: 1. 在工程目录中创建lib文件夹,将下载好的JDBC放到该文件夹下,如下图所示: 2. 右键工程名,在java build path中的Libraries分页中选择Add JARs...,选择刚才添加的JDBC,如下图: 也可以在项目"右击",选择&

java连接linux的三种方式(附执行命令)

# 本地调用使用JDK自带的RunTime类和Process类实现 public static void main(String[] args){ Process proc = RunTime.getRunTime().exec("cd /home/winnie; ls;") // 标准输入流(必须写在 waitFor 之前) String inStr = consumeInputStream(proc.getInputStream()); // 标准错误流(必须写在 waitFor

C#连接mysql三种方式

第一种方式: 使用MySQLDriverCS.dll连接 MySQLDriverCS软件下载:http://sourceforge.net/projects/mysqldrivercs/?source=typ_redirect 安装完之后再引用中添加引用,找到安装目录,找到MySQLDriverCS.dll文件,然后添加using MySQLDriverCS.dll文件 参考网址:http://www.cnblogs.com/genli/articles/1956537.html C#连接mys

java基础-jdbc——三种方式加载驱动建立连接

1 String url = "jdbc:mysql://localhost:3306/student?Unicode=true&characterEncoding=utf-8"; 2 Properties info = new Properties(); 3 info.put("user", "canon"); 4 info.put("password", "123456"); 5 6 /** 7

【整理】Linux下中文检索引擎coreseek4安装,以及PHP使用sphinx的三种方式(sphinxapi,sphinx的php扩展,SphinxSe作为mysql存储引擎)

一,软件准备 coreseek4.1 (包含coreseek测试版和mmseg最新版本,以及测试数据包[内置中文分词与搜索.单字切分.mysql数据源.python数据源.RT实时索引等测试配置]) Mysql源码包 (必须选择与你已安装mysql的版本一致) 为了避免安装中出现依赖包缺失,你需要打一句鸡血: yum install make gcc g++ gcc-c++ libtool autoconf automake imake mysql-devel libxml2-devel exp

[PHP]PHP编程操作Mysql数据库的三种方式

当我开始去接触PHP的时候,真切的感受到其所具有的魅力,本着学习的态度和打破固有的语言和模式的想法,开始了PHP之旅,总的来说,走的还是比较顺利,在其中能够看到C,Java,Perl影子,学习曲线不大,但是做好产品仍然有着一条漫漫长路. 多余的话不说了,慢慢感受和领悟,本文主要讲述PHP操作数据库的三种扩展. 如下图是PHP访问数据库的三种扩展方式: 下面举出三种方式访问数据库并查询数据的实例代码: 1.mysql扩展 <?php //1:获取数据库连接 $connection = @ mysq

了解mysql的三种不同安装方式的区别

学习目的:了解mysql的三种不同安装方式的区别 学习内容: mysql 的安装有三种:分别是源码安装.二进制安装.rpm安装. 源码安装的优势:linux操作系统开放源代码,因此在其上面安装的软件大部分也都是开源软件.开源软件基本都提供源码下载和源码安装的方式.源码安装的好处是用户可以自己定制软件的功能,安装需要的模块,不需要的功能可以不用安装,此外,用户还可以自己选择安装的路径,方便管理.卸载软件也很方便,只需要删除对应的安装目录即可.没有windows所谓的注册表之说. 源码安装软件的基本