SQL注入真有那么"神奇"?
嗨,大家晚上好啊,今天来聊聊SQL注入,其实很久之前我就听说过这玩意,只是当时不怎么重视,没去深入研究它,今天就彻底弄懂它,并试试看如何实现SQL注入。
SQL注入的定义, SQL注入即是指web应用程序对用户输入数据的合法性没有判断或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句的结尾上添加额外的SQL语句,在管理员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询,从而进一步得到相应的数据信息。
那么它造成的危害有哪些呢,但凡使用数据库开发的应用系统,就可能存在SQL注入攻击的媒介。自1999年起,SQL注入漏洞就成了常见安全漏洞之一。至今SQL注入漏洞仍然在CVE列表中排前10。
2011年美国国土安全局,Mitre和SANA研究所将SQL注入作为第一危险的安全漏洞。至今,SQL注入仍然是首要的难以修复的安全威胁漏洞(数据库生产厂商难以通过维护数据库自身功能或提高数据库安全策略来防范SQL注入)。
2012年,Barclaycard的一个代表声称97%的数据泄露都是由SQL注入引起的。2011年年末和2012年年初,在不到一个月的时间里,超过百万的网页遭受到SQL注入攻击。2008年见证了由于SQL注入引起的经济失调。甚至在2010年秋季,联合国官方网站也遭受SQL注入攻击。2014年一个叫“TeamDigi7al”的黑客组织攻击了美国海军的一个名为“Smart Web Move”的web应用。此次事件直接造成美国海军数据库超过22万服役人员的个人信息被泄露。而事后,美国海军动用了超过50万美元来弥补此次的数据泄密事故。
随着Web安全事件的不断频发,我们不得不思考SQL注入攻击的代价。显然,SQL注入不是一个过期的安全问题,恰恰相反,它是一种非常容易被使用的攻击方式,SQL注入并不需要高深的攻击手段便可以轻易使敏感的数据库信息被非法浏览或删除。事实上,由于SQL注入攻击简单而又非常高效,高级黑客们已开始采用某些软件自动搜索web应用程序的SQL漏洞,并利用SQL注入自动化工具来制造僵尸,并建立可自动攻击的僵尸网络。
显然,SQL注入攻击并不会在短时间内消失,而其所造成影响更是一个刻不容缓、代价不菲的重大威胁,处理一次web应用安全事故几乎要花掉20万美元。网络安全工程师们务必要意识到,研究与防范SQL注入攻击是必要的,也是首要的安全任务。简单来说,就是会造成服务器瘫痪,造成用户损失,数据泄露等等,虽然这记录的是很多年的数据,但是SQL注入还是值得我们去了解与学习的,限于篇幅原因,更多信息请看SQL注入的百度百科 ,里面记录的信息准确和齐全。
SQL注入的例子
先来看看查询的例子,一个典型的例子
var ShipCity;
ShipCity = Request.from("ShipCity");
var sql = "select * from OrdersTable where ShipCity = '"+ShipCity+"'";
变量ShipCity的值由用户提交, 在正常情况下,加入用户输入的是"Beijing" 那么SQL 语句会执行:
SELECT * FROM OrdersTable WHERE ShipCity = 'Beijing';
但假如用户输入一段有语义的SQL语句,比如:
beijing';drop table OrdersTable--
他的请求为
http://localhost:3452/ExcelUsingXSLT/Default.aspx?shipcity=beijing';drop table OrdersTable--
那么, SQL 语句在实际执行时就会如下:
SELECT * FROM OrdersTable WHERE ShipCity='Beijing'; drop table OrdersTable--'
我们看到,原本正常执行的查询语句, 现在变成了查询完后, 再执行一个drop表的操作, 而这个操作,是用户构造了恶意数据的结果。
回过头来看看注入攻击的两个条件;
用户能够控制数据的输入 - 在这里, 用户能够控制变量 ShipCity.
原本要执行的代码, 拼接成了用户的输入。
在sql 注入的过程中, 如果网站的Web 服务器, 开启了错误回显,则会为攻击者提供极大的便利。想想,这是很恐怖的一件事,所以做好sql注入防御还是很有必要的。
再来看一个登录的例子
这是一个很常见的登录页面,正常情况下,登录执行的sql语句,是这样
正常
select * from UserTable where username = 'admin' and password = '123456';
如果是SQL注入,则变成了这样,
不正常:password: ' or '1'='1
select * from UserTable where username = "admin" and password = "' or '1'='" ;
试试效果,
嗯嗯,很明显,如果宝塔服务器没有做防御,那这个系统已经攻击成功了,那么为什么能成功呢,因为密码中的1=1,所以true,就能成功登录了,这是我的理解,有什么不对的地方大佬们多多指教哈,那么,肯定有人,问我直接对输入框进行限制输入不就能预防吗,也就是前端限制,其实还是不行的,就拿这个登录来说,如果直接对接口发起请求呢,通过F12开发者工具中的network获取接口URL,然后利用postman等等测试接口工具,携带SQL语句发起攻击也是可以的,那就绕开了前端,测试接口工具postman如图
那么如何预防呢,这种情况只能在后端实现了,因为已经绕开了前端,那么肯定有人会问,这是因为知道了登录接口URL,如果我对接口进行加密处理,让它不直接暴露在浏览器中是否可行呢,这是一种想法,或许能实现,后端预防SQL注入,本文后端采用的是nodejs, 为了防止SQL注入,可以将SQL中传入参数进行编码,而不是直接进行字符串拼接。在node-mysql中,防止SQL注入的常用方法有以下四种.
方法一:使用escape()对传入参数进行编码:
参数编码方法有如下三个:
mysql.escape(param)
connection.escape(param)
pool.escape(param)
例如:
var userId = 1, name = 'test';
var query = connection.query('SELECT * FROM users WHERE id = ' + connection.escape(userId) + ', name = ' + connection.escape(name), function(err, results) {
// ...
});
console.log(query.sql); // SELECT * FROM users WHERE id = 1, name = 'test'
escape()方法编码规则如下:
Numbers不进行转换;
Booleans转换为true/false;
Date对象转换为’YYYY-mm-dd HH:ii:ss’字符串;
Buffers转换为hex字符串,如X’0fa5’;
Strings进行安全转义;
Arrays转换为列表,如[‘a’, ‘b’]会转换为’a’, ‘b’;
多维数组转换为组列表,如[[‘a’, ‘b’], [‘c’, ‘d’]]会转换为’a’, ‘b’), (‘c’, ‘d’);
Objects会转换为key=value键值对的形式。嵌套的对象转换为字符串;
undefined/null会转换为NULL;
MySQL不支持NaN/Infinity,并且会触发MySQL错误。
方法二:使用connection.query()的查询参数占位符:
可使用 ?作为查询参数占位符。在使用查询参数占位符时,在其内部自动调用 connection.escape()方法对传入参数进行编码。
如:
var userId = 1, name = 'test';
var query = connection.query('SELECT * FROM users WHERE id = ?, name = ?', [userId, name], function(err, results) {
// ...
});
console.log(query.sql); // SELECT * FROM users WHERE id = 1, name = 'test'
上面程序还可以改写成如下:
var post = {userId: 1, name: 'test'};
var query = connection.query('SELECT * FROM users WHERE ?', post, function(err, results) {
// ...
});
console.log(query.sql); // SELECT * FROM users WHERE id = 1, name = 'test'
方法三:使用escapeId()编码SQL查询标识符:
如果你不信任用户传入的SQL标识符(数据库、表、字符名),可以使用escapeId()方法进行编码。最常用于排序等。escapeId()有如下三个功能相似的方法:
mysql.escapeId(identifier)
connection.escapeId(identifier)
pool.escapeId(identifier)
例如:
var sorter = 'date';
var sql = 'SELECT * FROM posts ORDER BY ' + connection.escapeId(sorter);
connection.query(sql, function(err, results) {
// ...
});
方法四:使用mysql.format()转义参数:
准备查询,该函数会选择合适的转义方法转义参数 mysql.format()用于准备查询语句,该函数会自动的选择合适的方法转义参数。
例如:
var userId = 1;
var sql = "SELECT * FROM ?? WHERE ?? = ?";
var inserts = ['users', 'id', userId];
sql = mysql.format(sql, inserts); // SELECT * FROM users WHERE id = 1
接下来测试一下这些方法是否可行,使用了方法二,即,
connection.query("select*from user where userName=? and userPwd=?", [userName, userPwd], (error, results) => {
if (error) {
console.log(`error`);
return;
}
if (results.length > 0) {
response.writeHead(301, { 'Content-Type': "text/html;charset=utf8" });
fs.readFile('login-success.html', 'utf-8', function (err, data) {
if (err) {
throw err;
}
response.end(data);
});
效果,
这个接口正确输入密码是能登录成功的,SQL注入下,看看能否登录成功。
很明显登录失败,SQL注入失败,预防成功。在这些操作下,我们的网站才能够更加的安全,不容易被攻击。最后再介绍一个前端库,就是json-server, json-server (opens new window)是为前端提供简单易操作的 RestFul 接口的服务。下面来介绍使用方法,更详细的操作请查看官方文档 (opens new window)
安装配置
在项目中执行以下命令来安装json-server
npm install -g json-server
或
yarn global add json-server
启动服务
然后在项目根目录中执行以下命令
port 指运行服务的端口号
db.json 为数据为文件(如不存在,系统会自动创建)
如果本地有nginx等服务,必须设置--host
json-server --watch --port 3002 --host 127.0.0.1 db.json
文件结构
下面是生成的db.json文件结构,posts/comments/profile 为数据列表
{
"posts": [
{
"id": 1,
"title": "json-server",
"author": "typicode"
}
],
"comments": [
{
"id": 1,
"body": "some comment",
"postId": 1
}
],
"profile": {
"name": "typicode"
}
}
接口请求
json-server支持restful api操作方式
POST, PUT 或 PATCH 请求时设置
Content-Type: application/json
GET /posts
GET /posts/1
POST /posts
PUT /posts/1
PATCH /posts/1
DELETE /posts/1
获取列表
发送get请求来获取列表
http://localhost:3002/posts
单个资源
下面是发送get请求,用于获取单个资源
http://127.0.0.1:3002/posts/1
删除数据
删除id为 2 的posts数据,需要以delete请求以下接口
http://localhost:3002/posts/2
添加数据
发送post请求以下接口,完成数据添加
http://localhost:3002/posts
更新数据
put请求以下接口,完成数据的更新
http://localhost:3002/posts
json-server与 mock.js 结合创建测试数据,可以节省我们录入的时间。
数据配置
首先创建db.js内容如下
const Mock = require('mockjs')
//Mock.Random 是一个工具类,用于生成各种随机数据
const Random = Mock.Random
module.exports = () => {
//定义json-server需要的数据结构
let data = { news: [] }
//添加20条包含中文的内容
for (let i = 1; i <= 20; i++) {
data.news.push({
id: i,
title: Random.cword(10, 20),
content: Random.cparagraph(10),
})
}
return data
}
启动服务
然后执行json-server命令,并指定数据源为db.js
文件
json-server --watch --port 3002 --host 127.0.0.1 db.js
服务启动后访问 http://127.0.0.1:3002/news
将可以查看到数据,内容如下
[
{
"id": 1,
"title": "多实集文新步众与就关较区才信术无",
"content": "步究证断时参龙领属强林导太便拉。太问应做收织法利十到原走县设。那而复了西热会层成识院增代求。道活子政九象十维历与会较热革地。压将入酸水主花性难天相战可们青传。运电化世难以共即第音小是的说可身。存界专题先积劳级必列还究性。件想次规好江半度己为设称劳照划便。议白形从两这历重里程术术众风。数变重马米收出只受织型么压者安声作。"
},
{
"id": 2,
"title": "边例主较称问六上果维快商引比月么近",
"content": "节八场或非力切一型京保农快十三等道。科太技取参近样已入成发写名。清制并验族无维属成党数其为劳流器与东。不制持至带务极商形一时参意族。物华少革始样路县其而各件还究由物运。代须线真能格部质新万世越连常。观声市维们量速活团学须并口示面布总。比空前带反只难指问属及共给号如。明直位天活理华价今及更加。导算别温整金复造起属感路商她层提般。"
}
....
]
全文完