第三篇:服务器端应用安全
《白帽子讲Web安全》7-8章
7、注入攻击
注入攻击的本质,是把用户输入的数据当做代码执行。这里有两个关键条件,第一个是用户能够控制输入;第二个是原本程序要执行的代码,拼接了用户输入的数据。
7.1、SQL注入
在SQL注入的过程中,如果网站的Web服务器开启了错误回显,则会为攻击者提供极大的便利,比如攻击者在参数中输入一个单引号“'”,引起执行查询语句的语法错误,服务器直接返回了错误信息:
MicrosoftJET Database Engine 错误'80040e14' 字符串的语法错误在查询表达式'ID=49' '中。 /showdetail.asp,行8
从错误信息中可以知道,服务器用的是Access作为数据库,查询语句的伪代码极有可能是:
sql select xxx from table_X where id = $id
错误回显披露了敏感信息,对于攻击者来说,构造SQL注入的语句就可以更加得心应手了。
这个例子属于报错注入(Error-based Injection),其中,Microsoft JET 数据库引擎(Joint Engine Technology)是微软开发的一种数据库引擎,最初用于 Microsoft Access 和 Visual Basic。JET 数据库引擎曾经是 Microsoft Access 的默认数据库引擎,但现在已经被其他产品(如 SQL Server 和 SQL Server Express)所取代。
7.1.1、盲注(Blind Injection)
所谓“盲注”,就是在服务器没有错误回显时完成的注入攻击。服务器没有错误回显,对于攻击者来说缺少了非常重要的“调试信息”,所以攻击者必须找到一个方法来验证注入的 SQL语句是否得到执行。
常见的盲注验证方法是构造简单的条件语句,根据返回页面是否发生变化来判断SQL语句是否执行。例如,通过构造 AND 1=1
和 AND 1=2
这样的条件语句,观察页面返回结果的不同来判断注入是否成功。
7.1.2、Timing Attack
利用BENCHMARK()函数,可以让同一个函数执行若干次,使得结果返回的时间比平时要长;通过时间长短的变化,可以判断出注入语句是否执行成功。这是一种边信道攻击,这个技巧在盲注中被称为Timing Attack。
攻击者接下来要实施的就是利用Timing Attack完成这次攻击,这是一个需要等待的过程。比如构造的攻击参数id值为:
1170 UNION SELECT IF(SUBSTRING (current, 1,1)= CHAR(119),BENCHMARK (5000000, ENCODE ( 'MSG', 'by 5 seconds')) ,null) FROM (Select Database() as current) as tbl;
这段 Payload 判断库名的第一个字母是否为CHAR(119),即小写的w。如果判断结果为真,则会通过BENCHMARK()函数造成较长延时;如果不为真,则该语句将很快执行完。攻击者遍历所有字母,直到将整个数据库名全部验证完成为止。
对于这个SQL语句:
-
SUBSTRING(current,1,1)
:从current
字符串的第一个字符开始,截取长度为1的子字符串。这里的current
是通过子查询SELECT Database() as current
获取的当前数据库名称。 -
CHAR(119)
:将ASCII码119转换为字符,即小写字母w
。 -
IF(SUBSTRING(current,1,1)=CHAR(119), BENCHMARK(5000000, ENCODE('MSG', 'by 5 seconds')), null)
:- 条件:
SUBSTRING(current,1,1)=CHAR(119)
,即判断当前数据库名称的第一个字符是否为w
。 - 结果A:如果条件为真,执行
BENCHMARK(5000000, ENCODE('MSG', 'by 5 seconds'))
。BENCHMARK
函数会执行500万次ENCODE('MSG', 'by 5 seconds')
,这会导致明显的延迟。 - 结果B:如果条件为假,返回
null
。
- 条件:
Q:读之前有想到对于Timing Attack(时间盲注),硬件的性能、系统负载、网络延迟等因素都可能影响执行时间,这样不会影响结果的判断吗?
A:例子中使用if语句,如果为真就会执行BENCHMARK()函数来明显的增加运行时间,而硬件等因素的影响一般不会这么大
不同的数据库系统中有类似于MySQL中的BENCHMARK()
函数,可以被Timing Attack(时间盲注)所利用:
-
MySQL:
BENCHMARK(count, expr)
:重复执行表达式expr
指定的次数count
,用于测量执行时间。
-
PostgreSQL:
pg_sleep(seconds)
:使当前会话暂停指定的秒数。可以用于时间盲注,通过延迟响应时间来判断条件是否成立。- 示例:
SELECT CASE WHEN (SELECT SUBSTRING(current_database(), 1, 1)) = 'w' THEN pg_sleep(5) ELSE pg_sleep(0) END;
-
Microsoft SQL Server:
WAITFOR DELAY 'time'
:使当前会话等待指定的时间。- 示例:
IF (SUBSTRING(DB_NAME(), 1, 1) = 'w') WAITFOR DELAY '00:00:05';
-
Oracle:
DBMS_LOCK.SLEEP(seconds)
:使当前会话暂停指定的秒数。- 示例:
BEGIN IF SUBSTR((SELECT SYS_CONTEXT('USERENV', 'DB_NAME') FROM DUAL), 1, 1) = 'W' THEN DBMS_LOCK.SLEEP(5); END IF; END;
7.2、数据库攻击技巧
7.2.1、常见的攻击技巧
在注入攻击的过程中,常常会用到一些读写文件的技巧。比如在 MySQL中,就可以通过
LOAD_FILE()
读取系统文件,并通过INTO DUMPFILE
写入本地文件。当然这要求当前数据库用户有读写系统相应文件或目录的权限。
... union select 1,1, LOAD_FILE('/etc/passwd') ,1,1;
如果要将文件读出后,再返回结果给攻击者,则可以使用下面这个技巧:
CREATE TABLE potatoes (line BLOB); UNION SELECT 1,1,HEX(LOAD_FILE('/etc/passwd')),1,1 INTO DUMPFILE ' /tmp/potatoes';LOAD DATA INEILE '/tmp/potatoes' INTO TABLE potatoes;
这需要当前数据库用户有创建表的权限。首先通过
LOAD_FILE()
将系统文件读出,再通过INTO DUMPFILE
将该文件写入系统中,然后通过LOAD DATA INFILE
将文件导入创建的表中,最后就可以通过一般的注入技巧直接操作表数据了。
CREATE TABLE potatoes (line BLOB);
:创建一个名为potatoes
的表,包含一个BLOB
类型的列。UNION SELECT 1,1,HEX(LOAD_FILE('/etc/passwd')),1,1 INTO DUMPFILE '/tmp/potatoes';
:将/etc/passwd
文件的内容读取并转换为十六进制,然后写入到/tmp/potatoes
文件中。LOAD DATA INFILE '/tmp/potatoes' INTO TABLE potatoes;
:将/tmp/potatoes
文件中的数据加载到potatoes
表中。
通过SQL注入和文件操作来读取服务器上的敏感文件,并将其内容存储在数据库表中,以便攻击者进一步访问。
除了可以使用
INTO DUMPFILE
外,还可以使用INTO OUTFILE
,两者的区别是DUMPFILE
适用于二进制文件,它会将目标文件写入同一行内;而OUTFILE
则更适用于文本文件。写入文件的技巧,经常被用于导出一个Webshell,为攻击者的进一步攻击做铺垫。因此在设计数据库安全方案时,可以禁止普通数据库用户具备操作文件的权限。
7.2.2、命令执行
在 MySQL 中,除了可以通过导出 webshell间接地执行命令外,还可以利用“用户自定义函数”的技巧,即UDF (User-Defined Functions)来执行命令。
-
概述:
- 命令执行漏洞是指攻击者通过Web应用执行系统命令的漏洞。这类漏洞通常出现在应用程序直接使用用户输入构建系统命令的情况下。
-
常见数据库中的UDF:
- MySQL(通过
lib_mysqludf_sys
提供的几个函数执行系统命令):sys_eval
,执行任意命令,并将输出返回。sys_exec
,执行任意命令,并将退出码返回。sys_get
,获取一个环境变量。sys_set
,创建或修改一个环境变量。
- MySQL(通过
- MS SQL Sever:直接使用存储过程“xp_cmdshell”执行系统命令。
- Oracle:在Oracle数据库中,如果服务器同时还有Java环境,那么也可能造成命令执行。当SQL注入后可以执行多语句的情况下,可以在Oracle中创建Java 的存储过程执行系统命令。
- 防御措施:
- 一般来说,在数据库中执行系统命令,要求具有较高的权限。在数据库加固时,可以参阅官方文档给出的安全指导文档。
- 在建立数据库账户时应该遵循“最小权限原则”,尽量避免给Web应用使用数据库的管理员权限。
7.2.3、攻击存储过程
什么是存储过程?
存储过程(Stored Procedure)是一组为了完成特定功能的SQL语句集,存储在数据库中。它们经过编译后存储在数据库中,可以通过调用存储过程的名字并给定参数(如果该存储过程带有参数)来执行,有以下特点:
- 预编译:存储过程在创建时就已经编译好,因此执行速度较快。
- 可重用:存储过程可以在多个应用程序中重复使用,减少代码冗余。
- 安全性:通过存储过程可以控制对数据库的访问权限,增强安全性。
- 减少网络流量:存储过程在服务器端执行,减少了客户端与服务器之间的数据传输量。
存储过程为数据库提供了强大的功能,它与UDF很像,但存储过程必须使用
CALL
或者EXECUTE
来执行。在MS SQL Server和Oracle数据库中,都有大量内置的存储过程。在注入攻击的过程中,存储过程将为攻击者提供很大的便利。
xp_cmdshell
是 SQL Server 中的一个扩展存储过程,允许用户在数据库服务器上执行操作系统命令。xp_cmdshell
可以执行任何有效的操作系统命令,并将输出结果返回给SQL Server。它的基本语法如下:
EXEC xp_cmdshell 'command_string';
其中,command_string
是操作系统命令。示例:
-
列出当前目录中的文件:
EXEC xp_cmdshell 'dir';
这条命令会返回当前目录中的文件列表。
-
创建一个新文件:
EXEC xp_cmdshell 'echo Hello World > C:\example.txt';
这条命令会在C盘根目录下创建一个名为
example.txt
的文件,并写入“Hello World”。
除了xp_cmdshell 外,还有一些其他的存储过程对攻击过程也是有帮助的。比如xp_regread可以操作注册表,以及其他可以操作注册表的存储过程。
此外,以下存储过程对攻击者也非常有用。
xp_servicecontrol
,允许用户启动、停止服务。如: (exec master..xp_servicecontrol 'start', 'schedule'
exec master..xp_servicecontrol 'start', 'server'
)xp_availablemedia
,显示机器上有用的驱动器。xp_dirtree
,允许获得一个目录树。xp_enumdsn
,列举服务器上的ODBC 数据源。xp_loginconfig
,获取服务器安全信息。xp_makecab
,允许用户在服务器上创建一个压缩文件。xp_ntsec_enumdomains
,列举服务器可以进入的域。xp_terminate_process
,提供进程的进程ID,终止此进程。 除了利用存储过程直接攻击外,存储过程本身也可能会存在注入漏洞。
7.2.4、编码问题
不同编码方式(如UTF-8、GBK等)可能会导致安全漏洞。例如,某些字符在不同编码下可能被解释为不同的内容,从而被攻击者利用,防御方法:
- 统一数据库、操作系统、Web应用所使用的字符集,以避免各层对字符的理解存在差异。统一设置为UTF-8是一个很好的方法。
- 如果因为种种原因无法统一字符编码,则需要单独实现一个用于过滤或转义的安全函数,在其中需要考虑到字符的可能范围。
比如,GBK编码的字符范围为:
分区 | 高位 | 低位 |
---|---|---|
GBK/1:GB2312非汉字符号 | A1~A9 | A1~FE |
GBK/2:GB2312汉字 | B0~F7 | A1~FE |
GBK/3:扩充汉字 | 81~A0 | 40~FE |
GBK/4:扩充汉字 | AA~FE | 40~A0 |
GBK/5:扩充非汉字 | A8~A9 | 40~A0 |
根据系统所使用的不同字符集来限制用户输入数据的字符允许范围,以实现安全过滤。
基于字符集的攻击并不局限于SQL注入,凡是会解析数据的地方都可能存在此问题。比如在XSS攻击时,由于浏览器与服务器返回的字符编码不同,也可能会存在字符集攻击。解决方法就是在HTML页面的
<meta>
标签中指定当前页面的charset。
7.2.5、SQL Column Truncation
在 MySQL的配置选项中,有一个sql_mode选项。当MySQL的sql-mode设置为default时,即没有开启STRICT_ALL_TABLES 选项时,MySQL对于用户插入的超长值只会提示warning,而不是error(如果是error则插入不成功),这可能会导致发生一些“截断”问题。
7.3、正确地防御SQL注入
本章中分析了很多注入攻击的技巧,从防御的角度来看,要做的事情有两件:
- 找到所有的SQL注入漏洞;
- 修补这些漏洞。
- 只对用户输入做一些escape处理,这是不够的。
- 而在SQL保留字中,像“HAVING"、“ORDER BY”等都可能出现在自然语言中,用户提交的正常数据可能也会有这些单词,从而造成误杀,因此不能轻易过滤。
7.3.1、使用预编译语句
一般来说,防御SOL注入的最佳方式,就是使用预编译语句,绑定变量。比如在Java中使用预编译的SQL语句:
string custname = request.getParameter("customerName");//This should REALLY be validated too
// perform input validation to detect attacks
String query = "SELECT account_balance FROM user_data WHERE user_name =? ";
Preparedstatement pstmt = connection.preparestatement( query );
pstmt.setString (1, custname);
ResultSet results = pstmt.executeQuery();
使用预编译的SQL语句,SQL语句的语义不会发生改变。在SQL语句中,变量用?表示,攻击者无法改变SQL的结构,在上面的例子中,即使攻击者插入类似于tom’ or ‘1’='1的字符串,也只会将此字符串当做username来查询。
在不同的语言中,都有着使用预编译语句的方法。
7.3.2、使用存储过程
除了使用预编译语句外,我们还可以使用安全的存储过程对抗SQL注入。使用存储过程的效果和使用预编语句译类似,其区别就是存储过程需要先将SQL语句定义在数据库中。但需要注意的是,存储过程中也可能会存在注入问题,因此应该尽量避免在存储过程内使用动态的SQL语句。如果无法避免,则应该使用严格的输入过滤或者是编码函数来处理用户的输入数据。
但是有的时候,可能无法使用预编译语句或存储过程,该怎么办?这的候只能再次回到输入过滤和编码等方法上来。
7.3.3、检查数据类型
- 强制数据类型:确保输入的数据类型与数据库字段的数据类型匹配。例如,
- 如果一个字段只接受整数,那么就应该验证输入是否为整数;
- 用户在输入邮箱时,必须严格按照邮箱的格式;
- 输入时间、日期时,必须严格按照时间、日期的格式,等等。
7.3.4、使用安全函数
- 使用官方的编码函数
- 参考OWASP ESAPI中的实现,这个函数由安全专家编写,更值得信赖。
ESAPI.encoder ().encodeForSQL( new ORACLE_CODEC, queryparam);
在使用时:
Codec ORACLE CODEC = new OracleCodec();
String query = "SELECT user_id FROM user_data WHERE user_name = '"+
ESAPI.encoder ().encodeForSQL( ORACLE_CODEC, req.getParameter("userID"))+"'and
user_password = '"
+ ESAPI.encoder().encodeForsQL (ORACLE_CODEC,reg.getParameter ("pwd"))+"'";
- 从数据库自身的角度来说,应该使用最小权限原则:
- 避免Web应用直接使用root、dbowner等高权限账户直接连接数据库。
- 如果有多个不同的应用在使用同一个数据库,则也应该为每个应用分配不同的账户。
- Web应用使用的数据库账户,不应该有创建自定义函数、操作本地文件的权限。
7.4、其他注入攻击
7.4.1、XML注入
XML是一种常用的标记语言,通过标签对数据进行结构化表示.XML与HTML都是SGML( Standard Generalized Markup Language,标准通用标记语言)。XML与HTML一样,也存在注入攻击,甚至在注入的方法上也非常相似。
XML,全称为可扩展标记语言(Extensible Markup Language),是一种用于传输和存储数据的标记语言,HTML和XML都是标记语言,主要区别有:
区别 | HTML | XML |
---|---|---|
目的 | 用于定义网页的结构和内容,主要用于显示信息 | 用于传输和存储数据,主要用于描述数据 |
标签 | 标签是预定义的,例如<p> 、<a> 、<div> 等 | 标签是自定义的,用户可以根据需要创建自己的标签 |
语法 | 标签不区分大小写,例如<P> 和<p> 是等效的 | 标签区分大小写,例如<Note> 和<note> 是不同的标签 |
结构 | 允许一些标签不闭合,例如<img> 标签 | 所有标签必须正确闭合,文档必须是良构的(well-formed) |
用途 | 主要用于网页设计和开发 | 广泛用于数据交换、配置文件、文档存储等 |
7.4.2、代码注入
对抗代码注入和命令注入的措施:
- 禁用危险函数:如
eval()
、system()
等可以执行命令的函数。 - 处理用户输入:在必须使用危险函数的情况下,确保对用户输入进行严格处理。
- 避免动态包含远程文件:在PHP/JSP中,避免动态
include
远程文件,或确保安全处理。
代码注入的原因及预防:
- 不安全的编程习惯:代码注入通常源于不安全的编程习惯。
- 避免使用危险函数:在开发中尽量避免使用危险函数。
- 制定开发规范:在开发规范中明确禁止使用哪些函数。
- 参考官方文档:开发语言的官方文档中通常会提供一些关于危险函数的建议。
代码注入多见于脚本语言,有时候代码注入可以造成命令注入(Command Injection)。
Q:怎么区分出代码注入呢?XSS也是代码注入的一种
7.4.3、CRLF注入
CRLF实际上是两个字符:CR是Carriage Return (ASCIl 13, \r),LF是Line Feed (ASCII 10, \n)。\r\n这两个字符是用于表示换行的,其十六进制编码分别为0x0d、0x0a。
CRLF常被用做不同语义之间的分隔符。因此通过“注入CRLF字符”,就有可能改变原有的语义。
不同操作系统中的换行符:
- Windows (CRLF):使用\r\n作为换行符;
- Unix/Linux (LF):使用\n作为换行符;
- Mac OS (CR):旧版Mac OS使用\r作为换行符,但现代Mac OS与Unix/Linux一致,使用\n。
注入场景:
-
在日志文件中,通过CRLF有可能构造出一条新的日志。
-
在HTTP协议中,HTTP头是通过“\r\n”来分隔的。因此如果服务器端没有过滤“\r\n”,而又把用户输入的数据放在HTTP头中,则有可能导致安全隐患。这种在HTTP头中的CRLF注入,又可以称为“Http Response Splitting”。
- 两次“\r\n”味着HTTP头的结束,在两次CRLF之后跟着的是HTTP Body。可以通过两次CRLF注入到HTTP Body。
- 注入HTTP头。
防御:
对抗CRLF的方法非常简单,只需要处理好“\r”、“\n”这两个保留字符即可,尤其是那些使用“换行符”作为分隔符的应用。
7.5、小结
注入攻击是应用违背了“数据与代码分离原则”导致的结果。它有两个条件:一是用户能够控制数据的输入;二是代码拼凑了用户输入的数据,把数据当做代码执行了。
8、文件上传漏洞
8.1、文件上传漏洞概述
文件上传后导致的常见安全问题一般有:
- 上传文件是Web脚本语言,服务器的Web容器解释并执行了用户上传的脚本,导致代码执行;
- 上传文件是Flash 的策略文件crossdomain.xml,黑客用以控制Flash在该域下的行为(其他通过类似方式控制策略文件的情况类似);
- 上传文件是病毒、木马文件,黑客用以诱骗用户或者管理员下载执行;
- 上传文件是钓鱼图片或为包含了脚本的图片,在某些版本的浏览器中会被作为脚本执行,被用于钓鱼和欺诈。
除此之外,还有一些不常见的利用方法,比如将上传文件作为一个入口,溢出服务器后台处理程序,如图片解析模块;或者上传一个合法的文本文件,其内容包含了PHP脚本,再通过“本地文件包含漏洞(Local File Include)”执行此脚本;等等。此类问题不在此细述。
在大多数情况下,文件上传漏洞一般都是指“上传 Web脚本能够被服务器解析”的问题,也就是通常所说的
webshell的问题。要完成这个攻击,要满足如下几个条件:
- 首先,上传的文件能够被Web容器解释执行。所以文件上传后所在的目录要是Web 容器所覆盖到的路径。
- 其次,用户能够从Web上访问这个文件。如果文件上传了,但用户无法通过Web访问,或者无法使得Web 容器解释这个脚本,那么也不能称之为漏洞。
- 最后,用户上传的文件若被安全检查、格式化、图片压缩等功能改变了内容,则也可能导致攻击不成功。
8.1.1、从FCKEditor文件上传漏洞谈起
FCKEditor是一款非常流行的富文本编辑器,为了方便用户,它带有一个上传文件功能,但是这个功能却出过许多次漏洞。
由于FCKEditor一般是作为第三方应用集成到网站中的,因此文件上传的目录一般默认都会被Web容器所解析,很容易形成文件上传漏洞。很多开发者在使用FCKEditor时,可能都不知道它存在一个文件上传功能,如果不是特别需要,建议删除FCKEditor的文件上传代码,一般情况下也用不到它。
8.1.2、绕过文件上传检查功能
-
检查文件后缀名
- 攻击者手动修改了上传过程的POST包,在文件名后添加一个%00字节,则可以截断某些函数对文件名的判断。因为在许多语言的函数中,比如在C、PHP等语言的常用字符串处理函数中,0x00被认为是终止符。
-
检查文件头
- 正常情况下,通过判断前10个字节,基本就能判断出一个文件的真是类型,浏览器的MIME Sniff功能实际上也是通过读取文件的前256个字节,来判断文件的类型的。因此,为了绕过应用中类似MIME Sniff的功能,常见的技巧是伪造一个合法的文件头,而将真实的PHP等脚本代码附在合法的文件头之后。
MIME Sniff是浏览器用来确定服务器传输内容类型的一种行为,当服务器没有明确指定内容类型时,浏览器会尝试猜测文件的类型,以便正确显示或处理该文件。
8.2、功能还是漏洞
8.2.1、Apache文件解析漏洞
Apache对于文件名的解析是从后往前解析的,直到遇见一个Apache认识的文件类型为止。那么Apache怎么知道哪些文件是它所认识的呢?这些文件类型定义在Apache的mime.types文件中。
8.2.2、IIS文件解析问题
IIS 是 Internet Information Services(互联网信息服务)的缩写,是由微软公司提供的基于 Windows 操作系统的互联网基本服务。IIS 文件通常指的是与 IIS 相关的配置文件、日志文件和网站内容文件。
谈到IIS,就不得不谈在IIS中,支持PUT功能所导致的若干上传脚本问题。
PUT是在WebDav中定义的一个方法。WebDav大大扩展了HTTP协议中GET、POST.HEAD等功能,它所包含的PUT方法,允许用户上传文件到指定的路径下。
基于Web的分布式创作和版本控制(Web Distributed Authoring and Versioning, WebDAV)是一种扩展HTTP协议的技术,在GET、POST、HEAD等几个HTTP标准方法以外添加了一些新的方法,使应用程序可对Web Server直接读写,并支持写文件锁定(Locking)及解锁(Unlock),还可以支持文件的版本控制。WebDav可以用来实现多人协同文档
在许多 Web Server 中,默认都禁用了此方法,或者对能够上传的文件类型做了严格限制。但在IIS中,如果目录支持写权限,同时开启了WebDav,则会支持PUT方法,再结合MOVE方法,就能够将原本只允许上传文本文件改写为脚本文件,从而执行webshell。MOVE能否执行成功,取决于IIS服务器是否勾选了“脚本资源访问”复选框。
一般要实施此攻击过程,攻击者应先通过OPTIONS方法探测服务器支持的HTTP方法类型,如果支持PUT,则使用PUT上传一个指定的文本文件,最后再通过MOVE改写为脚本文件。
HTTP 的 OPTIONS 方法 用于获取目的资源所支持的通信选项。客户端可以对特定的 URL 使用 OPTIONS 方法,也可以对整站(通过将 URL 设置为“*”)使用该方法。
8.2.3、PHP CGI路径解析问题
PHP CGI 路径解析问题主要涉及 cgi.fix_pathinfo 配置选项。这个选项决定了 PHP 在处理路径信息时的行为,特别是在使用 FastCGI 时。
当 cgi.fix_pathinfo 设置为 1(默认值)时,PHP 会根据请求的 URL 解析路径信息,并将其传递给脚本。这意味着,如果 URL 包含额外的路径信息(如 /index.php/some/path),PHP 会将 /some/path 解析为 PATH_INFO,并将其传递给脚本。
/*
* if the file doesn't exist, try to extract PATH_INFO out
* of it by stat 'ing back through the '/'
* this fixes ur1's like /info.php/ test
*/
if (script_path_translated &&
(script_path_translated_len = strlen(script_path_translated)) > 0 &&(script_path_translated[script_path_translated_len-1] == '/'||
..../以下省略.
这个往前递归的功能原本是想解决/info.php/test这种URL,能够正确地解析到info上。
此时SCRIPT_FILENAME需要检查文件是否存在,所以会是/path/test.jpg。而PATH_INFO此时还是notexist.php,在最终执行时,test.jpg会被当做PHP进行解析。
书中提到了URI,去了解了一下:
统一资源标识符(Uniform Resource Identifier, URI)是一个广泛的概念,用于标识和命名互联网上的资源。URI 可以是 URL 或 URN。换句话说,URI 是一个超集,包含了 URL 和 URN。例如:
URL: https://www.example.com/path/to/resource
URN: urn:isbn:0451450523
统一资源定位符(Uniform Resource Locator, URL) 用于定位和访问资源。它提供了资源的具体位置和访问方法,通常包括协议(如 HTTP、HTTPS)、域名、路径、查询参数等。例如:
https://www.example.com/path/to/resource?query=param
统一资源名称(Uniform Resource Name, URN)用于提供资源的持久名称,而不受资源位置的影响。它通常用于标识特定命名空间中的资源,例如书籍的 ISBN 号:
urn:isbn:0451450523
关系和区别
- URL 是 URI 的一种类型,它不仅标识资源,还提供了访问资源的方法。
- URN 是 URI 的另一种类型,它提供了资源的持久名称,但不包含访问资源的方法。
8.2.4、利用上传文件钓鱼
钓鱼网站在传播时,会通过利用XSS、服务器端302跳转等功能,从正常的网站跳转到钓鱼网站。不小心的用户,在一开始,看到的是正常的域名,如下是一个利用服务器端302跳转功能的钓鱼URL:
http://member1.taobao. com/member/login.jhtml?redirect_url=http://iten. taobao.avcvtion.com/auction/item_detail.asp?id=1981&a283d5d7c9443d8.jhtml?cm_cat=0
但这种钓鱼,仍然会在URL中暴露真实的钓鱼网站地址,细心点的用户可能不会上当。而利用文件上传功能,钓鱼者可以先将包含了HTML的文件(比如一张图片)上传到目标网站,然后通过传播这个文件的URL进行钓鱼,则URL中不会出现钓鱼地址,更具有欺骗性。
8.3、设计安全的文件的上传功能
本章一开始就提到,文件上传功能本身并没错,只是在一些条件下会被攻击者利用,从而成为漏洞。根据攻击的原理,笔者结合实际经验总结了以下几点。
文件上传的目录设置为不可执行 只要Web容器无法解析该目录下的文件,即使攻击者上传了脚本文件,服务器本身也不会受到影响,因此此点至关重要。在实际应用中,很多大型网站的上传应用,文件上传后会放到独立的存储上,做静态文件处理,一方面方便使用缓存加速,降低性能损耗;另一方面也杜绝了脚本执行的可能。但是对于一些边边角角的小应用,如果存在文件上传功能,则仍需要多加关注。
判断文件类型 在判断文件类型时,可以结合使用MIME Type、后缀检查等方式。在文件类型检查中,强烈推荐白名单的方式,黑名单的方式已经无数次被证明是不可靠的。此外,对于图片的处理,可以使用压缩函数或者resize函数,在处理图片的同时破坏图片中可能包含的HTML 代码。
使用随机数改写文件名和文件路径 文件上传如果要执行代码,则需要用户能够访问到这个文件。在某些环境中,用户能上传,但不能访问。如果应用使用随机数改写了文件名和路径,将极大地增加攻击的成本。与此同时,像shell.php.rar.rar这种文件,或者是 crossdomain.xml 这种文件,都将因为文件名被改写而无法成功实施攻击。
单独设置文件服务器的域名 由于浏览器同源策略的关系,一系列客户端攻击将失效,比如上传crossdomain.xml 上传包含JavaScript的XSS利用等问题将得到解决。但能否如此设置,还需要看具体的业务环境。
文件上传问题,看似简单,但要实现一个安全的上传功能,殊为不易。如果还要考虑到病毒、木马、色情图片与视频、反动政治文件等与具体业务结合更紧密的问题,则需要做的工作就更多了。不断地发现问题,结合业务需求,才能设计出最合理、最安全的上传功能。