阅读须知
亲爱的读者,我们诚挚地提醒您,WebSec实验室的技术文章仅供个人研究学习参考。任何因传播或利用本实验室提供的信息而造成的直接或间接后果及损失,均由使用者自行承担责任。WebSec实验室及作者对此概不负责。如有侵权,请立即告知,我们将立即删除并致歉。感谢您的理解与支持!
01
SQL注入审计基本思路
首先看下基本SQL注入漏洞原理的示例:
<?php
$id=$_GET['user_id'];
try{
$pdo=new \PDO('mysql:host=localhost;port=3306','root','root');
$sql="select * from dvwa.users where user_id={$id}";
$row=$pdo->query($sql);
foreach ($row as $key => $value){
print_r($value);
}
}
catch(POOException $e){
echo $e->getMessage();
}
?>
我们知道通过GET方式来获取user_id,在sql语句中,直接把用户所输入的user_id值当作查询的条件,也没有任何的过滤等等,是可以直接构造Payload,看下面示例:
http://127.0.0.1/sqltest.php?user_id=1 or 1=1
SQL语句为---select * from dvwa.users where user_id=1 or 1=1
原本的语句中没有加单引号或者加转义的函数,所以并不需要单引号
当然这是最基本的,目前市面上的设备或者系统基本不会这样写,因为这种无过滤的SQL语句大多被框架和书写规范给杜绝掉了。很多师傅在面试的时候大多面试官会问,怎么防御SQL注入,大多人会回答进行预编译处理,下来看一个使用预编译用法错误的例子:
<?php
$id=$_GET['user_id'];
try{
$pdo=new \PDO('mysql:host=localhost;port=3306','root','root');
$sql="select * from dvwa.users where user_id={$id}";
$stmt=$pdo->prepare($sql);
$stmt->execute();
foreach ($stmt as $key => $value){
print_r($value);
}
}
catch(POOException $e){
echo $e->getMessage();
}
?>
这里使用 PDO 的 prepare
方法准备 SQL 查询语句,但是SQL执行语句还是把用户输入的点直接带到查询条件中,从而存在sql注入漏洞 。下面看一个可以防御SQL注入的最基本的写法:
<?php
$id=$_GET['user_id'];
try{
$id=$pdo->quote($id);
$sql="select * from dvwa.users where user_id={$id}";
$row=$pdo->query($sql);
foreach ($row as $key => $value){
print_r($value);
}
}
catch(POOException $e){
echo $e->getMessage();
}
?>
$pdo->quote($id)
是 PDO 对象的一个方法,用于对字符串进行安全的转义和引号处理。例如,如果 $id
的值是 O'Reilly
,那么调用 $pdo->quote($id)
后返回的结果将是 'O\'Reilly'
。转义后的字符串可以直接用于构建 SQL 查询中的参数。这是一种方法,还有一种预编译可防御SQL注入的写法:
<?php
$id=$_GET['user_id'];
try{
$pdo=new \PDO('mysql:host=localhost;port=3306','root','root');
$sql = "SELECT * FROM dvwa.users WHERE user_id = :id";
$stmt = $pdo->prepare($sql);
$stmt->bindParam(':id', $id, PDO::PARAM_INT);
$stmt->execute();
foreach ($stmt as $key => $value){
print_r($value);
}
}
catch(POOException $e){
echo $e->getMessage();
}
?>
$stmt->bindParam(':id', $id, PDO::PARAM_INT)
: 这个方法用于将参数绑定到准备好的 SQL 查询语句中。在这个例子中,将变量 $id
绑定到查询语句的 :id
参数位置上,并指定参数类型为整数(PDO::PARAM_INT
)。02
实战审计通用SQL注入
在审计某cms系统时,在后台发现执行SQL语句的模块,抓包看看路由怎么走
其中,swith是一个控制变量,当mudi的值为sql时,执行SqlDeal函数
跟踪一下SqlDeal函数
if (strlen($sqlContent) == 0){
JS::AlertBackEnd('SQL语句不能为空.');
}
$newSql = strtolower($sqlContent);
if (strpos($newSql,'into outfile') !== false){
JS::AlertBackEnd('SQL语句中不能含有内容“into outfile”.'
);
}elseif (strpos($newSql,'global general_log') !== false){
JS::AlertBackEnd('SQL语句中不能含有内容“global general_log”.');
}elseif (strpos($newSql,'0x') !== false){
JS::AlertBackEnd('SQL语句中不能含有内容“0x”.');
}
$urow=$DB->GetRow('select MB_userpwd,MB_userKey from '. OT_dbPref .'member where MB_ID='. $MB->mUserID);
$userpwd = OT::DePwdData($userpwd, $pwdMode, $pwdKey);
$userpwd = md5(md5($userpwd) . $urow['MB_userKey']);
if ($urow['MB_userpwd'] != $userpwd){
JS::AlertBackEnd('登录密码错误!');
}
unset($urow);
'select MB_userpwd,MB_userKey from '.
OT_dbPref .'member where MB_ID='. $MB->mUserID
,其中包含了表名和查询条OT_dbPref
是一个常量,表示数据表前缀。$MB->mUserID
表示当前用户的 ID。查询结果将会返回一行记录,包含了两个字段 MB_userpwd
和
MB_userKey
的值。这一行记录被赋值给变量 $urow,可以通过$sqlArr = array();
if (strpos($sqlContent, ';') !== false){
preg_match_all( "@([\s\S]+?;)\h*[\n\r]@" , $sqlContent . PHP_EOL , $sqlArr ); // 数据以分号;\n\r换行 为分段标记
!empty( $sqlArr[1] ) && $sqlArr = $sqlArr[1];
$sqlArr = array_filter($sqlArr);
}else{
$sqlArr[] = $sqlContent;
}
$sqlArr
数组中。由于 preg_match_all()
函数的返回值是一个二$sqlArr[1]
不为空,则将其赋值给 $sqlArr
变量。最后使用array_filter函数,由于没有给他传递回调函数,所以默null
、false
、''
和 0
的元素,并返回一个新SET GLOBAL/**/general_log='on' 等
03
信 安 考 证
CISP、PTE、PTS、DSG、IRE、IRS、NISP、PMP、CCSK、CISSP、ISO27001...
推荐阅读
渗透实战|记一次简单的Docker逃逸+反编译jar接管云主机
由于传播、利用本公众号藏剑安全所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,公众号藏剑安全及作者不为此承担任何责任,一旦造成后果请自行承担!如有侵权烦请告知,我们会立即删除并致歉。谢谢!