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=1SQL语句为---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语句的模块,抓包看看路由怎么走
抓包看看路由怎么走,可以看到是sysCheckFile_deal.php文件,执行SQL语句的参数为sqlContent
其中,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”.');}
第一个if过滤掉了sql语句为空的情况,strtolower函数将$sqlContent(SQL语句)
中的所有字符转换为小写字母,并将结果保存在 $newSql 变量中strpos函数的
作用是查看$newsql变量中是否包含'into outfile',如果包含,则为ture,剩下的if
语句是为了过滤掉sql语句中的'into outfile'、'global general_log'、'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);
该语句使用了一个名为 $DB 的对象,通过调用对象的 GetRow() 方法来查询数
据库中的一行数据。查询的 SQL 语句是 'select MB_userpwd,MB_userKey from '.
OT_dbPref .'member where MB_ID='. $MB->mUserID,其中包含了表名和查询条
件。OT_dbPref 是一个常量,表示数据表前缀。$MB->mUserID 表示当前用户的 ID。查询结果将会返回一行记录,包含了两个字段 MB_userpwd 和
MB_userKey 的值。这一行记录被赋值给变量 $urow,可以通过
$urow['MB_userpwd'] 和 $urow['MB_userKey'] 分别获取这两个字段的值。
之后则是核对密码,密码核对成功则开始执行sql语句
$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为空数组,之后使用strpos函数查找sql语句中的分号,如果没有分
号,直接把$sqlContent的值赋给数组;如果有分号,则使用preg_match_all函
数使用正则表达式来匹配 SQL 语句中的分号,并将分号前面的内容作为一个完
整的语句存储到 $sqlArr 数组中。由于 preg_match_all() 函数的返回值是一个二
维数组,需要将其中的第二维提取出来。如果 $sqlArr[1] 不为空,则将其赋值给 $sqlArr 变量。最后使用array_filter函数,由于没有给他传递回调函数,所以默
认情况下,函数会过滤掉所有值为 null、false、'' 和 0 的元素,并返回一个新
的数组。可以发现可以使用query来执行sql语句,复现时构造恶意的SQL语句即可。
SET GLOBAL/**/general_log='on' 等
★
欢 迎 加 入 星 球 !
代码审计+免杀+渗透学习资源+各种资料文档+各种工具+付费会员
进成员内部群
星球的最近主题和星球内部工具一些展示
加入安全交流群
关 注 有 礼
还在等什么?赶紧点击下方名片关注学习吧!
推荐阅读