서문
데이터베이스의 매개변수화된 쿼리 방식이 점점 보편화되면서 SQL 인젝션 취약점은 이전에 비해 크게 줄어들었고, PHP에서 가장 일반적인 사전 컴파일된 쿼리 방식인 PDO를 점점 더 많이 사용하고 있습니다. 넓게.
우리 모두 알고 있듯이 PDO는 PHP에서 SQL 주입을 방지하는 가장 좋은 방법이지만 SQL 주입을 100% 제거하는 방법은 아닙니다. 열쇠는 어떻게 사용하느냐에 달려 있습니다.
이전에 기사(https://xz.aliyun.com/t/3950)에서 PDO 시나리오에서 제어 가능한 매개변수로 인해 발생하는 다중 문장 실행과 같은 문제에 대해 배웠기 때문에 SQL 주입에 대한 또 다른 연구를 다음에서 수행했습니다. PDO 시나리오를 조사했습니다.
PDO 쿼리문의 제어 하에 존재하는 보안 문제:
먼저 로컬에서 새 라이브러리와 테이블을 만들고 아무렇게나 작성합니다.
그런 다음 test.php를 작성하고 PDO를 사용하여 간단한 쿼리를 수행합니다.
<?php try{ $db = new PDO('mysql:host=localhost;dbname=pdotest','root',''); } catch(Exception $e) { echo $e->getMessage(); }if(isset($_GET['id'])) { $id = $_GET['id']; }else{ $id=1; } $query = "select balabala from table1 where 1=?";echo "id:".$id."</br>"; $row = $db->prepare($query); $row->bindParam(1,$id); $row->execute(); $result = $row->fetch(PDO::FETCH_ASSOC);if($result) { echo "结果为:"; print_r($result); echo "</br>"; }
입력 내용과 페이지에서 얻은 결과를 인쇄합니다.
PDO 보안 문제와 관련된 주요 설정은 다음과 같습니다. 다음 세 가지:
PDO::ATTR_EMULATE_PREPARES PDO::ATTR_ERRMODE PDO::MYSQL_ATTR_MULTI_STATEMENTS
는 각각 시뮬레이션된 사전 컴파일, 오류 보고 및 다중 문장 실행과 관련이 있습니다.
PDO는 기본적으로 다중 문장 실행과 시뮬레이션된 사전 컴파일을 허용합니다. 매개변수를 제어할 수 있으면 스택 주입으로 이어진다는 내용이 이전 기사에 많이 나와 있습니다.
예를 들어 쿼리 문을
$query = "select balabala from table1 where 1={$id}"; $row = $db->query($query);
로 변경하면 $db->query() 단계가 실행되기 전에 $query에 대해 잘못된 작업을 수행할 수 있으며 PDO는 쓸모가 없습니다.
PDO 기본 설정의 보안 위험:
쿼리문에 제어 가능한 매개변수가 없고 입력 매개변수를 prepare->bindParam->execute 메소드에 작성하면 문제가 없을까요?
다음 명령문에 따라 쿼리합니다.
$query = "select balabala from table1 where 1=?"; $row = $db->prepare($query); $row->bindParam(1,$_GET[‘id’]); $row->execute();
URL에 임의의 매개변수 ?id=asdasd를 입력한 다음 SET GLOBAL GENERAL_LOG=ON을 설정하여 .log에서 실시간으로 모니터링하여 SQL 문이 무엇인지 확인합니다. 실행됨 :
시뮬레이션된 사전 컴파일된 요청 전송 방법은 이전 mysqli와 다르지 않지만 원래 쿼리 문에서는 매개변수가 작은따옴표로 묶이지 않았으나 여기서는 묶인 것을 확인했습니다. 작은 따옴표가 묶여 있으므로 작은 따옴표와 같은 일부 특수 문자를 입력할 수 있습니다.
작은 따옴표가 이스케이프된 것을 발견하고 무슨 일이 일어날지 생각하지 않을 수 없었습니다. gbk 인코딩이 설정된 경우:
select * from table1이 성공적으로 실행되었음을 알 수 있습니다. PDO는 하나의 결과만 반환하지만 실제로는 실행됩니다.
즉, 쿼리문에 제어 가능한 매개변수가 없더라도 ? 또는 :id와 같은 바인딩된 매개변수만 수행할 수 있습니다.
다문장 실행을 끄면 어떻게 되나요?
PDO::MYSQL_ATTR_MULTI_STATEMENTS를 false로 설정하고 위 작업
을 반복했지만 더 이상 작동하지 않는 것으로 나타났습니다.
실제로는 gbk 설정문만 실행되었습니다
근데 이게 끝인가요?
유합주입 등 다른 방법을 시도해 보는 것은 어떨까요?
해본 결과 유니온 주입도 가능하다는 걸 알게 되었어요! 다중 문장 실행이 전혀 필요하지 않습니다!
实际上,在模拟预编译的情况下,PDO对于SQL注入的防范(PDO::queto()),无非就是将数字型的注入转变为字符型的注入,又用类似mysql_real_escape_string()的方法将单引号、双引号、反斜杠等字符进行了转义。
这种防范方法在GBK编码的情况下便可用宽字节进行绕过,而在非GBK编码的情况下,若存在二次注入的情况,是否能利用呢?
答案是否定的。
二次注入是由于对添加进数据库中的数据没有再次处理和转义而导致的,而预编译对每次查询都进行转义,则不存在二次注入的情况。
上述安全隐患,是由于未正确设置PDO造成的,在PDO的默认设置中,PDO::ATTR_EMULATE_PREPARES和PDO::MYSQL_ATTR_MULTI_STATEMENTS都是true,意味着模拟预编译和多句执行是默认开启的。
而在非模拟预编译的情况下,若语句中没有可控参数,是否还能这样做呢?
答案是否定的。
我们将PDO::ATTR_EMULATE_PREPARES设为false,来看看sql语句到底执行了什么:
它对每一句sql语句都进行了预编译和执行两个操作,在执行select balabala from table1 where 1=?这句时,如果是GBK编码,那么它将会把?绑定的参数转化成16进制,这样无论输入什么样的东西都无法再进行注入了。
如果不是GBK编码,如上面所说,也不存在二次注入的情况,故可以避免SQL注入漏洞。
相同原理的Prepare Statement方法
PDO的原理,与Mysql中prepare语句是一样的。上面PDO所执行的SQL语句,用如下的方式可以等效替代:
Set @x=0x31 Prepare a from “select balabala from table1 where 1=?” Execute a using @x
我们可以手动将输入的参数设置为@x,并将其转化为16进制,随后预编译,再执行
也就是说,不用PDO也可以仿照其原理手动设置预编译:
$db = new mysqli('localhost','root','','pdotest');if(isset($_GET['id'])) { $id = "0x".bin2hex($_GET['id']); }else{ $id=1; }echo "id:".$id."</br>"; $db->query("set names gbk"); $db->query("set @x={$id}"); $db->query("prepare a from 'select balabala from table1 where 1=?'"); $row = $db->query("execute a using @x"); $result = $row->fetch_assoc();if($result) { echo "结果为:"; print_r($result); echo "</br>"; }
得到的结果和使用PDO是一样的:
这样设置不用担心没有合理地设置PDO,或是用了GBK编码等情况。
Prepare Statement在SQL注入中的利用
Prepare语句在防范SQL注入方面起到了非常大的作用,但是对于SQL注入攻击却也提供了新的手段。
Prepare语句最大的特点就是它可以将16进制串转为语句字符串并执行。如果我们发现了一个存在堆叠注入的场景,但过滤非常严格,便可以使用prepare语句进行绕过。
例如我们将createtable table2 like table1转化成16进制,然后执行:
我们发现数据库中已经多了一个表table2。则语句成功执行了。
总结
对于此类问题的防范,主要有以下三个方面:
1. 合理、安全地使用gbk编码。即使采用PDO预编译的方式,如若配置不当,依然可造成宽字节注入
2. 使用PDO时,一定要将模拟预编译设为false
3. 可采用使用Prepare Statement手动预编译,杜绝SQL注入
相关文章教程推荐:网站安全教程
위 내용은 PDO 원리와 올바른 사용방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!