实验环境:Windows11&&Apache2.4.39&&php5.4.45
pass1 前端验证&禁用js
直接上传php木马,出现了“禁止上传”该文件的提示。
这个提示框一眼js的alert,而且burp也没抓到请求包,怀疑是前端js验证,于是禁用js。我这里用的是火狐浏览器,直接在url地址栏输入about:config,这里可以修改浏览器的相关配置;搜索javascript.enabled,改成false禁用它
再尝试上传a.php,成功,蚁剑连接成功
pass2 Content-Type
直接上传a.php木马,提示上传不了,并且也能抓到包,把包放出去之后才会提示上传不了,所以是后端验证。这里直接看后端的文件过滤的源码
发现代码只对$_FILES[‘upload_file’]数组中[‘type’]值进行验证。$_FILES数组是php中的一个数组,存储的是当前http请求中上传的文件信息。这个[‘upload_file’]则是前端表单的name属性的值,文件信息存储在$_FILES[‘upload_file’]数组中。这个数组有name、type、tmp_name等属性。其中[‘type’]中存储的值就是前端浏览器对原始文件名生成的值,发包时存储在Content-Type中。尝试修改Content-Type的值为白名单的其中之一,再发包上传:
上传成功,蚁剑连接成功
pass3 Apache解析漏洞
直接看后端验证原代码:
$deny_ext = array('.asp','.aspx','.php','.jsp');
后缀名的黑名单
$file_name = trim($_FILES['upload_file']['name']);
php中trim函数用来去除字符串首尾的空格、换行符、制表符等符号
后面的代码都有注释,这里多提一下,deldot()函数php内部并没有声明,是这个靶场自己造的轮子,用来删除字符串末尾的点,遇到不是点的字符则停止。
观察黑名单,只有简单的四个后缀名。某些情况下apache对php4、php5、php7、phtml等后缀名当成php文件进行解析。因此可以尝试上传这些后缀名的php文件。
上传后进行访问,发现无法当做php文件进行解析,我的本地apache环境有些问题,尝试在Apache的全局配置文件httpd.conf文件中添加AddType application/x-httpd-php .php .php3 .php4 .php5 .phtml ,依旧无用,因此尝试换个思路。
发现后端代码没有对.htaccess文件进行过滤,.htaccess也是Apache的配置文件,不过只对当前的目录以及子目录生效,优先级高于httpd.conf。上传.htaccess文件,内容为AddType application/x-httpd-php .png,即为把当前目录及子目录下所有png文件当成php文件进行解析。
上传.htaccess并上传a.php,上传a.php时抓包把原始文件名改成a.png,尝试访问a.png:
依旧无法解析。可能是apache版本限制了所有的自定义解析配置。不打算换apache版本了,尝试换个思路
尝试上传.user.ini文件,再上传内容为一句话木马的a.png。upload目录中存在readme.php,在.user.ini写入
auto_prepend_file=a.png 进行文件包含,打开readme.php时会先加载a.png,a.png加载进了readme.php,就会被当做php执行。这时访问readme.php,就能连接后门。
观察源码,发现存储文件时会对文件重命名,这样先前上传的.user.ini就失效了。不过没关系,木马已经上传,再上传一个.user.ini,包含这个路径名的文件。
发现可以访问,但是是乱码;蚁剑尝试连接依旧不成功。猜测php版本问题,换成7版本:
蚁剑连接不成功,还是没成功包含。看一下php的全局配置文件php.ini,有没有禁用.user.ini:
发现并没有。。。实在是没辙,估计是phpstudy的问题(只能甩锅给phpstudy了)
直接用图片马绕过。靶场存在一个include.php的文件,存在文件包含漏洞。拼接路径:
访问成功。蚁剑尝试连接:
后面的pass4 pass5观察源码,发现最显然的都是apache解析漏洞。因为环境问题,不再尝试,通通用图片马!
pass6 大写绕过
观察源码,很显然发现并没有对后缀转换成小写,且是黑名单验证,故直接大写绕过,上传成功。
但是保存在服务器上的时候,我的windows大写并不会直接转换成小写。。。而我的apache有一百个问题,自然是无法解析大写PHP的。但是我看有的人的windows不管怎样改后缀名大写,保存的时候会默认转变成小写。。
用docker搭了一个upload-labs windows,尝试在这上面进行大写绕过
这里环境中的apache可以直接解析大写的PHP文件。。。
pass7 空格绕过
观察源码,发现并没有对后缀进行空格过滤:
于是抓包,给filename加一个空格,上传成功。windows中后缀名的空格会自动删除,还有.也是。因此上传上去后文件名变成a.php。
但是这一关我提示上传出错,看源码能知道是已经绕过了黑名单,是在临时文件路径保存到upload路径上时发生错误,不知道什么原因。。
pass8 .绕过
观察源码发现没有对后缀名过滤. 可以在后缀名加.绕过黑名单。
pass9 默认数据流截断
分析源码,一堆黑名单,并且保存的时候是用随机数加上提取的后缀名进行保存,所以没办法用后缀加点或空格,或. .的方式绕过了。但是发现代码并没有对后缀名去除::$DATA。::DATA在NTFS文件系统中是默认的数据流,即a.php::$DATA和a.php是等价的。在存储时,windows会把::$DATA截断$故保存的是a.php,达到木马上传目的
pass10 . .绕过
首先看源码,基本的对文件名后缀过滤都有,黑名单也基本上没有空子可钻。发现最后保存文件的时候是直接以原始文件名保存,故考虑. .绕过。假设我上传的原始文件是1.php. . ,首先删除末尾的点,变成去1.php. (php后面是点空格),然后以.分割文件后缀名,此时后缀名为点空格,之后的过滤都不影响后缀名。点空格不在黑名单内,故直接将原始文件名1.php. .存储。windows系统存储时自动截断后面的点空格点,存储为1.php,木马上传成功。
pass11 双写后缀绕过
首先分析源码。源码对filename原始文件名进行操作,删除其中的存在于黑名单中的子字符串,将删除后的字符串作为处理后的要保存的文件名。可以使用双写后缀来绕过,原理如下:
假设我上传的是a.pphphp,读到第三位时,一直读到第五位,发现第三位到第五位是php,因此把php删掉;之后继续读,后面是hp,并不存在于黑名单中;但是删除第三位到第五位的php后,文件名的后缀变成了php,并且保存在了服务器上。
直接抓包改a.php的后缀名为a.pphphp上传成功,蚁剑连接成功
pass12 白名单 空字符截断 %00
首先观察源码。发现其中有一个文件后缀名的白名单,并且把第一个点后面的字符串当作是后缀名。如果后缀名存在于白名单中,那么从$_GET全局数组中获取路径,拼接一个随机数再拼接后缀名,作为最后的保存路径。既然保存的路径名从GET方法中获取,我们可以在这方面上做文章。
在c语言中,一个字符串结尾是以空字符(0x00)作为标志。读取一个字符串,读到空字符就不会读下去了。php是用c语言写的,因此保留了这个特性。在源码中,两个路径($temp_file $img_file)都是字符串,因此可以考虑在真实的路径名中添加空字符,以截断后面的字符,伪造路径。这一关是在GET请求上传递路径,因此空字符要用URL编码。对一个字符的URL编码,是把其的ASICC码转换成16进制,再在前面加%。空字符ASICC码值是0,因此在URL编码中就是%00。
我们上传a.png,其中内容是一句话木马。先抓个包,对save_path进行修改:save_path=../upload/a.php%00/ 在后端完成拼接后,存储的路径名就是../upload/a.php%00/a.png
%00阻断了后面的字符,所以真实保存的路径名是../upload/a.php 这样木马就上传成功了。
pass13 空字符截断 0x00
首先分析源码。发现依旧是上传路径可控,但是是以POST的方式传递上传路径。依旧考虑空字符截断。
上传a.png(内容是一句话木马),抓包。
发现默认的文件上传路径。这里不再是GET请求在URL栏传参,而是POST传参,因此空字符的编码不需要URL编码。但是也不能直接写0x00,会被默认当成是字符串。正确的做法是先在a.php后弄一个占位符,然后把它用16进制的00替代。这样就插入空字符了。forward,上传成功,连接upload/a.php,连接成功
pass14 图片字节标识绕过
对于不同格式的图片,在内容的开头都会有两个字节的标识符:
- JPEG/JFIF(常见的照片格式):头两个字节为·0xFF 0xD8。
- PNG(无损压缩格式):头两个字节为·0x89 0x50。
- GIF(支持动画的图像格式):头两个字节为·0x47 0x49。
- BMP(Windows 位图格式):头两个字节为·0x42 0x4D。
- TIFF(标签图像文件格式):头两个字节可以是不同的数值。
这关的要求是上传图片码,该靶场存在文件包含漏洞用于测试图片码。首先分析源码,发现他会以二进制的方式读取前面两个字节,并把这两个字节的十进制表示数拼接起来。
因此思路就很简单了,直接把木马格式改成图片格式,并在前面两个字节添加对应的图片标识字节。
上传a.png,成功通过验证,并利用文件包含漏洞进行连接,连接成功
pass15 图片马绕过 && getimagesize()函数
首先分析源码:通过getimagesize()获取图片的元数据(getimagesize()函数是php中一个用于检测文件信息的核心函数,完全基于文件内容来返回返回一个数组,数组存储图片元数据),其中第二项是图片类型。在白名单中找是否存在返回的图片类型,如果是的话返回存在,即该文件是图片文件。
但是getimagesize()函数有一个缺陷,就是我在其中插入一句话木马,因为对图片内容整体影响不大,所以getimagesize()返回的还是图片类型。因此可以直接在一个正常图片后面加一句话木马,再通过文件包含漏洞连接。
上传了成功,但是访问时提示“Warning: Unexpected character in input: ‘ in D:\hackertools\phpstudy\phpstudy_pro\WWW\upload-labs-master\upload\2920250418084519.jpeg on line 489 ”的报错信息。经检查发现,是因为php.ini中设置了short_open_tag=On时会把“ <? ” 当成php代码的开头从而开始解析。我将short_open_tag设置为了OFF就没有报错了。
蚁剑测试连接,连接成功
pass16 图片马 && exif_imagetype()
首先分析源码,定义了一个isImage()函数,该函数使用exif_imagetype()函数来判断图片类型。这个函数只检查文件的头部字节来判断文件类型,因此比较好绕过,(效率也比较高)。
还是一样的构造图片马,上传图片马,利用include.php进行连接。这里用上一关构造的图片马hack.jpg
pass17 二次渲染绕过
首先分析源码。首先验证末尾的文件后缀是不是三个后缀之一,如果是的话先把文件从临时目录存储到本地;接着利用imagecreatefromjpeg()函数读取传入的图像文件内容,重写一个新的图像文件,返回一个图像资源给$im。成功的话利用imagejpeg()函数将图像资源加载到新创建的路径中。
可以显然发现,原始文件只要能绕过,就会短暂地停留在服务器中。因此可以考虑条件竞争绕过。同时,imagecreatefromjpeg()对图像文件重写,并不会完全改变他的二进制编码,存在一些二进制片段是完全不变的,因此可以在这些片段中插入一句话木马,重写之后木马不会被覆盖掉。这里用绕过重写的方法。
首先直接上传一个正常的图像文件,(我用的是gif,gif处理前后的相同部分较长,成功率较高)然后拿到上传到服务器本地的该图片,两者进行对比,观察哪些部分是没有改变的。
直接在相同的部分中加插入一句话木马:
上传构造后的111.gif,蚁剑连接成功
下载上传到本地的图片,也能发现其中的木马并没被覆盖掉
pass 18 条件竞争
首先分析源码。发现文件是把原始文件先保存在本地,然后对原始文件名后缀进行判断,如果不在白名单内则删除,如果在的话给文件路径重命名。所以即便上传的不是白名单,总会在服务器本地保存一瞬的时间,这个时候就可以考虑条件竞争。
所谓条件竞争就是在后端代码还没有删除文件前,卡着那一瞬的时间去访问上传的木马。因为删除的只是上传的文件,所以这个木马的内容可以是在当前文件夹下生成一个一句话木马,这样只要访问原始文件成功,即便原始文件被删除,生成的木马也会保留。所以木马文件可以这么写:
然后用burp的intruder模块,先不断地发送b.php文件上传的请求包,在攻击的过程中不断发送访问b.php的请求包:
这个跟运气有关,有的时候要多试几次
shell创建成功,蚁剑连接成功
pass19 apache解析漏洞&条件竞争
首先分析源码。源码很长,首先会对后缀进行白名单验证。发现白名单中有7z。apache解析文件名时,是从右往左解析;假设我上传的是a.php.7z,apache是解析不了7z的,继续往左读,解析成php;所以7z可以绕过白名单,存储在本地上,访问的时候以php文件解析。但是后面的代码对存储到本地的文件重命名,这时a.php.7z会被重命名成xxxx.7z,无法解析。因此,要赶在文件上传后,原始文件名被修改前那一瞬间的时间,和后端代码竞争,访问a.php.7z,达到种下小马的目的。
依旧是抓一个上传文件的包,抓一个访问文件的包,丢到intruder开始爆破
pass20 后缀绕过
首先看源码。这关的后端很宽松,没有对后缀名进行任何的处理(如去空格,去.),因此随便尝试一种后缀绕过都可以。这关更像是对后缀绕过的总结。
pass21 数组后缀绕过
分析源码。首先对content-type进行验证,因此要改一下。随后定义一个$file变量,如果post传递参数save_name值存在,则把save_name传递给$file;如果不存在,把原始文件名传递给$file。
接下来判断$file是不是一个数组,如果不是的话,以” . ” 进行分割成数组,赋值给$file。
之后拿到$file数组的最后一个元素,作为文件后缀名,判断是否在白名单内。如果在的话,计算数组的大小,拿到数组实际有效大小-1的位置的值作为文件后缀。这里就是漏洞产生的原因。前面用来判断的后缀是数组最后一个元素,这里保存时的后缀是数组实际有效大小-1位置的元素。
假设我POST上传的是一个数组,file[0]=1.php file[2]=png 这样的话数组实际有效大小是2,判断白名单时是取数组最后一个元素,即file[2],能绕过;存储时是取数组第一个元素+‘.’+file[1]。file[0]为空,所以最后的存储路径就是 a.php. 存储在本地时windows自动截断. 木马就上传成功了。
原请求包:
修改后请求包(POST参数值 和 content-type):
文件上传成功。蚁剑尝试连接,连接成功。